Skip to content

Commit 9132d25

Browse files
reapply CairoFunctionRunner and add search_pc with related tests and error handling
1 parent b977c96 commit 9132d25

3 files changed

Lines changed: 121 additions & 4 deletions

File tree

vm/src/types/errors/program_errors.rs

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,10 @@ pub enum ProgramError {
1919
StrippedProgramNoMain,
2020
#[error("Hint PC ({0}) is greater or equal to program length ({1})")]
2121
InvalidHintPc(usize, usize),
22+
#[error("Identifier \"{0}\" is type alias but has no destination")]
23+
AliasMissingDestination(String),
24+
#[error("invalid identifier type \"{1}\" for \"{0}\": expected \"alias\" or \"function\"")]
25+
InvalidIdentifierTypeForPc(String, String),
2226
}
2327

2428
#[cfg(test)]
@@ -31,4 +35,28 @@ mod tests {
3135
let formatted_error = format!("{error}");
3236
assert_eq!(formatted_error, "Entrypoint my_function not found");
3337
}
38+
39+
#[test]
40+
fn format_alias_missing_destination_error() {
41+
let error =
42+
ProgramError::AliasMissingDestination(String::from("__main__.assert_nn"));
43+
let formatted_error = format!("{error}");
44+
assert_eq!(
45+
formatted_error,
46+
"Identifier \"__main__.assert_nn\" is type alias but has no destination"
47+
);
48+
}
49+
50+
#[test]
51+
fn format_invalid_identifier_type_for_pc_error() {
52+
let error = ProgramError::InvalidIdentifierTypeForPc(
53+
String::from("__main__.my_struct"),
54+
String::from("struct"),
55+
);
56+
let formatted_error = format!("{error}");
57+
assert_eq!(
58+
formatted_error,
59+
"invalid identifier type \"struct\" for \"__main__.my_struct\": expected \"alias\" or \"function\""
60+
);
61+
}
3462
}

vm/src/vm/runners/cairo_function_runner.rs

Lines changed: 61 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
77
use crate::hint_processor::builtin_hint_processor::builtin_hint_processor_definition::BuiltinHintProcessor;
88
use crate::hint_processor::hint_processor_definition::HintProcessor;
9+
use crate::serde::deserialize_program::Identifier;
910
use crate::types::builtin_name::BuiltinName;
1011
use crate::types::errors::program_errors::ProgramError;
1112
use crate::types::instance_definitions::mod_instance_def::ModInstanceDef;
@@ -260,17 +261,73 @@ impl CairoFunctionRunner {
260261
.map(|builtin_runner| MaybeRelocatable::from((builtin_runner.base() as isize, 0)))
261262
}
262263

263-
/// Gets the program counter (PC) for a function entrypoint.
264+
/// Returns the program counter (PC) for a function entrypoint by name.
265+
///
266+
/// Looks up the identifier `__main__.{entrypoint}` in the program, then resolves it to a PC
267+
/// (following alias chains if needed) via [`get_pc_from_identifier`](Self::get_pc_from_identifier).
268+
///
269+
/// # Errors
270+
/// - [`ProgramError::EntrypointNotFound`] if no identifier exists for the given name.
271+
/// - [`RunnerError::NoPC`] if the resolved identifier has no PC (e.g. corrupt alias).
272+
/// - [`ProgramError::AliasMissingDestination`] if an alias has no `destination`.
273+
/// - [`ProgramError::InvalidIdentifierTypeForPc`] if the identifier type is not `"function"` or `"alias"`.
264274
#[allow(clippy::result_large_err)]
265-
fn get_function_pc(&self, entrypoint: &str) -> std::result::Result<usize, CairoRunError> {
275+
pub(crate) fn get_function_pc(
276+
&self,
277+
entrypoint: &str,
278+
) -> std::result::Result<usize, CairoRunError> {
266279
let full_name = format!("__main__.{entrypoint}");
267280
let identifier = self
268281
.program
269282
.get_identifier(&full_name)
270283
.ok_or_else(|| ProgramError::EntrypointNotFound(entrypoint.to_string()))?;
271284

272-
let pc = identifier.pc.ok_or(RunnerError::NoPC)?;
285+
self.get_pc_from_identifier(identifier)
286+
}
273287

274-
Ok(pc)
288+
/// Resolves an identifier to its program counter (PC), following alias chains.
289+
///
290+
/// - **`function`**: returns the identifier's `pc` if present.
291+
/// - **`alias`**: resolves `destination` to another identifier and recurses until a function is found.
292+
/// - **Other types** (e.g. `struct`, `const`): returns [`ProgramError::InvalidIdentifierTypeForPc`].
293+
///
294+
/// # Errors
295+
/// - [`RunnerError::NoPC`] when the identifier is a function but has no `pc`.
296+
/// - [`ProgramError::AliasMissingDestination`] when the identifier is an alias but has no `destination`.
297+
/// - [`ProgramError::EntrypointNotFound`] when the alias destination is not in the program.
298+
/// - [`ProgramError::InvalidIdentifierTypeForPc`] when the identifier type is not `"function"` or `"alias"`.
299+
#[allow(clippy::result_large_err)]
300+
fn get_pc_from_identifier(
301+
&self,
302+
idetifier: &Identifier,
303+
) -> std::result::Result<usize, CairoRunError> {
304+
match idetifier.type_.as_deref() {
305+
Some("function") => {
306+
let pc = idetifier.pc.ok_or(RunnerError::NoPC)?;
307+
Ok(pc)
308+
}
309+
Some("alias") => {
310+
let destination = idetifier.destination.as_deref().ok_or(
311+
ProgramError::AliasMissingDestination(
312+
idetifier.full_name.as_deref().unwrap_or("").to_string(),
313+
),
314+
)?;
315+
316+
let destination_identifier = self
317+
.runner
318+
.program
319+
.get_identifier(destination)
320+
.ok_or_else(|| ProgramError::EntrypointNotFound(destination.to_string()))?;
321+
self.get_pc_from_identifier(destination_identifier)
322+
}
323+
v => {
324+
let name = idetifier
325+
.full_name
326+
.clone()
327+
.unwrap_or_else(|| "<unknown>".to_string());
328+
let type_str = v.unwrap_or("<unknown>").to_string();
329+
Err(ProgramError::InvalidIdentifierTypeForPc(name, type_str).into())
330+
}
331+
}
275332
}
276333
}

vm/src/vm/runners/cairo_function_runner_test.rs

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,38 @@ fn run_default_cairo0_happy_path() {
165165
assert_eq!(function_runner.get_return_values(0).unwrap(), vec![]);
166166
}
167167

168+
#[test]
169+
// Test that get_function_pc resolves "__main__.assert_nn" (alias) to the PC of starkware.cairo.common.math.assert_nn (0).
170+
fn get_function_pc_assert_nn_resolves_alias_to_pc_0() {
171+
let program = load_program(include_bytes!(
172+
"../../../../cairo_programs/example_program.json"
173+
));
174+
let function_runner = CairoFunctionRunner::new(&program).unwrap();
175+
176+
let pc = function_runner.get_function_pc("assert_nn").unwrap();
177+
assert_eq!(
178+
pc, 0,
179+
"assert_nn is an alias to starkware.cairo.common.math.assert_nn which has pc 0"
180+
);
181+
}
182+
183+
#[test]
184+
// Test that get_function_pc returns the direct PC for "__main__.assert_nn_manual_implementation" (function with pc 4).
185+
fn get_function_pc_assert_nn_manual_implementation_returns_pc_4() {
186+
let program = load_program(include_bytes!(
187+
"../../../../cairo_programs/example_program.json"
188+
));
189+
let function_runner = CairoFunctionRunner::new(&program).unwrap();
190+
191+
let pc = function_runner
192+
.get_function_pc("assert_nn_manual_implementation")
193+
.unwrap();
194+
assert_eq!(
195+
pc, 4,
196+
"assert_nn_manual_implementation is a function with pc 4"
197+
);
198+
}
199+
168200
#[test]
169201
// Test that running a missing function name returns `EntrypointNotFound`.
170202
fn run_missing_entrypoint_returns_entrypoint_not_found() {

0 commit comments

Comments
 (0)