Files
regorus/bindings/java/src/lib.rs
Anand Krishnamoorthi 86b4a279fa fix(ffi): eliminate aliasing UB + add Azure Policy JSON compilation FFI (#727)
* fix(ffi): eliminate aliasing UB via to_shared_ref migration

Add to_shared_ref() helper that creates &T (shared reference) from raw
pointers instead of &mut T. This eliminates undefined behavior caused by
violating Rust's aliasing invariant when C# SafeHandle permits concurrent
FFI calls on the same handle.

With &mut T, the compiler may assume exclusive (noalias) access and
reorder or elide reads/writes — a miscompilation risk when another thread
holds a reference to the same object. Switching to &T removes that
assumption; actual mutation is mediated by the interior RwLock inside
Handle<T>, which is the sole synchronization mechanism.

Migrated sites:
- rvm.rs: 20 non-drop call sites
- engine.rs: 30 non-drop call sites + with_unwind_guard for timer fns
- compiled_policy.rs: 2 call sites
- Fix null-data UB in regorus_program_deserialize_binary

Drop paths retain to_ref() where exclusive access is guaranteed by the
caller contract (preventing use-after-free).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* feat(ffi): add Azure Policy JSON compilation FFI and C# bindings

- AliasRegistry builder pattern: RegorusAliasRegistryBuilder (mutable,
  single-threaded) + RegorusAliasRegistry (immutable, Arc-wrapped)
- Azure Policy JSON compilation: regorus_compile_azure_policy_rule and
  regorus_compile_azure_policy_definition with alias registry support
- regorus_rvm_set_context for host-supplied ambient data
- C# AliasRegistryBuilder and AliasRegistry classes with convenience
  factories (FromJson, FromManifest, Empty)
- C# AzurePolicyCompiler static class for policy rule/definition compilation
- Compile functions take *const RegorusAliasRegistry (read-only via
  to_shared_ref for concurrent compilation safety)
- Fix pre-existing clippy warnings across multiple crates

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-05-22 12:50:16 -05:00

834 lines
24 KiB
Rust

// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
use anyhow::Result;
use core::num::{NonZeroU32, NonZeroUsize};
use jni::objects::{JBooleanArray, JByteArray, JClass, JObject, JObjectArray, JString};
use jni::strings::JNIString;
use jni::sys::{jboolean, jbooleanArray, jbyteArray, jlong, jobjectArray, jstring};
use jni::{jni_str, Env, EnvUnowned, Outcome};
use regorus::languages::rego::compiler::Compiler;
use regorus::rvm::program::{
generate_assembly_listing, AssemblyListingConfig, DeserializationResult, Program as RvmProgram,
};
use regorus::rvm::vm::{ExecutionMode, RegoVM};
use regorus::{compile_policy_with_entrypoint, Engine, PolicyModule, Rc, Value};
use std::sync::Arc;
#[no_mangle]
pub extern "system" fn Java_com_microsoft_regorus_Engine_nativeNewEngine(
_env: EnvUnowned,
_class: JClass,
) -> jlong {
let engine = Engine::new();
Box::into_raw(Box::new(engine)) as jlong
}
#[no_mangle]
pub extern "system" fn Java_com_microsoft_regorus_Engine_nativeClone(
_env: EnvUnowned,
_class: JClass,
engine_ptr: jlong,
) -> jlong {
let engine = unsafe { &mut *(engine_ptr as *mut Engine) };
let c = engine.clone();
Box::into_raw(Box::new(c)) as jlong
}
#[no_mangle]
pub extern "system" fn Java_com_microsoft_regorus_Engine_nativeSetRegoV0(
env: EnvUnowned,
_class: JClass,
engine_ptr: jlong,
enable: bool,
) {
let _ = throw_err(env, |_env| {
let engine = unsafe { &mut *(engine_ptr as *mut Engine) };
engine.set_rego_v0(enable);
Ok(())
});
}
#[no_mangle]
pub extern "system" fn Java_com_microsoft_regorus_Engine_nativeAddPolicy(
env: EnvUnowned,
_class: JClass,
engine_ptr: jlong,
path: JString,
rego: JString,
) -> jstring {
let res = throw_err(env, |env| {
let engine = unsafe { &mut *(engine_ptr as *mut Engine) };
let path: String = path.try_to_string(env)?;
let rego: String = rego.try_to_string(env)?;
let pkg = JString::new(env, engine.add_policy(path, rego)?)?;
Ok(pkg.into_raw())
});
match res {
Ok(val) => val,
Err(_) => JObject::null().into_raw(),
}
}
#[no_mangle]
pub extern "system" fn Java_com_microsoft_regorus_Engine_nativeAddPolicyFromFile(
env: EnvUnowned,
_class: JClass,
engine_ptr: jlong,
path: JString,
) -> jstring {
let res = throw_err(env, |env| {
let engine = unsafe { &mut *(engine_ptr as *mut Engine) };
let path: String = path.try_to_string(env)?;
let pkg = JString::new(env, engine.add_policy_from_file(path)?)?;
Ok(pkg.into_raw())
});
match res {
Ok(val) => val,
Err(_) => JObject::null().into_raw(),
}
}
#[no_mangle]
pub extern "system" fn Java_com_microsoft_regorus_Engine_nativeGetPackages(
env: EnvUnowned,
_class: JClass,
engine_ptr: jlong,
) -> jstring {
let res = throw_err(env, |env| {
let engine = unsafe { &mut *(engine_ptr as *mut Engine) };
let packages = engine.get_packages()?;
let packages_json = JString::new(env, serde_json::to_string_pretty(&packages)?)?;
Ok(packages_json.into_raw())
});
match res {
Ok(val) => val,
Err(_) => JObject::null().into_raw(),
}
}
#[no_mangle]
pub extern "system" fn Java_com_microsoft_regorus_Engine_nativeGetPolicies(
env: EnvUnowned,
_class: JClass,
engine_ptr: jlong,
) -> jstring {
let res = throw_err(env, |env| {
let engine = unsafe { &mut *(engine_ptr as *mut Engine) };
let policies = engine.get_policies_as_json()?;
let policies_json = JString::new(env, &policies)?;
Ok(policies_json.into_raw())
});
match res {
Ok(val) => val,
Err(_) => JObject::null().into_raw(),
}
}
#[no_mangle]
pub extern "system" fn Java_com_microsoft_regorus_Engine_nativeClearData(
env: EnvUnowned,
_class: JClass,
engine_ptr: jlong,
) {
let _ = throw_err(env, |_env| {
let engine = unsafe { &mut *(engine_ptr as *mut Engine) };
engine.clear_data();
Ok(())
});
}
#[no_mangle]
pub extern "system" fn Java_com_microsoft_regorus_Engine_nativeAddDataJson(
env: EnvUnowned,
_class: JClass,
engine_ptr: jlong,
data: JString,
) {
let _ = throw_err(env, |env| {
let engine = unsafe { &mut *(engine_ptr as *mut Engine) };
let data: String = data.try_to_string(env)?;
engine.add_data_json(&data)?;
Ok(())
});
}
#[no_mangle]
pub extern "system" fn Java_com_microsoft_regorus_Engine_nativeAddDataJsonFromFile(
env: EnvUnowned,
_class: JClass,
engine_ptr: jlong,
path: JString,
) {
let _ = throw_err(env, |env| {
let engine = unsafe { &mut *(engine_ptr as *mut Engine) };
let path: String = path.try_to_string(env)?;
engine.add_data(Value::from_json_file(path)?)?;
Ok(())
});
}
#[no_mangle]
pub extern "system" fn Java_com_microsoft_regorus_Engine_nativeSetInputJson(
env: EnvUnowned,
_class: JClass,
engine_ptr: jlong,
input: JString,
) {
let _ = throw_err(env, |env| {
let engine = unsafe { &mut *(engine_ptr as *mut Engine) };
let input: String = input.try_to_string(env)?;
engine.set_input_json(&input)?;
Ok(())
});
}
#[no_mangle]
pub extern "system" fn Java_com_microsoft_regorus_Engine_nativeSetInputJsonFromFile(
env: EnvUnowned,
_class: JClass,
engine_ptr: jlong,
path: JString,
) {
let _ = throw_err(env, |env| {
let engine = unsafe { &mut *(engine_ptr as *mut Engine) };
let path: String = path.try_to_string(env)?;
engine.set_input(Value::from_json_file(path)?);
Ok(())
});
}
#[no_mangle]
pub extern "system" fn Java_com_microsoft_regorus_Engine_nativeEvalQuery(
env: EnvUnowned,
_class: JClass,
engine_ptr: jlong,
query: JString,
) -> jstring {
let res = throw_err(env, |env| {
let engine = unsafe { &mut *(engine_ptr as *mut Engine) };
let query: String = query.try_to_string(env)?;
let results = engine.eval_query(query, false)?;
let output = JString::new(env, serde_json::to_string(&results)?)?;
Ok(output.into_raw())
});
match res {
Ok(val) => val,
Err(_) => JObject::null().into_raw(),
}
}
#[no_mangle]
pub extern "system" fn Java_com_microsoft_regorus_Engine_nativeEvalRule(
env: EnvUnowned,
_class: JClass,
engine_ptr: jlong,
rule: JString,
) -> jstring {
let res = throw_err(env, |env| {
let engine = unsafe { &mut *(engine_ptr as *mut Engine) };
let rule: String = rule.try_to_string(env)?;
let value = engine.eval_rule(rule)?;
let output = JString::new(env, value.to_json_str()?)?;
Ok(output.into_raw())
});
match res {
Ok(val) => val,
Err(_) => JObject::null().into_raw(),
}
}
#[no_mangle]
#[cfg(feature = "coverage")]
pub extern "system" fn Java_com_microsoft_regorus_Engine_nativeSetEnableCoverage(
env: EnvUnowned,
_class: JClass,
engine_ptr: jlong,
enable: bool,
) {
let _ = throw_err(env, |_| {
let engine = unsafe { &mut *(engine_ptr as *mut Engine) };
engine.set_enable_coverage(enable);
Ok(())
});
}
#[no_mangle]
#[cfg(feature = "coverage")]
pub extern "system" fn Java_com_microsoft_regorus_Engine_nativeGetCoverageReport(
env: EnvUnowned,
_class: JClass,
engine_ptr: jlong,
) -> jstring {
let res = throw_err(env, |env| {
let engine = unsafe { &mut *(engine_ptr as *mut Engine) };
let report = engine.get_coverage_report()?;
let output = JString::new(env, serde_json::to_string_pretty(&report)?)?;
Ok(output.into_raw())
});
match res {
Ok(val) => val,
Err(_) => JObject::null().into_raw(),
}
}
#[no_mangle]
#[cfg(feature = "coverage")]
pub extern "system" fn Java_com_microsoft_regorus_Engine_nativeGetCoverageReportPretty(
env: EnvUnowned,
_class: JClass,
engine_ptr: jlong,
) -> jstring {
let res = throw_err(env, |env| {
let engine = unsafe { &mut *(engine_ptr as *mut Engine) };
let report = engine.get_coverage_report()?.to_string_pretty()?;
let output = JString::new(env, &report)?;
Ok(output.into_raw())
});
match res {
Ok(val) => val,
Err(_) => JObject::null().into_raw(),
}
}
#[no_mangle]
#[cfg(feature = "coverage")]
pub extern "system" fn Java_com_microsoft_regorus_Engine_nativeClearCoverageData(
env: EnvUnowned,
_class: JClass,
engine_ptr: jlong,
) {
let _ = throw_err(env, |_| {
let engine = unsafe { &mut *(engine_ptr as *mut Engine) };
engine.clear_coverage_data();
Ok(())
});
}
#[no_mangle]
pub extern "system" fn Java_com_microsoft_regorus_Engine_nativeSetGatherPrints(
env: EnvUnowned,
_class: JClass,
engine_ptr: jlong,
b: bool,
) {
let _ = throw_err(env, |_| {
let engine = unsafe { &mut *(engine_ptr as *mut Engine) };
engine.set_gather_prints(b);
Ok(())
});
}
#[no_mangle]
pub extern "system" fn Java_com_microsoft_regorus_Engine_nativeTakePrints(
env: EnvUnowned,
_class: JClass,
engine_ptr: jlong,
) -> jstring {
let res = throw_err(env, |env| {
let engine = unsafe { &mut *(engine_ptr as *mut Engine) };
let prints = engine.take_prints()?;
let output = JString::new(env, serde_json::to_string_pretty(&prints)?)?;
Ok(output.into_raw())
});
match res {
Ok(val) => val,
Err(_) => JObject::null().into_raw(),
}
}
#[no_mangle]
#[cfg(feature = "ast")]
pub extern "system" fn Java_com_microsoft_regorus_Engine_getAstAsJson(
env: EnvUnowned,
_class: JClass,
engine_ptr: jlong,
) -> jstring {
let res = throw_err(env, |env| {
let engine = unsafe { &mut *(engine_ptr as *mut Engine) };
let ast = engine.get_ast_as_json()?;
let output = JString::new(env, &ast)?;
Ok(output.into_raw())
});
match res {
Ok(val) => val,
Err(_) => JObject::null().into_raw(),
}
}
#[no_mangle]
pub extern "system" fn Java_com_microsoft_regorus_Engine_nativeSetPolicyLengthConfig(
env: EnvUnowned,
_class: JClass,
engine_ptr: jlong,
max_col: u32,
max_file_bytes: jlong,
max_lines: jlong,
) {
let _ = throw_err(env, |_env| {
let engine = unsafe { &mut *(engine_ptr as *mut Engine) };
engine.set_policy_length_config(regorus::PolicyLengthConfig {
max_col: NonZeroU32::new(max_col)
.ok_or_else(|| anyhow::anyhow!("maxCol must be non-zero"))?,
max_file_bytes: NonZeroUsize::new(max_file_bytes as usize)
.ok_or_else(|| anyhow::anyhow!("maxFileBytes must be non-zero"))?,
max_lines: NonZeroUsize::new(max_lines as usize)
.ok_or_else(|| anyhow::anyhow!("maxLines must be non-zero"))?,
});
Ok(())
});
}
#[no_mangle]
pub extern "system" fn Java_com_microsoft_regorus_Engine_nativeClearPolicyLengthConfig(
_env: EnvUnowned,
_class: JClass,
engine_ptr: jlong,
) {
let engine = unsafe { &mut *(engine_ptr as *mut Engine) };
engine.clear_policy_length_config();
}
#[cfg(feature = "cache")]
#[no_mangle]
pub extern "system" fn Java_com_microsoft_regorus_CacheConfig_nativeSetCacheConfig(
_env: EnvUnowned,
_class: JClass,
regex: jlong,
glob: jlong,
) {
regorus::cache::configure(regorus::cache::Config {
regex: if regex < 0 {
0
} else {
usize::try_from(regex).unwrap_or(usize::MAX)
},
glob: if glob < 0 {
0
} else {
usize::try_from(glob).unwrap_or(usize::MAX)
},
});
}
#[cfg(feature = "cache")]
#[no_mangle]
pub extern "system" fn Java_com_microsoft_regorus_CacheConfig_nativeClearCache(
_env: EnvUnowned,
_class: JClass,
) {
regorus::cache::clear();
}
#[no_mangle]
pub extern "system" fn Java_com_microsoft_regorus_Engine_nativeDestroyEngine(
_env: EnvUnowned,
_class: JClass,
engine_ptr: jlong,
) {
unsafe {
let _engine = Box::from_raw(engine_ptr as *mut Engine);
}
}
#[no_mangle]
pub extern "system" fn Java_com_microsoft_regorus_Program_nativeCompileFromModules(
env: EnvUnowned,
_class: JClass,
data_json: JString,
module_ids: jobjectArray,
module_contents: jobjectArray,
entry_points: jobjectArray,
) -> jlong {
let res = throw_err(env, |env| {
let data_json: String = data_json.try_to_string(env)?;
let data = Value::from_json_str(&data_json)?;
let ids = get_string_array(env, module_ids)?;
let contents = get_string_array(env, module_contents)?;
if ids.len() != contents.len() {
return Err(anyhow::anyhow!("module id/content length mismatch"));
}
let mut modules = Vec::with_capacity(ids.len());
for (id, content) in ids.into_iter().zip(contents) {
modules.push(PolicyModule {
id: Rc::from(id.as_str()),
content: Rc::from(content.as_str()),
});
}
let entry_points_vec = get_string_array(env, entry_points)?;
if entry_points_vec.is_empty() {
return Err(anyhow::anyhow!(
"entry_points must contain at least one entry"
));
}
let entry_points_ref: Vec<&str> = entry_points_vec.iter().map(|s| s.as_str()).collect();
let entry_rule = entry_points_ref[0];
let compiled = compile_policy_with_entrypoint(data, &modules, Rc::from(entry_rule))?;
let program = Compiler::compile_from_policy(&compiled, &entry_points_ref)?;
Ok(Box::into_raw(Box::new(program)) as jlong)
});
res.unwrap_or_default()
}
#[no_mangle]
pub extern "system" fn Java_com_microsoft_regorus_Program_nativeCompileFromEngine(
env: EnvUnowned,
_class: JClass,
engine_ptr: jlong,
entry_points: jobjectArray,
) -> jlong {
let res = throw_err(env, |env| {
let engine = unsafe { &mut *(engine_ptr as *mut Engine) };
let entry_points_vec = get_string_array(env, entry_points)?;
if entry_points_vec.is_empty() {
return Err(anyhow::anyhow!(
"entry_points must contain at least one entry"
));
}
let entry_points_ref: Vec<&str> = entry_points_vec.iter().map(|s| s.as_str()).collect();
let entry_rule = Rc::from(entry_points_ref[0]);
let compiled = engine.compile_with_entrypoint(&entry_rule)?;
let program = Compiler::compile_from_policy(&compiled, &entry_points_ref)?;
Ok(Box::into_raw(Box::new(program)) as jlong)
});
res.unwrap_or_default()
}
#[no_mangle]
pub extern "system" fn Java_com_microsoft_regorus_Program_nativeGenerateListing(
env: EnvUnowned,
_class: JClass,
program_ptr: jlong,
) -> jstring {
let res = throw_err(env, |env| {
let program = unsafe { &*(program_ptr as *mut Arc<RvmProgram>) };
let listing =
generate_assembly_listing(program.as_ref(), &AssemblyListingConfig::default());
let output = JString::new(env, &listing)?;
Ok(output.into_raw())
});
match res {
Ok(val) => val,
Err(_) => JObject::null().into_raw(),
}
}
#[no_mangle]
pub extern "system" fn Java_com_microsoft_regorus_Program_nativeSerializeBinary(
env: EnvUnowned,
_class: JClass,
program_ptr: jlong,
) -> jbyteArray {
let res = throw_err(env, |env| {
let program = unsafe { &*(program_ptr as *mut Arc<RvmProgram>) };
let bytes = program.serialize_binary().map_err(|e| anyhow::anyhow!(e))?;
let array = env.byte_array_from_slice(&bytes)?;
Ok(array.into_raw())
});
match res {
Ok(val) => val,
Err(_) => JObject::null().into_raw(),
}
}
#[no_mangle]
/// # Safety
///
/// The `data` and `is_partial` pointers must be valid JNI array references
/// for the duration of the call. They must come from the JVM for the current
/// thread and not be used after this function returns.
pub unsafe extern "system" fn Java_com_microsoft_regorus_Program_nativeDeserializeBinary(
env: EnvUnowned,
_class: JClass,
data: jbyteArray,
is_partial: jbooleanArray,
) -> jlong {
let res = throw_err(env, |env| {
if data.is_null() {
return Err(anyhow::anyhow!("data must not be null"));
}
let data = unsafe { JByteArray::from_raw(env, data) };
let bytes = env.convert_byte_array(&data)?;
let (program, partial) =
match RvmProgram::deserialize_binary(&bytes).map_err(|e| anyhow::anyhow!(e))? {
DeserializationResult::Complete(program) => (program, false),
DeserializationResult::Partial(program) => (program, true),
};
if !is_partial.is_null() {
let is_partial = unsafe { JBooleanArray::from_raw(env, is_partial) };
let len = is_partial.len(env)?;
if len > 0 {
let value: [jboolean; 1] = [if partial {
jni::sys::JNI_TRUE
} else {
jni::sys::JNI_FALSE
}];
is_partial.set_region(env, 0, &value)?;
}
}
Ok(Box::into_raw(Box::new(Arc::new(program))) as jlong)
});
res.unwrap_or_default()
}
#[no_mangle]
pub extern "system" fn Java_com_microsoft_regorus_Program_nativeDrop(
_env: EnvUnowned,
_class: JClass,
program_ptr: jlong,
) {
unsafe {
let _program = Box::from_raw(program_ptr as *mut Arc<RvmProgram>);
}
}
#[no_mangle]
pub extern "system" fn Java_com_microsoft_regorus_Rvm_nativeNew(
_env: EnvUnowned,
_class: JClass,
) -> jlong {
let vm = RegoVM::new();
Box::into_raw(Box::new(vm)) as jlong
}
#[no_mangle]
pub extern "system" fn Java_com_microsoft_regorus_Rvm_nativeLoadProgram(
env: EnvUnowned,
_class: JClass,
vm_ptr: jlong,
program_ptr: jlong,
) {
let _ = throw_err(env, |_env| {
let vm = unsafe { &mut *(vm_ptr as *mut RegoVM) };
let program = unsafe { &*(program_ptr as *mut Arc<RvmProgram>) };
vm.load_program(program.clone());
Ok(())
});
}
#[no_mangle]
pub extern "system" fn Java_com_microsoft_regorus_Rvm_nativeSetDataJson(
env: EnvUnowned,
_class: JClass,
vm_ptr: jlong,
data_json: JString,
) {
let _ = throw_err(env, |env| {
let vm = unsafe { &mut *(vm_ptr as *mut RegoVM) };
let data_json: String = data_json.try_to_string(env)?;
let data = Value::from_json_str(&data_json)?;
vm.set_data(data)?;
Ok(())
});
}
#[no_mangle]
pub extern "system" fn Java_com_microsoft_regorus_Rvm_nativeSetInputJson(
env: EnvUnowned,
_class: JClass,
vm_ptr: jlong,
input_json: JString,
) {
let _ = throw_err(env, |env| {
let vm = unsafe { &mut *(vm_ptr as *mut RegoVM) };
let input_json: String = input_json.try_to_string(env)?;
let input = Value::from_json_str(&input_json)?;
vm.set_input(input);
Ok(())
});
}
#[no_mangle]
pub extern "system" fn Java_com_microsoft_regorus_Rvm_nativeSetExecutionMode(
env: EnvUnowned,
_class: JClass,
vm_ptr: jlong,
mode: u8,
) {
let _ = throw_err(env, |_env| {
let vm = unsafe { &mut *(vm_ptr as *mut RegoVM) };
let mode = match mode {
0 => ExecutionMode::RunToCompletion,
1 => ExecutionMode::Suspendable,
_ => return Err(anyhow::anyhow!("invalid execution mode")),
};
vm.set_execution_mode(mode);
Ok(())
});
}
#[no_mangle]
pub extern "system" fn Java_com_microsoft_regorus_Rvm_nativeExecute(
env: EnvUnowned,
_class: JClass,
vm_ptr: jlong,
) -> jstring {
let res = throw_err(env, |env| {
let vm = unsafe { &mut *(vm_ptr as *mut RegoVM) };
let result = vm.execute()?;
let output = JString::new(env, result.to_json_str()?)?;
Ok(output.into_raw())
});
match res {
Ok(val) => val,
Err(_) => JObject::null().into_raw(),
}
}
#[no_mangle]
pub extern "system" fn Java_com_microsoft_regorus_Rvm_nativeExecuteEntryPoint(
env: EnvUnowned,
_class: JClass,
vm_ptr: jlong,
entry_point: JString,
) -> jstring {
let res = throw_err(env, |env| {
let vm = unsafe { &mut *(vm_ptr as *mut RegoVM) };
let entry_point: String = entry_point.try_to_string(env)?;
let result = vm.execute_entry_point_by_name(&entry_point)?;
let output = JString::new(env, result.to_json_str()?)?;
Ok(output.into_raw())
});
match res {
Ok(val) => val,
Err(_) => JObject::null().into_raw(),
}
}
#[no_mangle]
pub extern "system" fn Java_com_microsoft_regorus_Rvm_nativeResume(
env: EnvUnowned,
_class: JClass,
vm_ptr: jlong,
resume_json: JString,
has_value: bool,
) -> jstring {
let res = throw_err(env, |env| {
let vm = unsafe { &mut *(vm_ptr as *mut RegoVM) };
let value = if has_value {
let resume_json: String = resume_json.try_to_string(env)?;
Some(Value::from_json_str(&resume_json)?)
} else {
None
};
let result = vm.resume(value)?;
let output = JString::new(env, result.to_json_str()?)?;
Ok(output.into_raw())
});
match res {
Ok(val) => val,
Err(_) => JObject::null().into_raw(),
}
}
#[no_mangle]
pub extern "system" fn Java_com_microsoft_regorus_Rvm_nativeGetExecutionState(
env: EnvUnowned,
_class: JClass,
vm_ptr: jlong,
) -> jstring {
let res = throw_err(env, |env| {
let vm = unsafe { &mut *(vm_ptr as *mut RegoVM) };
let output = JString::new(env, format!("{:?}", vm.execution_state()))?;
Ok(output.into_raw())
});
match res {
Ok(val) => val,
Err(_) => JObject::null().into_raw(),
}
}
#[no_mangle]
pub extern "system" fn Java_com_microsoft_regorus_Rvm_nativeDrop(
_env: EnvUnowned,
_class: JClass,
vm_ptr: jlong,
) {
unsafe {
let _vm = Box::from_raw(vm_ptr as *mut RegoVM);
}
}
fn throw_err<T>(mut env: EnvUnowned, f: impl FnOnce(&mut Env) -> Result<T>) -> Result<T> {
let outcome = env.with_env(|env| -> Result<T> {
match f(env) {
Ok(val) => Ok(val),
Err(err) => {
if let Err(throw_err) = env.throw_new(
jni_str!("java/lang/RuntimeException"),
JNIString::new(err.to_string()),
) {
return Err(anyhow::anyhow!(
"Failed to throw Java RuntimeException for error '{err}': {throw_err}"
));
}
Err(err)
}
}
});
match outcome.into_outcome() {
Outcome::Ok(val) => Ok(val),
Outcome::Err(err) => Err(err),
Outcome::Panic(payload) => {
let msg = payload
.downcast_ref::<String>()
.map(|s| s.as_str())
.or_else(|| payload.downcast_ref::<&str>().copied())
.unwrap_or("unknown panic");
let err = anyhow::anyhow!("panic: {msg}");
// Try to surface the panic as a Java exception.
let _ = env.with_env(|env| -> Result<()> {
env.throw_new(
jni_str!("java/lang/RuntimeException"),
JNIString::new(format!("Rust panic: {msg}")),
)?;
Ok(())
});
Err(err)
}
}
}
fn get_string_array(env: &mut Env, array: jobjectArray) -> Result<Vec<String>> {
if array.is_null() {
return Ok(Vec::new());
}
let array = unsafe { JObjectArray::<JObject>::from_raw(env, array) };
let len = array.len(env)?;
let mut values = Vec::with_capacity(len);
for i in 0..len {
let obj = array.get_element(env, i)?;
let jstr = unsafe { JString::from_raw(env, obj.into_raw()) };
let value: String = jstr.try_to_string(env)?;
values.push(value);
}
Ok(values)
}