Эх сурвалжийг харах

Implement tests for unused variable detection

Signed-off-by: LucasSte <lucas.tnagel@gmail.com>
LucasSte 4 жил өмнө
parent
commit
be73b62d98

+ 1 - 2
.gitignore

@@ -2,5 +2,4 @@
 Cargo.lock
 /target
 **/*.rs.bk
-.DS_Store
-.idea
+

+ 2 - 2
src/sema/contracts.rs

@@ -870,9 +870,9 @@ fn resolve_bodies(
         {
             broken = true;
         } else {
-            for (_, variable) in &ns.functions[function_no].symtable.vars {
+            for variable in ns.functions[function_no].symtable.vars.values() {
                 if let Some(warning) = emit_warning_local_variable(&variable) {
-                    ns.diagnostics.push(*warning);
+                    ns.diagnostics.push(warning);
                 }
             }
         }

+ 39 - 34
src/sema/expression.rs

@@ -1321,17 +1321,25 @@ pub fn expression(
     resolve_to: Option<&Type>,
 ) -> Result<Expression, ()> {
     match expr {
-        pt::Expression::ArrayLiteral(loc, exprs) => resolve_array_literal(
-            loc,
-            exprs,
-            file_no,
-            contract_no,
-            ns,
-            symtable,
-            is_constant,
-            diagnostics,
-            resolve_to,
-        ),
+        pt::Expression::ArrayLiteral(loc, exprs) => {
+            let res = resolve_array_literal(
+                loc,
+                exprs,
+                file_no,
+                contract_no,
+                ns,
+                symtable,
+                is_constant,
+                diagnostics,
+                resolve_to,
+            );
+
+            if let Ok(exp) = &res {
+                used_variable(ns, exp, symtable);
+            }
+
+            res
+        }
         pt::Expression::BoolLiteral(loc, v) => Ok(Expression::BoolLiteral(*loc, *v)),
         pt::Expression::StringLiteral(v) => {
             // Concatenate the strings
@@ -2479,6 +2487,7 @@ pub fn expression(
                 diagnostics,
                 resolve_to,
             )?;
+            used_variable(ns, &cond, symtable);
 
             let cond = cast(&c.loc(), cond, &Type::Bool, true, ns, diagnostics)?;
 
@@ -2759,26 +2768,18 @@ pub fn expression(
             is_constant,
             diagnostics,
         ),
-        pt::Expression::MemberAccess(loc, e, id) => {
-            let res = member_access(
-                loc,
-                e,
-                id,
-                file_no,
-                contract_no,
-                ns,
-                symtable,
-                is_constant,
-                diagnostics,
-                resolve_to,
-            );
-
-            if let Ok(exp) = &res {
-                used_variable(ns, &exp, symtable);
-            }
-
-            res
-        }
+        pt::Expression::MemberAccess(loc, e, id) => member_access(
+            loc,
+            e,
+            id,
+            file_no,
+            contract_no,
+            ns,
+            symtable,
+            is_constant,
+            diagnostics,
+            resolve_to,
+        ),
         pt::Expression::Or(loc, left, right) => {
             let boolty = Type::Bool;
             let l = cast(
@@ -4426,7 +4427,10 @@ fn member_access(
             if id.name == "length" {
                 return match dim.last().unwrap() {
                     None => Ok(Expression::DynamicArrayLength(*loc, Box::new(expr))),
-                    Some(d) => bigint_to_expression(loc, d, ns, diagnostics, Some(&Type::Uint(32))),
+                    Some(d) => {
+                        used_variable(ns, &expr, symtable);
+                        bigint_to_expression(loc, d, ns, diagnostics, Some(&Type::Uint(32)))
+                    }
                 };
             }
         }
@@ -4793,7 +4797,7 @@ fn struct_literal(
                 diagnostics,
                 Some(&struct_def.fields[i].ty),
             )?;
-
+            used_variable(ns, &expr, symtable);
             fields.push(cast(
                 loc,
                 expr,
@@ -5408,7 +5412,7 @@ fn named_struct_literal(
                         diagnostics,
                         Some(&f.ty),
                     )?;
-
+                    used_variable(ns, &expr, symtable);
                     fields[i] = cast(loc, expr, &f.ty, true, ns, diagnostics)?;
                 }
                 None => {
@@ -6640,6 +6644,7 @@ fn resolve_array_literal(
         first.ty()
     };
 
+    used_variable(ns, &first, symtable);
     let mut exprs = vec![first];
 
     for e in flattened {

+ 8 - 6
src/sema/mod.rs

@@ -41,16 +41,18 @@ pub const SOLANA_BUCKET_SIZE: u64 = 251;
 pub const SOLANA_FIRST_OFFSET: u64 = 16;
 pub const SOLANA_SPARSE_ARRAY_SIZE: u64 = 1024;
 
-/// Performs semantic analysis and checks for unused variables
+/// Load a file file from the cache, parse and resolve it. The file must be present in
+/// the cache.
 pub fn sema(file: ResolvedFile, cache: &mut FileCache, ns: &mut ast::Namespace) {
-    perform_sema(file, cache, ns);
+    sema_file(file, cache, ns);
+
+    // Checks for unused variables
     check_unused_namespace_variables(ns);
     check_unused_events(ns);
 }
 
-/// Load a file file from the cache, parse and resolve it. The file must be present in
-/// the cache. This function is recursive for imports.
-pub fn perform_sema(file: ResolvedFile, cache: &mut FileCache, ns: &mut ast::Namespace) {
+/// Parse and resolve a file and its imports in a recursive manner.
+fn sema_file(file: ResolvedFile, cache: &mut FileCache, ns: &mut ast::Namespace) {
     let file_no = ns.files.len();
 
     let source_code = cache.get_file_contents(&file.full_path);
@@ -177,7 +179,7 @@ fn resolve_import(
         }
         Ok(file) => {
             if !ns.files.iter().any(|f| *f == file.full_path) {
-                perform_sema(file.clone(), cache, ns);
+                sema_file(file.clone(), cache, ns);
 
                 // give up if we failed
                 if diagnostics::any_errors(&ns.diagnostics) {

+ 38 - 32
src/sema/statements.rs

@@ -319,9 +319,7 @@ fn statement(
                 diagnostics,
             )?;
 
-            let mut initialized = false;
             let initializer = if let Some(init) = initializer {
-                initialized = true;
                 let expr = expression(
                     init,
                     file_no,
@@ -332,6 +330,7 @@ fn statement(
                     diagnostics,
                     Some(&var_ty),
                 )?;
+
                 used_variable(ns, &expr, symtable);
 
                 Some(cast(&expr.loc(), expr, &var_ty, true, ns, diagnostics)?)
@@ -343,8 +342,8 @@ fn statement(
                 &decl.name,
                 var_ty.clone(),
                 ns,
-                initialized,
-                VariableUsage::StateVariable,
+                initializer.is_some(),
+                VariableUsage::LocalVariable,
             ) {
                 ns.check_shadowing(file_no, contract_no, &decl.name);
 
@@ -717,11 +716,6 @@ fn statement(
                 return Err(());
             }
 
-            for offset in symtable.returns.iter() {
-                let elem = symtable.vars.get_mut(offset).unwrap();
-                (*elem).assigned = true;
-            }
-
             res.push(Statement::Return(
                 *loc,
                 symtable
@@ -1261,27 +1255,35 @@ fn destructure_values(
     diagnostics: &mut Vec<Diagnostic>,
 ) -> Result<Expression, ()> {
     let expr = match expr {
-        pt::Expression::FunctionCall(loc, ty, args) => function_call_expr(
-            loc,
-            ty,
-            args,
-            file_no,
-            contract_no,
-            ns,
-            symtable,
-            false,
-            diagnostics,
-        )?,
-        pt::Expression::NamedFunctionCall(loc, ty, args) => named_function_call_expr(
-            loc,
-            ty,
-            args,
-            file_no,
-            contract_no,
-            ns,
-            symtable,
-            diagnostics,
-        )?,
+        pt::Expression::FunctionCall(loc, ty, args) => {
+            let res = function_call_expr(
+                loc,
+                ty,
+                args,
+                file_no,
+                contract_no,
+                ns,
+                symtable,
+                false,
+                diagnostics,
+            )?;
+            check_function_call(ns, &res, symtable);
+            res
+        }
+        pt::Expression::NamedFunctionCall(loc, ty, args) => {
+            let res = named_function_call_expr(
+                loc,
+                ty,
+                args,
+                file_no,
+                contract_no,
+                ns,
+                symtable,
+                diagnostics,
+            )?;
+            check_function_call(ns, &res, symtable);
+            res
+        }
         pt::Expression::Ternary(loc, cond, left, right) => {
             let cond = expression(
                 cond,
@@ -1294,6 +1296,7 @@ fn destructure_values(
                 Some(&Type::Bool),
             )?;
 
+            used_variable(ns, &cond, symtable);
             let left = destructure_values(
                 &left.loc(),
                 left,
@@ -1305,7 +1308,7 @@ fn destructure_values(
                 ns,
                 diagnostics,
             )?;
-
+            used_variable(ns, &left, symtable);
             let right = destructure_values(
                 &right.loc(),
                 right,
@@ -1317,6 +1320,7 @@ fn destructure_values(
                 ns,
                 diagnostics,
             )?;
+            used_variable(ns, &right, symtable);
 
             return Ok(Expression::Ternary(
                 *loc,
@@ -1363,7 +1367,9 @@ fn destructure_values(
                         ));
                         return Err(());
                     }
-                    _ => (),
+                    _ => {
+                        used_variable(ns, &e, symtable);
+                    }
                 }
 
                 list.push(e);

+ 1 - 1
src/sema/symtable.rs

@@ -22,7 +22,7 @@ pub enum VariableUsage {
     Parameter,
     ReturnVariable,
     AnonymousReturnVariable,
-    StateVariable,
+    LocalVariable,
     DestructureVariable,
     TryCatchReturns,
     TryCatchErrorString,

+ 73 - 47
src/sema/unused_variable.rs

@@ -5,7 +5,7 @@ use crate::sema::ast::{
 use crate::sema::symtable::{Symtable, VariableUsage};
 use crate::sema::{ast, symtable};
 
-/// Mark variables as assigned, either in the symbol table (for state variables) or in the
+/// Mark variables as assigned, either in the symbol table (for local variables) or in the
 /// Namespace (for storage variables)
 pub fn assigned_variable(ns: &mut Namespace, exp: &Expression, symtable: &mut Symtable) {
     match &exp {
@@ -29,11 +29,17 @@ pub fn assigned_variable(ns: &mut Namespace, exp: &Expression, symtable: &mut Sy
             used_variable(ns, index, symtable);
         }
 
+        Expression::Trunc(_, _, var)
+        | Expression::Cast(_, _, var)
+        | Expression::BytesCast(_, _, _, var) => {
+            assigned_variable(ns, var, symtable);
+        }
+
         _ => {}
     }
 }
 
-/// Mark variables as used, either in the symbol table (for state variables) or in the
+/// Mark variables as used, either in the symbol table (for local variables) or in the
 /// Namespace (for global constants and storage variables)
 /// The functions handles complex expressions in a recursive fashion, such as array length call,
 /// assign expressions and array subscripts.
@@ -56,10 +62,6 @@ pub fn used_variable(ns: &mut Namespace, exp: &Expression, symtable: &mut Symtab
             ns.constants[*offset].read = true;
         }
 
-        Expression::ZeroExt(_, _, variable) => {
-            used_variable(ns, variable, symtable);
-        }
-
         Expression::StructMember(_, _, str, _) => {
             used_variable(ns, str, symtable);
         }
@@ -71,16 +73,8 @@ pub fn used_variable(ns: &mut Namespace, exp: &Expression, symtable: &mut Symtab
             used_variable(ns, index, symtable);
         }
 
-        Expression::Assign(_, _, assigned, assignee) => {
-            assigned_variable(ns, assigned, symtable);
-            used_variable(ns, assignee, symtable);
-        }
-
-        Expression::DynamicArrayLength(_, array) => {
-            used_variable(ns, array, symtable);
-        }
-
-        Expression::StorageArrayLength {
+        Expression::DynamicArrayLength(_, array)
+        | Expression::StorageArrayLength {
             loc: _,
             ty: _,
             array,
@@ -89,7 +83,12 @@ pub fn used_variable(ns: &mut Namespace, exp: &Expression, symtable: &mut Symtab
             used_variable(ns, array, symtable);
         }
 
-        Expression::StorageLoad(_, _, expr) => {
+        Expression::StorageLoad(_, _, expr)
+        | Expression::SignExt(_, _, expr)
+        | Expression::ZeroExt(_, _, expr)
+        | Expression::Trunc(_, _, expr)
+        | Expression::Cast(_, _, expr)
+        | Expression::BytesCast(_, _, _, expr) => {
             used_variable(ns, expr, symtable);
         }
 
@@ -135,12 +134,12 @@ pub fn check_function_call(ns: &mut Namespace, exp: &Expression, symtable: &mut
         Expression::ExternalFunctionCallRaw {
             loc: _,
             ty: _,
-            address: function,
+            address,
             args,
             ..
         } => {
             used_variable(ns, args, symtable);
-            check_function_call(ns, function, symtable);
+            used_variable(ns, address, symtable);
         }
 
         Expression::ExternalFunction {
@@ -197,25 +196,25 @@ pub fn check_var_usage_expression(
 }
 
 /// Generate warnings for unused varibles
-fn generate_unused_warning(loc: Loc, text: &String, notes: Vec<Note>) -> Box<Diagnostic> {
-    Box::new(Diagnostic {
+fn generate_unused_warning(loc: Loc, text: &str, notes: Vec<Note>) -> Diagnostic {
+    Diagnostic {
         level: Level::Warning,
         ty: ErrorType::Warning,
         pos: Some(loc),
-        message: text.clone(),
+        message: text.parse().unwrap(),
         notes,
-    })
+    }
 }
 
-/// Emit different warning types according to the state variable usage
-pub fn emit_warning_local_variable(variable: &symtable::Variable) -> Option<Box<Diagnostic>> {
+/// Emit different warning types according to the function variable usage
+pub fn emit_warning_local_variable(variable: &symtable::Variable) -> Option<Diagnostic> {
     match &variable.usage_type {
         VariableUsage::Parameter => {
             if !variable.read {
                 return Some(generate_unused_warning(
                     variable.id.loc,
                     &format!(
-                        "Function parameter '{}' has never been used.",
+                        "function parameter '{}' has never been read",
                         variable.id.name
                     ),
                     vec![],
@@ -229,7 +228,7 @@ pub fn emit_warning_local_variable(variable: &symtable::Variable) -> Option<Box<
                 return Some(generate_unused_warning(
                     variable.id.loc,
                     &format!(
-                        "Return variable '{}' has never been assigned",
+                        "return variable '{}' has never been assigned",
                         variable.id.name
                     ),
                     vec![],
@@ -238,12 +237,12 @@ pub fn emit_warning_local_variable(variable: &symtable::Variable) -> Option<Box<
             None
         }
 
-        VariableUsage::StateVariable => {
+        VariableUsage::LocalVariable => {
             if !variable.assigned && !variable.read {
                 return Some(generate_unused_warning(
                     variable.id.loc,
                     &format!(
-                        "State variable '{}' has never been read nor assigned",
+                        "local variable '{}' has never been read nor assigned",
                         variable.id.name
                     ),
                     vec![],
@@ -252,7 +251,7 @@ pub fn emit_warning_local_variable(variable: &symtable::Variable) -> Option<Box<
                 return Some(generate_unused_warning(
                     variable.id.loc,
                     &format!(
-                        "State variable '{}' has been assigned, but never read.",
+                        "local variable '{}' has been assigned, but never read",
                         variable.id.name
                     ),
                     vec![],
@@ -261,7 +260,7 @@ pub fn emit_warning_local_variable(variable: &symtable::Variable) -> Option<Box<
                 return Some(generate_unused_warning(
                     variable.id.loc,
                     &format!(
-                        "State variable '{}' has never been assigned a value, but has been read.",
+                        "local variable '{}' has never been assigned a value, but has been read",
                         variable.id.name
                     ),
                     vec![],
@@ -275,7 +274,7 @@ pub fn emit_warning_local_variable(variable: &symtable::Variable) -> Option<Box<
                 return Some(generate_unused_warning(
                     variable.id.loc,
                     &format!(
-                        "Destructure variable '{}' has never been used.",
+                        "destructure variable '{}' has never been used",
                         variable.id.name
                     ),
                     vec![],
@@ -290,7 +289,22 @@ pub fn emit_warning_local_variable(variable: &symtable::Variable) -> Option<Box<
                 return Some(generate_unused_warning(
                     variable.id.loc,
                     &format!(
-                        "Try catch returns variable '{}' has never been read.",
+                        "try-catch returns variable '{}' has never been read",
+                        variable.id.name
+                    ),
+                    vec![],
+                ));
+            }
+
+            None
+        }
+
+        VariableUsage::TryCatchErrorBytes => {
+            if !variable.read {
+                return Some(generate_unused_warning(
+                    variable.id.loc,
+                    &format!(
+                        "try-catch error bytes '{}' has never been used",
                         variable.id.name
                     ),
                     vec![],
@@ -300,27 +314,39 @@ pub fn emit_warning_local_variable(variable: &symtable::Variable) -> Option<Box<
             None
         }
 
-        VariableUsage::AnonymousReturnVariable
-        | VariableUsage::TryCatchErrorBytes
-        | VariableUsage::TryCatchErrorString => None,
+        VariableUsage::TryCatchErrorString => {
+            if !variable.read {
+                return Some(generate_unused_warning(
+                    variable.id.loc,
+                    &format!(
+                        "try-catch error string '{}' has never been used",
+                        variable.id.name
+                    ),
+                    vec![],
+                ));
+            }
+
+            None
+        }
+        VariableUsage::AnonymousReturnVariable => None,
     }
 }
 
-/// Emit warnings depending on the state variable usage
-fn emit_warning_contract_variables(variable: &ast::Variable) -> Option<Box<Diagnostic>> {
-    if !variable.assigned && !variable.read {
+/// Emit warnings depending on the storage variable usage
+fn emit_warning_contract_variables(variable: &ast::Variable) -> Option<Diagnostic> {
+    if variable.assigned && !variable.read {
         return Some(generate_unused_warning(
             variable.loc,
             &format!(
-                "Storage variable '{}' has been assigned, but never used.",
+                "storage variable '{}' has been assigned, but never read",
                 variable.name
             ),
             vec![],
         ));
-    } else if variable.assigned && !variable.read {
+    } else if !variable.assigned && !variable.read {
         return Some(generate_unused_warning(
             variable.loc,
-            &format!("Storage variable '{}' has never been used.", variable.name),
+            &format!("storage variable '{}' has never been used", variable.name),
             vec![],
         ));
     }
@@ -335,7 +361,7 @@ pub fn check_unused_namespace_variables(ns: &mut Namespace) {
     for contract in &ns.contracts {
         for variable in &contract.variables {
             if let Some(warning) = emit_warning_contract_variables(variable) {
-                ns.diagnostics.push(*warning);
+                ns.diagnostics.push(warning);
             }
         }
     }
@@ -343,9 +369,9 @@ pub fn check_unused_namespace_variables(ns: &mut Namespace) {
     //Global constants should have been initialized during declaration
     for constant in &ns.constants {
         if !constant.read {
-            ns.diagnostics.push(*generate_unused_warning(
+            ns.diagnostics.push(generate_unused_warning(
                 constant.loc,
-                &format!("Global constant '{}' has never been used.", constant.name),
+                &format!("global constant '{}' has never been used", constant.name),
                 vec![],
             ))
         }
@@ -356,9 +382,9 @@ pub fn check_unused_namespace_variables(ns: &mut Namespace) {
 pub fn check_unused_events(ns: &mut Namespace) {
     for event in &ns.events {
         if !event.used {
-            ns.diagnostics.push(*generate_unused_warning(
+            ns.diagnostics.push(generate_unused_warning(
                 event.loc,
-                &format!("Event '{}' has never been emitted.", event.name),
+                &format!("event '{}' has never been emitted", event.name),
                 vec![],
             ))
         }

+ 1 - 3
src/sema/variables.rs

@@ -98,7 +98,6 @@ pub fn var_decl(
 
     let mut is_constant = false;
     let mut visibility: Option<pt::Visibility> = None;
-    let mut initialized = false;
 
     for attr in attrs {
         match &attr {
@@ -179,7 +178,6 @@ pub fn var_decl(
 
     let initializer = if let Some(initializer) = &s.initializer {
         let mut diagnostics = Vec::new();
-        initialized = true;
 
         let res = match expression(
             &initializer,
@@ -251,9 +249,9 @@ pub fn var_decl(
         visibility: visibility.clone(),
         ty: ty.clone(),
         constant: is_constant,
+        assigned: initializer.is_some(),
         initializer,
         read: false,
-        assigned: initialized,
     };
 
     let pos = if let Some(contract_no) = contract_no {

+ 1 - 1
tests/substrate_tests/tags.rs

@@ -401,7 +401,7 @@ fn functions() {
         Target::Substrate,
     );
 
-    assert_eq!(ns.diagnostics.len(), 3);
+    assert_eq!(ns.diagnostics.len(), 5);
 
     assert_eq!(ns.functions[0].tags[0].tag, "param");
     assert_eq!(ns.functions[0].tags[0].value, "sadad");

+ 901 - 0
tests/unused_variable_detection.rs

@@ -0,0 +1,901 @@
+use itertools::Itertools;
+use solang::file_cache::FileCache;
+use solang::sema::ast;
+use solang::sema::ast::{Diagnostic, Level};
+use solang::{parse_and_resolve, Target};
+
+fn generic_target_parse(src: &'static str) -> ast::Namespace {
+    let mut cache = FileCache::new();
+    cache.set_file_contents("test.sol", src.to_string());
+
+    parse_and_resolve("test.sol", &mut cache, Target::Generic)
+}
+
+fn generic_parse_two_files(src1: &'static str, src2: &'static str) -> ast::Namespace {
+    let mut cache = FileCache::new();
+    cache.set_file_contents("test.sol", src1.to_string());
+    cache.set_file_contents("test2.sol", src2.to_string());
+
+    parse_and_resolve("test.sol", &mut cache, Target::Generic)
+}
+
+fn count_warnings(diagnostics: &[Diagnostic]) -> usize {
+    diagnostics
+        .iter()
+        .filter(|&x| x.level == Level::Warning)
+        .count()
+}
+
+fn get_first_warning(diagnostics: &[Diagnostic]) -> &Diagnostic {
+    diagnostics
+        .iter()
+        .find_or_first(|&x| x.level == Level::Warning)
+        .unwrap()
+}
+
+fn get_warnings(diagnostics: &[Diagnostic]) -> Vec<&Diagnostic> {
+    let mut res = Vec::new();
+    for elem in diagnostics {
+        if elem.level == Level::Warning {
+            res.push(elem);
+        }
+    }
+
+    res
+}
+
+fn assert_message_in_warnings(diagnostics: &[Diagnostic], message: &str) -> bool {
+    let warnings = get_warnings(diagnostics);
+    for warning in warnings {
+        if warning.message == message {
+            return true;
+        }
+    }
+
+    false
+}
+
+#[test]
+fn emit_event() {
+    //Used event
+    let case_1 = r#"
+    contract usedEvent {
+        event Hey(uint8 n);
+        function emitEvent(uint8 n) public {
+            emit Hey(n);
+        }
+    }
+    "#;
+
+    let ns = generic_target_parse(case_1);
+    assert_eq!(count_warnings(&ns.diagnostics), 0);
+
+    // Unused event
+    let case_2 = r#"
+    contract usedEvent {
+        event Hey(uint8 n);
+        event Hello(uint8 n);
+        function emitEvent(uint8 n) public {
+            emit Hey(n);
+        }
+    }
+    "#;
+
+    let ns = generic_target_parse(case_2);
+    assert_eq!(count_warnings(&ns.diagnostics), 1);
+    assert_eq!(
+        get_first_warning(&ns.diagnostics).message,
+        "event 'Hello' has never been emitted"
+    );
+}
+
+#[test]
+fn constant_variable() {
+    let file_2 = r#"
+        uint32 constant outside = 2;
+    "#;
+
+    let file_1 = r#"
+        import "test2.sol";
+        contract Testing {
+            uint32 test;
+            uint32 constant cte = 5;
+            constructor() {
+                test = outside;
+                test = cte;
+            }
+
+            function get() public view returns (uint32) {
+                return test;
+            }
+        }
+    "#;
+
+    //Constant properly read
+    let ns = generic_parse_two_files(file_1, file_2);
+    assert_eq!(count_warnings(&ns.diagnostics), 0);
+
+    let file_1 = r#"
+        import "test2.sol";
+        contract Testing {
+            uint32 test;
+            uint32 constant cte = 5;
+            constructor() {
+                test = 45;
+            }
+
+            function get() public view returns (uint32) {
+                return test;
+            }
+        }
+    "#;
+
+    let ns = generic_parse_two_files(file_1, file_2);
+    assert_eq!(count_warnings(&ns.diagnostics), 2);
+    assert!(assert_message_in_warnings(
+        &ns.diagnostics,
+        "storage variable 'cte' has been assigned, but never read"
+    ));
+    assert!(assert_message_in_warnings(
+        &ns.diagnostics,
+        "global constant 'outside' has never been used"
+    ));
+}
+
+#[test]
+fn storage_variable() {
+    let file = r#"
+        contract Test {
+            string str = "This is a test";
+            string str2;
+            constructor() {
+                str = "This is another test";
+            }
+        }
+    "#;
+
+    let ns = generic_target_parse(file);
+    let warnings = get_warnings(&ns.diagnostics);
+    assert_eq!(warnings.len(), 2);
+    assert_eq!(
+        warnings[0].message,
+        "storage variable 'str' has been assigned, but never read"
+    );
+    assert_eq!(
+        warnings[1].message,
+        "storage variable 'str2' has never been used"
+    );
+
+    let file = r#"
+        contract Test {
+            string str = "This is a test";
+            string str2;
+            constructor() {
+                str = "This is another test";
+                str2 = str;
+            }
+        }
+    "#;
+
+    let ns = generic_target_parse(file);
+    assert_eq!(count_warnings(&ns.diagnostics), 1);
+    assert_eq!(
+        get_first_warning(&ns.diagnostics).message,
+        "storage variable 'str2' has been assigned, but never read"
+    );
+
+    let file = r#"
+        contract Test {
+            string str = "This is a test";
+            constructor() {
+                str = "This is another test";
+            }
+        }
+
+        contract Test2 is Test {
+            function get() public view returns (string) {
+                return str;
+            }
+        }
+    "#;
+
+    let ns = generic_target_parse(file);
+    assert_eq!(count_warnings(&ns.diagnostics), 0);
+}
+
+#[test]
+fn state_variable() {
+    let file = r#"
+        contract Test {
+            function get() public pure {
+                uint32 a = 1;
+                uint32 b;
+                b = 1;
+                uint32 c;
+
+                uint32 d;
+                d = 1;
+                uint32 e;
+                e = d*5;
+                d = e/5;
+            }
+        }
+    "#;
+
+    let ns = generic_target_parse(file);
+    assert_eq!(count_warnings(&ns.diagnostics), 3);
+    assert!(assert_message_in_warnings(
+        &ns.diagnostics,
+        "local variable 'b' has been assigned, but never read"
+    ));
+    assert!(assert_message_in_warnings(
+        &ns.diagnostics,
+        "local variable 'a' has been assigned, but never read"
+    ));
+    assert!(assert_message_in_warnings(
+        &ns.diagnostics,
+        "local variable 'c' has never been read nor assigned"
+    ));
+}
+
+#[test]
+fn struct_usage() {
+    let file = r#"
+        struct testing {
+            uint8 it;
+            bool tf;
+        }
+
+        contract Test {
+            testing t1;
+            testing t4;
+            testing t6;
+            constructor() {
+                t1 = testing(8, false);
+            }
+
+            function modify() public returns (uint8) {
+                testing memory t2;
+                t2.it = 4;
+
+
+                t4 = testing(1, true);
+                testing storage t3 = t4;
+                uint8 k = 2*4/t3.it;
+                testing t5;
+
+               return k;
+            }
+        }
+    "#;
+
+    let ns = generic_target_parse(file);
+    assert_eq!(count_warnings(&ns.diagnostics), 4);
+    assert!(assert_message_in_warnings(
+        &ns.diagnostics,
+        "local variable 't2' has been assigned, but never read"
+    ));
+    assert!(assert_message_in_warnings(
+        &ns.diagnostics,
+        "storage variable 't1' has been assigned, but never read"
+    ));
+    assert!(assert_message_in_warnings(
+        &ns.diagnostics,
+        "local variable 't5' has never been read nor assigned"
+    ));
+    assert!(assert_message_in_warnings(
+        &ns.diagnostics,
+        "storage variable 't6' has never been used"
+    ));
+}
+
+#[test]
+fn subscript() {
+    let file = r#"
+        contract Test {
+            int[] arr1;
+            int[4] arr2;
+            int[4] arr3;
+            bytes byteArr;
+
+            uint constant e = 1;
+
+            function get() public {
+                uint8 a = 1;
+                uint8 b = 2;
+
+                arr1[a] = 1;
+                arr2[a+b] = 2;
+
+                uint8 c = 1;
+                uint8 d = 1;
+                int[] memory arr4;
+                arr4[0] = 1;
+                int[4] storage arr5 = arr3;
+                arr5[c*d] = 1;
+
+                byteArr[e] = 0x05;
+            }
+        }
+    "#;
+
+    let ns = generic_target_parse(file);
+    assert_eq!(count_warnings(&ns.diagnostics), 5);
+    assert!(assert_message_in_warnings(
+        &ns.diagnostics,
+        "local variable 'arr4' has been assigned, but never read"
+    ));
+    assert!(assert_message_in_warnings(
+        &ns.diagnostics,
+        "local variable 'arr5' has been assigned, but never read"
+    ));
+    assert!(assert_message_in_warnings(
+        &ns.diagnostics,
+        "storage variable 'arr1' has been assigned, but never read"
+    ));
+    assert!(assert_message_in_warnings(
+        &ns.diagnostics,
+        "storage variable 'arr2' has been assigned, but never read"
+    ));
+    assert!(assert_message_in_warnings(
+        &ns.diagnostics,
+        "storage variable 'byteArr' has been assigned, but never read"
+    ));
+
+    let file = r#"
+        contract Test {
+            int[] arr1;
+            int[4] arr2;
+            int[4] arr3;
+            bytes byteArr;
+
+            uint constant e = 1;
+
+            function get() public {
+                uint8 a = 1;
+                uint8 b = 2;
+
+                arr1[a] = 1;
+                arr2[a+b] = 2;
+                assert(arr1[a] == arr2[b]);
+
+                uint8 c = 1;
+                uint8 d = 1;
+                int[] memory arr4;
+                arr4[0] = 1;
+                int[4] storage arr5 = arr3;
+                arr5[c*d] = 1;
+                assert(arr4[c] == arr5[d]);
+                assert(arr3[c] == arr5[d]);
+
+                byteArr[e] = 0x05;
+                assert(byteArr[e] == byteArr[e]);
+            }
+        }
+    "#;
+
+    let ns = generic_target_parse(file);
+    assert_eq!(count_warnings(&ns.diagnostics), 0);
+}
+
+#[test]
+fn assign_trunc_cast() {
+    //This covers ZeroExt as well
+    let file = r#"
+    contract Test {
+        bytes byteArr;
+        bytes32 baRR;
+
+        function get() public  {
+            string memory s = "Test";
+            byteArr = bytes(s);
+            uint16 a = 1;
+            uint8 b;
+            b = uint8(a);
+
+            uint256 c;
+            c = b;
+            bytes32 b32;
+            bytes memory char = bytes(bytes32(uint(a) * 2 ** (8 * b)));
+            baRR = bytes32(c);
+            bytes32 cdr = bytes32(char);
+            assert(b32 == baRR);
+            if(b32 != cdr) {
+
+            }
+        }
+    }
+"#;
+
+    let ns = generic_target_parse(file);
+    assert_eq!(count_warnings(&ns.diagnostics), 2);
+    assert!(assert_message_in_warnings(
+        &ns.diagnostics,
+        "local variable 'b32' has never been assigned a value, but has been read"
+    ));
+    assert!(assert_message_in_warnings(
+        &ns.diagnostics,
+        "storage variable 'byteArr' has been assigned, but never read"
+    ));
+}
+
+#[test]
+fn array_length() {
+    let file = r#"
+        contract Test {
+        int[5] arr1;
+        int[] arr2;
+
+        function get() public view returns (bool) {
+            int[5] memory arr3;
+            int[] memory arr4;
+
+            bool test = false;
+            if(arr1.length == arr2.length) {
+                test = true;
+            }
+            else if(arr3.length != arr4.length) {
+                test = false;
+            }
+
+            return test;
+        }
+    }
+    "#;
+
+    let ns = generic_target_parse(file);
+    assert_eq!(count_warnings(&ns.diagnostics), 2);
+    assert!(assert_message_in_warnings(
+        &ns.diagnostics,
+        "local variable 'arr3' has never been assigned a value, but has been read",
+    ));
+    assert!(assert_message_in_warnings(
+        &ns.diagnostics,
+        "local variable 'arr4' has never been assigned a value, but has been read"
+    ));
+}
+
+#[test]
+fn sign_ext_storage_load() {
+    let file = r#"
+        contract Test {
+        bytes a;
+
+        function use(bytes memory b) pure public {
+            assert(b[0] == b[1]);
+        }
+
+        function get() public pure returns (int16 ret) {
+            use(a);
+
+            int8 b = 1;
+            int16 c = 1;
+            int16 d;
+            d = c << b;
+            ret = d;
+        }
+    }
+    "#;
+
+    let ns = generic_target_parse(file);
+    assert_eq!(count_warnings(&ns.diagnostics), 0);
+}
+
+#[test]
+fn statements() {
+    let file = r#"
+    contract AddNumbers { function add(uint256 a, uint256 b) external pure returns (uint256 c) {c = b;} }
+    contract Example {
+        AddNumbers addContract;
+        event StringFailure(string stringFailure);
+        event BytesFailure(bytes bytesFailure);
+
+        function exampleFunction(uint256 _a, uint256 _b) public returns (uint256 _c) {
+
+            try addContract.add(_a, _b) returns (uint256 _value) {
+                return (_value);
+            } catch Error(string memory _err) {
+                // This may occur if there is an overflow with the two numbers and the `AddNumbers` contract explicitly fails with a `revert()`
+                emit StringFailure(_err);
+            } catch (bytes memory _err) {
+                emit BytesFailure(_err);
+            }
+        }
+
+        function testFunction() pure public {
+            int three = 3;
+             {
+                 int test = 2;
+                 int c = test*3;
+
+                 while(c != test) {
+                     c -= three;
+                 }
+             }
+
+             int four = 4;
+             int test = 3;
+             do {
+                int ct = 2;
+             } while(four > test);
+
+        }
+
+         function bytesToUInt(uint v) public pure returns (uint ret) {
+        if (v == 0) {
+            ret = 0;
+        }
+        else {
+            while (v > 0) {
+                ret = uint(uint(ret) / (2 ** 8));
+                ret |= uint(((v % 10) + 48) * 2 ** (8 * 31));
+                v /= 10;
+            }
+        }
+        return ret;
+    }
+
+        function stringToUint(string s) public pure returns (uint result) {
+            bytes memory b = bytes(s);
+            uint i;
+            result = 0;
+            for (i = 0; i < b.length; i++) {
+                uint c = uint(b[i]);
+                if (c >= 48 && c <= 57) {
+                    result = result * 10 + (c - 48);
+                }
+            }
+        }
+    }
+    "#;
+    let ns = generic_target_parse(file);
+    assert_eq!(count_warnings(&ns.diagnostics), 2);
+    assert!(assert_message_in_warnings(
+        &ns.diagnostics,
+        "function parameter 'a' has never been read"
+    ));
+    assert!(assert_message_in_warnings(
+        &ns.diagnostics,
+        "local variable 'ct' has been assigned, but never read",
+    ));
+}
+
+#[test]
+fn function_call() {
+    let file = r#"
+    contract Test1 {
+        uint32 public a;
+
+        constructor(uint32 b) {
+            a = b;
+        }
+
+    }
+
+    contract Test2{
+        function test(uint32 v1, uint32 v2) private returns (uint32) {
+            uint32 v = 1;
+            Test1 t = new Test1(v);
+            uint32[2] memory vec = [v2, v1];
+
+            return vec[0] + t.a();
+        }
+
+        function callTest() public {
+            uint32 ta = 1;
+            uint32 tb = 2;
+
+            ta = test(ta, tb);
+        }
+    }
+
+    contract C {
+        uint public data;
+        function x() public returns (uint) {
+            data = 3;
+            return this.data();
+        }
+    }
+    "#;
+
+    let ns = generic_target_parse(file);
+    assert_eq!(count_warnings(&ns.diagnostics), 0);
+
+    let file = r#"
+    contract Test1 {
+        uint32 public a;
+
+        constructor(uint32 b) {
+            a = b;
+        }
+
+    }
+
+    contract Test2 is Test1{
+
+        constructor(uint32 val) Test1(val) {}
+
+        function test(uint32 v1, uint32 v2) private returns (uint32) {
+            uint32 v = 1;
+            Test1 t = new Test1(v);
+            uint32[2] memory vec = [v2, v1];
+
+            return vec[0] + t.a();
+        }
+
+        function callTest() public {
+            uint32 ta = 1;
+            uint32 tb = 2;
+
+            ta = test(ta, tb);
+        }
+    }
+    "#;
+
+    let ns = generic_target_parse(file);
+    assert_eq!(count_warnings(&ns.diagnostics), 0);
+}
+
+#[test]
+fn array_push_pop() {
+    let file = r#"
+        contract Test1 {
+        uint32[] vec1;
+
+        function testVec() public {
+            uint32 a = 1;
+            uint32 b = 2;
+            uint32[] memory vec2;
+
+            vec1.push(a);
+            vec2.push(b);
+        }
+    }
+    "#;
+
+    let ns = generic_target_parse(file);
+    assert_eq!(count_warnings(&ns.diagnostics), 2);
+    assert!(assert_message_in_warnings(
+        &ns.diagnostics,
+        "local variable 'vec2' has been assigned, but never read"
+    ));
+    assert!(assert_message_in_warnings(
+        &ns.diagnostics,
+        "storage variable 'vec1' has been assigned, but never read"
+    ));
+
+    let file = r#"
+        contract Test1 {
+        uint32[] vec1;
+
+        function testVec() public {
+            uint32 a = 1;
+            uint32 b = 2;
+            uint32[] memory vec2;
+
+            vec1.push(a);
+            vec2.push(b);
+            vec1.pop();
+            vec2.pop();
+        }
+    }
+    "#;
+
+    let ns = generic_target_parse(file);
+    assert_eq!(count_warnings(&ns.diagnostics), 0);
+}
+
+#[test]
+fn return_variable() {
+    let file = r#"
+    contract Test1 {
+    string testing;
+
+    function test1() public pure returns (uint32 ret, string memory ret2) {
+        return (2, "Testing is fun");
+    }
+
+    function test2() public returns (uint32 hey) {
+
+        (uint32 a, string memory t) = test1();
+        testing = t;
+
+    }
+    }
+    "#;
+
+    let ns = generic_target_parse(file);
+    assert_eq!(count_warnings(&ns.diagnostics), 3);
+    assert!(assert_message_in_warnings(
+        &ns.diagnostics,
+        "destructure variable 'a' has never been used"
+    ));
+    assert!(assert_message_in_warnings(
+        &ns.diagnostics,
+        "return variable 'hey' has never been assigned"
+    ));
+    assert!(assert_message_in_warnings(
+        &ns.diagnostics,
+        "storage variable 'testing' has been assigned, but never read"
+    ));
+}
+
+#[test]
+fn try_catch() {
+    let file = r#"
+    contract CalledContract {}
+
+    contract TryCatcher {
+
+       event SuccessEvent(bool t);
+       event CatchEvent(bool t);
+
+        function execute() public {
+
+            try new CalledContract() returns(CalledContract returnedInstance) {
+                emit SuccessEvent(true);
+            }  catch Error(string memory revertReason) {
+                emit CatchEvent(true);
+            } catch (bytes memory returnData) {
+                emit CatchEvent(false);
+            }
+        }
+    }
+    "#;
+
+    let ns = generic_target_parse(file);
+    assert_eq!(count_warnings(&ns.diagnostics), 3);
+    assert!(assert_message_in_warnings(
+        &ns.diagnostics,
+        "try-catch error bytes 'returnData' has never been used"
+    ));
+    assert!(assert_message_in_warnings(
+        &ns.diagnostics,
+        "try-catch returns variable 'returnedInstance' has never been read"
+    ));
+    assert!(assert_message_in_warnings(
+        &ns.diagnostics,
+        "try-catch error string 'revertReason' has never been used"
+    ));
+
+    let file = r#"
+    contract CalledContract {
+        bool public ok = true;
+        bool public notOk = false;
+    }
+
+    contract TryCatcher {
+
+       event SuccessEvent(bool t);
+       event CatchEvent(string t);
+       event CatchBytes(bytes t);
+
+        function execute() public {
+
+            try new CalledContract() returns(CalledContract returnedInstance) {
+                // returnedInstance can be used to obtain the address of the newly deployed contract
+                emit SuccessEvent(returnedInstance.ok());
+            }  catch Error(string memory revertReason) {
+                emit CatchEvent(revertReason);
+            } catch (bytes memory returnData) {
+                emit CatchBytes(returnData);
+            }
+        }
+      }
+    "#;
+
+    let ns = generic_target_parse(file);
+    assert_eq!(count_warnings(&ns.diagnostics), 1);
+    assert!(assert_message_in_warnings(
+        &ns.diagnostics,
+        "storage variable 'notOk' has been assigned, but never read"
+    ));
+}
+
+#[test]
+fn destructure() {
+    let file = r#"
+       contract Test2{
+
+            function callTest() public view returns (uint32 ret) {
+                uint32 ta = 1;
+                uint32 tb = 2;
+                uint32 te = 3;
+
+                string memory tc = "hey";
+                bytes memory td = bytes(tc);
+                address nameReg = address(this);
+                (bool tf,) = nameReg.call(td);
+
+
+                ta = tf? tb : te;
+                uint8 tg = 1;
+                uint8 th = 2;
+                (tg, th) = (th, tg);
+                return ta;
+            }
+    }
+    "#;
+
+    let ns = generic_target_parse(file);
+    assert_eq!(count_warnings(&ns.diagnostics), 0);
+}
+
+#[test]
+fn struct_initialization() {
+    let file = r#"
+        contract Test1{
+        struct Test2{
+            uint8 a;
+            uint8 b;
+        }
+
+        function callTest() public pure returns (uint32 ret) {
+            uint8 tg = 1;
+            uint8 th = 2;
+
+            Test2 memory t2;
+            t2 = Test2(tg, th);
+            ret = t2.a;
+        }
+    }
+    "#;
+
+    let ns = generic_target_parse(file);
+    assert_eq!(count_warnings(&ns.diagnostics), 0);
+}
+
+#[test]
+fn subarray_mapping_struct_literal() {
+    let file = r#"
+        contract T {
+        int p;
+        constructor(int b) {
+            p = b;
+        }
+
+        function sum(int a, int b) virtual public returns (int){
+            uint8 v1 = 1;
+            uint8 v2 = 2;
+            uint8 v3 = 3;
+            uint8 v4 = 4;
+            uint8[2][2] memory v = [[v1, v2], [v3, v4]];
+            return a + b * p/v[0][1];
+        }
+    }
+
+    contract Test is T(2){
+
+        struct fooStruct {
+            int foo;
+            int figther;
+        }
+        mapping(string => int) public mp;
+        enum FreshJuiceSize{ SMALL, MEDIUM, LARGE }
+        FreshJuiceSize choice;
+
+        function sum(int a, int b) override public returns (int) {
+            choice = FreshJuiceSize.LARGE;
+            return a*b;
+        }
+
+        function test() public returns (int){
+            int a = 1;
+            int b = 2;
+            int c = super.sum(a, b);
+            int d = 3;
+            fooStruct memory myStruct = fooStruct({foo: c, figther: d});
+            string memory t = "Do some tests";
+            mp[t] = myStruct.figther;
+            return mp[t];
+        }
+    }
+    "#;
+
+    let ns = generic_target_parse(file);
+    assert_eq!(count_warnings(&ns.diagnostics), 1);
+    assert!(assert_message_in_warnings(
+        &ns.diagnostics,
+        "storage variable 'choice' has been assigned, but never read"
+    ));
+}