def main(files: List[str], draw_cfg: str = "", binary: str = "../output/bin/iec_checker"): for f in files: if not os.path.isfile(f): continue checker_warnings, rc = run_checker(f, binary) if rc != 0: print(f'Report for {f}:') for w in checker_warnings: print(f'{w}') continue dump_name = f'{f}.dump.json' plugins_warnings = [] with DumpManager(dump_name) as dm: plugins_warnings = dm.run_all_inspections() if draw_cfg: cfg_plotter = CFGPlotter(dm.scheme.cfgs) cfg_plotter.save_file(draw_cfg) print(f'Report for {f}:') if checker_warnings or plugins_warnings: for w in checker_warnings: print(f'{w}') for p in plugins_warnings: print(f'{w}') else: print('No errors found!')
def test_no_parser_errors(): f = os.path.join('./test/plcopen/example.xml') fdump = f'{f}.dump.json' checker_warnings, rc = run_checker(f, '-input-format', 'xml') assert rc == 0, f"Incorrect exit code for {f}" with DumpManager(fdump): pass
def test_cfg_func_call_statement(): fdump = f'stdin.dump.json' checker_warnings, rc = check_program( """ PROGRAM test_func_call VAR j : INT := 0; END_VAR j := fn0(INVAL := 19); j := 0; END_PROGRAM """.replace('\n', '')) assert rc == 0 with DumpManager(fdump) as dm: scheme = dm.scheme assert scheme assert len(scheme.programs) == 1 assert len(scheme.cfgs) == 1 cfg = scheme.cfgs[0] bbs = cfg.basic_blocks assert len(bbs) == 1 # NOTE: I'm not sure about evaluation order for expressions in function # paramters. Need check how does it implemented in the modern IDEs. # Need revisit Mario de Sousa's paper for this topic. # j := fn0(INVAL := 19) # INVAL := 19 # j := 0 # fn0() # P.S. This is a good idea for the additional inspection. assert bbs[0].id == 0 assert bbs[0].type == "BBExit" assert bbs[0].preds == set() assert bbs[0].succs == set()
def test_cfg_single_bb_from_linear_statements_sequence(): fdump = f'stdin.dump.json' checker_warnings, rc = check_program( """ PROGRAM linear VAR a : INT; END_VAR a := 1; a := 2; a := 3; END_PROGRAM """.replace('\n', '')) assert rc == 0 with DumpManager(fdump) as dm: scheme = dm.scheme assert scheme assert len(scheme.programs) == 1 assert len(scheme.cfgs) == 1 cfg = scheme.cfgs[0] bbs = cfg.basic_blocks assert len(bbs) == 1 # a := 1 # a := 2 # a := 3 assert bbs[0].id == 0 assert bbs[0].type == "BBExit" assert bbs[0].preds == set() assert bbs[0].succs == set()
def test_cfg_function_calls_in_condition_stmt(): fdump = f'stdin.dump.json' checker_warnings, rc = check_program( """ PROGRAM func_calls VAR a : INT; END_VAR IF fn1(a1 := 19) AND fn2(a1 := 35, a2 := 40) THEN a := 30; END_IF; END_PROGRAM """.replace('\n', '')) assert rc == 0 with DumpManager(fdump) as dm: scheme = dm.scheme assert scheme assert len(scheme.programs) == 1 assert len(scheme.cfgs) == 1 cfg = scheme.cfgs[0] bbs = cfg.basic_blocks assert len(bbs) == 2 # if .. then # and # fn1() # a := 19 # fn2() # a := 35 # a := 40 assert len(bbs[0].stmt_ids) == 7
def test_cfg_single_if_statement_bb_has_exit_type(): fdump = f'stdin.dump.json' checker_warnings, rc = check_program( """ PROGRAM f VAR a : INT := 0; END_VAR IF a < 16 THEN a := 1; END_IF; END_PROGRAM """.replace('\n', '')) assert rc == 0 with DumpManager(fdump) as dm: scheme = dm.scheme assert scheme assert len(scheme.programs) == 1 assert len(scheme.cfgs) == 1 cfg = scheme.cfgs[0] bbs = cfg.basic_blocks assert len(bbs) == 2 # if .. then # a < 16 assert bbs[0].id == 0 assert bbs[0].type == "BBExit" assert bbs[0].preds == set() assert bbs[0].succs == {1} # a := 1 assert bbs[1].id == 1 assert bbs[1].type == "BBExit" assert bbs[1].preds == {0} assert bbs[1].succs == set()
def test_direct_variables(): f = './test/st/good/direct-variables.st' fdump = f'{f}.dump.json' checker_warnings, rc = run_checker(f) assert rc == 0 with DumpManager(fdump) as dm: _ = dm.scheme # TODO
def test_cfg_return_statement(): fdump = f'stdin.dump.json' checker_warnings, rc = check_program( """ FUNCTION test_return : INT VAR A : INT; END_VAR A := 0; RETURN; A := 1; A := 42; END_FUNCTION """.replace('\n', '')) assert rc == 0 with DumpManager(fdump) as dm: scheme = dm.scheme assert scheme assert len(scheme.functions) == 1 assert len(scheme.cfgs) == 1 cfg = scheme.cfgs[0] bbs = cfg.basic_blocks assert len(bbs) == 2 # a := 0 # RETURN assert bbs[0].id == 0 assert bbs[0].type == "BBExit" assert bbs[0].preds == set() assert bbs[0].succs == set()
def test_cfa_dead_code_in_the_loops(): fdump = f'stdin.dump.json' warns, rc = check_program( """ PROGRAM dead_code_in_the_loops VAR a : INT; i : INT; END_VAR WHILE i < 10 DO IF i = 5 THEN i := i + 1; EXIT; i := 19; (* UnreachableCode error *) i := 42; (* No additional warnings *) i := 42; ELSIF i = 6 THEN CONTINUE; i := 3; (* UnreachableCode error *) i := 44; (* No additional warnings *) i := 19; END_IF; i := i + 2; END_WHILE; i := 0; END_PROGRAM """.replace('\n', '')) assert rc == 0 assert len(filter_warns(warns, 'UnreachableCode')) == 2 with DumpManager(fdump) as dm: scheme = dm.scheme assert scheme
def test_l10(): f = 'st/plcopen-l10.st' fdump = f'{f}.dump.json' checker_warnings, rc = run_checker(f) assert rc == 0 checker_warnings.count('PLCOPEN-L10') == 3 with DumpManager(fdump): pass
def test_cp9(): f = 'st/plcopen-cp9.st' fdump = f'{f}.dump.json' warns, rc = run_checker(f) assert rc == 0 assert len(filter_warns(warns, 'PLCOPEN-CP9')) == 2 with DumpManager(fdump): pass
def test_zerodiv(): f = 'st/zero-division.st' fdump = f'{f}.dump.json' checker_warnings, rc = run_checker(f) assert rc == 0 checker_warnings.count('ZeroDivision') == 2 with DumpManager(fdump): pass
def test_cp25(): f = 'st/plcopen-cp25.st' fdump = f'{f}.dump.json' checker_warnings, rc = run_checker(f) assert rc == 0 checker_warnings.count('PLCOPEN-CP25') == 2 with DumpManager(fdump): pass
def test_parser_errors(): for fname in os.listdir('./test/st/bad/'): f = os.path.join('./test/st/bad/', fname) fdump = f'{f}.dump.json' checker_warnings, rc = run_checker(f) assert rc == 1, f"Incorrect exit code for {f}" assert len(checker_warnings) > 0 with DumpManager(fdump): pass
def test_no_parser_errors(): for fname in os.listdir('./test/st/good/'): if not fname.endswith('.st'): continue f = os.path.join('./test/st/good/', fname) fdump = f'{f}.dump.json' checker_warnings, rc = run_checker(f) assert rc == 0, f"Incorrect exit code for {f}" with DumpManager(fdump): pass
def test_cfg_continue(): fdump = f'stdin.dump.json' checker_warnings, rc = check_program( """ PROGRAM test_exit VAR a : INT; i : INT; END_VAR WHILE i < 10 DO IF i = 5 THEN i := i + 1; CONTINUE; END_IF; i := i + 2; END_WHILE; i := 0; END_PROGRAM """.replace('\n', '')) assert rc == 0 with DumpManager(fdump) as dm: scheme = dm.scheme assert scheme assert len(scheme.programs) == 1 assert len(scheme.cfgs) == 1 cfg = scheme.cfgs[0] bbs = cfg.basic_blocks assert len(bbs) == 5 # while # i < 10 assert bbs[0].id == 0 assert bbs[0].type == "BBEntry" assert bbs[0].preds == {2, 3} assert bbs[0].succs == {1, 4} # if # i = 5 assert bbs[1].id == 1 assert bbs[1].type == "BB" assert bbs[1].preds == {0} assert bbs[1].succs == {2, 3} # i := i + 1 # continue assert bbs[2].id == 2 assert bbs[2].type == "BB" assert bbs[2].preds == {1} assert bbs[2].succs == {0} assert len(bbs[2].stmt_ids) == 2 # i := i + 2 assert bbs[3].id == 3 assert bbs[3].type == "BB" assert bbs[3].preds == {1} assert bbs[3].succs == {0} # i := 0 assert bbs[4].id == 4 assert bbs[4].type == "BBExit" assert bbs[4].preds == {0} assert bbs[4].succs == set()
def test_cfg_while_nested_statements(): fdump = f'stdin.dump.json' checker_warnings, rc = check_program( """ PROGRAM test_cfg_while_nested_statements VAR a : INT; i : INT; END_VAR WHILE i < 10 DO a := 1; IF i > 10 THEN a := 2; END_IF; a := 3; END_WHILE; i := 0; END_PROGRAM """.replace('\n', '')) assert rc == 0 with DumpManager(fdump) as dm: scheme = dm.scheme assert scheme assert len(scheme.programs) == 1 assert len(scheme.cfgs) == 1 cfg = scheme.cfgs[0] bbs = cfg.basic_blocks assert len(bbs) == 5 # while # i < 10 assert bbs[0].id == 0 assert bbs[0].type == "BBEntry" assert bbs[0].preds == {3} assert bbs[0].succs == {1, 4} # a := 1 # if # if > 10 assert bbs[1].id == 1 assert bbs[1].type == "BB" assert bbs[1].preds == {0} assert bbs[1].succs == {2, 3} # a := 2 assert bbs[2].id == 2 assert bbs[2].type == "BB" assert bbs[2].preds == {1} assert bbs[2].succs == {3} # a := 3 assert bbs[3].id == 3 assert bbs[3].type == "BB" assert bbs[3].preds == {1, 2} assert bbs[3].succs == {0} # i := 0 assert bbs[4].id == 4 assert bbs[4].type == "BBExit" assert bbs[4].preds == {0} assert bbs[4].succs == set()
def test_lexing_error(): f = './test/st/bad/lexing-error.st' fdump = f'{f}.dump.json' checker_warnings, rc = run_checker(f) assert rc == 1 assert len(checker_warnings) == 1 cv = checker_warnings[0] assert cv.id == 'LexingError' assert cv.linenr == 9 assert cv.column == 6 with DumpManager(fdump): pass
def test_initialization_literal(): f = 'st/declaration-analysis.st' fdump = f'{f}.dump.json' checker_warnings, rc = run_checker(f) assert rc == 0 assert len(checker_warnings) == 3 cv = checker_warnings[0] assert cv.id == 'OutOfBounds' # assert cv.linenr == 8 # assert cv.column == 31 with DumpManager(fdump): pass
def test_n3(): f = 'st/plcopen-n3.st' fdump = f'{f}.dump.json' checker_warnings, rc = run_checker(f) assert rc == 0 assert len(checker_warnings) >= 1 cv = checker_warnings[0] assert cv.id == 'PLCOPEN-N3' assert cv.linenr == 6 assert cv.column == 7 with DumpManager(fdump): pass
def test_l17(): f = 'st/plcopen-l17.st' fdump = f'{f}.dump.json' checker_warnings, rc = run_checker(f) assert rc == 0 assert len(checker_warnings) >= 1 cv = checker_warnings[1] assert cv.id == 'PLCOPEN-L17' assert cv.linenr == 10 assert cv.column == 4 with DumpManager(fdump): pass
def test_cp13(): f = './test/st/plcopen-cp13.st' fdump = f'{f}.dump.json' checker_warnings, rc = run_checker(f) assert rc == 0 assert len(checker_warnings) >= 1 cv = checker_warnings[0] assert cv.id == 'PLCOPEN-CP13' assert cv.linenr == 8 assert cv.column == 30 with DumpManager(fdump): pass
def test_array_types(): fdump = f'stdin.dump.json' checker_warnings, rc = check_program(""" TYPE BITS: ARRAY [0..7] OF BOOL; END_TYPE """.replace('\n', '')) assert rc == 0 with DumpManager(fdump) as dm: scheme = dm.scheme assert scheme assert len(scheme.types) == 1 ty = scheme.types[0] assert ty.name == 'BITS' assert ty.type == 'Array'
def test_ref_types(): fdump = f'stdin.dump.json' checker_warnings, rc = check_program(""" TYPE myRef: REF_TO INT; END_TYPE """.replace('\n', '')) assert rc == 0 with DumpManager(fdump) as dm: scheme = dm.scheme assert scheme assert len(scheme.types) == 1 ty = scheme.types[0] assert ty.name == 'MYREF' assert ty.type == 'Ref'
def test_enum_types(): fdump = f'stdin.dump.json' checker_warnings, rc = check_program(""" TYPE Traffic_Light: (Red, Amber, Green); END_TYPE """.replace('\n', '')) assert rc == 0 with DumpManager(fdump) as dm: scheme = dm.scheme assert scheme assert len(scheme.types) == 1 ty = scheme.types[0] assert ty.name == 'TRAFFIC_LIGHT' assert ty.type == 'Enum'
def test_statements_order(): """Test that POU statements are arranged in the correct order.""" fdump = f'stdin.dump.json' checker_warnings, rc = check_program(""" PROGRAM p VAR a : INT; i : INT; END_VAR a := 1; i := 22; a := 16#42; END_PROGRAM """.replace('\n', '')) assert rc == 0 with DumpManager(fdump) as dm: scheme = dm.scheme assert scheme assert len(scheme.programs) == 1
def test_cfg_if_else(): fdump = f'stdin.dump.json' checker_warnings, rc = check_program( """ PROGRAM p VAR a : INT; i : INT; END_VAR a := 1; IF a > 1 THEN i := 1; ELSE i := 42; END_IF; i := 0; END_PROGRAM """.replace('\n', '')) assert rc == 0 with DumpManager(fdump) as dm: scheme = dm.scheme assert scheme assert len(scheme.programs) == 1 assert len(scheme.cfgs) == 1 cfg = scheme.cfgs[0] bbs = cfg.basic_blocks assert len(bbs) == 4 # a := 1 # if ... then # a > 1 assert bbs[0].id == 0 assert bbs[0].type == "BBEntry" assert bbs[0].preds == set() assert bbs[0].succs == {1, 2} # i := 1 assert bbs[1].id == 1 assert bbs[1].type == "BB" assert bbs[1].preds == {0} assert bbs[1].succs == {3} # else # i := 42 assert bbs[2].id == 2 assert bbs[2].type == "BB" assert bbs[2].preds == {0} assert bbs[2].succs == {3} # i := 0 assert bbs[3].id == 3 assert bbs[3].type == "BBExit" assert bbs[3].preds == {1, 2} assert bbs[3].succs == set()
def test_unused_local_variable(): fdump = f'stdin.dump.json' warns, rc = check_program(""" PROGRAM p VAR a : INT; b : INT; c : INT; END_VAR b := 1 + c; END_PROGRAM """.replace('\n', '')) assert rc == 0 assert len(filter_warns(warns, 'UnusedVariable')) == 1 with DumpManager(fdump) as dm: scheme = dm.scheme assert scheme
def test_cfg_return_statement_inside_branch(): fdump = f'stdin.dump.json' checker_warnings, rc = check_program( """ FUNCTION test_return_nested : INT VAR i : INT; a : INT; END_VAR IF i = 0 THEN a := a + 1; RETURN; a := a + 2; END_IF; a := a + 3; END_FUNCTION """.replace('\n', '')) assert rc == 0 with DumpManager(fdump) as dm: scheme = dm.scheme assert scheme assert len(scheme.functions) == 1 assert len(scheme.cfgs) == 1 cfg = scheme.cfgs[0] bbs = cfg.basic_blocks assert len(bbs) == 4 # if # i = 0 assert bbs[0].id == 0 assert bbs[0].type == "BBEntry" assert bbs[0].preds == set() assert bbs[0].succs == {1, 3} # a := a + 1 # RETURN assert bbs[1].id == 1 assert bbs[1].type == "BBExit" assert bbs[1].preds == {0} assert bbs[1].succs == set() # a := a + 2 assert bbs[2].id == 2 assert bbs[2].type == "BB" assert bbs[2].preds == set() assert bbs[2].succs == {3} # a := a + 3 assert bbs[3].id == 3 assert bbs[3].type == "BBExit" assert bbs[3].preds == {0, 2} assert bbs[3].succs == set()
def test_struct_types(): fdump = f'stdin.dump.json' checker_warnings, rc = check_program(""" TYPE Cooler: STRUCT Temp: INT; Cooling: TOF; END_STRUCT; END_TYPE """.replace('\n', '')) assert rc == 0 with DumpManager(fdump) as dm: scheme = dm.scheme assert scheme assert len(scheme.types) == 1 ty = scheme.types[0] assert ty.name == 'COOLER' assert ty.type == 'Struct'