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
+106 -16
View File
@@ -1,12 +1,8 @@
#![allow(
clippy::missing_const_for_fn,
clippy::as_conversions,
clippy::unused_trait_names
)]
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
use alloc::string::{String, ToString};
use alloc::format;
use alloc::string::{String, ToString as _};
use alloc::vec::Vec;
use anyhow::Result as AnyResult;
use indexmap::IndexMap;
@@ -49,13 +45,13 @@ pub struct Program {
pub instruction_spans: Vec<Option<SpanInfo>>,
/// Main program entry point
pub main_entry_point: usize,
pub main_entry_point: u32,
/// Maximum register window size observed across all rule definitions
pub max_rule_window_size: usize,
pub max_rule_window_size: u8,
/// Register window size needed for entry point dispatch
pub dispatch_window_size: usize,
pub dispatch_window_size: u8,
/// Program metadata
pub metadata: ProgramMetadata,
@@ -92,6 +88,20 @@ impl Program {
pub const SERIALIZATION_VERSION: u32 = 3;
/// Magic bytes to identify Regorus program files
pub const MAGIC: [u8; 4] = *b"REGO";
/// Maximum instructions supported (matches u16 jump targets)
pub const MAX_INSTRUCTIONS: usize = 65_535; // u16::MAX
/// Maximum literals supported (matches u16 literal indices)
pub const MAX_LITERALS: usize = 65_535; // u16::MAX
/// Generous cap for rules within a single policy bundle
pub const MAX_RULES: usize = 4_000;
/// Generous cap for exported entry points
pub const MAX_ENTRY_POINTS: usize = 1_000;
/// Generous cap for source/helper files
pub const MAX_SOURCES: usize = 256;
/// Generous cap for builtin declarations
pub const MAX_BUILTINS: usize = 512;
/// Maximum path depth for rule paths (e.g., data.a.b.c.d.rule)
pub const MAX_PATH_DEPTH: usize = 32;
/// Create a new empty program
pub fn new() -> Self {
@@ -121,6 +131,86 @@ impl Program {
}
}
/// Validate that the program stays within supported bounds
pub fn validate_limits(&self) -> Result<(), String> {
if self.instructions.len() > Self::MAX_INSTRUCTIONS {
return Err(format!(
"Program exceeds max instructions ({} > {})",
self.instructions.len(),
Self::MAX_INSTRUCTIONS
));
}
if self.literals.len() > Self::MAX_LITERALS {
return Err(format!(
"Program exceeds max literals ({} > {})",
self.literals.len(),
Self::MAX_LITERALS
));
}
if self.rule_infos.len() > Self::MAX_RULES {
return Err(format!(
"Program exceeds max rules ({} > {})",
self.rule_infos.len(),
Self::MAX_RULES
));
}
if self.entry_points.len() > Self::MAX_ENTRY_POINTS {
return Err(format!(
"Program exceeds max entry points ({} > {})",
self.entry_points.len(),
Self::MAX_ENTRY_POINTS
));
}
if self.sources.len() > Self::MAX_SOURCES {
return Err(format!(
"Program exceeds max sources ({} > {})",
self.sources.len(),
Self::MAX_SOURCES
));
}
if self.builtin_info_table.len() > Self::MAX_BUILTINS {
return Err(format!(
"Program exceeds max builtins ({} > {})",
self.builtin_info_table.len(),
Self::MAX_BUILTINS
));
}
for params in &self.instruction_data.loop_params {
let body_end = core::cmp::max(params.body_start, params.loop_end);
if usize::from(body_end) > Self::MAX_INSTRUCTIONS {
return Err("Loop offsets exceed supported instruction range".to_string());
}
}
for params in &self.instruction_data.comprehension_begin_params {
let body_end = core::cmp::max(params.body_start, params.comprehension_end);
if usize::from(body_end) > Self::MAX_INSTRUCTIONS {
return Err("Comprehension offsets exceed supported instruction range".to_string());
}
}
for instr in &self.instructions {
if let &crate::rvm::Instruction::LoopNext {
body_start,
loop_end,
} = instr
{
let body_end = core::cmp::max(body_start, loop_end);
if usize::from(body_end) > Self::MAX_INSTRUCTIONS {
return Err("LoopNext offsets exceed supported instruction range".to_string());
}
}
}
Ok(())
}
/// Add a source file and return its index
pub fn add_source(&mut self, name: String, content: String) -> usize {
let source_file = SourceFile::new(name.clone(), content);
@@ -162,12 +252,12 @@ impl Program {
pub fn add_builtin_info(&mut self, builtin_info: BuiltinInfo) -> u16 {
let index = self.builtin_info_table.len();
self.builtin_info_table.push(builtin_info);
index as u16
u16::try_from(index).unwrap_or(u16::MAX)
}
/// Get builtin info by index
pub fn get_builtin_info(&self, index: u16) -> Option<&BuiltinInfo> {
self.builtin_info_table.get(index as usize)
self.builtin_info_table.get(usize::from(index))
}
/// Update loop parameters by index
@@ -281,7 +371,7 @@ impl Program {
/// Get resolved builtin function by index
pub fn get_resolved_builtin(&self, index: u16) -> Option<&BuiltinFcn> {
self.resolved_builtins.get(index as usize)
self.resolved_builtins.get(usize::from(index))
}
/// Check if resolved builtins are initialized
@@ -300,22 +390,22 @@ impl Program {
}
/// Get all entry points as IndexMap
pub fn get_entry_points(&self) -> &IndexMap<String, usize> {
pub const fn get_entry_points(&self) -> &IndexMap<String, usize> {
&self.entry_points
}
/// Check if recompilation is needed due to partial deserialization failure
pub fn needs_recompilation(&self) -> bool {
pub const fn needs_recompilation(&self) -> bool {
self.needs_recompilation
}
/// Mark that recompilation is needed
pub fn set_needs_recompilation(&mut self, needs_recompilation: bool) {
pub const fn set_needs_recompilation(&mut self, needs_recompilation: bool) {
self.needs_recompilation = needs_recompilation;
}
/// Check if the program is fully functional (not needing recompilation)
pub fn is_fully_functional(&self) -> bool {
pub const fn is_fully_functional(&self) -> bool {
!self.needs_recompilation
}
}
+125 -100
View File
@@ -1,26 +1,23 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
#![allow(
clippy::unwrap_used,
clippy::arithmetic_side_effects,
clippy::indexing_slicing,
clippy::option_if_let_else,
clippy::missing_const_for_fn,
clippy::as_conversions,
clippy::unused_trait_names,
clippy::pattern_type_mismatch
)]
#![allow(clippy::option_if_let_else)]
use alloc::format;
use alloc::string::{String, ToString};
use alloc::string::{String, ToString as _};
use alloc::vec::Vec;
use core::fmt::Write;
use core::fmt::{self, Write as _};
use crate::rvm::{
instructions::{Instruction, InstructionData, LoopMode},
program::Program,
};
// Writing into a String via fmt never fails, so we intentionally ignore writeln! results.
fn push_line(buf: &mut String, args: fmt::Arguments) {
let _ = buf.write_fmt(args);
let _ = buf.write_char('\n');
}
/// Configuration for assembly listing output
#[derive(Debug, Clone)]
pub struct AssemblyListingConfig {
@@ -61,65 +58,81 @@ pub fn generate_assembly_listing(program: &Program, config: &AssemblyListingConf
let mut active_ends: Vec<u16> = Vec::new();
// Add header
writeln!(
output,
"; RVM Assembly - {} instructions, {} literals, {} builtins",
program.instructions.len(),
program.literals.len(),
program.builtin_info_table.len()
)
.unwrap();
push_line(
&mut output,
format_args!(
"; RVM Assembly - {} instructions, {} literals, {} builtins",
program.instructions.len(),
program.literals.len(),
program.builtin_info_table.len()
),
);
// Add builtins table
if !program.builtin_info_table.is_empty() {
writeln!(output, ";").unwrap();
writeln!(output, "; BUILTINS TABLE:").unwrap();
push_line(&mut output, format_args!(";"));
push_line(&mut output, format_args!("; BUILTINS TABLE:"));
for (idx, builtin_info) in program.builtin_info_table.iter().enumerate() {
writeln!(output, "; B{:2}: {}", idx, builtin_info.name).unwrap();
push_line(
&mut output,
format_args!("; B{:2}: {}", idx, builtin_info.name),
);
}
}
// Add literals table
if config.show_literal_values && !program.literals.is_empty() {
writeln!(output, ";").unwrap();
writeln!(output, "; LITERALS (JSON values):").unwrap();
push_line(&mut output, format_args!(";"));
push_line(&mut output, format_args!("; LITERALS (JSON values):"));
for (idx, literal) in program.literals.iter().enumerate() {
let literal_json =
serde_json::to_string(literal).unwrap_or_else(|_| "<invalid>".to_string());
writeln!(output, "; L{:2}: {}", idx, literal_json).unwrap();
push_line(
&mut output,
format_args!("; L{:2}: {}", idx, literal_json),
);
}
}
// Add rules table if available
if !program.rule_infos.is_empty() {
writeln!(output, ";").unwrap();
writeln!(output, "; RULES TABLE:").unwrap();
push_line(&mut output, format_args!(";"));
push_line(&mut output, format_args!("; RULES TABLE:"));
for (idx, rule_info) in program.rule_infos.iter().enumerate() {
writeln!(output, "; R{:2}: {}", idx, rule_info.name).unwrap();
push_line(
&mut output,
format_args!("; R{:2}: {}", idx, rule_info.name),
);
}
}
writeln!(output, ";").unwrap();
push_line(&mut output, format_args!(";"));
for (pc, instruction) in program.instructions.iter().enumerate() {
// Handle rule transitions and add gaps
if let Instruction::RuleInit { rule_index, .. } = instruction {
if let &Instruction::RuleInit { rule_index, .. } = instruction {
// Add gap before new rule (except for the first rule)
if current_rule_index.is_some() {
writeln!(output).unwrap();
push_line(&mut output, format_args!(""));
}
current_rule_index = Some(*rule_index);
current_rule_index = Some(rule_index);
// Add rule name prefix
if let Some(rule_info) = program.rule_infos.get(*rule_index as usize) {
writeln!(output, "; ===== RULE: {} =====", rule_info.name).unwrap();
if let Some(rule_info) = program.rule_infos.get(usize::from(rule_index)) {
push_line(
&mut output,
format_args!("; ===== RULE: {} =====", rule_info.name),
);
} else {
writeln!(output, "; ===== RULE: rule_{} =====", rule_index).unwrap();
push_line(
&mut output,
format_args!("; ===== RULE: rule_{} =====", rule_index),
);
}
}
// Check if current PC matches any active end addresses (loops, comprehensions, rules)
let current_pc = pc as u16;
let current_pc = u16::try_from(pc).unwrap_or(u16::MAX);
while let Some(&end_addr) = active_ends.last() {
if current_pc >= end_addr {
active_ends.pop();
@@ -130,7 +143,7 @@ pub fn generate_assembly_listing(program: &Program, config: &AssemblyListingConf
}
// Handle explicit end instructions
match instruction {
match *instruction {
Instruction::LoopNext { .. } => {
// LoopNext already handled by end address tracking above
}
@@ -141,13 +154,13 @@ pub fn generate_assembly_listing(program: &Program, config: &AssemblyListingConf
}
// Special case: Block end instructions should be indented at their block level (one level out)
let effective_indent_level = match instruction {
let effective_indent_level = match *instruction {
Instruction::ComprehensionEnd {} => indent_level.saturating_sub(1),
Instruction::LoopNext { .. } => indent_level.saturating_sub(1),
_ => indent_level,
};
let indent = " ".repeat(effective_indent_level * config.indent_size);
let indent = " ".repeat(effective_indent_level.saturating_mul(config.indent_size));
// Format address
let addr_str = if config.show_addresses {
@@ -165,27 +178,27 @@ pub fn generate_assembly_listing(program: &Program, config: &AssemblyListingConf
config,
);
writeln!(output, "{}{}", addr_str, inst_str).unwrap();
push_line(&mut output, format_args!("{}{}", addr_str, inst_str));
// Increase indentation for loop/rule/comprehension starts and track their end addresses
match instruction {
match *instruction {
Instruction::LoopStart { params_index } => {
if let Some(params) = program.instruction_data.get_loop_params(*params_index) {
if let Some(params) = program.instruction_data.get_loop_params(params_index) {
active_ends.push(params.loop_end);
indent_level += 1;
indent_level = indent_level.saturating_add(1);
}
}
Instruction::ComprehensionBegin { params_index } => {
if let Some(params) = program
.instruction_data
.get_comprehension_begin_params(*params_index)
.get_comprehension_begin_params(params_index)
{
active_ends.push(params.comprehension_end);
indent_level += 1;
indent_level = indent_level.saturating_add(1);
}
}
Instruction::RuleInit { .. } => {
indent_level += 1;
indent_level = indent_level.saturating_add(1);
// Note: Rules end with RuleReturn, not an address, so we don't track them here
}
_ => {}
@@ -201,7 +214,7 @@ fn align_comment(base_text: &str, comment: &str, target_column: usize) -> String
if current_len >= target_column {
format!("{} ; {}", base_text, comment)
} else {
let padding = " ".repeat(target_column - current_len);
let padding = " ".repeat(target_column.saturating_sub(current_len));
format!("{}{} ; {}", base_text, padding, comment)
}
}
@@ -214,15 +227,16 @@ fn format_instruction_readable(
program: &Program,
config: &AssemblyListingConfig,
) -> String {
match instruction {
match *instruction {
Instruction::Load { dest, literal_idx } => {
let base = format!("{}Load r{} ← L{}", indent, dest, literal_idx);
let comment = if *literal_idx < program.literals.len() as u16 {
let literal_json = serde_json::to_string(&program.literals[*literal_idx as usize])
.unwrap_or_else(|_| "<invalid>".to_string());
format!("Load literal: {}", literal_json)
} else {
"Load literal: <invalid index>".to_string()
let comment = match program.literals.get(usize::from(literal_idx)) {
Some(literal) => {
let literal_json =
serde_json::to_string(literal).unwrap_or_else(|_| "<invalid>".to_string());
format!("Load literal: {}", literal_json)
}
None => "Load literal: <invalid index>".to_string(),
};
align_comment(&base, &comment, config.comment_column)
}
@@ -348,7 +362,7 @@ fn format_instruction_readable(
align_comment(&base, &comment, config.comment_column)
}
Instruction::BuiltinCall { params_index } => {
if let Some(params) = instruction_data.get_builtin_call_params(*params_index) {
if let Some(params) = instruction_data.get_builtin_call_params(params_index) {
let args_str = params
.arg_registers()
.iter()
@@ -358,7 +372,7 @@ fn format_instruction_readable(
let builtin_name = program
.builtin_info_table
.get(params.builtin_index as usize)
.get(usize::from(params.builtin_index))
.map(|info| info.name.as_str())
.unwrap_or("<invalid>");
@@ -381,7 +395,7 @@ fn format_instruction_readable(
}
}
Instruction::FunctionCall { params_index } => {
if let Some(params) = instruction_data.get_function_call_params(*params_index) {
if let Some(params) = instruction_data.get_function_call_params(params_index) {
let args_str = params
.arg_registers()
.iter()
@@ -391,7 +405,7 @@ fn format_instruction_readable(
let func_name = program
.rule_infos
.get(params.func_rule_index as usize)
.get(usize::from(params.func_rule_index))
.map(|info| info.name.as_str())
.unwrap_or("<invalid>");
@@ -440,7 +454,7 @@ fn format_instruction_readable(
Instruction::ObjectCreate { params_index } => {
let params = program
.instruction_data
.get_object_create_params(*params_index);
.get_object_create_params(params_index);
let base = format!(
"{}ObjectCreate r{}{{...}}",
indent,
@@ -477,15 +491,16 @@ fn format_instruction_readable(
"{}IndexLiteral r{} ← r{}[L{}]",
indent, dest, container, literal_idx
);
let comment = if *literal_idx < program.literals.len() as u16 {
let literal_json = serde_json::to_string(&program.literals[*literal_idx as usize])
.unwrap_or_else(|_| "<invalid>".to_string());
format!("Index with literal key: r{}[{}]", container, literal_json)
} else {
format!(
let comment = match program.literals.get(usize::from(literal_idx)) {
Some(literal) => {
let literal_json =
serde_json::to_string(literal).unwrap_or_else(|_| "<invalid>".to_string());
format!("Index with literal key: r{}[{}]", container, literal_json)
}
None => format!(
"Index with literal: r{}[L{}] (invalid index)",
container, literal_idx
)
),
};
align_comment(&base, &comment, config.comment_column)
}
@@ -499,7 +514,7 @@ fn format_instruction_readable(
align_comment(&base, &comment, config.comment_column)
}
Instruction::ArrayCreate { params_index } => {
if let Some(params) = instruction_data.get_array_create_params(*params_index) {
if let Some(params) = instruction_data.get_array_create_params(params_index) {
let elements = params
.element_registers()
.iter()
@@ -526,7 +541,7 @@ fn format_instruction_readable(
align_comment(&base, &comment, config.comment_column)
}
Instruction::SetCreate { params_index } => {
if let Some(params) = instruction_data.get_set_create_params(*params_index) {
if let Some(params) = instruction_data.get_set_create_params(params_index) {
let elements = params
.element_registers()
.iter()
@@ -574,7 +589,7 @@ fn format_instruction_readable(
align_comment(&base, &comment, config.comment_column)
}
Instruction::LoopStart { params_index } => {
if let Some(params) = instruction_data.get_loop_params(*params_index) {
if let Some(params) = instruction_data.get_loop_params(params_index) {
let mode_str = match params.mode {
LoopMode::Any => "any",
LoopMode::Every => "every",
@@ -620,7 +635,7 @@ fn format_instruction_readable(
Instruction::CallRule { dest, rule_index } => {
let rule_name = program
.rule_infos
.get(*rule_index as usize)
.get(usize::from(rule_index))
.map(|info| info.name.as_str())
.unwrap_or("<invalid>");
@@ -634,7 +649,7 @@ fn format_instruction_readable(
} => {
let rule_name = program
.rule_infos
.get(*rule_index as usize)
.get(usize::from(rule_index))
.map(|info| info.name.as_str())
.unwrap_or("<invalid>");
@@ -651,16 +666,16 @@ fn format_instruction_readable(
}
Instruction::ChainedIndex { params_index } => {
let (base, comment) =
if let Some(params) = instruction_data.get_chained_index_params(*params_index) {
if let Some(params) = instruction_data.get_chained_index_params(params_index) {
let chain_parts: Vec<String> = params
.path_components
.iter()
.map(|component| match component {
.map(|component| match *component {
crate::rvm::instructions::LiteralOrRegister::Literal(idx) => {
if let Some(literal) = program.literals.get(*idx as usize) {
match literal {
crate::Value::String(s) => format!(".{}", s.as_ref()),
other => format!(
if let Some(literal) = program.literals.get(usize::from(idx)) {
match *literal {
crate::Value::String(ref s) => format!(".{}", s.as_ref()),
ref other => format!(
"[{}]",
serde_json::to_string(other)
.unwrap_or_else(|_| "?".to_string())
@@ -723,7 +738,7 @@ fn format_instruction_readable(
align_comment(&base, "Stop execution", config.comment_column)
}
Instruction::ComprehensionBegin { params_index } => {
if let Some(params) = instruction_data.get_comprehension_begin_params(*params_index) {
if let Some(params) = instruction_data.get_comprehension_begin_params(params_index) {
let mode_str = match params.mode {
crate::rvm::instructions::ComprehensionMode::Array => "array",
crate::rvm::instructions::ComprehensionMode::Set => "set",
@@ -785,21 +800,28 @@ pub fn generate_tabular_assembly_listing(
let mut indent_level: usize = 0;
// Add header
writeln!(output, "; RVM Assembly (Tabular Format)").unwrap();
writeln!(
output,
"; {} instructions, {} literals",
program.instructions.len(),
program.literals.len()
)
.unwrap();
writeln!(output, ";").unwrap();
writeln!(output, "; PC | Instruction | Operation").unwrap();
writeln!(output, ";-----|--------------|----------").unwrap();
push_line(&mut output, format_args!("; RVM Assembly (Tabular Format)"));
push_line(
&mut output,
format_args!(
"; {} instructions, {} literals",
program.instructions.len(),
program.literals.len()
),
);
push_line(&mut output, format_args!(";"));
push_line(
&mut output,
format_args!("; PC | Instruction | Operation"),
);
push_line(
&mut output,
format_args!(";-----|--------------|----------"),
);
for (pc, instruction) in program.instructions.iter().enumerate() {
// Handle loop indentation
match instruction {
match *instruction {
Instruction::LoopNext { .. } => {
indent_level = indent_level.saturating_sub(1);
}
@@ -809,7 +831,7 @@ pub fn generate_tabular_assembly_listing(
_ => {}
}
let indent = " ".repeat(indent_level * 2); // Smaller indent for tabular format
let indent = " ".repeat(indent_level.saturating_mul(2)); // Smaller indent for tabular format
// Format in tabular style
let addr_str = format!("{:03}", pc);
@@ -817,15 +839,18 @@ pub fn generate_tabular_assembly_listing(
let operation =
format_operation_compact(instruction, &indent, &program.instruction_data, program);
writeln!(output, "{:>4} | {:12} | {}", addr_str, inst_name, operation).unwrap();
push_line(
&mut output,
format_args!("{:>4} | {:12} | {}", addr_str, inst_name, operation),
);
// Increase indentation for loop/rule starts
match instruction {
match *instruction {
Instruction::LoopStart { .. } => {
indent_level += 1;
indent_level = indent_level.saturating_add(1);
}
Instruction::RuleInit { .. } => {
indent_level += 1;
indent_level = indent_level.saturating_add(1);
}
_ => {}
}
@@ -834,8 +859,8 @@ pub fn generate_tabular_assembly_listing(
output
}
fn get_instruction_name(instruction: &Instruction) -> &'static str {
match instruction {
const fn get_instruction_name(instruction: &Instruction) -> &'static str {
match *instruction {
Instruction::Load { .. } => "LOAD",
Instruction::LoadTrue { .. } => "LOAD_TRUE",
Instruction::LoadFalse { .. } => "LOAD_FALSE",
@@ -897,7 +922,7 @@ fn format_operation_compact(
instruction_data: &InstructionData,
_program: &Program,
) -> String {
match instruction {
match *instruction {
Instruction::Load { dest, literal_idx } => {
format!("{}r{} ← L{}", indent, dest, literal_idx)
}
@@ -928,7 +953,7 @@ fn format_operation_compact(
format!("{}r{} ← r{}[L{}]", indent, dest, container, literal_idx)
}
Instruction::LoopStart { params_index } => {
if let Some(params) = instruction_data.get_loop_params(*params_index) {
if let Some(params) = instruction_data.get_loop_params(params_index) {
format!(
"{}loop r{} in r{} {{",
indent, params.value_reg, params.collection
+1 -3
View File
@@ -1,9 +1,7 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
#![allow(clippy::unused_trait_names)]
use super::Program;
use alloc::string::{String, ToString};
use alloc::string::{String, ToString as _};
impl Program {
/// Compile a partial deserialized program to a complete one
+18 -14
View File
@@ -1,13 +1,8 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
#![allow(
clippy::arithmetic_side_effects,
clippy::unused_trait_names,
clippy::pattern_type_mismatch
)]
use alloc::format;
use alloc::string::{String, ToString};
use alloc::string::{String, ToString as _};
use alloc::vec::Vec;
use anyhow::Result as AnyResult;
@@ -26,7 +21,16 @@ impl Program {
rule_name: &str,
rule_index: usize,
) -> AnyResult<()> {
let mut full_path = Vec::with_capacity(path.len() + 1);
if path.len() >= Program::MAX_PATH_DEPTH {
return Err(anyhow::anyhow!(
"Rule path depth exceeds maximum ({} >= {})",
path.len(),
Program::MAX_PATH_DEPTH
));
}
let capacity = path.len().checked_add(1).unwrap_or(path.len());
let mut full_path = Vec::with_capacity(capacity);
full_path.extend(path.iter().map(|s| s.as_str()));
full_path.push(rule_name);
@@ -41,9 +45,9 @@ impl Program {
pub fn check_rule_data_conflicts(&self, data: &Value) -> Result<(), crate::rvm::vm::VmError> {
let actual_rule_tree = &self.rule_tree["data"];
match actual_rule_tree {
match *actual_rule_tree {
Value::Undefined => return Ok(()),
Value::Object(rule_obj) if rule_obj.is_empty() => return Ok(()),
Value::Object(ref rule_obj) if rule_obj.is_empty() => return Ok(()),
_ => {}
}
@@ -55,15 +59,15 @@ impl Program {
data: &Value,
current_path: &mut Vec<String>,
) -> Result<(), crate::rvm::vm::VmError> {
match rule_tree {
Value::Object(rule_obj) => {
match *rule_tree {
Value::Object(ref rule_obj) => {
for (key, rule_value) in rule_obj.iter() {
if let Value::String(key_str) = key {
if let Value::String(ref key_str) = *key {
current_path.push(key_str.to_string());
let data_value = &data[key];
match rule_value {
match *rule_value {
Value::Number(_) => {
if data_value != &Value::Undefined {
return Err(crate::rvm::vm::VmError::RuleDataConflict(format!(
@@ -73,7 +77,7 @@ impl Program {
}
}
Value::Object(_) => {
if let Value::Object(_) = data_value {
if let Value::Object(_) = *data_value {
Self::check_conflicts_recursive(
rule_value,
data_value,
+187 -264
View File
@@ -1,32 +1,79 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
#![allow(
clippy::indexing_slicing,
clippy::arithmetic_side_effects,
clippy::as_conversions,
clippy::unused_trait_names
)]
use alloc::format;
use alloc::string::{String, ToString};
use alloc::string::{String, ToString as _};
use alloc::vec::Vec;
use bincode::config::standard;
use bincode::serde::{decode_from_slice, encode_to_vec};
use indexmap::IndexMap;
use super::super::types::SourceFile;
use super::{DeserializationResult, Program};
use crate::value::Value;
use super::value::{
binaries_to_values, binary_to_value, BinaryValue, BinaryValueRef, BinaryValueSlice,
};
type ArtifactData = (IndexMap<String, usize>, Vec<SourceFile>, bool);
/// Helper for tracking offsets with overflow checking
struct OffsetTracker {
current: usize,
}
impl OffsetTracker {
const fn new(start: usize) -> Self {
Self { current: start }
}
fn advance(&mut self, amount: usize) -> Result<usize, String> {
let start = self.current;
self.current = self.current.checked_add(amount).ok_or("Offset overflow")?;
Ok(start)
}
const fn current(&self) -> usize {
self.current
}
}
impl Program {
/// Helper: safely read u32 from 4 bytes starting at offset
fn read_u32(data: &[u8], offset: usize) -> Result<u32, String> {
let bytes = data
.get(offset..offset.checked_add(4).ok_or("Offset overflow")?)
.ok_or_else(|| format!("Cannot read u32 at offset {}", offset))?;
Ok(u32::from_le_bytes([
*bytes.first().ok_or("Missing byte 0")?,
*bytes.get(1).ok_or("Missing byte 1")?,
*bytes.get(2).ok_or("Missing byte 2")?,
*bytes.get(3).ok_or("Missing byte 3")?,
]))
}
/// Helper: safely read u32 as usize
fn read_u32_as_usize(data: &[u8], offset: usize) -> Result<usize, String> {
Self::read_u32(data, offset)?
.try_into()
.map_err(|_| "Size conversion overflow".to_string())
}
/// Helper: safely get a byte at offset
fn read_byte(data: &[u8], offset: usize) -> Result<u8, String> {
data.get(offset)
.copied()
.ok_or_else(|| format!("Cannot read byte at offset {}", offset))
}
/// Helper: safely get slice
fn get_slice(data: &[u8], start: usize, end: usize) -> Result<&[u8], String> {
data.get(start..end)
.ok_or_else(|| format!("Cannot get slice [{}..{}]", start, end))
}
/// Serialize program to binary format.
/// Uses pure bincode for all sections now that `Value` supports serde.
pub fn serialize_binary(&self) -> Result<Vec<u8>, String> {
self.validate_limits()?;
let mut buffer = Vec::new();
buffer.extend_from_slice(&Self::MAGIC);
@@ -47,10 +94,26 @@ impl Program {
let binary_data = encode_to_vec(self, standard())
.map_err(|e| format!("Program structure binary serialization failed: {}", e))?;
buffer.extend_from_slice(&(entry_points_bin.len() as u32).to_le_bytes());
buffer.extend_from_slice(&(sources_bin.len() as u32).to_le_bytes());
buffer.extend_from_slice(&(literals_bin.len() as u32).to_le_bytes());
buffer.extend_from_slice(&(rule_tree_bin.len() as u32).to_le_bytes());
buffer.extend_from_slice(
&u32::try_from(entry_points_bin.len())
.map_err(|_| "Entry points size too large")?
.to_le_bytes(),
);
buffer.extend_from_slice(
&u32::try_from(sources_bin.len())
.map_err(|_| "Sources size too large")?
.to_le_bytes(),
);
buffer.extend_from_slice(
&u32::try_from(literals_bin.len())
.map_err(|_| "Literals size too large")?
.to_le_bytes(),
);
buffer.extend_from_slice(
&u32::try_from(rule_tree_bin.len())
.map_err(|_| "Rule tree size too large")?
.to_le_bytes(),
);
buffer.push(if self.rego_v0 { 1 } else { 0 });
buffer.extend_from_slice(&entry_points_bin);
@@ -58,105 +121,28 @@ impl Program {
buffer.extend_from_slice(&literals_bin);
buffer.extend_from_slice(&rule_tree_bin);
buffer.extend_from_slice(&(binary_data.len() as u32).to_le_bytes());
buffer.extend_from_slice(
&u32::try_from(binary_data.len())
.map_err(|_| "Binary data size too large")?
.to_le_bytes(),
);
buffer.extend_from_slice(&binary_data);
Ok(buffer)
}
/// Deserialize only the artifact section (entry_points and sources) from binary format
pub fn deserialize_artifacts_only(data: &[u8]) -> Result<ArtifactData, String> {
if data.len() < 9 {
return Err("Data too short for artifact header".to_string());
}
if data[0..4] != Self::MAGIC {
return Err("Invalid file format - magic number mismatch".to_string());
}
let version = u32::from_le_bytes([data[4], data[5], data[6], data[7]]);
match version {
1 => {
if data.len() < 17 {
return Err("Data too short for artifact header".to_string());
}
let entry_points_len =
u32::from_le_bytes([data[8], data[9], data[10], data[11]]) as usize;
let sources_len =
u32::from_le_bytes([data[12], data[13], data[14], data[15]]) as usize;
let rego_v0 = data[16] != 0;
let entry_points_start = 17;
let sources_start = entry_points_start + entry_points_len;
let sources_end = sources_start + sources_len;
if data.len() < sources_end {
return Err("Data truncated in artifact section".to_string());
}
let entry_points =
decode_from_slice(&data[entry_points_start..sources_start], standard())
.map(|(value, _)| value)
.unwrap_or_else(|_| IndexMap::new());
let sources = decode_from_slice(&data[sources_start..sources_end], standard())
.map(|(value, _)| value)
.unwrap_or_else(|_| Vec::new());
Ok((entry_points, sources, rego_v0))
}
2 | 3 => {
if data.len() < 25 {
return Err("Data too short for artifact header".to_string());
}
let entry_points_len =
u32::from_le_bytes([data[8], data[9], data[10], data[11]]) as usize;
let sources_len =
u32::from_le_bytes([data[12], data[13], data[14], data[15]]) as usize;
let literals_len =
u32::from_le_bytes([data[16], data[17], data[18], data[19]]) as usize;
let rule_tree_len =
u32::from_le_bytes([data[20], data[21], data[22], data[23]]) as usize;
let rego_v0 = data[24] != 0;
let entry_points_start = 25;
let sources_start = entry_points_start + entry_points_len;
let literals_start = sources_start + sources_len;
let rule_tree_start = literals_start + literals_len;
let rule_tree_end = rule_tree_start + rule_tree_len;
if data.len() < rule_tree_end {
return Err("Data truncated in artifact section".to_string());
}
let entry_points =
decode_from_slice(&data[entry_points_start..sources_start], standard())
.map(|(value, _)| value)
.unwrap_or_else(|_| IndexMap::new());
let sources = decode_from_slice(&data[sources_start..literals_start], standard())
.map(|(value, _)| value)
.unwrap_or_else(|_| Vec::new());
Ok((entry_points, sources, rego_v0))
}
v => Err(format!("Unsupported version {}", v)),
}
}
/// Deserialize program from binary format with version checking
pub fn deserialize_binary(data: &[u8]) -> Result<DeserializationResult, String> {
if data.len() < 9 {
return Err("Data too short for header".to_string());
}
if data[0..4] != Self::MAGIC {
let magic = Self::get_slice(data, 0, 4)?;
if magic != Self::MAGIC {
return Err("Invalid file format - magic number mismatch".to_string());
}
let version = u32::from_le_bytes([data[4], data[5], data[6], data[7]]);
let version = Self::read_u32(data, 4)?;
if version > Self::SERIALIZATION_VERSION {
return Err(format!(
"Unsupported version {}. Maximum supported version is {}",
@@ -171,59 +157,63 @@ impl Program {
return Err("Data too short for header".to_string());
}
let entry_points_len =
u32::from_le_bytes([data[8], data[9], data[10], data[11]]) as usize;
let sources_len =
u32::from_le_bytes([data[12], data[13], data[14], data[15]]) as usize;
let rego_v0 = data[16] != 0;
let entry_points_start = 17;
let sources_start = entry_points_start + entry_points_len;
let binary_len_start = sources_start + sources_len;
let entry_points_len = Self::read_u32_as_usize(data, 8)?;
let sources_len = Self::read_u32_as_usize(data, 12)?;
let rego_v0 = Self::read_byte(data, 16)? != 0;
if data.len() < binary_len_start + 4 {
let mut offset = OffsetTracker::new(17);
let entry_points_start = offset.advance(entry_points_len)?;
let sources_start = offset.advance(sources_len)?;
let binary_len_start = offset.current();
if data.len() < binary_len_start.checked_add(4).ok_or("Offset overflow")? {
return Err("Data too short for binary length".to_string());
}
let binary_len = u32::from_le_bytes([
data[binary_len_start],
data[binary_len_start + 1],
data[binary_len_start + 2],
data[binary_len_start + 3],
]) as usize;
let binary_len = Self::read_u32_as_usize(data, binary_len_start)?;
let json_len_start = binary_len_start + 4 + binary_len;
if data.len() < json_len_start + 4 {
let mut binary_offset = OffsetTracker::new(binary_len_start);
binary_offset.advance(4)?; // Skip the binary_len u32
let binary_start = binary_offset.advance(binary_len)?;
let json_start = binary_offset.current();
if data.len() < json_start.checked_add(4).ok_or("Offset overflow")? {
return Err("Data too short for JSON length".to_string());
}
let json_len = u32::from_le_bytes([
data[json_len_start],
data[json_len_start + 1],
data[json_len_start + 2],
data[json_len_start + 3],
]) as usize;
let json_len = Self::read_u32_as_usize(data, json_start)?;
let total_expected = json_len_start + 4 + json_len;
let total_expected = json_start
.checked_add(4)
.and_then(|v| v.checked_add(json_len))
.ok_or("Offset overflow")?;
if data.len() < total_expected {
return Err("Data truncated".to_string());
}
let binary_start = binary_len_start + 4;
let json_start = json_len_start + 4;
let json_end = json_start
.checked_add(4)
.and_then(|v| v.checked_add(json_len))
.ok_or("Offset overflow")?;
let entry_points =
decode_from_slice(&data[entry_points_start..sources_start], standard())
.map(|(value, _)| value)
.map_err(|e| format!("Entry points deserialization failed: {}", e))?;
let entry_points = decode_from_slice(
Self::get_slice(data, entry_points_start, sources_start)?,
standard(),
)
.map(|(value, _)| value)
.map_err(|e| format!("Entry points deserialization failed: {}", e))?;
let sources = decode_from_slice(&data[sources_start..binary_len_start], standard())
.map(|(value, _)| value)
.map_err(|e| format!("Sources deserialization failed: {}", e))?;
let sources = decode_from_slice(
Self::get_slice(data, sources_start, binary_len_start)?,
standard(),
)
.map(|(value, _)| value)
.map_err(|e| format!("Sources deserialization failed: {}", e))?;
let mut needs_recompilation = false;
let mut program = match decode_from_slice::<Program, _>(
&data[binary_start..json_start],
Self::get_slice(data, binary_start, json_start)?,
standard(),
) {
Ok((prog, _)) => prog,
@@ -233,33 +223,36 @@ impl Program {
}
};
let (literals, rule_tree) = match serde_json::from_slice::<serde_json::Value>(
&data[json_start..json_start + json_len],
) {
Ok(combined) => {
let literals = combined
.get("literals")
.and_then(|v| serde_json::from_value::<Vec<Value>>(v.clone()).ok())
.unwrap_or_else(|| {
needs_recompilation = true;
Vec::new()
});
let (literals, rule_tree) =
match serde_json::from_slice::<serde_json::Value>(Self::get_slice(
data,
json_start.checked_add(4).ok_or("Offset overflow")?,
json_end,
)?) {
Ok(combined) => {
let literals = combined
.get("literals")
.and_then(|v| serde_json::from_value::<Vec<Value>>(v.clone()).ok())
.unwrap_or_else(|| {
needs_recompilation = true;
Vec::new()
});
let rule_tree = combined
.get("rule_tree")
.and_then(|v| serde_json::from_value::<Value>(v.clone()).ok())
.unwrap_or_else(|| {
needs_recompilation = true;
Value::new_object()
});
let rule_tree = combined
.get("rule_tree")
.and_then(|v| serde_json::from_value::<Value>(v.clone()).ok())
.unwrap_or_else(|| {
needs_recompilation = true;
Value::new_object()
});
(literals, rule_tree)
}
Err(_e) => {
needs_recompilation = true;
(Vec::new(), Value::new_object())
}
};
(literals, rule_tree)
}
Err(_e) => {
needs_recompilation = true;
(Vec::new(), Value::new_object())
}
};
program.entry_points = entry_points;
program.sources = sources;
@@ -285,53 +278,52 @@ impl Program {
return Err("Data too short for header".to_string());
}
let entry_points_len =
u32::from_le_bytes([data[8], data[9], data[10], data[11]]) as usize;
let sources_len =
u32::from_le_bytes([data[12], data[13], data[14], data[15]]) as usize;
let literals_len =
u32::from_le_bytes([data[16], data[17], data[18], data[19]]) as usize;
let rule_tree_len =
u32::from_le_bytes([data[20], data[21], data[22], data[23]]) as usize;
let rego_v0 = data[24] != 0;
let entry_points_len = Self::read_u32_as_usize(data, 8)?;
let sources_len = Self::read_u32_as_usize(data, 12)?;
let literals_len = Self::read_u32_as_usize(data, 16)?;
let rule_tree_len = Self::read_u32_as_usize(data, 20)?;
let rego_v0 = Self::read_byte(data, 24)? != 0;
let entry_points_start = 25;
let sources_start = entry_points_start + entry_points_len;
let literals_start = sources_start + sources_len;
let rule_tree_start = literals_start + literals_len;
let binary_len_start = rule_tree_start + rule_tree_len;
let mut offset = OffsetTracker::new(25);
let entry_points_start = offset.advance(entry_points_len)?;
let sources_start = offset.advance(sources_len)?;
let literals_start = offset.advance(literals_len)?;
let rule_tree_start = offset.advance(rule_tree_len)?;
let binary_len_start = offset.current();
if data.len() < binary_len_start + 4 {
if data.len() < binary_len_start.checked_add(4).ok_or("Offset overflow")? {
return Err("Data too short for binary length".to_string());
}
let binary_len = u32::from_le_bytes([
data[binary_len_start],
data[binary_len_start + 1],
data[binary_len_start + 2],
data[binary_len_start + 3],
]) as usize;
let binary_len = Self::read_u32_as_usize(data, binary_len_start)?;
let binary_start = binary_len_start + 4;
let binary_end = binary_start + binary_len;
let mut binary_offset = OffsetTracker::new(binary_len_start);
binary_offset.advance(4)?; // Skip the binary_len u32
let binary_start = binary_offset.advance(binary_len)?;
let binary_end = binary_offset.current();
if data.len() < binary_end {
return Err("Data truncated".to_string());
}
let entry_points =
decode_from_slice(&data[entry_points_start..sources_start], standard())
.map(|(value, _)| value)
.map_err(|e| format!("Entry points deserialization failed: {}", e))?;
let entry_points = decode_from_slice(
Self::get_slice(data, entry_points_start, sources_start)?,
standard(),
)
.map(|(value, _)| value)
.map_err(|e| format!("Entry points deserialization failed: {}", e))?;
let sources = decode_from_slice(&data[sources_start..literals_start], standard())
.map(|(value, _)| value)
.map_err(|e| format!("Sources deserialization failed: {}", e))?;
let sources = decode_from_slice(
Self::get_slice(data, sources_start, literals_start)?,
standard(),
)
.map(|(value, _)| value)
.map_err(|e| format!("Sources deserialization failed: {}", e))?;
let mut needs_recompilation = false;
let literals = match decode_from_slice::<Vec<BinaryValue>, _>(
&data[literals_start..rule_tree_start],
Self::get_slice(data, literals_start, rule_tree_start)?,
standard(),
) {
Ok((binary_literals, _)) => match binaries_to_values(binary_literals) {
@@ -348,7 +340,7 @@ impl Program {
};
let rule_tree = match decode_from_slice::<BinaryValue, _>(
&data[rule_tree_start..binary_len_start],
Self::get_slice(data, rule_tree_start, binary_len_start)?,
standard(),
) {
Ok((binary_tree, _)) => match binary_to_value(binary_tree) {
@@ -365,7 +357,7 @@ impl Program {
};
let mut program = match decode_from_slice::<Program, _>(
&data[binary_start..binary_end],
Self::get_slice(data, binary_start, binary_end)?,
standard(),
) {
Ok((prog, _)) => prog,
@@ -404,85 +396,16 @@ impl Program {
return Ok(false);
}
if data[0..4] != Self::MAGIC {
let magic = Self::get_slice(data, 0, 4).ok();
if magic != Some(&Self::MAGIC[..]) {
return Ok(false);
}
let version = u32::from_le_bytes([data[4], data[5], data[6], data[7]]);
let version = Self::read_u32(data, 4).ok();
match version {
1..=3 => Ok(true),
Some(1..=3) => Ok(true),
_ => Ok(false),
}
}
/// Get file format information without deserializing
pub fn get_file_info(data: &[u8]) -> Result<(u32, usize), String> {
if data.len() < 9 {
return Err("Data too short for header".to_string());
}
if data[0..4] != Self::MAGIC {
return Err("Invalid file format".to_string());
}
let version = u32::from_le_bytes([data[4], data[5], data[6], data[7]]);
match version {
1 => {
if data.len() < 25 {
return Err("Data too short for header".to_string());
}
let entry_points_len =
u32::from_le_bytes([data[8], data[9], data[10], data[11]]) as usize;
let sources_len =
u32::from_le_bytes([data[12], data[13], data[14], data[15]]) as usize;
let binary_len_start = 17 + entry_points_len + sources_len;
if data.len() < binary_len_start + 4 {
return Err("Data too short for binary length".to_string());
}
let binary_len = u32::from_le_bytes([
data[binary_len_start],
data[binary_len_start + 1],
data[binary_len_start + 2],
data[binary_len_start + 3],
]) as usize;
Ok((version, binary_len))
}
2 | 3 => {
if data.len() < 29 {
return Err("Data too short for header".to_string());
}
let entry_points_len =
u32::from_le_bytes([data[8], data[9], data[10], data[11]]) as usize;
let sources_len =
u32::from_le_bytes([data[12], data[13], data[14], data[15]]) as usize;
let literals_len =
u32::from_le_bytes([data[16], data[17], data[18], data[19]]) as usize;
let rule_tree_len =
u32::from_le_bytes([data[20], data[21], data[22], data[23]]) as usize;
let binary_len_start =
25 + entry_points_len + sources_len + literals_len + rule_tree_len;
if data.len() < binary_len_start + 4 {
return Err("Data too short for binary length".to_string());
}
let binary_len = u32::from_le_bytes([
data[binary_len_start],
data[binary_len_start + 1],
data[binary_len_start + 2],
data[binary_len_start + 3],
]) as usize;
Ok((version, binary_len))
}
v => Err(format!("Unsupported version {}", v)),
}
}
}
+9 -7
View File
@@ -1,10 +1,8 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
#![allow(clippy::as_conversions, clippy::unused_trait_names)]
use alloc::format;
use alloc::string::{String, ToString};
use alloc::string::{String, ToString as _};
use alloc::vec::Vec;
use super::super::types::SourceFile;
@@ -84,7 +82,8 @@ impl Program {
let optimization_level = metadata
.get("optimization_level")
.and_then(|v| v.as_u64())
.unwrap_or(0) as u8;
.and_then(|v| u8::try_from(v).ok())
.unwrap_or(0);
let rego_v0 = metadata
.get("rego_v0")
.and_then(|v| v.as_bool())
@@ -104,15 +103,18 @@ impl Program {
let main_entry_point = program_structure
.get("main_entry_point")
.and_then(|v| v.as_u64())
.unwrap_or(0) as usize;
.and_then(|v| u32::try_from(v).ok())
.unwrap_or(0);
let max_rule_window_size = program_structure
.get("max_rule_window_size")
.and_then(|v| v.as_u64())
.unwrap_or(0) as usize;
.and_then(|v| u8::try_from(v).ok())
.unwrap_or(0);
let dispatch_window_size = program_structure
.get("dispatch_window_size")
.and_then(|v| v.as_u64())
.unwrap_or(0) as usize;
.and_then(|v| u8::try_from(v).ok())
.unwrap_or(0);
let instructions: Vec<Instruction> = serde_json::from_value(
json_data
+2 -4
View File
@@ -1,10 +1,8 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
#![allow(clippy::redundant_pub_crate)]
pub(crate) mod binary;
pub mod binary;
mod json;
pub(crate) mod value;
pub mod value;
use serde::{Deserialize, Serialize};
+14 -20
View File
@@ -1,19 +1,13 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
#![allow(
clippy::redundant_pub_crate,
clippy::unused_trait_names,
clippy::pattern_type_mismatch
)]
use alloc::collections::{BTreeMap, BTreeSet};
use alloc::format;
use alloc::string::String;
use alloc::vec::Vec;
use core::fmt;
use core::str::FromStr;
use serde::de::{self, EnumAccess, VariantAccess, Visitor};
use serde::ser::{SerializeSeq, SerializeTuple};
use core::str::FromStr as _;
use serde::de::{self, EnumAccess, VariantAccess as _, Visitor};
use serde::ser::{SerializeSeq as _, SerializeTuple as _};
use serde::{Deserialize, Serialize};
use crate::number::Number;
@@ -33,19 +27,19 @@ const VARIANT_NUMBER_F64: u32 = 10;
/// Wrapper type for zero-copy binary serialization of a `Value`.
/// Keeps references into the original data so collections and strings are not cloned.
pub(crate) struct BinaryValueRef<'a>(pub &'a Value);
pub struct BinaryValueRef<'a>(pub &'a Value);
impl<'a> Serialize for BinaryValueRef<'a> {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
match self.0 {
match *self.0 {
Value::Null => serializer.serialize_unit_variant("BinaryValue", VARIANT_NULL, "Null"),
Value::Bool(b) => {
serializer.serialize_newtype_variant("BinaryValue", VARIANT_BOOL, "Bool", &b)
}
Value::Number(n) => {
Value::Number(ref n) => {
if let Some(value) = n.as_i64() {
serializer.serialize_newtype_variant(
"BinaryValue",
@@ -76,25 +70,25 @@ impl<'a> Serialize for BinaryValueRef<'a> {
)
}
}
Value::String(s) => serializer.serialize_newtype_variant(
Value::String(ref s) => serializer.serialize_newtype_variant(
"BinaryValue",
VARIANT_STRING,
"String",
s.as_ref(),
),
Value::Array(items) => serializer.serialize_newtype_variant(
Value::Array(ref items) => serializer.serialize_newtype_variant(
"BinaryValue",
VARIANT_ARRAY,
"Array",
&BinaryValueSlice(items.as_slice()),
),
Value::Set(items) => serializer.serialize_newtype_variant(
Value::Set(ref items) => serializer.serialize_newtype_variant(
"BinaryValue",
VARIANT_SET,
"Set",
&BinarySetRef(items.as_ref()),
),
Value::Object(entries) => serializer.serialize_newtype_variant(
Value::Object(ref entries) => serializer.serialize_newtype_variant(
"BinaryValue",
VARIANT_OBJECT,
"Object",
@@ -108,7 +102,7 @@ impl<'a> Serialize for BinaryValueRef<'a> {
}
/// Slice wrapper allowing zero-copy serialization of value collections.
pub(crate) struct BinaryValueSlice<'a>(pub &'a [Value]);
pub struct BinaryValueSlice<'a>(pub &'a [Value]);
impl<'a> Serialize for BinaryValueSlice<'a> {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
@@ -169,7 +163,7 @@ impl<'a> Serialize for BinaryEntryRef<'a> {
/// Owned counterpart used during deserialization.
#[derive(Debug, Clone)]
pub(crate) struct BinaryValue(pub Value);
pub struct BinaryValue(pub Value);
impl BinaryValue {
fn into_value(self) -> Value {
@@ -290,10 +284,10 @@ impl<'de> Deserialize<'de> for BinaryValue {
}
}
pub(crate) fn binaries_to_values(binaries: Vec<BinaryValue>) -> Result<Vec<Value>, String> {
pub fn binaries_to_values(binaries: Vec<BinaryValue>) -> Result<Vec<Value>, String> {
Ok(binaries.into_iter().map(BinaryValue::into_value).collect())
}
pub(crate) fn binary_to_value(binary: BinaryValue) -> Result<Value, String> {
pub fn binary_to_value(binary: BinaryValue) -> Result<Value, String> {
Ok(binary.into_value())
}
+6 -12
View File
@@ -1,11 +1,5 @@
#![allow(clippy::missing_const_for_fn)]
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
#![allow(
clippy::indexing_slicing,
clippy::arithmetic_side_effects,
clippy::as_conversions
)]
use alloc::string::String;
use alloc::vec::Vec;
use serde::{Deserialize, Serialize};
@@ -34,7 +28,7 @@ pub struct SpanInfo {
}
impl SpanInfo {
pub fn new(source_index: usize, line: usize, column: usize, length: usize) -> Self {
pub const fn new(source_index: usize, line: usize, column: usize, length: usize) -> Self {
Self {
source_index,
line,
@@ -47,8 +41,8 @@ impl SpanInfo {
pub fn from_lexer_span(span: &crate::lexer::Span, source_index: usize) -> Self {
Self {
source_index,
line: span.line as usize,
column: span.col as usize,
line: span.line.try_into().unwrap_or(usize::MAX),
column: span.col.try_into().unwrap_or(usize::MAX),
length: span.text().len(),
}
}
@@ -135,7 +129,7 @@ impl RuleInfo {
result_reg: u8,
num_registers: u8,
) -> Self {
let num_params = param_names.len() as u32;
let num_params = u32::try_from(param_names.len()).unwrap_or(u32::MAX);
let num_definitions = definitions.len();
Self {
name,
@@ -153,7 +147,7 @@ impl RuleInfo {
}
/// Set the default literal index for this rule
pub fn set_default_literal_index(&mut self, default_literal_index: u16) {
pub const fn set_default_literal_index(&mut self, default_literal_index: u16) {
self.default_literal_index = Some(default_literal_index);
}
}
@@ -168,7 +162,7 @@ pub struct SourceFile {
}
impl SourceFile {
pub fn new(name: String, content: String) -> Self {
pub const fn new(name: String, content: String) -> Self {
Self { name, content }
}
}