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