1use std::{collections::BTreeMap, fmt::Display, sync::Arc};
18
19use crate::{
20 analysis::{stmt_src, SourceAnalysis, VariableRef},
21 find_index_of_first_statement_in_block, find_index_of_first_statement_in_block_or_statement,
22 find_next_index_of_last_statement_in_block, find_next_index_of_source_location,
23 find_next_index_of_statement,
24 instrumentation::codegen,
25 USID, UVID,
26};
27
28use eyre::Result;
29use foundry_compilers::artifacts::{ast::SourceLocation, BlockOrStatement, Statement};
30use semver::Version;
31
32const LEFT_BRACKET_PRIORITY: u8 = 255; const FUNCTION_ENTRY_PRIORITY: u8 = 191; const VISIBILITY_PRIORITY: u8 = 128; const VARIABLE_UPDATE_PRIORITY: u8 = 127; const BEFORE_STEP_PRIORITY: u8 = 63; const RIGHT_BRACKET_PRIORITY: u8 = 0; pub type VersionRef = Arc<Version>;
41
42pub struct SourceModifications {
44 source_id: u32,
45 modifications: BTreeMap<usize, Modification>,
47}
48
49impl SourceModifications {
50 pub fn new(source_id: u32) -> Self {
52 Self { source_id, modifications: BTreeMap::new() }
53 }
54
55 pub fn add_modification(&mut self, modification: Modification) {
61 assert_eq!(modification.source_id(), self.source_id, "modification source id mismatch");
62
63 let loc = modification.loc();
64 if let Some((immediate_prev_loc, immediate_prev)) =
66 self.modifications.range_mut(..loc).next_back()
67 {
68 assert!(
69 immediate_prev_loc + immediate_prev.modified_length() <= loc,
70 "modification location overlaps with previous modification"
71 );
72 }
73 if let Some((immediate_next_loc, immediate_next)) =
75 self.modifications.range_mut(loc..).next()
76 {
77 assert!(
78 loc + modification.modified_length() <= *immediate_next_loc,
79 "modification location overlaps with next modification"
80 );
81 if immediate_next.is_instrument()
83 && modification.is_instrument()
84 && *immediate_next_loc == loc
85 {
86 immediate_next.modify_instrument_action(|act| {
87 act.content = if act.priority >= modification.as_instrument_action().priority {
88 InstrumentContent::Plain(format!(
89 "{} {}",
90 act.content,
91 modification.as_instrument_action().content,
92 ))
93 } else {
94 InstrumentContent::Plain(format!(
95 "{} {}",
96 modification.as_instrument_action().content,
97 act.content,
98 ))
99 };
100 });
101 return;
102 }
103 }
104 self.modifications.insert(loc, modification);
106 }
107
108 pub fn extend_modifications(&mut self, modifications: Vec<Modification>) {
110 for modification in modifications {
111 self.add_modification(modification);
112 }
113 }
114
115 pub fn modify_source(&self, source: &str) -> String {
117 let mut modified_source = source.to_string();
118 for (_, modification) in self.modifications.iter().rev() {
120 match modification {
121 Modification::Instrument(instrument_action) => {
122 modified_source.insert_str(
123 instrument_action.loc,
124 instrument_action.content.to_string().as_str(),
125 );
126 }
127 Modification::Remove(remove_action) => {
128 modified_source.replace_range(remove_action.start()..remove_action.end(), "");
129 }
130 }
131 }
132 modified_source
133 }
134}
135
136#[allow(clippy::large_enum_variant)]
138#[derive(Debug, Clone, derive_more::From)]
139pub enum Modification {
140 Instrument(#[from] InstrumentAction),
142 Remove(#[from] RemoveAction),
144}
145
146impl Modification {
147 pub fn source_id(&self) -> u32 {
149 match self {
150 Self::Instrument(instrument_action) => instrument_action.source_id,
151 Self::Remove(remove_action) => {
152 remove_action.src.index.expect("remove action index not found") as u32
153 }
154 }
155 }
156
157 pub fn loc(&self) -> usize {
159 match self {
160 Self::Instrument(instrument_action) => instrument_action.loc,
161 Self::Remove(remove_action) => remove_action.src.start.unwrap_or(0),
162 }
163 }
164
165 pub const fn modified_length(&self) -> usize {
167 match self {
168 Self::Instrument(_) => 0,
169 Self::Remove(remove_action) => {
170 remove_action.src.length.expect("remove action length not found")
171 }
172 }
173 }
174
175 pub const fn is_instrument(&self) -> bool {
177 matches!(self, Self::Instrument(_))
178 }
179
180 pub const fn is_remove(&self) -> bool {
182 matches!(self, Self::Remove(_))
183 }
184
185 pub fn as_instrument_action(&self) -> &InstrumentAction {
187 match self {
188 Self::Instrument(instrument_action) => instrument_action,
189 Self::Remove(_) => panic!("cannot get instrument action from remove action"),
190 }
191 }
192
193 pub fn as_remove_action(&self) -> &RemoveAction {
195 match self {
196 Self::Instrument(_) => panic!("cannot get remove action from instrument action"),
197 Self::Remove(remove_action) => remove_action,
198 }
199 }
200
201 pub fn modify_remove_action(&mut self, f: impl FnOnce(&mut RemoveAction)) {
203 match self {
204 Self::Instrument(_) => {}
205 Self::Remove(remove_action) => {
206 f(remove_action);
207 }
208 }
209 }
210
211 pub fn modify_instrument_action(&mut self, f: impl FnOnce(&mut InstrumentAction)) {
213 match self {
214 Self::Instrument(instrument_action) => {
215 f(instrument_action);
216 }
217 Self::Remove(_) => {}
218 }
219 }
220}
221
222#[derive(Debug, Clone)]
224pub struct InstrumentAction {
225 pub source_id: u32,
227 pub loc: usize,
229 pub content: InstrumentContent,
231 pub priority: u8,
233}
234
235#[derive(Debug, Clone)]
237pub struct RemoveAction {
238 pub src: SourceLocation,
240}
241
242impl RemoveAction {
243 pub fn start(&self) -> usize {
245 self.src.start.expect("remove action start not found")
246 }
247
248 pub fn end(&self) -> usize {
250 self.start() + self.src.length.expect("remove action length not found")
251 }
252}
253
254#[derive(Debug, Clone)]
256pub enum InstrumentContent {
257 Plain(String),
259 ViewMethod {
261 variable: VariableRef,
263 },
264 BeforeStepHook {
266 version: VersionRef,
268 usid: USID,
270 function_calls: usize,
272 },
273 VariableUpdateHook {
275 version: VersionRef,
277 uvid: UVID,
279 variable: VariableRef,
281 },
282}
283
284impl Display for InstrumentContent {
285 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
286 let content = match self {
287 Self::Plain(content) => content.clone(),
288 Self::ViewMethod { variable } => {
289 codegen::generate_view_method(variable).unwrap_or_default()
290 }
291 Self::BeforeStepHook { version, usid, .. } => {
292 codegen::generate_step_hook(version, *usid).unwrap_or_default()
293 }
294 Self::VariableUpdateHook { version, uvid, variable } => {
295 codegen::generate_variable_update_hook(version, *uvid, variable).unwrap_or_default()
296 }
297 };
298 write!(f, "{content}")
299 }
300}
301
302impl SourceModifications {
303 pub fn collect_modifications(
305 &mut self,
306 compiler_version: VersionRef,
307 source: &str,
308 analysis: &SourceAnalysis,
309 ) -> Result<()> {
310 self.collect_view_method_modifications(analysis);
312
313 self.collect_statement_to_block_modifications(source, analysis)?;
315
316 self.collect_before_step_hook_modifications(compiler_version.clone(), source, analysis)?;
318
319 self.collect_variable_update_hook_modifications(compiler_version, source, analysis)?;
321
322 Ok(())
323 }
324
325 fn collect_view_method_modifications(&mut self, analysis: &SourceAnalysis) {
327 let source_id = self.source_id;
328 for state_variable in &analysis.state_variables {
329 let src = &state_variable.declaration().src;
330 let loc = src.start.unwrap_or(0) + src.length.unwrap_or(0) + 1; let instrument_action = InstrumentAction {
332 source_id,
333 loc,
334 content: InstrumentContent::ViewMethod { variable: state_variable.clone() },
335 priority: VISIBILITY_PRIORITY,
336 };
337 self.add_modification(instrument_action.into());
338 }
339 }
340
341 fn collect_statement_to_block_modifications(
343 &mut self,
344 source: &str,
345 analysis: &SourceAnalysis,
346 ) -> Result<()> {
347 let source_id = self.source_id;
348
349 let left_bracket = |loc: usize| -> InstrumentAction {
350 InstrumentAction {
351 source_id,
352 loc,
353 content: InstrumentContent::Plain("{".to_string()),
354 priority: LEFT_BRACKET_PRIORITY,
355 }
356 };
357 let right_bracket = |loc: usize| -> InstrumentAction {
358 InstrumentAction {
359 source_id,
360 loc,
361 content: InstrumentContent::Plain("}".to_string()),
362 priority: RIGHT_BRACKET_PRIORITY,
363 }
364 };
365 let wrap_statement_as_block = |stmt: &Statement| -> Vec<Modification> {
366 let stmt_src = stmt_src(stmt);
367 let start_pos = stmt_src.start.expect_with_context(
369 "statement start location not found",
370 source_id,
371 source,
372 &stmt_src,
373 );
374 let left_bracket = left_bracket(start_pos);
375
376 let end_pos =
378 find_next_index_of_statement(source, stmt).expect("statement end not found");
379 let right_bracket = right_bracket(end_pos);
380
381 vec![left_bracket.into(), right_bracket.into()]
382 };
383
384 fn indeed_statement(block_or_stmt: &BlockOrStatement) -> Option<&Statement> {
385 match block_or_stmt {
386 BlockOrStatement::Statement(stmt) => match stmt {
387 Statement::Block(_) => None,
388 _ => Some(stmt),
389 },
390 BlockOrStatement::Block(_) => None,
391 }
392 }
393
394 for step in &analysis.steps {
395 match &step.variant() {
396 crate::analysis::StepVariant::IfCondition(if_stmt) => {
397 if let Some(stmt) = indeed_statement(&if_stmt.true_body) {
399 let modifications = wrap_statement_as_block(stmt);
400 self.extend_modifications(modifications);
401 }
402
403 if let Some(stmt) =
405 if_stmt.false_body.as_ref().and_then(|body| indeed_statement(body))
406 {
407 let modifications = wrap_statement_as_block(stmt);
408 self.extend_modifications(modifications);
409 }
410 }
411 crate::analysis::StepVariant::ForLoop(for_stmt) => {
412 if let Some(stmt) = indeed_statement(&for_stmt.body) {
414 let modifications = wrap_statement_as_block(stmt);
415 self.extend_modifications(modifications);
416 }
417 }
418 crate::analysis::StepVariant::WhileLoop(while_stmt) => {
419 if let Some(stmt) = indeed_statement(&while_stmt.body) {
421 let modifications = wrap_statement_as_block(stmt);
422 self.extend_modifications(modifications);
423 }
424 }
425 _ => {}
426 }
427 }
428 Ok(())
429 }
430
431 fn collect_before_step_hook_modifications(
432 &mut self,
433 compiler_version: VersionRef,
434 source: &str,
435 analysis: &SourceAnalysis,
436 ) -> Result<()> {
437 let source_id = self.source_id;
438 for step in &analysis.steps {
439 let usid = step.usid();
440 let variant = step.variant();
441 let function_calls = step.function_calls();
442 let (loc, priority) = match variant {
443 crate::analysis::StepVariant::FunctionEntry(function_definition) => {
444 let Some(body) = &function_definition.body else {
446 continue;
448 };
449 let loc = find_index_of_first_statement_in_block(body)
451 .expect("function body start location not found");
452 (loc, FUNCTION_ENTRY_PRIORITY)
453 }
454 crate::analysis::StepVariant::ModifierEntry(modifier_definition) => {
455 let Some(body) = &modifier_definition.body else {
457 continue;
459 };
460 let loc = find_index_of_first_statement_in_block(body)
461 .expect("modifier body start location not found");
462 (loc, FUNCTION_ENTRY_PRIORITY)
463 }
464 crate::analysis::StepVariant::Statement(statement) => {
465 let loc =
467 stmt_src(statement).start.expect("statement start location not found");
468 (loc, BEFORE_STEP_PRIORITY)
469 }
470 crate::analysis::StepVariant::Statements(statements) => {
471 let loc =
473 stmt_src(&statements[0]).start.expect("statement start location not found");
474 (loc, BEFORE_STEP_PRIORITY)
475 }
476 crate::analysis::StepVariant::IfCondition(if_statement) => {
477 let loc =
479 if_statement.src.start.expect("if statement start location not found");
480 (loc, BEFORE_STEP_PRIORITY)
481 }
482 crate::analysis::StepVariant::ForLoop(for_statement) => {
483 let loc =
485 for_statement.src.start.expect("for statement start location not found");
486 (loc, BEFORE_STEP_PRIORITY)
487 }
488 crate::analysis::StepVariant::WhileLoop(while_statement) => {
489 let loc = while_statement
491 .src
492 .start
493 .expect("while statement start location not found");
494 (loc, BEFORE_STEP_PRIORITY)
495 }
496 crate::analysis::StepVariant::DoWhileLoop(do_while_statement) => {
497 let loc = find_next_index_of_last_statement_in_block(
499 source,
500 &do_while_statement.body,
501 )
502 .expect("do-while statement last statement location not found");
503 (loc, BEFORE_STEP_PRIORITY)
504 }
505 crate::analysis::StepVariant::Try(try_statement) => {
506 let loc =
508 try_statement.src.start.expect("try statement start location not found");
509 (loc, BEFORE_STEP_PRIORITY)
510 }
511 };
512 let instrument_action = InstrumentAction {
513 source_id,
514 loc,
515 content: InstrumentContent::BeforeStepHook {
516 version: compiler_version.clone(),
517 usid,
518 function_calls,
519 },
520 priority,
521 };
522 self.add_modification(instrument_action.into());
523 }
524
525 Ok(())
526 }
527
528 fn collect_variable_update_hook_modifications(
529 &mut self,
530 compiler_version: VersionRef,
531 source: &str,
532 analysis: &SourceAnalysis,
533 ) -> Result<()> {
534 let source_id = self.source_id;
535 for step in &analysis.steps {
536 let updated_variables = &step.read().updated_variables;
537 let locs: Vec<usize> = match step.variant() {
538 crate::analysis::StepVariant::FunctionEntry(function_definition) => {
539 let Some(body) = &function_definition.body else {
541 continue;
543 };
544 vec![body.src.start.expect("function body start location not found") + 1]
546 }
547 crate::analysis::StepVariant::ModifierEntry(modifier_definition) => {
548 let Some(body) = &modifier_definition.body else {
550 continue;
552 };
553 vec![body.src.start.expect("modifier body start location not found") + 1]
554 }
555 crate::analysis::StepVariant::Statement(statement) => {
556 match statement {
557 Statement::Block(_)
558 | Statement::UncheckedBlock(_)
559 | Statement::DoWhileStatement(_)
560 | Statement::ForStatement(_)
561 | Statement::IfStatement(_)
562 | Statement::TryStatement(_)
563 | Statement::WhileStatement(_) => {
564 unreachable!("should not be a statement step")
565 }
566 Statement::Break(_)
567 | Statement::Continue(_)
568 | Statement::PlaceholderStatement(_)
569 | Statement::Return(_)
570 | Statement::RevertStatement(_) => {
571 vec![]
573 }
574 Statement::EmitStatement(_)
575 | Statement::ExpressionStatement(_)
576 | Statement::InlineAssembly(_)
577 | Statement::VariableDeclarationStatement(_) => {
578 find_next_index_of_statement(source, statement)
580 .map(|loc| vec![loc])
581 .unwrap_or_default()
582 }
583 }
584 }
585 crate::analysis::StepVariant::Statements(statements) => {
586 statements
588 .last()
589 .and_then(|stmt| {
590 find_next_index_of_statement(source, stmt).map(|loc| vec![loc])
591 })
592 .unwrap_or_default()
593 }
594 crate::analysis::StepVariant::IfCondition(if_statement) => {
595 let mut locs = find_index_of_first_statement_in_block_or_statement(
597 &if_statement.true_body,
598 )
599 .map(|loc| vec![loc])
600 .unwrap_or_default();
601 if let Some(false_loc) = if_statement.false_body.as_ref().and_then(|body| {
602 find_index_of_first_statement_in_block_or_statement(body)
603 .map(|loc| vec![loc])
604 }) {
605 locs.extend(false_loc.into_iter());
606 }
607 locs
608 }
609 crate::analysis::StepVariant::ForLoop(for_statement) => {
610 find_index_of_first_statement_in_block_or_statement(&for_statement.body)
612 .map(|loc| vec![loc])
613 .unwrap_or_default()
614 }
615 crate::analysis::StepVariant::WhileLoop(while_statement) => {
616 find_index_of_first_statement_in_block_or_statement(&while_statement.body)
618 .map(|loc| vec![loc])
619 .unwrap_or_default()
620 }
621 crate::analysis::StepVariant::DoWhileLoop(do_while_statement) => {
622 find_next_index_of_source_location(&do_while_statement.src)
624 .map(|loc| vec![loc])
625 .unwrap_or_default()
626 }
627 crate::analysis::StepVariant::Try(try_statement) => {
628 try_statement
630 .clauses
631 .iter()
632 .filter_map(|clause| find_index_of_first_statement_in_block(&clause.block))
633 .collect()
634 }
635 };
636 for loc in locs {
637 for updated_variable in updated_variables {
638 let uvid = updated_variable.id();
639 let instrument_action = InstrumentAction {
640 source_id,
641 loc,
642 content: InstrumentContent::VariableUpdateHook {
643 version: compiler_version.clone(),
644 uvid,
645 variable: updated_variable.clone(),
646 },
647 priority: VARIABLE_UPDATE_PRIORITY,
648 };
649
650 self.add_modification(instrument_action.into());
651 }
652 }
653 }
654 Ok(())
655 }
656}
657
658trait ExpectWithContext<T> {
660 fn expect_with_context(
661 self,
662 error_msg: &str,
663 source_id: u32,
664 source: &str,
665 src_loc: &SourceLocation,
666 ) -> T;
667}
668
669impl<T> ExpectWithContext<T> for Option<T> {
670 fn expect_with_context(
671 self,
672 error_msg: &str,
673 source_id: u32,
674 source: &str,
675 src_loc: &SourceLocation,
676 ) -> T {
677 match self {
678 Some(value) => value,
679 None => {
680 let temp_dir = std::env::temp_dir();
682 let dump_path = temp_dir.join(format!("edb_fail_source_{source_id}.sol"));
683 let _ = std::fs::write(&dump_path, source);
684
685 let context = if let Some(start) = src_loc.start {
687 let context_start = start.saturating_sub(200);
688 let context_end = (start + 200).min(source.len());
689 let context_slice = &source[context_start..context_end];
690
691 let lines_before_context: Vec<&str> =
693 source[..context_start].split('\n').collect();
694 let context_lines: Vec<&str> = context_slice.split('\n').collect();
695 let start_line_num = lines_before_context.len();
696 let error_pos_in_context = start - context_start;
697
698 let mut current_pos = 0;
700 let mut error_line_idx = 0;
701 let mut error_col = 0;
702
703 for (idx, line) in context_lines.iter().enumerate() {
704 let line_end = current_pos + line.len();
705 if error_pos_in_context >= current_pos && error_pos_in_context <= line_end {
706 error_line_idx = idx;
707 error_col = error_pos_in_context - current_pos;
708 break;
709 }
710 current_pos = line_end + 1; }
712
713 let mut formatted_context = format!(
714 "\n Source context around line {}:",
715 start_line_num + error_line_idx
716 );
717
718 for (idx, line) in context_lines.iter().enumerate() {
719 if line.trim().is_empty() && idx != error_line_idx {
720 continue; }
722
723 let line_num = start_line_num + idx;
724 let marker = if idx == error_line_idx { " --> " } else { " " };
725 formatted_context.push_str(&format!("\n{marker}{line_num:4} | {line}"));
726
727 if idx == error_line_idx {
729 let pointer = format!(
730 "\n {} | {}{}^ error here",
731 " ".repeat(4),
732 "_".repeat(error_col),
733 ""
734 );
735 formatted_context.push_str(&pointer);
736 }
737 }
738
739 formatted_context
740 } else {
741 String::new()
742 };
743
744 panic!(
745 "{}\n Source ID: {}\n Source location: {:?}{}\n Full source dumped to: {}",
746 error_msg,
747 source_id,
748 src_loc,
749 context,
750 dump_path.display()
751 );
752 }
753 }
754 }
755}
756
757#[cfg(test)]
758mod tests {
759 use crate::analysis::{self, tests::compile_and_analyze};
760
761 use super::*;
762
763 #[test]
764 fn test_collect_statement_to_block_modifications() {
765 let source = r#"
766 contract C {
767 function a() public returns (uint256) {
768 if (false )return 0;
769
770 if (true)return 0;
771 else return 1 ;
772 }
773
774 function b() public returns (uint256 x) {
775 for (uint256 i = 0; i < 10; i++)x += i
776 ;
777 }
778
779 function c() public returns (uint256) {
780 while (true) return 0;
781 }
782 }
783 "#;
784
785 let (_sources, analysis) = analysis::tests::compile_and_analyze(source);
786
787 let mut modifications = SourceModifications::new(analysis::tests::TEST_CONTRACT_SOURCE_ID);
788 modifications.collect_statement_to_block_modifications(source, &analysis).unwrap();
789 assert_eq!(modifications.modifications.len(), 10);
790 let modified_source = modifications.modify_source(source);
791
792 let (_sources, _analysis2) = analysis::tests::compile_and_analyze(&modified_source);
794 }
795
796 #[test]
797 fn test_do_while_loop_step_modification() {
798 let source = r#"
799 contract C {
800 function a() public returns (uint256) {
801 do {
802 uint x = 1;
803 return 0;
804 } while (false);
805 }
806 }
807 "#;
808
809 let (_sources, analysis) = analysis::tests::compile_and_analyze(source);
810
811 let mut modifications = SourceModifications::new(analysis::tests::TEST_CONTRACT_SOURCE_ID);
812 let version = Arc::new(Version::parse("0.8.0").unwrap());
813 modifications.collect_before_step_hook_modifications(version, source, &analysis).unwrap();
814 assert_eq!(modifications.modifications.len(), 4);
815 let modified_source = modifications.modify_source(source);
816
817 let (_sources, _analysis2) = analysis::tests::compile_and_analyze(&modified_source);
819 }
820
821 #[test]
822 fn test_collect_function_entry_step_hook_modifications() {
823 let source = r#"
824 abstract contract C {
825 function v() public virtual returns (uint256);
826
827 function a() public returns (uint256) {
828 uint x = 1;
829 }
830 }
831 "#;
832
833 let (_sources, analysis) = analysis::tests::compile_and_analyze(source);
834
835 let mut modifications = SourceModifications::new(analysis::tests::TEST_CONTRACT_SOURCE_ID);
836 let version = Arc::new(Version::parse("0.8.0").unwrap());
837 modifications.collect_before_step_hook_modifications(version, source, &analysis).unwrap();
838 let modified_source = modifications.modify_source(source);
840
841 let (_sources, _analysis2) = analysis::tests::compile_and_analyze(&modified_source);
843 }
844
845 #[test]
846 fn test_collect_before_step_hook_modifications() {
847 let source = r#"
848 abstract contract C {
849 function a() public returns (uint256) {
850 if (false) {return 0;}
851 else {return 1;}
852 for (uint256 i = 0; i < 10; i++) {
853 return 0;
854 }
855 while (true) {
856 return 0;
857 }
858 do {
859 return 0;
860 } while (false);
861 try this.a() {
862 return 0;
863 }
864 catch {}
865 return 0;
866 }
867 }
868 "#;
869
870 let (_sources, analysis) = analysis::tests::compile_and_analyze(source);
871
872 let mut modifications = SourceModifications::new(analysis::tests::TEST_CONTRACT_SOURCE_ID);
873 let version = Arc::new(Version::parse("0.8.0").unwrap());
874 modifications.collect_before_step_hook_modifications(version, source, &analysis).unwrap();
875 assert_eq!(modifications.modifications.len(), 13);
876 let modified_source = modifications.modify_source(source);
877
878 let (_sources, _analysis2) = analysis::tests::compile_and_analyze(&modified_source);
880 }
881
882 #[test]
883 fn test_modifier_is_not_step() {
884 let source = r#"
885 contract C {
886 modifier m(uint x) {
887 _;
888 }
889
890 function a() public m(1) {}
891 }
892 "#;
893 let (_sources, analysis) = analysis::tests::compile_and_analyze(source);
894
895 let mut modifications = SourceModifications::new(analysis::tests::TEST_CONTRACT_SOURCE_ID);
896 let version = Arc::new(Version::parse("0.8.0").unwrap());
897 modifications.collect_before_step_hook_modifications(version, source, &analysis).unwrap();
898 assert_eq!(modifications.modifications.len(), 1);
899 let modified_source = modifications.modify_source(source);
900
901 let (_sources, _analysis2) = analysis::tests::compile_and_analyze(&modified_source);
903 }
904
905 #[test]
906 fn test_else_if_statement_to_block() {
907 let source = r#"
908contract TestContract {
909 function foo() public {
910 if (true)
911 revert();
912 else if (false)
913 return;
914 else {
915 require(true, "error");
916 }
917 }
918}
919"#;
920 let (_sources, analysis) = compile_and_analyze(source);
921
922 let mut modifications = SourceModifications::new(analysis::tests::TEST_CONTRACT_SOURCE_ID);
923 modifications.collect_statement_to_block_modifications(source, &analysis).unwrap();
924 assert_eq!(modifications.modifications.len(), 6);
925 let modified_source = modifications.modify_source(source);
926
927 let (_sources, _analysis2) = analysis::tests::compile_and_analyze(&modified_source);
929 }
930
931 #[test]
932 fn test_if_for_statement_to_block() {
933 let source = r#"
934contract TestContract {
935 function foo() public {
936 if (true)
937 for (uint256 i = 0; i < 10; i++)
938 return;
939 else
940 while (true)
941 return;
942 }
943}
944"#;
945 let (_sources, analysis) = compile_and_analyze(source);
946
947 let mut modifications = SourceModifications::new(analysis::tests::TEST_CONTRACT_SOURCE_ID);
948 modifications.collect_statement_to_block_modifications(source, &analysis).unwrap();
949 assert_eq!(modifications.modifications.len(), 6);
950 let modified_source = modifications.modify_source(source);
951
952 let (_sources, _analysis2) = analysis::tests::compile_and_analyze(&modified_source);
954 }
955
956 #[test]
957 fn test_variable_update_hook_modification_for_for_loop() {
958 let source = r#"
959 contract C {
960 function a() public returns (uint256) {
961 for (uint i = 0; i < 10; i++) {
962 return i;
963 }
964 }
965 }
966 "#;
967 let (_sources, analysis) = compile_and_analyze(source);
968
969 let mut modifications = SourceModifications::new(analysis::tests::TEST_CONTRACT_SOURCE_ID);
970 modifications
971 .collect_variable_update_hook_modifications(
972 Arc::new(Version::parse("0.8.0").unwrap()),
973 source,
974 &analysis,
975 )
976 .unwrap();
977 assert_eq!(modifications.modifications.len(), 1);
978 let modified_source = modifications.modify_source(source);
979
980 let (_sources, _analysis2) = analysis::tests::compile_and_analyze(&modified_source);
982 }
983}