Skip to content

Commit

Permalink
feat: implement Value::TryInto and Reference::TryInto for class vec
Browse files Browse the repository at this point in the history
  • Loading branch information
brianheineman committed Jan 19, 2025
1 parent 87a8b73 commit d5924fe
Show file tree
Hide file tree
Showing 8 changed files with 210 additions and 11 deletions.
Binary file added classes/JDBC.class
Binary file not shown.
23 changes: 23 additions & 0 deletions classes/JDBC.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.Statement;

public class JDBC {
public static void main(String ... args) throws Exception {
Class.forName("org.h2.Driver");
String url = "jdbc:h2:~/test";
String user = "sa";
String password = "";

try (Connection connection = DriverManager.getConnection(url, user, password);
Statement statement = connection.createStatement();
ResultSet resultSet = statement.executeQuery("SELECT H2VERSION()")) {

if (resultSet.next()) {
String version = resultSet.getString(1);
System.out.println("H2 Database Version: " + version);
}
}
}
}
Binary file modified classes/classes.jar
Binary file not shown.
28 changes: 26 additions & 2 deletions ristretto_classloader/src/reference.rs
Original file line number Diff line number Diff line change
Expand Up @@ -479,6 +479,14 @@ impl TryInto<Vec<f64>> for Reference {
}
}

impl TryInto<(Arc<Class>, Vec<Option<Reference>>)> for Reference {
type Error = crate::Error;

fn try_into(self) -> Result<(Arc<Class>, Vec<Option<Reference>>)> {
self.to_class_vec()
}
}

impl TryInto<bool> for Reference {
type Error = crate::Error;

Expand Down Expand Up @@ -1391,8 +1399,8 @@ mod tests {
let object = Object::new(class)?;
object.set_value("value", Value::Int(42))?;
let value = Value::from(object);
let original_value = vec![value];
let reference = Reference::try_from((original_class.clone(), original_value.clone()))?;
let original_values = vec![value];
let reference = Reference::try_from((original_class.clone(), original_values.clone()))?;
assert!(matches!(reference, Reference::Array(_, _)));
Ok(())
}
Expand Down Expand Up @@ -1542,6 +1550,22 @@ mod tests {
Ok(())
}

#[tokio::test]
async fn test_try_into_class_vec() -> Result<()> {
let original_class = Arc::new(Class::new_named("[Ljava/lang/Object;")?);
let class_name = "java.lang.Integer";
let class = load_class(class_name).await?;
let object = Object::new(class.clone())?;
object.set_value("value", Value::Int(42))?;
let value = Value::from(object);
let original_values = vec![value];
let reference = Reference::try_from((original_class.clone(), original_values.clone()))?;
let (reference_class, reference_values) = reference.try_into()?;
assert_eq!(original_class.name(), reference_class.name());
assert_eq!(original_values.len(), reference_values.len());
Ok(())
}

#[tokio::test]
async fn test_try_into_bool() -> Result<()> {
let class = load_class("java/lang/Boolean").await?;
Expand Down
29 changes: 27 additions & 2 deletions ristretto_classloader/src/value.rs
Original file line number Diff line number Diff line change
Expand Up @@ -441,6 +441,15 @@ impl TryInto<Vec<f64>> for Value {
}
}

impl TryInto<(Arc<Class>, Vec<Option<Reference>>)> for Value {
type Error = crate::Error;

fn try_into(self) -> Result<(Arc<Class>, Vec<Option<Reference>>)> {
let reference: Reference = self.try_into()?;
reference.try_into()
}
}

impl TryInto<bool> for Value {
type Error = crate::Error;

Expand Down Expand Up @@ -1009,8 +1018,8 @@ mod tests {
let object = Object::new(class)?;
object.set_value("value", Value::Int(42))?;
let value = Value::from(object);
let original_value = vec![value];
let value = Value::try_from((original_class.clone(), original_value.clone()))?;
let original_values = vec![value];
let value = Value::try_from((original_class.clone(), original_values.clone()))?;
assert!(matches!(value, Value::Object(Some(Reference::Array(_, _)))));
Ok(())
}
Expand Down Expand Up @@ -1170,6 +1179,22 @@ mod tests {
Ok(())
}

#[tokio::test]
async fn test_try_into_class_vec() -> Result<()> {
let original_class = Arc::new(Class::new_named("[Ljava/lang/Object;")?);
let class_name = "java/lang/Integer";
let class = load_class(class_name).await?;
let object = Object::new(class.clone())?;
object.set_value("value", Value::Int(42))?;
let value = Value::from(object);
let original_values = vec![value];
let value = Value::try_from((original_class.clone(), original_values.clone()))?;
let (reference_class, reference_values) = value.try_into()?;
assert_eq!(original_class.name(), reference_class.name());
assert_eq!(original_values.len(), reference_values.len());
Ok(())
}

#[tokio::test]
async fn test_try_into_bool() -> Result<()> {
let value: bool = Value::Int(1).try_into()?;
Expand Down
115 changes: 109 additions & 6 deletions ristretto_vm/src/native_methods/java/lang/class.rs
Original file line number Diff line number Diff line change
Expand Up @@ -698,7 +698,8 @@ async fn get_name_0(thread: Arc<Thread>, mut arguments: Arguments) -> Result<Opt
let object = arguments.pop_object()?;
let class = get_class(&thread, &object).await?;
let class_name = class.name().replace('/', ".");
let value = class_name.to_value();
let vm = thread.vm()?;
let value = class_name.to_object(&vm).await?;
Ok(Some(value))
}

Expand All @@ -720,18 +721,15 @@ async fn get_permitted_subclasses_0(
let object = arguments.pop_object()?;
let _class = get_class(&thread, &object).await?;
// TODO: add support for sealed classes
Ok(None)
Ok(Some(Value::Object(None)))
}

#[async_recursion(?Send)]
async fn get_primitive_class(
thread: Arc<Thread>,
mut arguments: Arguments,
) -> Result<Option<Value>> {
let Some(Reference::Object(primitive)) = arguments.pop_reference()? else {
return Err(InternalError("getPrimitiveClass: no arguments".to_string()));
};

let primitive: Object = arguments.pop_object()?;
let class_name: String = primitive.try_into()?;
let vm = thread.vm()?;
let class = thread.class(class_name).await?;
Expand Down Expand Up @@ -944,6 +942,57 @@ mod tests {
let _ = get_generic_signature_0(thread, Arguments::default()).await;
}

#[tokio::test]
async fn test_get_interfaces_0() -> Result<()> {
let (vm, thread) = crate::test::thread().await?;
let class = thread.class("java.lang.String").await?;
let object = class.to_object(&vm).await?;
let arguments = Arguments::new(vec![object]);
let result = get_interfaces_0(thread, arguments).await?;
let (class, values) = result.expect("interfaces").try_into()?;
assert_eq!(class.name(), "[Ljava/lang/Class;");
let mut class_names = Vec::new();
for reference in values.into_iter().flatten() {
let object = reference.to_object()?;
let class_name = object.value("name")?;
let class_name: String = class_name.try_into()?;
class_names.push(class_name);
}
assert_eq!(
class_names,
vec![
"java.io.Serializable",
"java.lang.Comparable",
"java.lang.CharSequence",
"java.lang.constant.Constable",
"java.lang.constant.ConstantDesc"
]
);
Ok(())
}

#[tokio::test]
async fn test_get_modifiers() -> Result<()> {
let (vm, thread) = crate::test::thread().await?;
let object = "foo".to_object(&vm).await?;
let arguments = Arguments::new(vec![object]);
let result = get_modifiers(thread, arguments).await?;
let modifiers: i32 = result.expect("modifiers").try_into()?;
assert_eq!(modifiers, 17);
Ok(())
}

#[tokio::test]
async fn test_get_name_0() -> Result<()> {
let (vm, thread) = crate::test::thread().await?;
let object = "foo".to_object(&vm).await?;
let arguments = Arguments::new(vec![object]);
let result = get_name_0(thread, arguments).await?;
let class_name: String = result.expect("object").try_into()?;
assert_eq!(class_name.as_str(), "java.lang.String");
Ok(())
}

#[tokio::test]
#[should_panic(
expected = "not yet implemented: java.lang.Class.getNestHost0()Ljava/lang/Class;"
Expand All @@ -962,6 +1011,28 @@ mod tests {
let _ = get_nest_members_0(thread, Arguments::default()).await;
}

#[tokio::test]
async fn test_get_permitted_subclasses_0() -> Result<()> {
let (vm, thread) = crate::test::thread().await?;
let object = "foo".to_object(&vm).await?;
let arguments = Arguments::new(vec![object]);
let result = get_permitted_subclasses_0(thread, arguments).await?;
assert_eq!(result, Some(Value::Object(None)));
Ok(())
}

#[tokio::test]
async fn test_get_primitive_class() -> Result<()> {
let (vm, thread) = crate::test::thread().await?;
let object = "int".to_object(&vm).await?;
let arguments = Arguments::new(vec![object]);
let result = get_primitive_class(thread, arguments).await?;
let class_object: Object = result.expect("class").try_into()?;
let class_name: String = class_object.value("name")?.try_into()?;
assert_eq!(class_name.as_str(), "int");
Ok(())
}

#[tokio::test]
#[should_panic(
expected = "not yet implemented: java.lang.Class.getProtectionDomain0()Ljava/security/ProtectionDomain;"
Expand Down Expand Up @@ -1002,6 +1073,38 @@ mod tests {
Ok(())
}

#[tokio::test]
async fn test_get_simple_binary_name_0() -> Result<()> {
let (_vm, thread) = crate::test::thread().await?;
let object = thread
.object(
"java.util.HashMap$Node",
"ILjava/lang/Object;Ljava/lang/Object;Ljava/util/HashMap$Node;",
vec![
Value::Int(0),
Value::Object(None),
Value::Object(None),
Value::Object(None),
],
)
.await?;
let arguments = Arguments::new(vec![object]);
let result = get_simple_binary_name_0(thread, arguments).await?;
let result_object: String = result.expect("string").try_into()?;
assert_eq!(result_object.as_str(), "Node");
Ok(())
}

#[tokio::test]
async fn test_get_simple_binary_name_0_non_inner_class_returns_none() -> Result<()> {
let (vm, thread) = crate::test::thread().await?;
let object = "foo".to_object(&vm).await?;
let arguments = Arguments::new(vec![object]);
let result = get_simple_binary_name_0(thread, arguments).await?;
assert_eq!(result, Some(Value::Object(None)));
Ok(())
}

#[tokio::test]
async fn test_get_superclass() -> Result<()> {
let (vm, thread) = crate::test::thread().await?;
Expand Down
2 changes: 1 addition & 1 deletion ristretto_vm/src/thread.rs
Original file line number Diff line number Diff line change
Expand Up @@ -376,7 +376,7 @@ impl Thread {
let class = self.class(class_name).await?;
let Some(constructor) = class.method("<init>", descriptor) else {
return Err(InternalError(format!(
"No constructor found: {class_name}.<init>({descriptor})"
"No constructor found: {class_name}.<init>{descriptor}"

Check warning on line 379 in ristretto_vm/src/thread.rs

View check run for this annotation

Codecov / codecov/patch

ristretto_vm/src/thread.rs#L379

Added line #L379 was not covered by tests
)));
};

Expand Down
24 changes: 24 additions & 0 deletions ristretto_vm/tests/jdbc.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
use ristretto_classloader::ClassPath;
use ristretto_vm::{ConfigurationBuilder, VM};
use std::path::PathBuf;

#[tokio::test]
async fn test_jdbc() -> ristretto_vm::Result<()> {
let cargo_manifest = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
let classes_directory = cargo_manifest.join("..").join("classes");
let class_path_entries = [
classes_directory.to_string_lossy().to_string(),
"https://repo1.maven.org/maven2/com/h2database/h2/2.3.232/h2-2.3.232.jar".to_string(),
]
.join(":");
let class_path = ClassPath::from(&class_path_entries);
let configuration = ConfigurationBuilder::new()
.class_path(class_path)
.main_class("JDBC")
.build()?;
let _vm = VM::new(configuration).await?;
let _arguments: Vec<&str> = Vec::new();
// Temporarily disable this test because it requires the invokedynamic instruction.
// let result = vm.invoke_main(arguments).await?;
Ok(())
}

0 comments on commit d5924fe

Please sign in to comment.