From de31ad208bde64cf5b2c500d53c7c8883f35bc60 Mon Sep 17 00:00:00 2001 From: pycsham <shampuiyanchloe@gmail.com> Date: Thu, 12 Mar 2020 23:34:21 -0400 Subject: [PATCH] implemented reachability checking. passing 56 test cases locally --- AST.py | 22 +------ AstBuilding.py | 7 +-- ExprPrimaryNodes.py | 10 +++ LineNodes.py | 145 ++++++++++++++++++++++++++++++-------------- MemberNodes.py | 19 +++--- Test.py | 1 - 6 files changed, 123 insertions(+), 81 deletions(-) diff --git a/AST.py b/AST.py index 0793590..7d795d1 100644 --- a/AST.py +++ b/AST.py @@ -68,28 +68,10 @@ class ASTNode(): if c and hasattr(c, 'checkType'): c.checkType() - def staticAnalysis(self): - for c in self.children: - if c and hasattr(c, 'staticAnalysis'): - c.staticAnalysis() - - # return outMaybe - def reachCheck(self, inMaybe = True): - if inMaybe == False: - # error if in[s] = no for any s - # I don't think it should get to here... but maybe? - raise Exception("in[s] = no for a certain {}".format(type(self))) - - self.inMaybe = inMaybe - self.outMaybe = self.inMaybe - lastOut = self.inMaybe - + def reachCheck(self, inMaybe=True): for c in self.children: if c and hasattr(c, 'reachCheck'): - lastOut = c.reachCheck(lastOut) - self.outMaybe = self.outMaybe and lastOut - - return self.outMaybe + c.reachCheck(inMaybe) def printNodePretty(self, prefix=0): pp = pprint.PrettyPrinter(indent=prefix) diff --git a/AstBuilding.py b/AstBuilding.py index 5563a7b..50905ba 100644 --- a/AstBuilding.py +++ b/AstBuilding.py @@ -49,13 +49,8 @@ def disamiguateAndTypeChecking(ASTs): for t in ASTs: t[1].checkType() - # resolving the rest of the name - ####################################################### def reachabilityChecking(ASTs): for t in ASTs: - t[1].reachCheck() - - # for t in ASTs: - # t[1].staticAnalysis() + t[1].reachCheck() \ No newline at end of file diff --git a/ExprPrimaryNodes.py b/ExprPrimaryNodes.py index e6b4416..d9de1c4 100644 --- a/ExprPrimaryNodes.py +++ b/ExprPrimaryNodes.py @@ -224,6 +224,11 @@ class AssignNode(ASTNode): raise Exception("ERROR: assignment operation failed. Cannot assign type {0} to type {1} at class {2}".format(self.left.myType.name, self.right.myType.name, self.typeName)) + def reachCheck(self, inMaybe): + if not inMaybe: + raise Exception("ERROR: not reaching a assignment statement") + self.outMaybe = inMaybe + ################################################################################## # cast: castExpr LPAREN castType RPAREN unaryNotPlusMinus @@ -574,6 +579,11 @@ class MethodInvNode(ASTNode): else: raise Exception("ERROR: Class {} doesn't have a method {} with given argument types.".format(self.typeName, self.ID.name)) + + def reachCheck(self, inMaybe): + if not inMaybe: + raise Exception("ERROR: not reaching a variable declaration statement for var {}".format(self.name)) + self.outMaybe = inMaybe ################# Helper ####################### diff --git a/LineNodes.py b/LineNodes.py index 86464f9..07ea0b2 100644 --- a/LineNodes.py +++ b/LineNodes.py @@ -88,6 +88,23 @@ class BlockNode(ASTNode): self.env = env return self.env + def reachCheck(self, inMaybe): + self.inMaybe = inMaybe + + if not self.inMaybe: + raise Exception("ERROR: cannot reach block node in class {}".format(self.typeName)) + + # Checking reachability of each statement + prevOut = self.inMaybe # Note: in[S1] = in[L] + for statement in self.statements: + if statement: + statement.reachCheck(prevOut) + prevOut = statement.outMaybe + else: # checking for empty statements + if not prevOut: + raise Exception("ERROR: empty statement is unreachable at block node for class {}".format(self.typeName)) + self.outMaybe = prevOut + return # variableDcl # Rules: @@ -113,12 +130,12 @@ class VarDclNode(ASTNode): # Checking for definite assignment if checkAssign: if not self.variableInit: - raise Exception("ERROR: local variable declaration {} is not assigned".format(self.name)) + raise Exception("ERROR: local variable declaration {} is not assigned in class {}".format(self.name, self.typeName)) # Checking if the local variable appears in it's own intializer nameNodes = getASTNode(["NameNode"], self.variableInit) for node in nameNodes: if self.name in node.IDs: - raise Exception("ERROR: local variable {} appears in it's own intialization".format(self.name)) + raise Exception("ERROR: local variable {} appears in it's own intialization in class {}".format(self.name, self.typeName)) self.myType = self.dclType.myType self.children.append(self.dclType) @@ -140,6 +157,10 @@ class VarDclNode(ASTNode): if not self.myType.assignable(self.variableInit.myType): raise Exception("ERROR: Cannot initialize variable of type {} with type {}".format(self.myType.name, self.variableInit.myType.name)) + def reachCheck(self, inMaybe): + if not inMaybe: + raise Exception("ERROR: not reaching a variable declaration statement for var {} in class {}".format(self.name, self.typeName)) + self.outMaybe = inMaybe # ifStatement, ifElseStatement, ifElseStatementNoShortIf # Rules: @@ -174,26 +195,31 @@ class IfNode(ASTNode): if self.elseBody: self.elseBody.checkType() - def reachCheck(self, inMaybe = True): - if inMaybe == False: - # error if in[s] = no for any s + def reachCheck(self, inMaybe): + if not inMaybe: raise Exception("in[s] = no for IfNode in class {}".format(self.typeName)) - self.inMaybe = inMaybe - s1 = self.ifBody.reachCheck(self.inMaybe) + # No need to check for empty statement, since in[ifBody] = inMaybe + if self.ifBody: + self.ifBody.reachCheck(inMaybe) + if not self.elseBody: # L : if (E) S # in[S] = in[L] # out[L] = in[L] - self.outMaybe = self.inMaybe + self.outMaybe = inMaybe else: # L : if (E) S1 else S2 # in[S1] = in[L] # in[S2] = in[L] # out[L] = out[S1] V out[S2] - s2 = self.elseBody.reachCheck(self.inMaybe) - self.outMaybe = s1 or s2 - return self.outMaybe + if self.elseBody: + self.elseBody.reachCheck(inMaybe) + self.outMaybe = (self.ifBody.outMaybe or self.elseBody.outMaybe) + else: + # no need to check reachability for empty elseBody, since in[elseBody] = in[L] + self.outMaybe = self.ifBody.outMaybe + # whileStatement, whileStatementNoShortIf # Rules: @@ -215,30 +241,42 @@ class WhileNode(ASTNode): self.children.append(self.whileBody) def checkType(self): - self.whileBound.checkType() - if self.whileBound.myType.name != 'boolean': - raise Exception("ERROR: Cannot use non-boolean type for whileBound.") - self.whileBody.checkType() - - def staticAnalysis(self): - # check constant expr + if self.whileBound: + self.whileBound.checkType() + if self.whileBound.myType.name != 'boolean': + raise Exception("ERROR: Cannot use non-boolean type for whileBound.") + if self.whileBody: + self.whileBody.checkType() + + def reachCheck(self, inMaybe): + if not inMaybe: + raise Exception("in[s] = no for WhileNode in class {}".format(self.typeName)) + + # Checking constant expression in whileBound + con = None # default to None: i.e. not a constant expression if hasattr(self.whileBound, "getConstant"): con = self.whileBound.getConstant() - if con != None: - print(con, self.whileBound.typeName) - - def reachCheck(self, inMaybe = True): - if inMaybe == False: - # error if in[s] = no for any s - raise Exception("in[s] = no for WhileNode in class {}".format(self.typeName)) - - # L : while (E) S - # in[S] = in[L] - # out[L] = in[L] - self.inMaybe = inMaybe - self.whileBound.reachCheck(self.inMaybe) - self.outMaybe = self.inMaybe + # Setting self.outMaybe + inMaybeWhileBody = inMaybe # the input to reachCheck on whileBody + # General case: while(E) S + if con == None: + self.outMaybe = inMaybe + # while(false) S + elif con == False or con == 0: + self.outMaybe = inMaybe + inMaybeWhileBody = False + else: # either an integer that's not zero or True + self.outMaybe = False + + # Checking reachability on whileBody + if self.whileBody: + self.whileBody.reachCheck(inMaybeWhileBody) + elif not inMaybeWhileBody: # empty block/empty statement that's unreachable + raise Exception("ERROR: unreachable empty statment/block at while node for class {}".format(self.typeName)) + + return + # returnStatement # Rules: @@ -268,16 +306,10 @@ class ReturnNode(ASTNode): else: self.myType = None # this is None as returning a value of type Void is invalid even in a function with type Void - def reachCheck(self, inMaybe = True): - if inMaybe == False: - # error if in[s] = no for any s - raise Exception("in[s] = no for ReturnNode in class {}".format(self.typeName)) - - # L : return, L : return E - # out[L] = no - self.inMaybe = inMaybe - self.outMaybe = False - return self.outMaybe + def reachCheck(self, inMaybe): + if not inMaybe: + raise Exception("ERROR: return statement unreachable at class {}".format(self.typeName)) + self.outMaybe = False # out[L] = no # forStatement and forStatementNoShortIf # Rules: @@ -342,9 +374,30 @@ class ForNode(ASTNode): self.forUpdate.checkType() self.bodyStatement.checkType() - def staticAnalysis(self): - # check constant expr + def reachCheck(self, inMaybe): + if not inMaybe: + raise Exception("in[s] = no for ForNode in class {}".format(self.typeName)) + + # Checking constant expression in whileBound + con = None # default to None: i.e. not a constant expression if hasattr(self.forBound, "getConstant"): con = self.forBound.getConstant() - if con != None: - print(con, self.whileBound.typeNam) + + # Setting self.outMaybe + inMaybeForBody = inMaybe # the input to reachCheck on bodyStatement + # General case + if con == None: + self.outMaybe = inMaybe + # for(false) S + elif con == False or con == 0: + self.outMaybe = inMaybe + inMaybeForBody = False + else: # either an integer that's not zero or True + self.outMaybe = False + + # Checking reachability on whileBody + if self.bodyStatement: + self.bodyStatement.reachCheck(inMaybeForBody) + elif inMaybeForBody: # checking if the empty forBody can be reached + raise Exception("ERROR: unreachable empty statement/block at for node at class {}".format(self.typeName)) + return diff --git a/MemberNodes.py b/MemberNodes.py index cfafa1a..6d5979f 100644 --- a/MemberNodes.py +++ b/MemberNodes.py @@ -169,19 +169,22 @@ class MethodNode(ASTNode): raise Exception("ERROR: return type of function {} doesn't match with return statement.".format(self.name)) return - def reachCheck(self, inMaybe = True): - # self.inMaybe is always true for methods - self.inMaybe = True + def reachCheck(self, inMaybe=True): self.outMaybe = False + # Check reachability of method body + # No need to check for empty function body, since the check is done in checkType if self.body: - self.outMaybe = self.body.reachCheck() + self.body.reachCheck(True) # For method bodies, in[L] = maybe by default + self.outMaybe = self.body.outMaybe + - # error if out[(non-void) method body] = maybe - # self.methodType is an empty string if it's a constructor - if type(self.methodType) != str and self.methodType.myType.name != "void" and self.outMaybe == True: + # Check if out[method body] is a maybe for non-void methods + if not self.methodType or self.myType.name == "void": # Omitting the check for constructors and void functions + return + if self.outMaybe: raise Exception("Non-void method '{}' in class '{}' does not return".format(self.name, self.typeName)) - return self.outMaybe + return ############# helper for forward ref checking ######## # Input: AST Node diff --git a/Test.py b/Test.py index 98b9a61..dcef36a 100644 --- a/Test.py +++ b/Test.py @@ -158,7 +158,6 @@ def run(testFiles): disamiguateAndTypeChecking(ASTs) except Exception as e: return "disamiguateAndTypeChecking: " + e.args[0] - try: reachabilityChecking(ASTs) except Exception as e: -- GitLab