// Copyright (c) Microsoft Corporation. // Licensed under the MIT License. use crate::rvm::instructions::{Instruction, LiteralOrRegister}; use crate::rvm::program::Program; use crate::value::Value; use alloc::collections::BTreeSet; use alloc::vec::Vec; use core::mem; use super::errors::{Result, VmError}; use super::execution_model::{ExecutionMode, SuspendReason}; use super::loops::LoopParams; use super::machine::RegoVM; pub(super) enum InstructionOutcome { Continue, Return(Value), Break, Suspend { reason: SuspendReason }, } impl RegoVM { pub(super) fn execute_instruction( &mut self, program: &Program, instruction: Instruction, ) -> Result { self.execute_load_and_move(program, instruction) } fn execute_load_and_move( &mut self, program: &Program, instruction: Instruction, ) -> Result { use Instruction::*; match instruction { Load { dest, literal_idx } => { if let Some(value) = program.literals.get(usize::from(literal_idx)) { self.set_register(dest, value.clone())?; Ok(InstructionOutcome::Continue) } else { Err(VmError::LiteralIndexOutOfBounds { index: literal_idx, pc: self.pc, }) } } LoadTrue { dest } => { self.set_register(dest, Value::Bool(true))?; Ok(InstructionOutcome::Continue) } LoadFalse { dest } => { self.set_register(dest, Value::Bool(false))?; Ok(InstructionOutcome::Continue) } LoadNull { dest } => { self.set_register(dest, Value::Null)?; Ok(InstructionOutcome::Continue) } LoadBool { dest, value } => { self.set_register(dest, Value::Bool(value))?; Ok(InstructionOutcome::Continue) } LoadData { dest } => { self.set_register(dest, self.data.clone())?; Ok(InstructionOutcome::Continue) } LoadInput { dest } => { self.set_register(dest, self.input.clone())?; Ok(InstructionOutcome::Continue) } Move { dest, src } => { let value = self.get_register(src)?.clone(); self.set_register(dest, value)?; Ok(InstructionOutcome::Continue) } other => self.execute_arithmetic_instruction(program, other), } } fn execute_arithmetic_instruction( &mut self, program: &Program, instruction: Instruction, ) -> Result { use Instruction::*; match instruction { Add { dest, left, right } => { let a = self.get_register(left)?; let b = self.get_register(right)?; if a == &Value::Undefined || b == &Value::Undefined { self.set_register(dest, Value::Undefined)?; return Ok(InstructionOutcome::Continue); } let result = self.add_values(a, b)?; self.set_register(dest, result)?; Ok(InstructionOutcome::Continue) } Sub { dest, left, right } => { let a = self.get_register(left)?; let b = self.get_register(right)?; if a == &Value::Undefined || b == &Value::Undefined { self.set_register(dest, Value::Undefined)?; return Ok(InstructionOutcome::Continue); } let result = self.sub_values(a, b)?; self.set_register(dest, result)?; Ok(InstructionOutcome::Continue) } Mul { dest, left, right } => { let a = self.get_register(left)?; let b = self.get_register(right)?; if a == &Value::Undefined || b == &Value::Undefined { self.set_register(dest, Value::Undefined)?; return Ok(InstructionOutcome::Continue); } let result = self.mul_values(a, b)?; self.set_register(dest, result)?; Ok(InstructionOutcome::Continue) } Div { dest, left, right } => { let a = self.get_register(left)?; let b = self.get_register(right)?; if a == &Value::Undefined || b == &Value::Undefined { self.set_register(dest, Value::Undefined)?; return Ok(InstructionOutcome::Continue); } let result = self.div_values(a, b)?; self.set_register(dest, result)?; Ok(InstructionOutcome::Continue) } Mod { dest, left, right } => { let a = self.get_register(left)?; let b = self.get_register(right)?; if a == &Value::Undefined || b == &Value::Undefined { self.set_register(dest, Value::Undefined)?; return Ok(InstructionOutcome::Continue); } let result = self.mod_values(a, b)?; self.set_register(dest, result)?; Ok(InstructionOutcome::Continue) } other => self.execute_comparison_instruction(program, other), } } fn execute_comparison_instruction( &mut self, program: &Program, instruction: Instruction, ) -> Result { use Instruction::*; match instruction { Eq { dest, left, right } => { let a = self.get_register(left)?; let b = self.get_register(right)?; if a == &Value::Undefined || b == &Value::Undefined { self.set_register(dest, Value::Undefined)?; return Ok(InstructionOutcome::Continue); } self.set_register(dest, Value::Bool(a == b))?; Ok(InstructionOutcome::Continue) } Ne { dest, left, right } => { let a = self.get_register(left)?; let b = self.get_register(right)?; if a == &Value::Undefined || b == &Value::Undefined { self.set_register(dest, Value::Undefined)?; return Ok(InstructionOutcome::Continue); } self.set_register(dest, Value::Bool(a != b))?; Ok(InstructionOutcome::Continue) } Lt { dest, left, right } => { let a = self.get_register(left)?; let b = self.get_register(right)?; if a == &Value::Undefined || b == &Value::Undefined { self.set_register(dest, Value::Undefined)?; return Ok(InstructionOutcome::Continue); } if self.strict_builtin_errors && mem::discriminant(a) != mem::discriminant(b) { return Err(VmError::ArithmeticError { message: alloc::format!( "#undefined: cannot compare values of different types (left={a:?}, right={b:?})" ), pc: self.pc, }); } self.set_register(dest, Value::Bool(a < b))?; Ok(InstructionOutcome::Continue) } Le { dest, left, right } => { let a = self.get_register(left)?; let b = self.get_register(right)?; if a == &Value::Undefined || b == &Value::Undefined { self.set_register(dest, Value::Undefined)?; return Ok(InstructionOutcome::Continue); } if self.strict_builtin_errors && mem::discriminant(a) != mem::discriminant(b) { return Err(VmError::ArithmeticError { message: alloc::format!( "#undefined: cannot compare values of different types (left={a:?}, right={b:?})" ), pc: self.pc, }); } self.set_register(dest, Value::Bool(a <= b))?; Ok(InstructionOutcome::Continue) } Gt { dest, left, right } => { let a = self.get_register(left)?; let b = self.get_register(right)?; if a == &Value::Undefined || b == &Value::Undefined { self.set_register(dest, Value::Undefined)?; return Ok(InstructionOutcome::Continue); } if self.strict_builtin_errors && mem::discriminant(a) != mem::discriminant(b) { return Err(VmError::ArithmeticError { message: alloc::format!( "#undefined: cannot compare values of different types (left={a:?}, right={b:?})" ), pc: self.pc, }); } self.set_register(dest, Value::Bool(a > b))?; Ok(InstructionOutcome::Continue) } Ge { dest, left, right } => { let a = self.get_register(left)?; let b = self.get_register(right)?; if a == &Value::Undefined || b == &Value::Undefined { self.set_register(dest, Value::Undefined)?; return Ok(InstructionOutcome::Continue); } if self.strict_builtin_errors && mem::discriminant(a) != mem::discriminant(b) { return Err(VmError::ArithmeticError { message: alloc::format!( "#undefined: cannot compare values of different types (left={a:?}, right={b:?})" ), pc: self.pc, }); } self.set_register(dest, Value::Bool(a >= b))?; Ok(InstructionOutcome::Continue) } And { dest, left, right } => { let left_value = self.get_register(left)?; let right_value = self.get_register(right)?; if left_value == &Value::Undefined || right_value == &Value::Undefined { self.set_register(dest, Value::Undefined)?; return Ok(InstructionOutcome::Continue); } match (self.to_bool(left_value), self.to_bool(right_value)) { (Some(a), Some(b)) => { self.set_register(dest, Value::Bool(a && b))?; Ok(InstructionOutcome::Continue) } _ => Err(VmError::ArithmeticError { message: alloc::format!( "#undefined: logical AND expects booleans (left={left_value:?}, right={right_value:?})" ), pc: self.pc, }), } } Or { dest, left, right } => { let left_value = self.get_register(left)?; let right_value = self.get_register(right)?; if left_value == &Value::Undefined || right_value == &Value::Undefined { self.set_register(dest, Value::Undefined)?; return Ok(InstructionOutcome::Continue); } match (self.to_bool(left_value), self.to_bool(right_value)) { (Some(a), Some(b)) => { self.set_register(dest, Value::Bool(a || b))?; Ok(InstructionOutcome::Continue) } _ => Err(VmError::ArithmeticError { message: alloc::format!( "#undefined: logical OR expects booleans (left={left_value:?}, right={right_value:?})" ), pc: self.pc, }), } } Not { dest, operand } => { let operand_value = self.get_register(operand)?; // In Rego, `not expr` succeeds when `expr` is undefined or false, // and fails for any other defined value (including non-booleans like 42). let negated = match *operand_value { Value::Undefined => true, Value::Bool(b) => !b, _ => false, }; self.set_register(dest, Value::Bool(negated))?; Ok(InstructionOutcome::Continue) } AssertEq { left, right } => { let a = self.get_register(left)?; let b = self.get_register(right)?; let passed = a != &Value::Undefined && b != &Value::Undefined && a == b; self.handle_condition(passed)?; Ok(InstructionOutcome::Continue) } AssertNot { operand } => { let value = self.get_register(operand)?; let passed = match *value { Value::Undefined => true, Value::Bool(b) => !b, _ => false, }; self.handle_condition(passed)?; Ok(InstructionOutcome::Continue) } AssertCondition { condition } => { let value = self.get_register(condition)?; let condition_result = match *value { Value::Bool(b) => b, Value::Undefined => false, _ => true, }; self.handle_condition(condition_result)?; Ok(InstructionOutcome::Continue) } AssertNotUndefined { register } => { let value = self.get_register(register)?; let is_undefined = matches!(value, Value::Undefined); self.handle_condition(!is_undefined)?; Ok(InstructionOutcome::Continue) } other => self.execute_call_instruction(program, other), } } fn execute_call_instruction( &mut self, program: &Program, instruction: Instruction, ) -> Result { use Instruction::*; match instruction { BuiltinCall { params_index } => { self.execute_builtin_call(params_index)?; Ok(InstructionOutcome::Continue) } HostAwait { dest, arg, id } => { let argument = self.get_register(arg)?.clone(); let identifier = self .registers .get(usize::from(id)) .cloned() .unwrap_or(Value::Undefined); match self.execution_mode { ExecutionMode::RunToCompletion => { let response = self.next_host_await_response(&identifier, dest)?; self.set_register(dest, response)?; Ok(InstructionOutcome::Continue) } ExecutionMode::Suspendable => Ok(InstructionOutcome::Suspend { reason: SuspendReason::HostAwait { dest, argument, identifier, }, }), } } FunctionCall { params_index } => { self.execute_function_call(params_index)?; Ok(InstructionOutcome::Continue) } Return { value } => { let result = self.get_register(value)?.clone(); Ok(InstructionOutcome::Return(result)) } CallRule { dest, rule_index } => { self.execute_call_rule(dest, rule_index)?; Ok(InstructionOutcome::Continue) } RuleInit { result_reg, rule_index, } => { self.execute_rule_init(result_reg, rule_index)?; Ok(InstructionOutcome::Continue) } DestructuringSuccess {} => Ok(InstructionOutcome::Break), RuleReturn {} => { self.execute_rule_return()?; Ok(InstructionOutcome::Break) } other => self.execute_collection_instruction(program, other), } } fn execute_collection_instruction( &mut self, program: &Program, instruction: Instruction, ) -> Result { use Instruction::*; match instruction { ObjectSet { obj, key, value } => { let key_value = self.get_register(key)?.clone(); let value_value = self.get_register(value)?.clone(); // Take ownership so Rc refcount stays at 1 and make_mut is a no-op. let mut obj_value = self.take_register(obj)?; if let Ok(obj_mut) = obj_value.as_object_mut() { obj_mut.insert(key_value, value_value); self.set_register(obj, obj_value)?; } else { let offending = obj_value.clone(); self.set_register(obj, obj_value)?; return Err(VmError::RegisterNotObject { register: obj, value: offending, pc: self.pc, }); } Ok(InstructionOutcome::Continue) } ObjectCreate { params_index } => { let params = program .instruction_data .get_object_create_params(params_index) .ok_or(VmError::InvalidObjectCreateParams { index: params_index, pc: self.pc, available: program.instruction_data.object_create_params.len(), })?; let mut any_undefined = false; for &(_, value_reg) in params.literal_key_field_pairs() { if matches!(self.get_register(value_reg)?, Value::Undefined) { any_undefined = true; break; } } if !any_undefined { for &(key_reg, value_reg) in params.field_pairs() { if matches!(self.get_register(key_reg)?, Value::Undefined) || matches!(self.get_register(value_reg)?, Value::Undefined) { any_undefined = true; break; } } } if any_undefined { self.set_register(params.dest, Value::Undefined)?; } else { let mut obj_value = program .literals .get(usize::from(params.template_literal_idx)) .ok_or(VmError::InvalidTemplateLiteralIndex { index: params.template_literal_idx, pc: self.pc, available: program.literals.len(), })? .clone(); if let Ok(obj_mut) = obj_value.as_object_mut() { let mut literal_updates = params.literal_key_field_pairs().iter(); let mut current_literal_update = literal_updates.next(); for (key, value) in obj_mut.iter_mut() { if let Some(&(literal_idx, value_reg)) = current_literal_update { if let Some(literal_key) = program.literals.get(usize::from(literal_idx)) { if key == literal_key { *value = self.get_register(value_reg)?.clone(); current_literal_update = literal_updates.next(); } } } else { break; } } while let Some(&(literal_idx, value_reg)) = current_literal_update { if let Some(key_value) = program.literals.get(usize::from(literal_idx)) { let value_value = self.get_register(value_reg)?.clone(); obj_mut.insert(key_value.clone(), value_value); } current_literal_update = literal_updates.next(); } for &(key_reg, value_reg) in params.field_pairs() { let key_value = self.get_register(key_reg)?.clone(); let value_value = self.get_register(value_reg)?.clone(); obj_mut.insert(key_value, value_value); } } else { return Err(VmError::ObjectCreateInvalidTemplate { template: obj_value, pc: self.pc, }); } self.set_register(params.dest, obj_value)?; } Ok(InstructionOutcome::Continue) } Index { dest, container, key, } => { let key_value = self.get_register(key)?; let container_value = self.get_register(container)?; let result = container_value[key_value].clone(); self.set_register(dest, result)?; Ok(InstructionOutcome::Continue) } IndexLiteral { dest, container, literal_idx, } => { let container_value = self.get_register(container)?; if let Some(key_value) = program.literals.get(usize::from(literal_idx)) { let result = container_value[key_value].clone(); self.set_register(dest, result)?; Ok(InstructionOutcome::Continue) } else { Err(VmError::LiteralIndexOutOfBounds { index: literal_idx, pc: self.pc, }) } } ArrayNew { dest } => { let empty_array = Value::Array(crate::Rc::new(Vec::new())); self.set_register(dest, empty_array)?; Ok(InstructionOutcome::Continue) } ArrayPush { arr, value } => { let value_to_push = self.get_register(value)?.clone(); // Take ownership so Rc refcount stays at 1 and make_mut is a no-op. let mut arr_value = self.take_register(arr)?; if let Ok(arr_mut) = arr_value.as_array_mut() { arr_mut.push(value_to_push); self.set_register(arr, arr_value)?; } else { let offending = arr_value.clone(); self.set_register(arr, arr_value)?; return Err(VmError::RegisterNotArray { register: arr, value: offending, pc: self.pc, }); } Ok(InstructionOutcome::Continue) } ArrayCreate { params_index } => { if let Some(params) = program .instruction_data .get_array_create_params(params_index) { let mut any_undefined = false; for ® in params.element_registers() { if matches!(self.get_register(reg)?, Value::Undefined) { any_undefined = true; break; } } if any_undefined { self.set_register(params.dest, Value::Undefined)?; } else { let elements: Vec = params .element_registers() .iter() .map(|®| self.get_register(reg).cloned()) .collect::>>()?; let array_value = Value::Array(crate::Rc::new(elements)); self.set_register(params.dest, array_value)?; } Ok(InstructionOutcome::Continue) } else { Err(VmError::InvalidArrayCreateParams { index: params_index, pc: self.pc, available: program.instruction_data.array_create_params.len(), }) } } SetNew { dest } => { let empty_set = Value::Set(crate::Rc::new(BTreeSet::new())); self.set_register(dest, empty_set)?; Ok(InstructionOutcome::Continue) } SetAdd { set, value } => { let value_to_add = self.get_register(value)?.clone(); // Take ownership so Rc refcount stays at 1 and make_mut is a no-op. let mut set_value = self.take_register(set)?; if let Ok(set_mut) = set_value.as_set_mut() { set_mut.insert(value_to_add); self.set_register(set, set_value)?; } else { let offending = set_value.clone(); self.set_register(set, set_value)?; return Err(VmError::RegisterNotSet { register: set, value: offending, pc: self.pc, }); } Ok(InstructionOutcome::Continue) } SetCreate { params_index } => { if let Some(params) = program.instruction_data.get_set_create_params(params_index) { let mut any_undefined = false; for ® in params.element_registers() { if matches!(self.get_register(reg)?, Value::Undefined) { any_undefined = true; break; } } if any_undefined { self.set_register(params.dest, Value::Undefined)?; } else { let mut set = BTreeSet::new(); for ® in params.element_registers() { set.insert(self.get_register(reg)?.clone()); } let set_value = Value::Set(crate::Rc::new(set)); self.set_register(params.dest, set_value)?; } Ok(InstructionOutcome::Continue) } else { Err(VmError::InvalidSetCreateParams { index: params_index, pc: self.pc, available: program.instruction_data.set_create_params.len(), }) } } Contains { dest, collection, value, } => { let value_to_check = self.get_register(value)?; let collection_value = self.get_register(collection)?; let result = match *collection_value { Value::Set(ref set_elements) => { Value::Bool(set_elements.contains(value_to_check)) } Value::Array(ref array_items) => { Value::Bool(array_items.contains(value_to_check)) } Value::Object(ref object_fields) => { Value::Bool(object_fields.values().any(|v| v == value_to_check)) } _ => Value::Bool(false), }; self.set_register(dest, result)?; Ok(InstructionOutcome::Continue) } Count { dest, collection } => { let collection_value = self.get_register(collection)?; let result = match *collection_value { Value::Array(ref array_items) => Value::from(array_items.len()), Value::Object(ref object_fields) => Value::from(object_fields.len()), Value::Set(ref set_elements) => Value::from(set_elements.len()), _ => Value::Undefined, }; self.set_register(dest, result)?; Ok(InstructionOutcome::Continue) } other => self.execute_loop_instruction(program, other), } } fn execute_loop_instruction( &mut self, program: &Program, instruction: Instruction, ) -> Result { use Instruction::*; match instruction { LoopStart { params_index } => { let loop_params_len = program.instruction_data.loop_params.len(); let loop_params = program .instruction_data .get_loop_params(params_index) .ok_or(VmError::InvalidLoopParams { index: params_index, pc: self.pc, available: loop_params_len, })?; let mode = loop_params.mode; let params = LoopParams { collection: loop_params.collection, key_reg: loop_params.key_reg, value_reg: loop_params.value_reg, result_reg: loop_params.result_reg, body_start: loop_params.body_start, loop_end: loop_params.loop_end, }; self.execute_loop_start(&mode, params)?; Ok(InstructionOutcome::Continue) } LoopNext { body_start, loop_end, } => { self.execute_loop_next(body_start, loop_end)?; Ok(InstructionOutcome::Continue) } Halt {} => { let result = self.get_register(0)?.clone(); Ok(InstructionOutcome::Return(result)) } other => self.execute_virtual_instruction(program, other), } } fn execute_virtual_instruction( &mut self, program: &Program, instruction: Instruction, ) -> Result { use Instruction::*; match instruction { ChainedIndex { params_index } => { let params = program .instruction_data .get_chained_index_params(params_index) .ok_or(VmError::InvalidChainedIndexParams { index: params_index, pc: self.pc, available: program.instruction_data.chained_index_params.len(), })?; let mut current_value = self.get_register(params.root)?.clone(); for component in ¶ms.path_components { let key_value = match *component { LiteralOrRegister::Literal(idx) => program .literals .get(usize::from(idx)) .ok_or(VmError::LiteralIndexOutOfBounds { index: idx, pc: self.pc, })? .clone(), LiteralOrRegister::Register(reg) => self.get_register(reg)?.clone(), }; current_value = current_value[&key_value].clone(); if current_value == Value::Undefined { break; } } self.set_register(params.dest, current_value)?; Ok(InstructionOutcome::Continue) } VirtualDataDocumentLookup { params_index } => { self.execute_virtual_data_document_lookup(params_index)?; Ok(InstructionOutcome::Continue) } ComprehensionBegin { params_index } => { let params = program .instruction_data .get_comprehension_begin_params(params_index) .ok_or(VmError::InvalidComprehensionBeginParams { index: params_index, pc: self.pc, available: program.instruction_data.comprehension_begin_params.len(), })? .clone(); self.execute_comprehension_begin(¶ms)?; Ok(InstructionOutcome::Continue) } ComprehensionYield { value_reg, key_reg } => { self.execute_comprehension_yield(value_reg, key_reg)?; Ok(InstructionOutcome::Continue) } ComprehensionEnd {} => { self.execute_comprehension_end()?; Ok(InstructionOutcome::Continue) } unexpected => Err(VmError::UnhandledInstruction { instruction: alloc::format!("{:?}", unexpected), pc: self.pc, }), } } }