chore: Harden instructions and program (#535)

Also enforce sane limits in program

Signed-off-by: Anand Krishnamoorthi <anakrish@microsoft.com>
This commit is contained in:
Anand Krishnamoorthi
2025-12-30 18:20:07 -06:00
committed by GitHub
parent 49958c2ece
commit 28891ef883
17 changed files with 670 additions and 634 deletions
+118 -116
View File
@@ -1,13 +1,8 @@
#![allow(
clippy::option_if_let_else,
clippy::unused_trait_names,
clippy::pattern_type_mismatch
)]
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
use alloc::format;
use alloc::string::{String, ToString};
use alloc::string::String;
use alloc::vec::Vec;
use super::{Instruction, InstructionData, LiteralOrRegister};
@@ -15,129 +10,136 @@ use super::{Instruction, InstructionData, LiteralOrRegister};
impl Instruction {
/// Get detailed display string with parameter resolution for debugging
pub fn display_with_params(&self, instruction_data: &InstructionData) -> String {
match self {
match *self {
Instruction::LoopStart { params_index } => {
if let Some(params) = instruction_data.get_loop_params(*params_index) {
format!(
"LOOP_START {:?} R({}) R({}) R({}) R({}) {} {}",
params.mode,
params.collection,
params.key_reg,
params.value_reg,
params.result_reg,
params.body_start,
params.loop_end
)
} else {
format!("LOOP_START P({}) [INVALID INDEX]", params_index)
}
}
Instruction::BuiltinCall { params_index } => {
if let Some(params) = instruction_data.get_builtin_call_params(*params_index) {
let args_str = params
.arg_registers()
.iter()
.map(|&r| format!("R({})", r))
.collect::<Vec<_>>()
.join(" ");
format!(
"BUILTIN_CALL R({}) B({}) [{}]",
params.dest, params.builtin_index, args_str
)
} else {
format!("BUILTIN_CALL P({}) [INVALID INDEX]", params_index)
}
instruction_data.get_loop_params(params_index).map_or_else(
|| format!("LOOP_START P({}) [INVALID INDEX]", params_index),
|params| {
format!(
"LOOP_START {:?} R({}) R({}) R({}) R({}) {} {}",
params.mode,
params.collection,
params.key_reg,
params.value_reg,
params.result_reg,
params.body_start,
params.loop_end
)
},
)
}
Instruction::BuiltinCall { params_index } => instruction_data
.get_builtin_call_params(params_index)
.map_or_else(
|| format!("BUILTIN_CALL P({}) [INVALID INDEX]", params_index),
|params| {
let args_str = params
.arg_registers()
.iter()
.map(|&r| format!("R({})", r))
.collect::<Vec<_>>()
.join(" ");
format!(
"BUILTIN_CALL R({}) B({}) [{}]",
params.dest, params.builtin_index, args_str
)
},
),
Instruction::HostAwait { dest, arg, id } => {
format!("HOST_AWAIT R({}) R({}) R({})", dest, arg, id)
}
Instruction::FunctionCall { params_index } => {
if let Some(params) = instruction_data.get_function_call_params(*params_index) {
let args_str = params
.arg_registers()
.iter()
.map(|&r| format!("R({})", r))
.collect::<Vec<_>>()
.join(" ");
format!(
"FUNCTION_CALL R({}) RULE({}) [{}]",
params.dest, params.func_rule_index, args_str
)
} else {
format!("FUNCTION_CALL P({}) [INVALID INDEX]", params_index)
}
}
Instruction::FunctionCall { params_index } => instruction_data
.get_function_call_params(params_index)
.map_or_else(
|| format!("FUNCTION_CALL P({}) [INVALID INDEX]", params_index),
|params| {
let args_str = params
.arg_registers()
.iter()
.map(|&r| format!("R({})", r))
.collect::<Vec<_>>()
.join(" ");
format!(
"FUNCTION_CALL R({}) RULE({}) [{}]",
params.dest, params.func_rule_index, args_str
)
},
),
Instruction::ObjectCreate { params_index } => {
if let Some(params) = instruction_data.get_object_create_params(*params_index) {
let mut field_parts = Vec::new();
instruction_data
.get_object_create_params(params_index)
.map_or_else(
|| format!("OBJECT_CREATE P({}) [INVALID INDEX]", params_index),
|params| {
let mut field_parts = Vec::new();
// Add literal key fields
for &(literal_idx, value_reg) in params.literal_key_field_pairs() {
field_parts.push(format!("L({}):R({})", literal_idx, value_reg));
}
// Add literal key fields
for &(literal_idx, value_reg) in params.literal_key_field_pairs() {
field_parts.push(format!("L({}):R({})", literal_idx, value_reg));
}
// Add non-literal key fields
for &(key_reg, value_reg) in params.field_pairs() {
field_parts.push(format!("R({}):R({})", key_reg, value_reg));
}
// Add non-literal key fields
for &(key_reg, value_reg) in params.field_pairs() {
field_parts.push(format!("R({}):R({})", key_reg, value_reg));
}
let fields_str = field_parts.join(" ");
format!(
"OBJECT_CREATE R({}) L({}) [{}]",
params.dest, params.template_literal_idx, fields_str
let fields_str = field_parts.join(" ");
format!(
"OBJECT_CREATE R({}) L({}) [{}]",
params.dest, params.template_literal_idx, fields_str
)
},
)
} else {
format!("OBJECT_CREATE P({}) [INVALID INDEX]", params_index)
}
}
Instruction::VirtualDataDocumentLookup { params_index } => {
if let Some(params) =
instruction_data.get_virtual_data_document_lookup_params(*params_index)
{
let components_str = params
.path_components
.iter()
.map(|comp| match comp {
LiteralOrRegister::Literal(idx) => format!("L({})", idx),
LiteralOrRegister::Register(reg) => format!("R({})", reg),
})
.collect::<Vec<_>>()
.join(".");
format!(
"VIRTUAL_DATA_DOCUMENT_LOOKUP R({}) [data.{}]",
params.dest, components_str
)
} else {
format!(
"VIRTUAL_DATA_DOCUMENT_LOOKUP P({}) [INVALID INDEX]",
params_index
)
}
}
Instruction::ComprehensionBegin { params_index } => {
if let Some(params) = instruction_data.get_comprehension_begin_params(*params_index)
{
format!(
"COMPREHENSION_BEGIN {:?} R({}) R({}) R({}) {} {}",
params.mode,
params.collection_reg,
params.key_reg,
params.value_reg,
params.body_start,
params.comprehension_end
)
} else {
format!("COMPREHENSION_BEGIN P({}) [INVALID INDEX]", params_index)
}
}
_ => self.to_string(),
Instruction::VirtualDataDocumentLookup { params_index } => instruction_data
.get_virtual_data_document_lookup_params(params_index)
.map_or_else(
|| {
format!(
"VIRTUAL_DATA_DOCUMENT_LOOKUP P({}) [INVALID INDEX]",
params_index
)
},
|params| {
let components_str = params
.path_components
.iter()
.map(|comp| match *comp {
LiteralOrRegister::Literal(idx) => format!("L({})", idx),
LiteralOrRegister::Register(reg) => format!("R({})", reg),
})
.collect::<Vec<_>>()
.join(".");
format!(
"VIRTUAL_DATA_DOCUMENT_LOOKUP R({}) [data.{}]",
params.dest, components_str
)
},
),
Instruction::ComprehensionBegin { params_index } => instruction_data
.get_comprehension_begin_params(params_index)
.map_or_else(
|| format!("COMPREHENSION_BEGIN P({}) [INVALID INDEX]", params_index),
|params| {
format!(
"COMPREHENSION_BEGIN {:?} R({}) R({}) R({}) {} {}",
params.mode,
params.collection_reg,
params.key_reg,
params.value_reg,
params.body_start,
params.comprehension_end
)
},
),
_ => format!("{}", self),
}
}
}
impl core::fmt::Display for Instruction {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
let text = match self {
let text = match *self {
Instruction::Load { dest, literal_idx } => {
format!("LOAD R({}) L({})", dest, literal_idx)
}
@@ -274,10 +276,10 @@ impl core::fmt::Display for Instruction {
Instruction::ComprehensionBegin { params_index } => {
format!("COMPREHENSION_BEGIN P({})", params_index)
}
Instruction::ComprehensionYield { value_reg, key_reg } => match key_reg {
Some(k) => format!("COMPREHENSION_YIELD R({}) R({})", k, value_reg),
None => format!("COMPREHENSION_YIELD R({})", value_reg),
},
Instruction::ComprehensionYield { value_reg, key_reg } => key_reg.as_ref().map_or_else(
|| format!("COMPREHENSION_YIELD R({})", value_reg),
|k| format!("COMPREHENSION_YIELD R({}) R({})", k, value_reg),
),
Instruction::ComprehensionEnd {} => String::from("COMPREHENSION_END"),
};
write!(f, "{}", text)
+12 -13
View File
@@ -1,4 +1,3 @@
#![allow(clippy::missing_const_for_fn)]
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
@@ -17,7 +16,7 @@ use serde::{Deserialize, Serialize};
/// RVM Instructions - simplified enum-based design
#[repr(C)]
#[derive(Debug, Clone, Serialize, Deserialize)]
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
pub enum Instruction {
/// Load literal value from literal table into register
Load {
@@ -320,47 +319,47 @@ pub enum Instruction {
impl Instruction {
/// Create a new LoopStart instruction with parameter table index
pub fn loop_start(params_index: u16) -> Self {
pub const fn loop_start(params_index: u16) -> Self {
Self::LoopStart { params_index }
}
/// Create a new BuiltinCall instruction with parameter table index
pub fn builtin_call(params_index: u16) -> Self {
pub const fn builtin_call(params_index: u16) -> Self {
Self::BuiltinCall { params_index }
}
/// Create a new HostAwait instruction
pub fn host_await(dest: u8, arg: u8, id: u8) -> Self {
pub const fn host_await(dest: u8, arg: u8, id: u8) -> Self {
Self::HostAwait { dest, arg, id }
}
/// Create a new FunctionCall instruction with parameter table index
pub fn function_call(params_index: u16) -> Self {
pub const fn function_call(params_index: u16) -> Self {
Self::FunctionCall { params_index }
}
/// Create a new ObjectCreate instruction with parameter table index
pub fn object_create(params_index: u16) -> Self {
pub const fn object_create(params_index: u16) -> Self {
Self::ObjectCreate { params_index }
}
/// Create a new ArrayCreate instruction with parameter table index
pub fn array_create(params_index: u16) -> Self {
pub const fn array_create(params_index: u16) -> Self {
Self::ArrayCreate { params_index }
}
/// Create a new SetCreate instruction with parameter table index
pub fn set_create(params_index: u16) -> Self {
pub const fn set_create(params_index: u16) -> Self {
Self::SetCreate { params_index }
}
/// Create a new ComprehensionBegin instruction with parameter table index
pub fn comprehension_begin(params_index: u16) -> Self {
pub const fn comprehension_begin(params_index: u16) -> Self {
Self::ComprehensionBegin { params_index }
}
/// Create a new ComprehensionYield instruction
pub fn comprehension_yield(value_reg: u8) -> Self {
pub const fn comprehension_yield(value_reg: u8) -> Self {
Self::ComprehensionYield {
value_reg,
key_reg: None,
@@ -368,7 +367,7 @@ impl Instruction {
}
/// Create a new ComprehensionYield instruction for object comprehensions
pub fn comprehension_yield_object(key_reg: u8, value_reg: u8) -> Self {
pub const fn comprehension_yield_object(key_reg: u8, value_reg: u8) -> Self {
Self::ComprehensionYield {
value_reg,
key_reg: Some(key_reg),
@@ -376,7 +375,7 @@ impl Instruction {
}
/// Create a new ComprehensionEnd instruction
pub fn comprehension_end() -> Self {
pub const fn comprehension_end() -> Self {
Self::ComprehensionEnd {}
}
}
+58 -56
View File
@@ -1,13 +1,5 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
#![allow(
clippy::indexing_slicing,
clippy::arithmetic_side_effects,
clippy::missing_const_for_fn,
clippy::as_conversions,
clippy::pattern_type_mismatch
)]
use alloc::vec::Vec;
use serde::{Deserialize, Serialize};
@@ -50,12 +42,13 @@ pub struct BuiltinCallParams {
impl BuiltinCallParams {
/// Get the number of arguments actually used
pub fn arg_count(&self) -> usize {
self.num_args as usize
usize::from(self.num_args)
}
/// Get argument register numbers as a slice
pub fn arg_registers(&self) -> &[u8] {
&self.args[..self.num_args as usize]
let count = usize::from(self.num_args).min(self.args.len());
self.args.get(..count).unwrap_or(&[])
}
}
@@ -76,12 +69,13 @@ pub struct FunctionCallParams {
impl FunctionCallParams {
/// Get the number of arguments actually used
pub fn arg_count(&self) -> usize {
self.num_args as usize
usize::from(self.num_args)
}
/// Get argument register numbers as a slice
pub fn arg_registers(&self) -> &[u8] {
&self.args[..self.num_args as usize]
let count = usize::from(self.num_args).min(self.args.len());
self.args.get(..count).unwrap_or(&[])
}
}
@@ -102,8 +96,10 @@ pub struct ObjectCreateParams {
impl ObjectCreateParams {
/// Get the total number of fields
pub fn field_count(&self) -> usize {
self.literal_key_fields.len() + self.fields.len()
pub const fn field_count(&self) -> usize {
self.literal_key_fields
.len()
.saturating_add(self.fields.len())
}
/// Get literal key field pairs as a slice
@@ -129,7 +125,7 @@ pub struct ArrayCreateParams {
impl ArrayCreateParams {
/// Get the number of elements
pub fn element_count(&self) -> usize {
pub const fn element_count(&self) -> usize {
self.elements.len()
}
@@ -151,7 +147,7 @@ pub struct SetCreateParams {
impl SetCreateParams {
/// Get the number of elements
pub fn element_count(&self) -> usize {
pub const fn element_count(&self) -> usize {
self.elements.len()
}
@@ -175,7 +171,7 @@ pub struct VirtualDataDocumentLookupParams {
impl VirtualDataDocumentLookupParams {
/// Get the number of path components
pub fn component_count(&self) -> usize {
pub const fn component_count(&self) -> usize {
self.path_components.len()
}
@@ -190,8 +186,8 @@ impl VirtualDataDocumentLookupParams {
pub fn literal_indices(&self) -> Vec<u16> {
self.path_components
.iter()
.filter_map(|c| match c {
LiteralOrRegister::Literal(idx) => Some(*idx),
.filter_map(|c| match *c {
LiteralOrRegister::Literal(idx) => Some(idx),
_ => None,
})
.collect()
@@ -201,8 +197,8 @@ impl VirtualDataDocumentLookupParams {
pub fn register_numbers(&self) -> Vec<u8> {
self.path_components
.iter()
.filter_map(|c| match c {
LiteralOrRegister::Register(reg) => Some(*reg),
.filter_map(|c| match *c {
LiteralOrRegister::Register(reg) => Some(reg),
_ => None,
})
.collect()
@@ -223,7 +219,7 @@ pub struct ChainedIndexParams {
impl ChainedIndexParams {
/// Get the number of path components
pub fn component_count(&self) -> usize {
pub const fn component_count(&self) -> usize {
self.path_components.len()
}
@@ -238,8 +234,8 @@ impl ChainedIndexParams {
pub fn literal_indices(&self) -> Vec<u16> {
self.path_components
.iter()
.filter_map(|c| match c {
LiteralOrRegister::Literal(idx) => Some(*idx),
.filter_map(|c| match *c {
LiteralOrRegister::Literal(idx) => Some(idx),
_ => None,
})
.collect()
@@ -249,8 +245,8 @@ impl ChainedIndexParams {
pub fn register_numbers(&self) -> Vec<u8> {
self.path_components
.iter()
.filter_map(|c| match c {
LiteralOrRegister::Register(reg) => Some(*reg),
.filter_map(|c| match *c {
LiteralOrRegister::Register(reg) => Some(reg),
_ => None,
})
.collect()
@@ -302,8 +298,13 @@ pub struct InstructionData {
}
impl InstructionData {
fn ensure_u16_index(len: usize) -> u16 {
debug_assert!(len <= usize::from(u16::MAX));
u16::try_from(len).unwrap_or(u16::MAX)
}
/// Create a new empty instruction data container
pub fn new() -> Self {
pub const fn new() -> Self {
Self {
loop_params: Vec::new(),
builtin_call_params: Vec::new(),
@@ -319,74 +320,74 @@ impl InstructionData {
/// Add loop parameters and return the index
pub fn add_loop_params(&mut self, params: LoopStartParams) -> u16 {
let index = self.loop_params.len();
let index = Self::ensure_u16_index(self.loop_params.len());
self.loop_params.push(params);
index as u16
index
}
/// Add builtin call parameters and return the index
pub fn add_builtin_call_params(&mut self, params: BuiltinCallParams) -> u16 {
let index = self.builtin_call_params.len();
let index = Self::ensure_u16_index(self.builtin_call_params.len());
self.builtin_call_params.push(params);
index as u16
index
}
/// Add function call parameters and return the index
pub fn add_function_call_params(&mut self, params: FunctionCallParams) -> u16 {
let index = self.function_call_params.len();
let index = Self::ensure_u16_index(self.function_call_params.len());
self.function_call_params.push(params);
index as u16
index
}
/// Add object create parameters and return the index
pub fn add_object_create_params(&mut self, params: ObjectCreateParams) -> u16 {
let index = self.object_create_params.len();
let index = Self::ensure_u16_index(self.object_create_params.len());
self.object_create_params.push(params);
index as u16
index
}
/// Add array create parameters and return the index
pub fn add_array_create_params(&mut self, params: ArrayCreateParams) -> u16 {
let index = self.array_create_params.len();
let index = Self::ensure_u16_index(self.array_create_params.len());
self.array_create_params.push(params);
index as u16
index
}
/// Add set create parameters and return the index
pub fn add_set_create_params(&mut self, params: SetCreateParams) -> u16 {
let index = self.set_create_params.len();
let index = Self::ensure_u16_index(self.set_create_params.len());
self.set_create_params.push(params);
index as u16
index
}
/// Get loop parameters by index
pub fn get_loop_params(&self, index: u16) -> Option<&LoopStartParams> {
self.loop_params.get(index as usize)
self.loop_params.get(usize::from(index))
}
/// Get builtin call parameters by index
pub fn get_builtin_call_params(&self, index: u16) -> Option<&BuiltinCallParams> {
self.builtin_call_params.get(index as usize)
self.builtin_call_params.get(usize::from(index))
}
/// Get function call parameters by index
pub fn get_function_call_params(&self, index: u16) -> Option<&FunctionCallParams> {
self.function_call_params.get(index as usize)
self.function_call_params.get(usize::from(index))
}
/// Get object create parameters by index
pub fn get_object_create_params(&self, index: u16) -> Option<&ObjectCreateParams> {
self.object_create_params.get(index as usize)
self.object_create_params.get(usize::from(index))
}
/// Get array create parameters by index
pub fn get_array_create_params(&self, index: u16) -> Option<&ArrayCreateParams> {
self.array_create_params.get(index as usize)
self.array_create_params.get(usize::from(index))
}
/// Get set create parameters by index
pub fn get_set_create_params(&self, index: u16) -> Option<&SetCreateParams> {
self.set_create_params.get(index as usize)
self.set_create_params.get(usize::from(index))
}
/// Add virtual data document lookup parameters and return the index
@@ -394,9 +395,9 @@ impl InstructionData {
&mut self,
params: VirtualDataDocumentLookupParams,
) -> u16 {
let index = self.virtual_data_document_lookup_params.len();
let index = Self::ensure_u16_index(self.virtual_data_document_lookup_params.len());
self.virtual_data_document_lookup_params.push(params);
index as u16
index
}
/// Get virtual data document lookup parameters by index
@@ -404,36 +405,37 @@ impl InstructionData {
&self,
index: u16,
) -> Option<&VirtualDataDocumentLookupParams> {
self.virtual_data_document_lookup_params.get(index as usize)
self.virtual_data_document_lookup_params
.get(usize::from(index))
}
/// Add chained index parameters and return the index
pub fn add_chained_index_params(&mut self, params: ChainedIndexParams) -> u16 {
let index = self.chained_index_params.len();
let index = Self::ensure_u16_index(self.chained_index_params.len());
self.chained_index_params.push(params);
index as u16
index
}
/// Get chained index parameters by index
pub fn get_chained_index_params(&self, index: u16) -> Option<&ChainedIndexParams> {
self.chained_index_params.get(index as usize)
self.chained_index_params.get(usize::from(index))
}
/// Get mutable reference to loop parameters by index
pub fn get_loop_params_mut(&mut self, index: u16) -> Option<&mut LoopStartParams> {
self.loop_params.get_mut(index as usize)
self.loop_params.get_mut(usize::from(index))
}
/// Add comprehension begin parameters and return the index
pub fn add_comprehension_begin_params(&mut self, params: ComprehensionBeginParams) -> u16 {
let index = self.comprehension_begin_params.len();
let index = Self::ensure_u16_index(self.comprehension_begin_params.len());
self.comprehension_begin_params.push(params);
index as u16
index
}
/// Get comprehension begin parameters by index
pub fn get_comprehension_begin_params(&self, index: u16) -> Option<&ComprehensionBeginParams> {
self.comprehension_begin_params.get(index as usize)
self.comprehension_begin_params.get(usize::from(index))
}
/// Get mutable reference to comprehension begin parameters by index
@@ -441,7 +443,7 @@ impl InstructionData {
&mut self,
index: u16,
) -> Option<&mut ComprehensionBeginParams> {
self.comprehension_begin_params.get_mut(index as usize)
self.comprehension_begin_params.get_mut(usize::from(index))
}
}