diff --git a/ast_generator/ast_generator.py b/ast_generator/ast_generator.py index 19b8e7d..b8b801e 100644 --- a/ast_generator/ast_generator.py +++ b/ast_generator/ast_generator.py @@ -3,6 +3,7 @@ import warnings from english_words import get_english_words_set +from ast_generator.tiny_py_unparse import TinyPyUnparser from ast_generator.utils import * from ast_generator.utils import filter_options, _choose_option from constants import * @@ -436,13 +437,66 @@ class AstGenerator: self.make_scoped_element(GAZ_LOOP_TAG, []) self.current_control_flow_nesting_depth += 1 - self.generate_expression(GAZ_BOOL_KEY) # the loop entry condition #TODO force true + truthiness = self.get_truthiness() + + while True: + self.generate_expression(GAZ_BOOL_KEY) # the loop entry condition #TODO force true, if false, generate a simple block and return, no need to nest code we won't use + + iterator = self.current_ast_element.iter() + next(iterator) + conditional = next(iterator) + if not self.truth_check(conditional, truthiness): + self.current_ast_element.remove(conditional) + else: + break + self.generate_block(block_type=GAZ_LOOP_TAG, loop_var=init_var) # append a variable increment and prepend a break statement if var is > max loop iterations self.current_control_flow_nesting_depth -= 1 self.exit_scoped_element(parent) + def get_truthiness(self): + return random.random() < self.settings['misc-weights']['truthiness'] + + def truth_check(self, element: ET.Element, truthiness: bool): + # evaluate the element + uncompiled = self.unparse_conditional_statement(element) + substr1 = uncompiled.find("**") + substr2 = uncompiled.find("**", substr1 + 1) + + if -1 not in [substr1, substr2]: + return False + + # print(uncompiled) + try: + res = eval(uncompiled) + except (OverflowError, ZeroDivisionError): + res = False + + # check it against truthiness and return the comparison + return res == truthiness + + def unparse_conditional_statement(self, element: ET.Element): + py_unparse = TinyPyUnparser(element, True) + unparsed = "" + if element.tag == GAZ_LIT_TAG: + py_unparse.unparse_literal(element) + unparsed = py_unparse.source + elif element.tag == GAZ_OPERATOR_TAG: + py_unparse.unparse_operator(element) + unparsed = py_unparse.source + elif element.tag == GAZ_VAR_TAG: + xml = self.current_scope.resolve(element.get("name")) + py_unparse.unparse_variable(xml) + unparsed = py_unparse.source + elif element.tag == GAZ_BRACKET_TAG: + py_unparse.unparse_brackets(element) + unparsed = py_unparse.source + else: + raise ValueError("Unknown element type for conditional argument: " + element.tag) + return unparsed + def generate_zero_declaration(self): """ @brief generate a declaration int a = 0 for some a diff --git a/ast_generator/test/config.yaml b/ast_generator/test/config.yaml index 5ad7b7a..8334169 100644 --- a/ast_generator/test/config.yaml +++ b/ast_generator/test/config.yaml @@ -90,9 +90,7 @@ misc-weights: type-qualifier-weights: const: 10 var: 60 - conditional-eval: - true: 50 - false: 50 + truthiness: 0.9 # Probability of conditionals being true block-termination-probability: 0.2 # probability for a block to terminate diff --git a/ast_generator/test/test_ast_generator.py b/ast_generator/test/test_ast_generator.py index 98fc68d..246b784 100644 --- a/ast_generator/test/test_ast_generator.py +++ b/ast_generator/test/test_ast_generator.py @@ -245,12 +245,13 @@ class TestGeneration(unittest.TestCase): self.assertFalse(self.is_no_op(operator)) def test_create_global(self): - self.ast_gen.ast = ET.Element("block") + element = ET.Element("block") + self.ast_gen.ast = element self.ast_gen.current_ast_element = self.ast_gen.ast self.ast_gen.generate_main() - global_block = self.ast_gen.current_ast_element - global_scope = self.ast_gen.current_scope + global_block = element + global_scope = self.ast_gen.current_scope.get_top_scope() self.assertIsNotNone(self.ast_gen.ast.find("procedure")) self.ast_gen.current_ast_element = self.ast_gen.ast.find("procedure") @@ -310,8 +311,10 @@ class TestGeneration(unittest.TestCase): return res def test_print_all_current_scope_vars(self): - self.ast_gen.ast = ET.Element("block") - self.ast_gen.current_ast_element = self.ast_gen.ast + element = ET.Element("block") + self.ast_gen.ast = element + self.ast_gen.current_ast_element = element + self.ast_gen.current_scope = Scope(None) self.ast_gen.generate_declaration(mut='var') self.ast_gen.generate_declaration(mut='var') self.ast_gen.generate_declaration(mut='var') @@ -319,12 +322,67 @@ class TestGeneration(unittest.TestCase): self.ast_gen.print_all_current_scope_vars() - print(ET.tostring(self.ast_gen.ast)) + + # print(ET.tostring(self.ast_gen.ast)) streams = self.ast_gen.ast.findall("stream") self.assertEqual(4, len(streams)) + def test_conditional_test_1(self): + element = ET.fromstring('') + + self.assertIsNotNone(element) + + res = self.ast_gen.truth_check(element, True) + self.assertFalse(res) + + def test_conditional_test_2(self): + element = ET.fromstring('') + self.assertIsNotNone(element) + + res = self.ast_gen.truth_check(element, True) + self.assertTrue(res) + + def test_conditional_test_3(self): + with open("xml/conditional.xml", 'r') as f: + element = ET.fromstring(f.read()) + + self.assertIsNotNone(element) + + res = self.ast_gen.truth_check(element, True) + self.assertTrue(res) + + def test_conditional_test_4(self): + with open("xml/conditional_2.xml", 'r') as f: + element = ET.fromstring(f.read()) + + self.assertIsNotNone(element) + + res = self.ast_gen.truth_check(element, True) + self.assertFalse(res) + + def test_conditional_test_5(self): + with open("xml/conditional_3.xml", 'r') as f: + element = ET.fromstring(f.read()) + + self.assertIsNotNone(element) + + res = self.ast_gen.truth_check(element, True) + self.assertTrue(res) + + # def test_conditional_test_variable(self): + # element = ET.fromstring('') + # + # var = Variable("harold", "int", "var") + # var.decl_xml = ET.fromstring('') + # self.ast_gen.current_scope.append("harold", element) + # self.assertIsNotNone(element) + # + # res = self.ast_gen.truth_check(element, True) + # self.assertTrue(res) + + def write_ast(self): dom = xml.dom.minidom.parseString(ET.tostring(self.ast_gen.ast).decode('utf-8')) pretty: str = dom.toprettyxml() diff --git a/ast_generator/test/xml/conditional.xml b/ast_generator/test/xml/conditional.xml new file mode 100644 index 0000000..1e90dff --- /dev/null +++ b/ast_generator/test/xml/conditional.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/ast_generator/test/xml/conditional_2.xml b/ast_generator/test/xml/conditional_2.xml new file mode 100644 index 0000000..8610df4 --- /dev/null +++ b/ast_generator/test/xml/conditional_2.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/ast_generator/test/xml/conditional_3.xml b/ast_generator/test/xml/conditional_3.xml new file mode 100644 index 0000000..8e8f9cc --- /dev/null +++ b/ast_generator/test/xml/conditional_3.xml @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/ast_generator/tiny_py_unparse.py b/ast_generator/tiny_py_unparse.py new file mode 100644 index 0000000..6468e84 --- /dev/null +++ b/ast_generator/tiny_py_unparse.py @@ -0,0 +1,101 @@ +from ast_parser.general_unparser import GeneralUnparser + + +import warnings +from xml.etree import ElementTree as ET + +from ast_parser.gaz_unparser import GazUnparser +from ast_parser.general_unparser import GeneralUnparser +from constants import * + + +def to_python_type(ty): + if ty == GAZ_INT_KEY: + return "int" + elif ty == GAZ_BOOL_KEY: + return "bool" + elif ty == GAZ_FLOAT_KEY: + return "float" + elif ty == GAZ_CHAR_KEY: + return "str" + elif ty == GAZ_STRING_KEY: + return "str" + else: + raise Exception("Unknown type: " + ty) + + +def to_python_op(param, ty): + if param == "negation" or param == "subtraction": + return "-" + elif param == "addition" or param == "noop": + return "+" + elif param == "multiplication": + return "*" + elif param == "division" and ty != GAZ_INT_KEY: + return "/" + elif param == "division" and ty == GAZ_INT_KEY: + return "//" + elif param == "modulo": + return "%" + elif param == "power": + return "**" + elif param == "equality": + return "==" + elif param == "inequality": + return "!=" + elif param == "less-than": + return "<" + elif param == "less-than-or-equal": + return "<=" + elif param == "greater-than": + return ">" + elif param == "greater-than-or-equal": + return ">=" + elif param == "xor": + return "!=" + else: + warnings.warn("Warning, unknown operator: " + param) + return param + + +class TinyPyUnparser(GeneralUnparser): + def __init__(self, ast: ET.Element, debug=False): + super().__init__(ast, debug, + endline='\n', + outstream_begin_delimiter="gprint(", + outstream_end_delimiter=", end='')", + function_return_type_indicator_predicate="->", + loop_start_delimiter="while ", + loop_end_delimiter=":", + conditional_case_delimiter="elif ", + conditional_start_delimiter="if ", + conditional_else_delimiter="else:", + conditional_end_delimiter=":", + block_start_delimiter="", + block_end_delimiter="", # TODO can this contain the pass? + strip_conditionals=True) + + self.source = '' + + def format_variable(self, mut, ty, name, declaration: bool = False): + if declaration: + return "{}: {}".format(name, ty) + else: + return "{}".format(name) + + def translate_value(self, val): + return str(val) + + def translate_op(self, param, ty=None): + return to_python_op(param, ty) + + def translate_type(self, ty): + return to_python_type(ty) + + def unparse_block(self, node): + raise TypeError("Cannot unparse blocks for this type of evaluation") + + def setup(self): + pass + + diff --git a/ast_generator/utils.py b/ast_generator/utils.py index a4d723f..292306e 100644 --- a/ast_generator/utils.py +++ b/ast_generator/utils.py @@ -5,12 +5,13 @@ from constants import GAZ_VAR_TAG, GAZ_ARG_TAG class Variable: - def __init__(self, name: str, type: str, qualifier: str, value: any = None): + def __init__(self, name: str, type: str, qualifier: str, value: any = None): # decl_xml: ET.Element = None, self.name = name self.type = type self.value = value self.qualifier = qualifier self.xml = self._build_xml() + # self.decl_xml = decl_xml def _build_xml(self): args = [ diff --git a/config.yaml b/config.yaml index 5ad7b7a..5010022 100644 --- a/config.yaml +++ b/config.yaml @@ -4,7 +4,7 @@ generation-options: max-nesting-depth: 5 # maximum nesting depth for statements max-conditionals-loops: 5 # maximum number of loops/conditionals per routine max-number-of-routines: 5 # maximum number of routines (main will always be generated) - generate-dead-code: True # generate dead code + generate-dead-code: False # generate dead code max-loop-iterations: 100 # maximum number of iterations in a loop max-globals: 5 # maximum number of global variables properties: @@ -90,9 +90,7 @@ misc-weights: type-qualifier-weights: const: 10 var: 60 - conditional-eval: - true: 50 - false: 50 + truthiness: 0.9 # Probability of conditionals being true block-termination-probability: 0.2 # probability for a block to terminate diff --git a/gazprea_fuzzer.py b/gazprea_fuzzer.py index e7d2722..c7088f6 100644 --- a/gazprea_fuzzer.py +++ b/gazprea_fuzzer.py @@ -14,6 +14,8 @@ import xml.etree.ElementTree as ET from constants import NoneTagException +import signal as sig + def gprint(expr, end=''): if type(expr) is bool: @@ -37,6 +39,9 @@ class Fuzzer(): self.fuzzer = fz.GazpreaFuzzer(config) def fuzz(self): + sig.signal(sig.SIGALRM, handler) + sig.alarm(30) + base_dir = os.getcwd() fuzzer_root = base_dir + '/fuzzer' @@ -71,7 +76,10 @@ class Fuzzer(): "Look for a top-level tag or send it to Ayrton and I'll see what I can see" "".format(r)) with open(f"{fuzzer_asterr}/{r}.xml", 'w') as f: - f.write(xml.dom.minidom.parseString(ET.tostring(self.fuzzer.ast).decode('utf-8')).toprettyxml()) + try: + f.write(xml.dom.minidom.parseString(ET.tostring(self.fuzzer.ast).decode('utf-8')).toprettyxml()) + except AttributeError: + print("Failed to write to the file", file=sys.stderr) if i - 1 >= min_i: i -= 1 else: @@ -90,7 +98,7 @@ class Fuzzer(): exec(read, globals()) except (OverflowError, ZeroDivisionError, ValueError, TypeError, SyntaxError): os.system("rm -f {}/{}_{}.py".format(fuzzer_ground_truth, self.file_name, i)) - os.system("rm -f {}/{}_{}.py".format(fuzzer_outputs, self.file_name, i)) + os.system("rm -f {}/{}_{}.out".format(fuzzer_outputs, self.file_name, i)) warnings.warn("Runtime error encountered, retrying") if i - 1 >= min_i: i -= 1 @@ -105,6 +113,31 @@ class Fuzzer(): f.write(xml.dom.minidom.parseString( ET.tostring(self.fuzzer.ast).decode('utf-8')).toprettyxml()) sys.exit(1) + except TimeoutError: + r = random.randint(0, 1000000) + warnings.warn("Execution timed out, result written to debug/ast/{}.xml\n" + "".format(r)) + with open("{}/{}.xml".format(fuzzer_asterr, r), 'w') as f: + f.write(xml.dom.minidom.parseString( + ET.tostring(self.fuzzer.ast).decode('utf-8')).toprettyxml()) + if i - 1 >= min_i: + i -= 1 + else: + i = min_i + continue + + with open("{}/{}_{}.out".format(fuzzer_outputs, self.file_name, i), 'r') as y: + out = y.read() + if out == "": + os.system("rm -f {}/{}_{}.py".format(fuzzer_ground_truth, self.file_name, i)) + os.system("rm -f {}/{}_{}.out".format(fuzzer_outputs, self.file_name, i)) + if i - 1 >= min_i: + i -= 1 + else: + i = min_i + continue + + with open("{}/{}_{}.in".format(fuzzer_input, self.file_name, i), 'w') as f: f.write(self.fuzzer.source) with open("{}/{}_{}.xml".format(fuzzer_debug, self.file_name, i), 'w') as f: @@ -114,11 +147,19 @@ class Fuzzer(): # f.write(self.fuzzer.source) # with open("fuzzer/outputs/{}.out".format(i), 'w') as f: # f.write(self.fuzzer.out) + print("test {}/{} generated".format(i, self.batch)) i += 1 min_i = i +def handler(signum, frame): + print("Execution Timeout") + raise TimeoutError + + if __name__ == '__main__': + + parser = argparse.ArgumentParser( description='Procedurally generate a test case for Gazprea' )