Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: return error when hint's PC is invalid #1340

Merged
merged 10 commits into from
Jul 20, 2023
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

#### Upcoming Changes

* fix: return error when a parsed hint's PC is invalid [#1340](https://github.com/lambdaclass/cairo-vm/pull/1340)

* chore(examples): remove _wee_alloc_ dependency from _wasm-demo_ example and _ensure-no_std_ dummy crate [#1337](https://github.com/lambdaclass/cairo-vm/pull/1337)

* docs: improved crate documentation [#1334](https://github.com/lambdaclass/cairo-vm/pull/1334)
Expand Down
32 changes: 32 additions & 0 deletions cairo_programs/manually_compiled/invalid_hint_pc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
{
"attributes": [],
"builtins": [],
"compiler_version": "0.11.0",
"data": [],
"debug_info": {
"instruction_locations": {}
},
"hints": {
"18446744073709551615": [
{
"accessible_scopes": [],
"code": "",
"flow_tracking_data": {
"ap_tracking": {
"group": 0,
"offset": 0
},
"reference_ids": {}
}
}
]
},
"identifiers": {
"__main__.main": {}
},
"main_scope": "",
"prime": "0x800000000000011000000000000000000000000000000000000000000000001",
"reference_manager": {
"references": []
}
}
2 changes: 1 addition & 1 deletion cairo_programs/manually_compiled/valid_program_a.json
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,7 @@
}
}
],
"46": [
"4": [
{
"accessible_scopes": [
"__main__",
Expand Down
56 changes: 52 additions & 4 deletions vm/src/serde/deserialize_program.rs
Original file line number Diff line number Diff line change
Expand Up @@ -441,7 +441,8 @@ pub fn parse_program_json(
}
}

let (hints, hints_ranges) = Program::flatten_hints(&program_json.hints);
let (hints, hints_ranges) =
Program::flatten_hints(&program_json.hints, program_json.data.len())?;

let shared_program_data = SharedProgramData {
data: program_json.data,
Expand Down Expand Up @@ -877,7 +878,7 @@ mod tests {
}],
),
(
46,
4,
vec![HintParams {
code: "import math".to_string(),
accessible_scopes: vec![
Expand Down Expand Up @@ -910,7 +911,7 @@ mod tests {
/// Deserialize a program without an entrypoint.
#[test]
#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)]
fn deserialize_program_without_entrypoint_test() {
fn deserialize_program_without_entrypoint() {
let reader =
include_bytes!("../../../cairo_programs/manually_compiled/valid_program_a.json");

Expand Down Expand Up @@ -946,7 +947,7 @@ mod tests {
}],
),
(
46,
4,
vec![HintParams {
code: "import math".to_string(),
accessible_scopes: vec![
Expand Down Expand Up @@ -1527,4 +1528,51 @@ mod tests {
.unwrap()
);
}

#[test]
#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)]
fn deserialize_program_with_invalid_hint_pc() {
let reader = br#"{
"attributes": [],
"builtins": [],
"compiler_version": "0.11.0",
"data": [
"0x41241"
],
"debug_info": {
"instruction_locations": {}
},
"hints": {
"1": [
{
"accessible_scopes": [],
"code": "",
"flow_tracking_data": {
"ap_tracking": {
"group": 0,
"offset": 0
},
"reference_ids": {}
}
}
]
},
"identifiers": {
"__main__.main": {}
},
"main_scope": "",
"prime": "0x800000000000011000000000000000000000000000000000000000000000001",
"reference_manager": {
"references": []
}
}"#;

let deserialization_result = deserialize_and_parse_program(reader, Some("main"));

assert!(deserialization_result.is_err());
assert_matches!(
deserialization_result.unwrap_err(),
ProgramError::InvalidHintPc(1, 1)
);
}
}
10 changes: 10 additions & 0 deletions vm/src/tests/cairo_run_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -982,3 +982,13 @@ fn cairo_run_overflowing_dict() {
include_bytes!("../../../cairo_programs/manually_compiled/overflowing_dict.json");
run_program_with_error(program_data, "Unknown memory cell at address");
}

#[test]
fn cairo_run_big_hint_pcs() {
let program_data =
include_bytes!("../../../cairo_programs/manually_compiled/invalid_hint_pc.json");
run_program_with_error(
program_data,
"Hint PC (18446744073709551615) is greater or equal to program length (0)",
);
}
2 changes: 2 additions & 0 deletions vm/src/types/errors/program_errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ pub enum ProgramError {
ConstWithoutValue(String),
#[error("Expected prime {PRIME_STR}, got {0}")]
PrimeDiffers(String),
#[error("Hint PC ({0}) is greater or equal to program length ({1})")]
InvalidHintPc(usize, usize),
}

#[cfg(test)]
Expand Down
27 changes: 19 additions & 8 deletions vm/src/types/program.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,8 @@ use arbitrary::Arbitrary;
pub(crate) struct SharedProgramData {
pub(crate) data: Vec<MaybeRelocatable>,
pub(crate) hints: Vec<HintParams>,
pub(crate) hints_ranges: Vec<Option<(usize, NonZeroUsize)>>,
/// This maps a PC to the range of hints in `hints` that correspond to it.
pub(crate) hints_ranges: Vec<HintRange>,
pub(crate) main: Option<usize>,
//start and end labels will only be used in proof-mode
pub(crate) start: Option<usize>,
Expand All @@ -60,6 +61,11 @@ pub(crate) struct SharedProgramData {
pub(crate) reference_manager: Vec<HintReference>,
}

/// Represents a range of hints corresponding to a PC.
///
/// Is [`None`] if the range is empty, and it is [`Some`] tuple `(start, length)` otherwise.
type HintRange = Option<(usize, NonZeroUsize)>;

#[cfg_attr(all(feature = "arbitrary", feature = "std"), derive(Arbitrary))]
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Program {
Expand Down Expand Up @@ -91,7 +97,7 @@ impl Program {
}
}

let (hints, hints_ranges) = Self::flatten_hints(&hints);
let (hints, hints_ranges) = Self::flatten_hints(&hints, data.len())?;

let shared_program_data = SharedProgramData {
data,
Expand All @@ -114,18 +120,23 @@ impl Program {

pub(crate) fn flatten_hints(
hints: &HashMap<usize, Vec<HintParams>>,
) -> (Vec<HintParams>, Vec<Option<(usize, NonZeroUsize)>>) {
program_length: usize,
) -> Result<(Vec<HintParams>, Vec<HintRange>), ProgramError> {
let bounds = hints
.iter()
.map(|(pc, hs)| (*pc, hs.len()))
.reduce(|(max_pc, full_len), (pc, len)| (max_pc.max(pc), full_len + len));
.reduce(|(max_hint_pc, full_len), (pc, len)| (max_hint_pc.max(pc), full_len + len));

let Some((max_pc, full_len)) = bounds else {
return (Vec::new(), Vec::new());
let Some((max_hint_pc, full_len)) = bounds else {
return Ok((Vec::new(), Vec::new()));
};

if max_hint_pc >= program_length {
return Err(ProgramError::InvalidHintPc(max_hint_pc, program_length));
}

let mut hints_values = Vec::with_capacity(full_len);
let mut hints_ranges = vec![None; max_pc + 1];
let mut hints_ranges = vec![None; max_hint_pc + 1];

for (pc, hs) in hints.iter().filter(|(_, hs)| !hs.is_empty()) {
let range = (
Expand All @@ -136,7 +147,7 @@ impl Program {
hints_values.extend_from_slice(&hs[..]);
}

(hints_values, hints_ranges)
Ok((hints_values, hints_ranges))
}

#[cfg(feature = "std")]
Expand Down
4 changes: 3 additions & 1 deletion vm/src/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -343,7 +343,9 @@ pub mod test_utils {

impl From<ProgramFlat> for Program {
fn from(val: ProgramFlat) -> Self {
let (hints, hints_ranges) = Program::flatten_hints(&val.hints);
// NOTE: panics if hints have PCs higher than the program length
let (hints, hints_ranges) =
Program::flatten_hints(&val.hints, val.data.len()).expect("hints are valid");
Program {
shared_program_data: Arc::new(SharedProgramData {
data: val.data,
Expand Down