//! Compile here; execute only in the designated remote validation environment. use endor_core::{ AnnotationType as T, CallEffect, CallSignature, CallType, CodeLayout, ContextNativeFunction, EndorCode, Instruction, NativeCallable, NativeContext, NativeResult, ObjectRef, Opcode, SourceSignature, TypeConstructor, Value, Vm, VmError, }; use std::collections::BTreeMap; fn source( vm: &mut Vm, globals: ObjectRef, owner: &str, parameters: &[&str], returns: &str, result: Value, ) -> ObjectRef { let code = EndorCode::from_instructions( &[ Instruction::new(Opcode::LoadConst, 0), Instruction::new(Opcode::StoreGlobal, 0), Instruction::new(Opcode::LoadConst, 1), Instruction::simple(Opcode::ReturnValue), ], vec![Value::Number(42.into()), result], CodeLayout { parameters: (0..parameters.len()).map(|i| format!("p{i}")).collect(), locals: (0..parameters.len()).map(|i| format!("p{i}")).collect(), names: vec!["ran".into()], ..CodeLayout::default() }, ) .unwrap() .with_source_signature(SourceSignature { parameters: parameters.iter().map(|s| (*s).into()).collect(), return_type: returns.into(), method_owner: Some(owner.into()), }) .unwrap(); let code = vm.new_code(code).unwrap(); let function = vm .new_function("check", code, globals, vec![], vec![]) .unwrap(); vm.release(code).unwrap(); function } fn class( vm: &mut Vm, name: &str, base: ObjectRef, function: ObjectRef, ) -> Result { vm.new_class( name, base, vec![], BTreeMap::from([("check".into(), function)]), ) } #[test] fn source_parameters_are_contravariant_and_returns_covariant_at_class_creation() { let mut vm = Vm::new().unwrap(); let roots = vm.bootstrap_types().unwrap(); vm.set_source_type_checks(true).unwrap(); let globals = vm.new_dict().unwrap(); let base_fn = source( &mut vm, globals, "Base", &["Base", "Number"], "Object", Value::Nil, ); let base = class(&mut vm, "Base", roots.object, base_fn).unwrap(); let good_fn = source( &mut vm, globals, "Good", &["Good", "Object"], "Number", Value::Number(7.into()), ); let good = class(&mut vm, "Good", base, good_fn).unwrap(); let narrow = source( &mut vm, globals, "Narrow", &["Narrow", "String"], "Number", Value::Nil, ); assert!(matches!( class(&mut vm, "Narrow", base, narrow), Err(VmError::OverrideViolation { reason: "override narrows a parameter type", .. }) )); let numeric = source( &mut vm, globals, "Numeric", &["Numeric", "Object"], "Number", Value::Number(0.into()), ); let numeric_type = class(&mut vm, "Numeric", roots.object, numeric).unwrap(); let broad = source( &mut vm, globals, "Broad", &["Broad", "Object"], "Object", Value::Nil, ); assert!(matches!( class(&mut vm, "Broad", numeric_type, broad), Err(VmError::OverrideViolation { reason: "override widens the return type", .. }) )); for object in [ good, good_fn, narrow, broad, numeric_type, numeric, base, base_fn, globals, roots.object, roots.type_type, ] { vm.release(object).unwrap(); } vm.collect_cycles(); assert_eq!(vm.object_count(), 0); } #[test] fn replacing_an_ancestor_validates_overriding_descendants_before_mutation() { let mut vm = Vm::new().unwrap(); let roots = vm.bootstrap_types().unwrap(); vm.set_source_type_checks(true).unwrap(); let globals = vm.new_dict().unwrap(); let base_fn = source( &mut vm, globals, "Base", &["Base", "Number"], "Object", Value::Nil, ); let base = class(&mut vm, "Base", roots.object, base_fn).unwrap(); let child_fn = source( &mut vm, globals, "Child", &["Child", "Number"], "Number", Value::Number(0.into()), ); let child = class(&mut vm, "Child", base, child_fn).unwrap(); let replacement = source( &mut vm, globals, "Base", &["Base", "Object"], "Object", Value::Nil, ); let version = vm.change_version(base).unwrap(); assert!(matches!( vm.define_method(base, "check", replacement), Err(VmError::OverrideViolation { .. }) )); assert_eq!(vm.change_version(base).unwrap(), version); assert_eq!( vm.object_store().lookup_method(base, "check").unwrap(), Some(base_fn) ); assert_eq!( vm.object_store().lookup_method(child, "check").unwrap(), Some(child_fn) ); for object in [ replacement, child, child_fn, base, base_fn, globals, roots.object, roots.type_type, ] { vm.release(object).unwrap(); } vm.collect_cycles(); assert_eq!(vm.object_count(), 0); } #[test] fn global_rebinding_is_rechecked_before_the_source_body_and_recovers() { let mut vm = Vm::new().unwrap(); let roots = vm.bootstrap_types().unwrap(); vm.set_source_type_checks(true).unwrap(); let parent = vm .new_type("Result", roots.object, TypeConstructor::Record) .unwrap(); let result_type = vm .new_type("ResultChild", parent, TypeConstructor::Record) .unwrap(); let other = vm .new_type("Other", roots.object, TypeConstructor::Record) .unwrap(); let result = vm.new_object().unwrap(); vm.attach_type(result, result_type).unwrap(); let base_globals = vm.new_dict().unwrap(); let globals = vm.new_dict().unwrap(); vm.dict_set( base_globals, Value::Text("Result".into()), Value::Object(parent), ) .unwrap(); vm.dict_set( globals, Value::Text("Actual".into()), Value::Object(result_type), ) .unwrap(); vm.dict_set(globals, Value::Text("ran".into()), Value::Number(0.into())) .unwrap(); let base_fn = source( &mut vm, base_globals, "Base", &["Base"], "Result", Value::Nil, ); let base = class(&mut vm, "Base", roots.object, base_fn).unwrap(); let child_fn = source( &mut vm, globals, "Child", &["Child"], "Actual", Value::Object(result), ); let child = class(&mut vm, "Child", base, child_fn).unwrap(); let receiver = vm.new_object().unwrap(); vm.attach_type(receiver, child).unwrap(); vm.dict_set(globals, Value::Text("Actual".into()), Value::Object(other)) .unwrap(); assert!(matches!( vm.call(child_fn, &[Value::Object(receiver)]), Err(VmError::OverrideViolation { .. }) )); assert_eq!( vm.dict_get(globals, &Value::Text("ran".into())).unwrap(), Some(Value::Number(0.into())) ); vm.dict_set( globals, Value::Text("Actual".into()), Value::Object(result_type), ) .unwrap(); let value = vm.call(child_fn, &[Value::Object(receiver)]).unwrap(); assert_eq!(value, Value::Object(result)); vm.release_value(value).unwrap(); for object in [ receiver, child, child_fn, base, base_fn, globals, base_globals, result, other, result_type, parent, roots.object, roots.type_type, ] { vm.release(object).unwrap(); } vm.collect_cycles(); assert_eq!(vm.object_count(), 0); } #[derive(Debug)] struct Native(CallSignature); impl NativeCallable for Native { fn signature(&self) -> Option { Some(self.0.clone()) } fn invoke(&self, _: &mut NativeContext<'_>, _: &[Value]) -> Result { Ok(NativeResult::Return(Value::Number(0.into()))) } } #[test] fn native_overrides_preserve_union_inputs_and_do_not_expand_effects() { let mut vm = Vm::new().unwrap(); let roots = vm.bootstrap_types().unwrap(); vm.set_source_type_checks(true).unwrap(); let base = vm .new_type("Base", roots.object, TypeConstructor::Record) .unwrap(); let mut signature = CallSignature::exact( vec![T::Nominal(base), T::Any], T::Number, CallEffect::ReadOnly, ); signature.parameters[1] = CallType::Union(vec![T::Number, T::String]); let base_fn = vm .allocate(ContextNativeFunction::new( "check", Native(signature), vec![], )) .unwrap(); vm.define_method(base, "check", base_fn).unwrap(); let child = vm.new_type("Child", base, TypeConstructor::Record).unwrap(); let effect_fn = vm .allocate(ContextNativeFunction::new( "effect", Native(CallSignature::exact( vec![T::Nominal(child), T::Any], T::Number, CallEffect::Mutating, )), vec![], )) .unwrap(); assert!(matches!( vm.define_method(child, "check", effect_fn), Err(VmError::OverrideViolation { reason: "override expands known effects", .. }) )); let narrow_fn = vm .allocate(ContextNativeFunction::new( "narrow", Native(CallSignature::exact( vec![T::Nominal(child), T::Number], T::Number, CallEffect::ReadOnly, )), vec![], )) .unwrap(); assert!(matches!( vm.define_method(child, "check", narrow_fn), Err(VmError::OverrideViolation { reason: "override narrows a parameter type", .. }) )); for object in [ narrow_fn, effect_fn, child, base_fn, base, roots.object, roots.type_type, ] { vm.release(object).unwrap(); } vm.collect_cycles(); assert_eq!(vm.object_count(), 0); } #[test] fn unknown_forward_names_defer_until_call_without_entering_the_body() { let mut vm = Vm::new().unwrap(); let roots = vm.bootstrap_types().unwrap(); vm.set_source_type_checks(true).unwrap(); let globals = vm.new_dict().unwrap(); vm.dict_set(globals, Value::Text("ran".into()), Value::Number(0.into())) .unwrap(); let base_fn = source( &mut vm, globals, "Base", &["Base", "Number"], "Number", Value::Number(0.into()), ); let base = class(&mut vm, "Base", roots.object, base_fn).unwrap(); let child_fn = source( &mut vm, globals, "Child", &["Child", "Later"], "Number", Value::Number(0.into()), ); let child = class(&mut vm, "Child", base, child_fn).unwrap(); let receiver = vm.new_object().unwrap(); vm.attach_type(receiver, child).unwrap(); assert!(matches!( vm.call( child_fn, &[Value::Object(receiver), Value::Number(1.into())] ), Err(VmError::UnknownAnnotation(_)) )); let later = vm .new_type("Later", roots.object, TypeConstructor::Record) .unwrap(); vm.dict_set(globals, Value::Text("Later".into()), Value::Object(later)) .unwrap(); assert!(matches!( vm.call( child_fn, &[Value::Object(receiver), Value::Number(1.into())] ), Err(VmError::OverrideViolation { .. }) )); assert_eq!( vm.dict_get(globals, &Value::Text("ran".into())).unwrap(), Some(Value::Number(0.into())) ); for object in [ receiver, child, child_fn, base, base_fn, later, globals, roots.object, roots.type_type, ] { vm.release(object).unwrap(); } vm.collect_cycles(); assert_eq!(vm.object_count(), 0); } #[test] fn verified_source_read_only_effects_constrain_native_overrides() { let mut vm = Vm::new().unwrap(); let roots = vm.bootstrap_types().unwrap(); vm.set_source_type_checks(true).unwrap(); let globals = vm.new_dict().unwrap(); let code = EndorCode::from_instructions( &[ Instruction::new(Opcode::LoadFast, 1), Instruction::simple(Opcode::ReturnValue), ], vec![], CodeLayout { parameters: vec!["self".into(), "value".into()], locals: vec!["self".into(), "value".into()], ..CodeLayout::default() }, ) .unwrap() .with_source_signature(SourceSignature { parameters: vec!["PureBase".into(), "Number".into()], return_type: "Number".into(), method_owner: Some("PureBase".into()), }) .unwrap(); assert!(code.is_statically_read_only()); let code = vm.new_code(code).unwrap(); let source = vm .new_function("check", code, globals, vec![], vec![]) .unwrap(); let base = class(&mut vm, "PureBase", roots.object, source).unwrap(); let child = vm.new_type("Child", base, TypeConstructor::Record).unwrap(); assert_eq!(vm.call_effect(source).unwrap(), Some(CallEffect::ReadOnly)); let receiver = vm.new_object().unwrap(); vm.attach_type(receiver, child).unwrap(); let Value::Object(bound) = vm.bind_attr(receiver, "check").unwrap() else { panic!("bound method") }; assert_eq!(vm.call_effect(bound).unwrap(), Some(CallEffect::ReadOnly)); let mut functions = vec![]; for effect in [ CallEffect::Mutating, CallEffect::External, CallEffect::ReadOnly, CallEffect::Delegated, ] { let function = vm .allocate(ContextNativeFunction::new( "check", Native(CallSignature::exact( vec![T::Any, T::Number], T::Number, effect, )), vec![], )) .unwrap(); let version = vm.change_version(child).unwrap(); let update = vm.define_method(child, "check", function); if matches!(effect, CallEffect::Mutating | CallEffect::External) { assert!(matches!( update, Err(VmError::OverrideViolation { reason: "override expands known effects", .. }) )); assert_eq!(vm.change_version(child).unwrap(), version); } else { update.unwrap(); } assert_eq!( vm.call_effect(function).unwrap(), (effect != CallEffect::Delegated).then_some(effect) ); functions.push(function); } for object in functions.into_iter().chain([ bound, receiver, child, base, source, code, globals, roots.object, roots.type_type, ]) { vm.release(object).unwrap(); } vm.collect_cycles(); assert_eq!(vm.object_count(), 0); } #[test] fn shared_local_assignment_and_dynamic_calls_are_not_inferred_pure() { let assignment = EndorCode::from_instructions( &[ Instruction::new(Opcode::LoadFast, 1), Instruction::new(Opcode::StoreFast, 0), Instruction::new(Opcode::LoadFast, 0), Instruction::simple(Opcode::ReturnValue), ], vec![], CodeLayout { parameters: vec!["target".into(), "value".into()], locals: vec!["target".into(), "value".into()], ..CodeLayout::default() }, ) .unwrap(); assert!(!assignment.is_statically_read_only()); let captured = EndorCode::from_instructions( &[ Instruction::new(Opcode::LoadConst, 0), Instruction::new(Opcode::StoreDeref, 0), Instruction::new(Opcode::LoadConst, 0), Instruction::simple(Opcode::ReturnValue), ], vec![Value::Nil], CodeLayout { free_variables: vec!["captured".into()], ..CodeLayout::default() }, ) .unwrap(); assert!(!captured.is_statically_read_only()); let call = EndorCode::from_instructions( &[ Instruction::new(Opcode::LoadGlobal, 0), Instruction::simple(Opcode::PushNull), Instruction::new(Opcode::Call, 0), Instruction::simple(Opcode::ReturnValue), ], vec![], CodeLayout { names: vec!["dynamic".into()], ..CodeLayout::default() }, ) .unwrap(); assert!(!call.is_statically_read_only()); } fn global_caller(vm: &mut Vm, globals: ObjectRef, name: &str) -> ObjectRef { let code = EndorCode::from_instructions( &[ Instruction::new(Opcode::LoadGlobal, 0), Instruction::simple(Opcode::PushNull), Instruction::new(Opcode::Call, 0), Instruction::simple(Opcode::ReturnValue), ], vec![], CodeLayout { names: vec![name.into()], ..CodeLayout::default() }, ) .unwrap(); let code = vm.new_code(code).unwrap(); let function = vm .new_function("caller", code, globals, vec![], vec![]) .unwrap(); vm.release(code).unwrap(); function } #[test] fn transitive_effect_queries_follow_rebinding_closures_and_recursive_graphs() { let mut vm = Vm::new().unwrap(); let globals = vm.new_dict().unwrap(); let builtins = vm.new_dict().unwrap(); vm.dict_set( globals, Value::Text("__builtins__".into()), Value::Object(builtins), ) .unwrap(); let pure = vm .allocate(ContextNativeFunction::new( "pure", Native(CallSignature::exact( vec![], T::Number, CallEffect::ReadOnly, )), vec![], )) .unwrap(); let impure = vm .allocate(ContextNativeFunction::new( "impure", Native(CallSignature::exact( vec![], T::Number, CallEffect::Mutating, )), vec![], )) .unwrap(); vm.dict_set(builtins, Value::Text("target".into()), Value::Object(pure)) .unwrap(); let wrapper = global_caller(&mut vm, globals, "target"); let outer = global_caller(&mut vm, globals, "wrapper"); vm.dict_set( globals, Value::Text("wrapper".into()), Value::Object(wrapper), ) .unwrap(); assert_eq!(vm.call_effect(outer).unwrap(), Some(CallEffect::ReadOnly)); vm.dict_set(globals, Value::Text("target".into()), Value::Object(impure)) .unwrap(); assert_eq!(vm.call_effect(outer).unwrap(), None); vm.dict_delete(globals, &Value::Text("target".into())) .unwrap(); assert_eq!(vm.call_effect(outer).unwrap(), Some(CallEffect::ReadOnly)); let cell = vm.new_cell(Some(Value::Object(pure))).unwrap(); let code = EndorCode::from_instructions( &[ Instruction::new(Opcode::LoadDeref, 0), Instruction::simple(Opcode::PushNull), Instruction::new(Opcode::Call, 0), Instruction::simple(Opcode::ReturnValue), ], vec![], CodeLayout { free_variables: vec!["target".into()], ..CodeLayout::default() }, ) .unwrap(); let code = vm.new_code(code).unwrap(); let closure = vm .new_function("closure", code, globals, vec![], vec![cell]) .unwrap(); assert_eq!(vm.call_effect(closure).unwrap(), Some(CallEffect::ReadOnly)); vm.cell_set(cell, Value::Object(impure)).unwrap(); assert_eq!(vm.call_effect(closure).unwrap(), None); vm.cell_set(cell, Value::Object(pure)).unwrap(); assert_eq!(vm.call_effect(closure).unwrap(), Some(CallEffect::ReadOnly)); let recursive = global_caller(&mut vm, globals, "recursive"); vm.dict_set( globals, Value::Text("recursive".into()), Value::Object(recursive), ) .unwrap(); // No function body is executed, including this non-terminating call graph. assert_eq!( vm.call_effect(recursive).unwrap(), Some(CallEffect::ReadOnly) ); vm.dict_set( globals, Value::Text("recursive".into()), Value::Object(impure), ) .unwrap(); assert_eq!(vm.call_effect(recursive).unwrap(), None); for object in [ recursive, closure, code, cell, outer, wrapper, pure, impure, builtins, globals, ] { vm.release(object).unwrap(); } vm.collect_cycles(); assert_eq!(vm.object_count(), 0); } #[test] fn effect_control_flow_joins_do_not_keep_a_stale_known_call_target() { let mut vm = Vm::new().unwrap(); let globals = vm.new_dict().unwrap(); let pure = vm .allocate(ContextNativeFunction::new( "pure", Native(CallSignature::exact( vec![], T::Number, CallEffect::ReadOnly, )), vec![], )) .unwrap(); let impure = vm .allocate(ContextNativeFunction::new( "impure", Native(CallSignature::exact( vec![], T::Number, CallEffect::External, )), vec![], )) .unwrap(); for name in ["left", "right"] { vm.dict_set(globals, Value::Text(name.into()), Value::Object(pure)) .unwrap(); } let code = EndorCode::from_instructions( &[ Instruction::new(Opcode::LoadFast, 0), Instruction::new(Opcode::PopJumpIfFalse, 2), Instruction::new(Opcode::LoadGlobal, 0), Instruction::new(Opcode::JumpForward, 1), Instruction::new(Opcode::LoadGlobal, 1), Instruction::simple(Opcode::PushNull), Instruction::new(Opcode::Call, 0), Instruction::simple(Opcode::ReturnValue), ], vec![], CodeLayout { parameters: vec!["condition".into()], locals: vec!["condition".into()], names: vec!["left".into(), "right".into()], ..CodeLayout::default() }, ) .unwrap(); let code = vm.new_code(code).unwrap(); let function = vm .new_function("choose", code, globals, vec![], vec![]) .unwrap(); assert_eq!( vm.call_effect(function).unwrap(), Some(CallEffect::ReadOnly) ); vm.dict_set(globals, Value::Text("right".into()), Value::Object(impure)) .unwrap(); assert_eq!(vm.call_effect(function).unwrap(), None); vm.dict_set(globals, Value::Text("right".into()), Value::Object(pure)) .unwrap(); assert_eq!( vm.call_effect(function).unwrap(), Some(CallEffect::ReadOnly) ); for object in [function, code, pure, impure, globals] { vm.release(object).unwrap(); } vm.collect_cycles(); assert_eq!(vm.object_count(), 0); } #[test] fn bound_source_effects_follow_the_actual_receiver_method_and_instance_field() { let mut vm = Vm::new().unwrap(); let roots = vm.bootstrap_types().unwrap(); let globals = vm.new_dict().unwrap(); let definition = vm .new_type("Receiver", roots.object, TypeConstructor::Record) .unwrap(); let pure = vm .allocate(ContextNativeFunction::new( "helper", Native(CallSignature::exact( vec![T::Any], T::Number, CallEffect::ReadOnly, )), vec![], )) .unwrap(); let impure = vm .allocate(ContextNativeFunction::new( "helper", Native(CallSignature::exact( vec![T::Any], T::Number, CallEffect::Mutating, )), vec![], )) .unwrap(); let code = EndorCode::from_instructions( &[ Instruction::new(Opcode::LoadFast, 0), Instruction::new(Opcode::LoadAttr, 0), Instruction::simple(Opcode::PushNull), Instruction::new(Opcode::Call, 0), Instruction::simple(Opcode::ReturnValue), ], vec![], CodeLayout { parameters: vec!["self".into()], locals: vec!["self".into()], names: vec!["helper".into()], ..CodeLayout::default() }, ) .unwrap(); let code = vm.new_code(code).unwrap(); let function = vm .new_function("check", code, globals, vec![], vec![]) .unwrap(); vm.define_method(definition, "helper", pure).unwrap(); vm.define_method(definition, "check", function).unwrap(); let receiver = vm.new_object().unwrap(); vm.attach_type(receiver, definition).unwrap(); let Value::Object(bound) = vm.bind_attr(receiver, "check").unwrap() else { panic!("bound") }; assert_eq!(vm.call_effect(function).unwrap(), None); assert_eq!(vm.call_effect(bound).unwrap(), Some(CallEffect::ReadOnly)); vm.define_method(definition, "helper", impure).unwrap(); assert_eq!(vm.call_effect(bound).unwrap(), None); vm.define_method(definition, "helper", pure).unwrap(); assert_eq!(vm.call_effect(bound).unwrap(), Some(CallEffect::ReadOnly)); vm.set_attr(receiver, "helper", Value::Object(impure)) .unwrap(); assert_eq!(vm.call_effect(bound).unwrap(), None); vm.del_attr(receiver, "helper").unwrap(); assert_eq!(vm.call_effect(bound).unwrap(), Some(CallEffect::ReadOnly)); for object in [ bound, receiver, function, code, pure, impure, definition, globals, roots.object, roots.type_type, ] { vm.release(object).unwrap(); } vm.collect_cycles(); assert_eq!(vm.object_count(), 0); }