Skip to main content

cairo_vm/vm/errors/
vm_exception.rs

1use std::fmt::{self, Display};
2
3use crate::types::relocatable::Relocatable;
4
5use thiserror::Error;
6
7use crate::{
8    hint_processor::hint_processor_utils::get_maybe_relocatable_from_reference,
9    serde::deserialize_program::{ApTracking, Attribute, Location, OffsetValue},
10    types::{instruction::Register, relocatable::MaybeRelocatable},
11    vm::runners::cairo_runner::CairoRunner,
12};
13
14use super::vm_errors::VirtualMachineError;
15#[derive(Debug, Error)]
16pub struct VmException {
17    pub pc: Relocatable,
18    pub inst_location: Option<Location>,
19    pub inner_exc: VirtualMachineError,
20    pub error_attr_value: Option<String>,
21    pub traceback: Option<String>,
22}
23
24impl VmException {
25    pub fn from_vm_error(runner: &CairoRunner, error: VirtualMachineError) -> Self {
26        let pc = runner.vm.run_context.pc;
27        let error_attr_value = if pc.segment_index == 0 {
28            get_error_attr_value(pc.offset, runner)
29        } else {
30            None
31        };
32        let hint_index = if let VirtualMachineError::Hint(ref bx) = error {
33            Some(bx.0)
34        } else {
35            None
36        };
37        VmException {
38            pc,
39            inst_location: if pc.segment_index == 0 {
40                get_location(pc.offset, runner, hint_index)
41            } else {
42                None
43            },
44            inner_exc: error,
45            error_attr_value,
46            traceback: get_traceback(runner),
47        }
48    }
49}
50
51pub fn get_error_attr_value(pc: usize, runner: &CairoRunner) -> Option<String> {
52    let mut errors = String::new();
53    for attribute in &runner.program.shared_program_data.error_message_attributes {
54        if attribute.start_pc <= pc && attribute.end_pc > pc {
55            errors.push_str(&format!(
56                "Error message: {}\n",
57                substitute_error_message_references(attribute, runner)
58            ));
59        }
60    }
61    if errors.is_empty() {
62        None
63    } else {
64        Some(errors)
65    }
66}
67
68pub fn get_location(
69    pc: usize,
70    runner: &CairoRunner,
71    hint_index: Option<usize>,
72) -> Option<Location> {
73    let instruction_location = runner
74        .program
75        .shared_program_data
76        .instruction_locations
77        .as_ref()?
78        .get(&pc)?;
79    if let Some(index) = hint_index {
80        instruction_location
81            .hints
82            .get(index)
83            .map(|hint_location| hint_location.location.clone())
84    } else {
85        Some(instruction_location.inst.clone())
86    }
87}
88
89// Returns the traceback at the current pc.
90pub fn get_traceback(runner: &CairoRunner) -> Option<String> {
91    let mut traceback = String::new();
92    for (_fp, traceback_pc) in runner.vm.get_traceback_entries() {
93        if let (0, Some(ref attr)) = (
94            traceback_pc.segment_index,
95            get_error_attr_value(traceback_pc.offset, runner),
96        ) {
97            traceback.push_str(attr)
98        }
99        match (
100            traceback_pc.segment_index,
101            get_location(traceback_pc.offset, runner, None),
102        ) {
103            (0, Some(location)) => traceback.push_str(&format!(
104                "{}\n",
105                location.to_string_with_content(&format!("(pc={})", traceback_pc))
106            )),
107            _ => traceback.push_str(&format!("Unknown location (pc={})\n", traceback_pc)),
108        }
109    }
110    (!traceback.is_empty())
111        .then(|| format!("Cairo traceback (most recent call last):\n{traceback}"))
112}
113
114// Substitutes references in the given error_message attribute with their actual value.
115// References are defined with '{}'. E.g., 'x must be positive. Got: {x}'.
116fn substitute_error_message_references(
117    error_message_attr: &Attribute,
118    runner: &CairoRunner,
119) -> String {
120    let mut error_msg = error_message_attr.value.clone();
121    if let Some(tracking_data) = &error_message_attr.flow_tracking_data {
122        let mut invalid_references = Vec::<String>::new();
123        // Iterate over the available references and check if one of them is addressed in the error message
124        for (cairo_variable_path, ref_id) in &tracking_data.reference_ids {
125            // Get the cairo variable name from its path ie: __main__.main.x -> x
126            let cairo_variable_name = match cairo_variable_path.rsplit('.').next() {
127                Some(string) => string,
128                None => continue,
129            };
130            // Format the variable name to make it easier to search for and replace in the error message
131            // ie: x -> {x}
132            let formated_variable_name = format!("{{{cairo_variable_name}}}");
133            // Look for the formated name inside the error message
134            if error_msg.contains(&formated_variable_name) {
135                // Get the value of the cairo variable from its reference id
136                match get_value_from_simple_reference(*ref_id, &tracking_data.ap_tracking, runner) {
137                    Some(cairo_variable) => {
138                        // Replace the value in the error message
139                        error_msg =
140                            error_msg.replace(&formated_variable_name, &format!("{cairo_variable}"))
141                    }
142                    None => {
143                        // If the reference is too complex or ap-based it might lead to a wrong value
144                        // So we append the variable's name to the list of invalid reference
145                        invalid_references.push(cairo_variable_name.to_string());
146                    }
147                }
148            }
149        }
150        if !invalid_references.is_empty() {
151            // Add the invalid references (if any) to the error_msg
152            error_msg.push_str(&format!(
153                " (Cannot evaluate ap-based or complex references: [{}])",
154                invalid_references
155                    .iter()
156                    .fold(String::new(), |acc, arg| acc + &format!("'{arg}'"))
157            ));
158        }
159    }
160    error_msg
161}
162
163fn get_value_from_simple_reference(
164    ref_id: usize,
165    ap_tracking: &ApTracking,
166    runner: &CairoRunner,
167) -> Option<MaybeRelocatable> {
168    let reference = runner
169        .program
170        .shared_program_data
171        .reference_manager
172        .get(ref_id)?;
173    // Filter ap-based references
174    match reference.offset1 {
175        OffsetValue::Reference(Register::AP, _, _, _) => None,
176        _ => {
177            // Filer complex types (only felt/felt pointers)
178            match reference.cairo_type {
179                Some(ref cairo_type) if cairo_type.contains("felt") => Some(
180                    get_maybe_relocatable_from_reference(&runner.vm, reference, ap_tracking)?,
181                ),
182                _ => None,
183            }
184        }
185    }
186}
187
188impl Display for VmException {
189    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
190        // Build initial message
191        let message = format!("Error at pc={}:\n{}", self.pc, self.inner_exc);
192        let mut error_msg = String::new();
193        // Add error attribute value
194        if let Some(ref string) = self.error_attr_value {
195            error_msg.push_str(string)
196        }
197        // Add location information
198        if let Some(ref location) = self.inst_location {
199            let mut location_msg = String::new();
200            let (mut location, mut message) = (location, &message);
201            loop {
202                location_msg = format!(
203                    "{}\n{}",
204                    location.to_string_with_content(message),
205                    location_msg
206                );
207                // Add parent location info
208                if let Some(parent) = &location.parent_location {
209                    (location, message) = (&parent.0, &parent.1)
210                } else {
211                    break;
212                }
213            }
214            error_msg.push_str(&location_msg);
215        } else {
216            error_msg.push_str(&format!("{message}\n"));
217        }
218        if let Some(ref string) = self.traceback {
219            error_msg.push_str(string);
220        }
221        // Write error message
222        write!(f, "{error_msg}")
223    }
224}
225
226impl Location {
227    ///  Prints the location with the passed message.
228    pub fn to_string(&self, message: &str) -> String {
229        let msg_prefix = if message.is_empty() { "" } else { ": " };
230        format!(
231            "{}:{}:{}{}{}",
232            self.input_file.filename, self.start_line, self.start_col, msg_prefix, message
233        )
234    }
235
236    pub fn to_string_with_content(&self, message: &str) -> String {
237        let mut string = self.to_string(message);
238        let input_file_path = std::path::Path::new(&self.input_file.filename);
239        #[cfg(test)]
240        let input_file_path = {
241            use std::path::PathBuf;
242            match std::env::current_dir() {
243                Ok(current_dir) => {
244                    let mut parent_dir: PathBuf = current_dir
245                        .parent()
246                        .expect("should have a parent directory")
247                        .into();
248                    parent_dir.push(input_file_path);
249                    parent_dir
250                }
251                // current_dir() can fail on some platforms
252                Err(_) => return string,
253            }
254        };
255        if let Ok(file_content) = std::fs::read(input_file_path) {
256            string.push_str(&format!("\n{}", self.get_location_marks(&file_content)));
257        }
258        string
259    }
260
261    pub fn get_location_marks(&self, file_contents: &[u8]) -> String {
262        let mut contents = String::new();
263        if let Ok(content) = str::from_utf8(file_contents) {
264            contents.push_str(content);
265        }
266        let split_lines: Vec<&str> = contents.split('\n').collect();
267        if !(0 < self.start_line && ((self.start_line - 1) as usize) < split_lines.len()) {
268            return String::new();
269        }
270        let start_line = split_lines[(self.start_line - 1) as usize];
271        let start_col = self.start_col as usize;
272        let mut result = format!("{start_line}\n");
273        let end_col = if self.start_line == self.end_line {
274            self.end_col as usize
275        } else {
276            start_line.len() + 1
277        };
278        let left_margin: String = vec![' '; start_col - 1].into_iter().collect();
279        if end_col > start_col + 1 {
280            let highlight: String = vec!['*'; end_col - start_col - 2].into_iter().collect();
281            result.push_str(&format!("{left_margin}^{highlight}^"));
282        } else {
283            result.push_str(&format!("{left_margin}^"))
284        }
285        result
286    }
287}
288#[cfg(test)]
289mod test {
290    use crate::types::layout_name::LayoutName;
291    use assert_matches::assert_matches;
292    use std::collections::HashMap;
293    use std::path::Path;
294
295    use crate::hint_processor::builtin_hint_processor::builtin_hint_processor_definition::BuiltinHintProcessor;
296    use crate::serde::deserialize_program::{
297        Attribute, HintLocation, InputFile, InstructionLocation,
298    };
299    use crate::types::program::Program;
300    use crate::types::relocatable::Relocatable;
301    use crate::utils::test_utils::*;
302
303    use super::*;
304    #[test]
305    fn get_vm_exception_from_vm_error() {
306        let pc: Relocatable = (0, 0).into();
307        let location = Location {
308            end_line: 2,
309            end_col: 2,
310            input_file: InputFile {
311                filename: String::from("Folder/file.cairo"),
312            },
313            parent_location: None,
314            start_line: 1,
315            start_col: 1,
316        };
317        let instruction_location = InstructionLocation {
318            inst: location.clone(),
319            hints: vec![],
320        };
321        let program = program!(
322            instruction_locations = Some(HashMap::from([(pc.offset, instruction_location)])),
323        );
324        let runner = cairo_runner!(program);
325        assert_matches!(
326            VmException::from_vm_error(&runner, VirtualMachineError::NoImm,),
327            VmException {
328                pc: x,
329                inst_location: Some(y),
330                inner_exc: VirtualMachineError::NoImm,
331                error_attr_value: None,
332                traceback: None,
333            } if x == pc && y == location
334        )
335    }
336
337    #[test]
338    fn location_to_string_no_message() {
339        let location = Location {
340            end_line: 2,
341            end_col: 2,
342            input_file: InputFile {
343                filename: String::from("Folder/file.cairo"),
344            },
345            parent_location: None,
346            start_line: 1,
347            start_col: 1,
348        };
349        let message = String::new();
350        assert_eq!(
351            location.to_string(&message),
352            String::from("Folder/file.cairo:1:1")
353        )
354    }
355
356    #[test]
357    fn location_to_string_with_message() {
358        let location = Location {
359            end_line: 2,
360            end_col: 2,
361            input_file: InputFile {
362                filename: String::from("Folder/file.cairo"),
363            },
364            parent_location: None,
365            start_line: 1,
366            start_col: 1,
367        };
368        let message = String::from("While expanding the reference");
369        assert_eq!(
370            location.to_string(&message),
371            String::from("Folder/file.cairo:1:1: While expanding the reference")
372        )
373    }
374
375    #[test]
376    fn vm_exception_display_instruction_no_location_no_attributes() {
377        let vm_excep = VmException {
378            pc: (0, 2).into(),
379            inst_location: None,
380            inner_exc: VirtualMachineError::FailedToComputeOperands(Box::new((
381                "op0".to_string(),
382                Relocatable::from((0, 4)),
383            ))),
384            error_attr_value: None,
385            traceback: None,
386        };
387        assert_eq!(
388            vm_excep.to_string(),
389            format!(
390                "Error at pc=0:2:\n{}\n",
391                VirtualMachineError::FailedToComputeOperands(Box::new((
392                    "op0".to_string(),
393                    Relocatable::from((0, 4))
394                )))
395            )
396        )
397    }
398
399    #[test]
400    fn vm_exception_display_instruction_no_location_with_attributes() {
401        let vm_excep = VmException {
402            pc: (0, 2).into(),
403            inst_location: None,
404            inner_exc: VirtualMachineError::FailedToComputeOperands(Box::new((
405                "op0".to_string(),
406                Relocatable::from((0, 4)),
407            ))),
408            error_attr_value: Some(String::from("Error message: Block may fail\n")),
409            traceback: None,
410        };
411        assert_eq!(
412            vm_excep.to_string(),
413            format!(
414                "Error message: Block may fail\nError at pc=0:2:\n{}\n",
415                VirtualMachineError::FailedToComputeOperands(Box::new((
416                    "op0".to_string(),
417                    Relocatable::from((0, 4))
418                )))
419            )
420        )
421    }
422
423    #[test]
424    fn vm_exception_display_instruction_no_attributes_no_parent() {
425        let location = Location {
426            end_line: 2,
427            end_col: 2,
428            input_file: InputFile {
429                filename: String::from("Folder/file.cairo"),
430            },
431            parent_location: None,
432            start_line: 1,
433            start_col: 1,
434        };
435        let vm_excep = VmException {
436            pc: (0, 2).into(),
437            inst_location: Some(location),
438            inner_exc: VirtualMachineError::FailedToComputeOperands(Box::new((
439                "op0".to_string(),
440                Relocatable::from((0, 4)),
441            ))),
442            error_attr_value: None,
443            traceback: None,
444        };
445        assert_eq!(
446            vm_excep.to_string(),
447            format!(
448                "Folder/file.cairo:1:1: Error at pc=0:2:\n{}\n",
449                VirtualMachineError::FailedToComputeOperands(Box::new((
450                    "op0".to_string(),
451                    Relocatable::from((0, 4))
452                )))
453            )
454        )
455    }
456
457    #[test]
458    fn vm_exception_display_instruction_no_attributes_with_parent() {
459        let location = Location {
460            end_line: 2,
461            end_col: 2,
462            input_file: InputFile {
463                filename: String::from("Folder/file.cairo"),
464            },
465            parent_location: Some((
466                Box::new(Location {
467                    end_line: 3,
468                    end_col: 3,
469                    input_file: InputFile {
470                        filename: String::from("Folder/file_b.cairo"),
471                    },
472                    parent_location: None,
473                    start_line: 2,
474                    start_col: 2,
475                }),
476                String::from("While expanding the reference:"),
477            )),
478            start_line: 1,
479            start_col: 1,
480        };
481        let vm_excep = VmException {
482            pc: (0, 2).into(),
483            inst_location: Some(location),
484            inner_exc: VirtualMachineError::FailedToComputeOperands(Box::new((
485                "op0".to_string(),
486                Relocatable::from((0, 4)),
487            ))),
488            error_attr_value: None,
489            traceback: None,
490        };
491        assert_eq!(
492            vm_excep.to_string(),
493            format!(
494                "Folder/file_b.cairo:2:2: While expanding the reference:\nFolder/file.cairo:1:1: Error at pc=0:2:\n{}\n",
495                VirtualMachineError::FailedToComputeOperands(Box::new(("op0".to_string(), Relocatable::from((0, 4)))))
496            )
497        )
498    }
499
500    #[test]
501    fn get_error_attr_value_some() {
502        let attributes = vec![Attribute {
503            name: String::from("Error message"),
504            start_pc: 1,
505            end_pc: 5,
506            value: String::from("Invalid hash"),
507            flow_tracking_data: None,
508        }];
509        let program = program!(error_message_attributes = attributes,);
510        let runner = cairo_runner!(program);
511        assert_eq!(
512            get_error_attr_value(2, &runner),
513            Some(String::from("Error message: Invalid hash\n"))
514        );
515    }
516
517    #[test]
518    fn get_error_attr_value_none() {
519        let attributes = vec![Attribute {
520            name: String::from("Error message"),
521            start_pc: 1,
522            end_pc: 5,
523            value: String::from("Invalid hash"),
524            flow_tracking_data: None,
525        }];
526        let program = program!(error_message_attributes = attributes,);
527        let runner = cairo_runner!(program);
528        assert_eq!(get_error_attr_value(5, &runner), None);
529    }
530
531    #[test]
532    fn get_location_some() {
533        let location = Location {
534            end_line: 2,
535            end_col: 2,
536            input_file: InputFile {
537                filename: String::from("Folder/file.cairo"),
538            },
539            parent_location: None,
540            start_line: 1,
541            start_col: 1,
542        };
543        let instruction_location = InstructionLocation {
544            inst: location.clone(),
545            hints: vec![],
546        };
547        let program =
548            program!(instruction_locations = Some(HashMap::from([(2, instruction_location)])),);
549        let runner = cairo_runner!(program);
550        assert_eq!(get_location(2, &runner, None), Some(location));
551    }
552
553    #[test]
554    fn get_location_none() {
555        let location = Location {
556            end_line: 2,
557            end_col: 2,
558            input_file: InputFile {
559                filename: String::from("Folder/file.cairo"),
560            },
561            parent_location: None,
562            start_line: 1,
563            start_col: 1,
564        };
565        let instruction_location = InstructionLocation {
566            inst: location,
567            hints: vec![],
568        };
569        let program =
570            program!(instruction_locations = Some(HashMap::from([(2, instruction_location)])),);
571        let runner = cairo_runner!(program);
572        assert_eq!(get_location(3, &runner, None), None);
573    }
574
575    #[test]
576    fn get_location_some_hint_index() {
577        let location_a = Location {
578            end_line: 2,
579            end_col: 2,
580            input_file: InputFile {
581                filename: String::from("Folder/file_a.cairo"),
582            },
583            parent_location: None,
584            start_line: 1,
585            start_col: 1,
586        };
587        let location_b = Location {
588            end_line: 3,
589            end_col: 2,
590            input_file: InputFile {
591                filename: String::from("Folder/file_b.cairo"),
592            },
593            parent_location: None,
594            start_line: 1,
595            start_col: 5,
596        };
597        let hint_location = HintLocation {
598            location: location_b.clone(),
599            n_prefix_newlines: 2,
600        };
601        let instruction_location = InstructionLocation {
602            inst: location_a,
603            hints: vec![hint_location],
604        };
605        let program =
606            program!(instruction_locations = Some(HashMap::from([(2, instruction_location)])),);
607        let runner = cairo_runner!(program);
608        assert_eq!(get_location(2, &runner, Some(0)), Some(location_b));
609    }
610
611    #[test]
612    fn get_traceback_bad_dict_update() {
613        let program = Program::from_bytes(
614            include_bytes!("../../../../cairo_programs/bad_programs/bad_dict_update.json"),
615            Some("main"),
616        )
617        .expect("Call to `Program::from_file()` failed.");
618
619        let mut hint_processor = BuiltinHintProcessor::new_empty();
620        let mut cairo_runner = cairo_runner!(program, LayoutName::all_cairo, false);
621
622        let end = cairo_runner.initialize(false).unwrap();
623        assert!(cairo_runner.run_until_pc(end, &mut hint_processor).is_err());
624
625        let expected_traceback = String::from("Cairo traceback (most recent call last):\ncairo_programs/bad_programs/bad_dict_update.cairo:10:5: (pc=0:34)\n    dict_update{dict_ptr=my_dict}(key=2, prev_value=3, new_value=4);\n    ^*************************************************************^\n");
626
627        let mut hint_processor = BuiltinHintProcessor::new_empty();
628        let mut cairo_runner = cairo_runner!(program, LayoutName::all_cairo, false);
629
630        let end = cairo_runner.initialize(false).unwrap();
631        assert!(cairo_runner.run_until_pc(end, &mut hint_processor).is_err());
632        assert_eq!(get_traceback(&cairo_runner), Some(expected_traceback));
633    }
634
635    #[test]
636    fn get_traceback_bad_usort() {
637        let program = Program::from_bytes(
638            include_bytes!("../../../../cairo_programs/bad_programs/bad_usort.json"),
639            Some("main"),
640        )
641        .unwrap();
642        let expected_traceback = r"Cairo traceback (most recent call last):
643cairo_programs/bad_programs/bad_usort.cairo:91:48: (pc=0:97)
644    let (output_len, output, multiplicities) = usort(input_len=3, input=input_array);
645                                               ^***********************************^
646cairo_programs/bad_programs/bad_usort.cairo:36:5: (pc=0:30)
647    verify_usort{output=output}(
648    ^**************************^
649cairo_programs/bad_programs/bad_usort.cairo:64:5: (pc=0:60)
650    verify_multiplicity(multiplicity=multiplicity, input_len=input_len, input=input, value=value);
651    ^*******************************************************************************************^
652";
653
654        let mut hint_processor = BuiltinHintProcessor::new_empty();
655        let mut cairo_runner = cairo_runner!(program, LayoutName::all_cairo, false);
656
657        let end = cairo_runner.initialize(false).unwrap();
658        assert!(cairo_runner.run_until_pc(end, &mut hint_processor).is_err());
659        assert_eq!(
660            get_traceback(&cairo_runner),
661            Some(expected_traceback.to_string())
662        );
663    }
664
665    #[test]
666    fn location_to_string_with_contents_no_contents() {
667        let location = Location {
668            end_line: 2,
669            end_col: 2,
670            input_file: InputFile {
671                filename: String::from("Folder/file.cairo"),
672            },
673            parent_location: None,
674            start_line: 1,
675            start_col: 1,
676        };
677        let message = String::from("While expanding the reference");
678        assert_eq!(
679            location.to_string_with_content(&message),
680            String::from("Folder/file.cairo:1:1: While expanding the reference")
681        )
682    }
683
684    #[test]
685    fn location_to_string_with_contents() {
686        let location = Location {
687            end_line: 5,
688            end_col: 2,
689            input_file: InputFile {
690                filename: String::from("cairo_programs/bad_programs/bad_usort.cairo"),
691            },
692            parent_location: None,
693            start_line: 5,
694            start_col: 1,
695        };
696        let message = String::from("Error at pc=0:75:");
697
698        let expected_message = "cairo_programs/bad_programs/bad_usort.cairo:5:1: Error at pc=0:75:\nfunc usort{range_check_ptr}(input_len: felt, input: felt*) -> (\n^";
699
700        assert_eq!(
701            location.to_string_with_content(&message),
702            expected_message.to_string()
703        )
704    }
705
706    #[test]
707    fn location_to_string_with_contents_no_file() {
708        let location = Location {
709            end_line: 5,
710            end_col: 2,
711            input_file: InputFile {
712                filename: String::from("cairo_programs/bad_prtypoograms/bad_usort.cairo"),
713            },
714            parent_location: None,
715            start_line: 5,
716            start_col: 1,
717        };
718        let message = String::from("Error at pc=0:75:\n");
719        assert_eq!(
720            location.to_string_with_content(&message),
721            String::from(
722                "cairo_programs/bad_prtypoograms/bad_usort.cairo:5:1: Error at pc=0:75:\n"
723            )
724        )
725    }
726
727    #[test]
728    fn location_get_location_marks() {
729        let location = Location {
730            end_line: 5,
731            end_col: 2,
732            input_file: InputFile {
733                filename: String::from("../cairo_programs/bad_programs/bad_usort.cairo"),
734            },
735            parent_location: None,
736            start_line: 5,
737            start_col: 1,
738        };
739        let input_file_path = Path::new(&location.input_file.filename);
740        let file_content = std::fs::read(input_file_path).expect("Failed to open file");
741        assert_eq!(
742            location.get_location_marks(&file_content),
743            String::from("func usort{range_check_ptr}(input_len: felt, input: felt*) -> (\n^")
744        )
745    }
746
747    #[test]
748    fn location_get_location_marks_empty_file() {
749        let location = Location {
750            end_line: 5,
751            end_col: 2,
752            input_file: InputFile {
753                filename: String::from("cairo_programs/bad_programs/bad_usort.cairo"),
754            },
755            parent_location: None,
756            start_line: 5,
757            start_col: 1,
758        };
759        let reader: &[u8] = &[];
760        assert_eq!(location.get_location_marks(reader), String::from(""))
761    }
762
763    #[test]
764    fn run_bad_range_check_and_check_error_displayed() {
765        let expected_error_string = r#"Error message: Failed range-check
766cairo_programs/bad_programs/bad_range_check.cairo:5:9: Error at pc=0:0:
767An ASSERT_EQ instruction failed: 4 != 5.
768        [range_check_ptr] = num;
769        ^*********************^
770Cairo traceback (most recent call last):
771cairo_programs/bad_programs/bad_range_check.cairo:23:5: (pc=0:29)
772    sub_by_1_check_range(6, 7);
773    ^************************^
774cairo_programs/bad_programs/bad_range_check.cairo:19:12: (pc=0:21)
775    return sub_by_1_check_range(sub_1_check_range(num), sub_amount -1);
776           ^*********************************************************^
777cairo_programs/bad_programs/bad_range_check.cairo:19:33: (pc=0:17)
778    return sub_by_1_check_range(sub_1_check_range(num), sub_amount -1);
779                                ^********************^
780cairo_programs/bad_programs/bad_range_check.cairo:11:5: (pc=0:6)
781    check_range(num - 1);
782    ^******************^
783"#;
784        let program = Program::from_bytes(
785            include_bytes!("../../../../cairo_programs/bad_programs/bad_range_check.json"),
786            Some("main"),
787        )
788        .unwrap();
789
790        let mut hint_processor = BuiltinHintProcessor::new_empty();
791        let mut cairo_runner = cairo_runner!(program, LayoutName::all_cairo, false);
792
793        let end = cairo_runner.initialize(false).unwrap();
794        let error = cairo_runner
795            .run_until_pc(end, &mut hint_processor)
796            .unwrap_err();
797        let vm_excepction = VmException::from_vm_error(&cairo_runner, error);
798        assert_eq!(vm_excepction.to_string(), expected_error_string);
799    }
800
801    #[test]
802    fn run_bad_usort_and_check_error_displayed() {
803        let expected_error_string = r#"cairo_programs/bad_programs/bad_usort.cairo:79:5: Error at pc=0:75:
804Got an exception while executing a hint: unexpected verify multiplicity fail: positions length != 0
805    %{ assert len(positions) == 0 %}
806    ^******************************^
807Cairo traceback (most recent call last):
808cairo_programs/bad_programs/bad_usort.cairo:91:48: (pc=0:97)
809    let (output_len, output, multiplicities) = usort(input_len=3, input=input_array);
810                                               ^***********************************^
811cairo_programs/bad_programs/bad_usort.cairo:36:5: (pc=0:30)
812    verify_usort{output=output}(
813    ^**************************^
814cairo_programs/bad_programs/bad_usort.cairo:64:5: (pc=0:60)
815    verify_multiplicity(multiplicity=multiplicity, input_len=input_len, input=input, value=value);
816    ^*******************************************************************************************^
817"#;
818        let program = Program::from_bytes(
819            include_bytes!("../../../../cairo_programs/bad_programs/bad_usort.json"),
820            Some("main"),
821        )
822        .unwrap();
823
824        let mut hint_processor = BuiltinHintProcessor::new_empty();
825        let mut cairo_runner = cairo_runner!(program, LayoutName::all_cairo, false);
826
827        let end = cairo_runner.initialize(false).unwrap();
828        let error = cairo_runner
829            .run_until_pc(end, &mut hint_processor)
830            .unwrap_err();
831        let vm_excepction = VmException::from_vm_error(&cairo_runner, error);
832        assert_eq!(vm_excepction.to_string(), expected_error_string);
833    }
834
835    #[test]
836    fn run_bad_ec_recover_product_mod() {
837        let expected_error_string = r#"cairo_programs/bad_programs/ec_recover_product_mod_m_zero.cairo:16:5: Error at pc=0:21:
838Got an exception while executing a hint: Attempted to divide by zero
839    %{
840    ^^
841Cairo traceback (most recent call last):
842cairo_programs/bad_programs/ec_recover_product_mod_m_zero.cairo:11:5: (pc=0:18)
843    ec_recover_product(a, b, m);
844    ^*************************^
845"#;
846        let program = Program::from_bytes(
847            include_bytes!(
848                "../../../../cairo_programs/bad_programs/ec_recover_product_mod_m_zero.json"
849            ),
850            Some("main"),
851        )
852        .unwrap();
853
854        let mut hint_processor = BuiltinHintProcessor::new_empty();
855        let mut cairo_runner = cairo_runner!(program, LayoutName::all_cairo, false);
856
857        let end = cairo_runner.initialize(false).unwrap();
858        let error = cairo_runner
859            .run_until_pc(end, &mut hint_processor)
860            .unwrap_err();
861        let vm_excepction = VmException::from_vm_error(&cairo_runner, error);
862        assert_eq!(vm_excepction.to_string(), expected_error_string);
863    }
864
865    #[test]
866    fn run_bad_ec_recover_div_mod_n_packed_n_zero() {
867        let expected_error_string = r#"cairo_programs/bad_programs/ec_recover_div_mod_n_packed_n_zero.cairo:16:5: Error at pc=0:21:
868Got an exception while executing a hint: Attempted to divide by zero
869    %{
870    ^^
871Cairo traceback (most recent call last):
872cairo_programs/bad_programs/ec_recover_div_mod_n_packed_n_zero.cairo:11:5: (pc=0:18)
873    ec_recover_product(x, s, n);
874    ^*************************^
875"#;
876        let program = Program::from_bytes(
877            include_bytes!(
878                "../../../../cairo_programs/bad_programs/ec_recover_div_mod_n_packed_n_zero.json"
879            ),
880            Some("main"),
881        )
882        .unwrap();
883
884        let mut hint_processor = BuiltinHintProcessor::new_empty();
885        let mut cairo_runner = cairo_runner!(program, LayoutName::all_cairo, false);
886
887        let end = cairo_runner.initialize(false).unwrap();
888        let error = cairo_runner
889            .run_until_pc(end, &mut hint_processor)
890            .unwrap_err();
891        let vm_excepction = VmException::from_vm_error(&cairo_runner, error);
892        assert_eq!(vm_excepction.to_string(), expected_error_string);
893    }
894
895    #[test]
896    fn run_bad_uint512_unsigned_div_rem() {
897        let expected_error_string = r#"cairo_programs/bad_programs/uint512_unsigned_div_rem_div_is_zero.cairo:24:1: Error at pc=0:17:
898Got an exception while executing a hint: Attempted to divide by zero
899%{
900^^
901Cairo traceback (most recent call last):
902cairo_programs/bad_programs/uint512_unsigned_div_rem_div_is_zero.cairo:15:2: (pc=0:12)
903	hint_func(x, div);
904 ^***************^
905"#;
906        let program = Program::from_bytes(
907            include_bytes!(
908                "../../../../cairo_programs/bad_programs/uint512_unsigned_div_rem_div_is_zero.json"
909            ),
910            Some("main"),
911        )
912        .unwrap();
913
914        let mut hint_processor = BuiltinHintProcessor::new_empty();
915        let mut cairo_runner = cairo_runner!(program, LayoutName::all_cairo, false);
916
917        let end = cairo_runner.initialize(false).unwrap();
918        let error = cairo_runner
919            .run_until_pc(end, &mut hint_processor)
920            .unwrap_err();
921        let vm_excepction = VmException::from_vm_error(&cairo_runner, error);
922        assert_eq!(vm_excepction.to_string(), expected_error_string);
923    }
924
925    #[test]
926    fn run_bad_uint256_sub_check_error_displayed() {
927        let expected_error_string = r#"cairo_programs/bad_programs/uint256_sub_b_gt_256.cairo:17:1: Error at pc=0:17:
928Got an exception while executing a hint: Inconsistent memory assignment at address Relocatable { segment_index: 1, offset: 6 }. Int(1) != Int(41367660292349381832802403122744918015)
929%{
930^^
931Cairo traceback (most recent call last):
932cairo_programs/bad_programs/uint256_sub_b_gt_256.cairo:10:2: (pc=0:12)
933	hint_func(a, b, res);
934 ^******************^
935"#;
936        let program = Program::from_bytes(
937            include_bytes!("../../../../cairo_programs/bad_programs/uint256_sub_b_gt_256.json"),
938            Some("main"),
939        )
940        .unwrap();
941
942        let mut hint_processor = BuiltinHintProcessor::new_empty();
943        let mut cairo_runner = cairo_runner!(program, LayoutName::all_cairo, false);
944
945        let end = cairo_runner.initialize(false).unwrap();
946        let error = cairo_runner
947            .run_until_pc(end, &mut hint_processor)
948            .unwrap_err();
949        let vm_excepction = VmException::from_vm_error(&cairo_runner, error);
950        assert_eq!(vm_excepction.to_string(), expected_error_string);
951    }
952
953    #[test]
954    fn get_value_from_simple_reference_ap_based() {
955        let program = Program::from_bytes(
956            include_bytes!("../../../../cairo_programs/bad_programs/error_msg_attr_tempvar.json"),
957            Some("main"),
958        )
959        .unwrap();
960        // This program uses a tempvar inside an error attribute
961        // This reference should be rejected when substituting the error attribute references
962        let runner = cairo_runner!(program);
963        // Ref id 0 corresponds to __main__.main.x, our tempvar
964        assert_eq!(
965            get_value_from_simple_reference(0, &ApTracking::default(), &runner),
966            None
967        )
968    }
969
970    #[test]
971    fn substitute_error_message_references_ap_based() {
972        let program = Program::from_bytes(
973            include_bytes!("../../../../cairo_programs/bad_programs/error_msg_attr_tempvar.json"),
974            Some("main"),
975        )
976        .unwrap();
977        // This program uses a tempvar inside an error attribute
978        // This reference should be rejected when substituting the error attribute references
979        let runner = cairo_runner!(program);
980        let attribute = &program.shared_program_data.error_message_attributes[0];
981        assert_eq!(
982            substitute_error_message_references(attribute, &runner),
983            format!(
984                "{} (Cannot evaluate ap-based or complex references: ['x'])",
985                attribute.value
986            )
987        );
988    }
989
990    #[test]
991    fn get_value_from_simple_reference_complex() {
992        let program = Program::from_bytes(
993            include_bytes!("../../../../cairo_programs/bad_programs/error_msg_attr_struct.json"),
994            Some("main"),
995        )
996        .unwrap();
997        // This program uses a struct inside an error attribute
998        // This reference should be rejected when substituting the error attribute references
999        let runner = cairo_runner!(program);
1000        // Ref id 0 corresponds to __main__.main.cat, our struct
1001        assert_eq!(
1002            get_value_from_simple_reference(0, &ApTracking::default(), &runner),
1003            None
1004        )
1005    }
1006
1007    #[test]
1008    fn substitute_error_message_references_complex() {
1009        let program = Program::from_bytes(
1010            include_bytes!("../../../../cairo_programs/bad_programs/error_msg_attr_struct.json"),
1011            Some("main"),
1012        )
1013        .unwrap();
1014        // This program uses a struct inside an error attribute
1015        // This reference should be rejected when substituting the error attribute references
1016        let runner = cairo_runner!(program);
1017        let attribute = &program.shared_program_data.error_message_attributes[0];
1018        assert_eq!(
1019            substitute_error_message_references(attribute, &runner),
1020            format!(
1021                "{} (Cannot evaluate ap-based or complex references: ['cat'])",
1022                attribute.value
1023            )
1024        );
1025    }
1026
1027    #[test]
1028    fn get_vm_exception_from_vm_error_pc_not_program_segment() {
1029        let pc = (9, 5).into();
1030        let location = Location {
1031            end_line: 2,
1032            end_col: 2,
1033            input_file: InputFile {
1034                filename: String::from("Folder/file.cairo"),
1035            },
1036            parent_location: None,
1037            start_line: 1,
1038            start_col: 1,
1039        };
1040        let instruction_location = InstructionLocation {
1041            inst: location,
1042            hints: vec![],
1043        };
1044        let program =
1045            program!(instruction_locations = Some(HashMap::from([(5, instruction_location)])),);
1046        let mut runner = cairo_runner!(program);
1047        runner.vm.set_pc(pc);
1048        assert_matches!(
1049            VmException::from_vm_error(&runner, VirtualMachineError::NoImm,),
1050            VmException {
1051                pc: x,
1052                inst_location: None,
1053                inner_exc: VirtualMachineError::NoImm,
1054                error_attr_value: None,
1055                traceback: None,
1056            } if x == pc
1057        )
1058    }
1059}