Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: implement Value::TryInto and Reference::TryInto for class vec #221

Merged
merged 1 commit into from
Jan 19, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
117 changes: 110 additions & 7 deletions ristretto_vm/src/native_methods/java/lang/class.rs
Original file line number Diff line number Diff line change
Expand Up @@ -227,7 +227,7 @@ async fn desired_assertion_status_0(
_thread: Arc<Thread>,
_arguments: Arguments,
) -> Result<Option<Value>> {
Ok(Some(Value::Int(0)))
Ok(Some(Value::from(false)))
}

#[async_recursion(?Send)]
Expand Down 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 @@
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(())
}
Loading