cairo_vm/vm/errors/
vm_exception.rs

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