def computeExpressionRaw(self, trace_collection): self.variable_trace = trace_collection.getVariableCurrentTrace( variable=self.variable ) replacement = self.variable_trace.getReplacementNode(self) if replacement is not None: trace_collection.signalChange( "new_expression", self.source_ref, "Value propagated for '%s' from '%s'." % ( self.variable.getName(), replacement.getSourceReference().getAsString(), ), ) # Need to compute the replacement still. return replacement.computeExpressionRaw(trace_collection) # If we cannot be sure if the value is set, then we need the fallback, # otherwise we could remove it simply. if self.variable_trace.mustHaveValue(): trace_collection.signalChange( "new_expression", self.source_ref, "Name '%s' must be in locals dict." % self.variable.getName(), ) result = ExpressionLocalsVariableRef( locals_scope=self.locals_scope, variable_name=self.variable.getName(), source_ref=self.source_ref, ) # Need to compute the replacement still. return result.computeExpressionRaw(trace_collection) else: trace_collection.onExceptionRaiseExit(BaseException) branch_fallback = TraceCollectionBranch( parent=trace_collection, name="fallback node usage" ) branch_fallback.computeBranch(self.subnode_fallback) trace_collection.mergeBranches(branch_fallback, None) # if we can be sure if doesn't have a value set, go to the fallback directly. if self.variable_trace.mustNotHaveValue(): return ( self.subnode_fallback, "new_expression", "Name '%s' cannot be in locals dict." % self.variable.getName(), ) else: return self, None, None
def computeStatement(self, trace_collection): outer_trace_collection = trace_collection trace_collection = TraceCollectionBranch( parent = trace_collection, name = "loop" ) loop_body, break_collections = self.computeLoopBody(trace_collection) # Consider trailing "continue" statements, these have no effect, so we # can remove them. if loop_body is not None: assert loop_body.isStatementsSequence() statements = loop_body.getStatements() assert statements # Cannot be empty # If the last statement is a "continue" statement, it can simply # be discarded. last_statement = statements[-1] if last_statement.isStatementLoopContinue(): if len(statements) == 1: self.setLoopBody(None) loop_body = None else: last_statement.replaceWith(None) trace_collection.signalChange( "new_statements", last_statement.getSourceReference(), """\ Removed useless terminal 'continue' as last statement of loop.""" ) if break_collections: outer_trace_collection.mergeMultipleBranches(break_collections) # Consider leading "break" statements, they should be the only, and # should lead to removing the whole loop statement. Trailing "break" # statements could also be handled, but that would need to consider if # there are other "break" statements too. Numbering loop exits is # nothing we have yet. if loop_body is not None: assert loop_body.isStatementsSequence() statements = loop_body.getStatements() assert statements # Cannot be empty if len(statements) == 1 and statements[-1].isStatementLoopBreak(): return None, "new_statements", """\ Removed useless loop with immediate 'break' statement.""" # Also consider the threading intermission. TODO: We ought to make it # explicit, so we can see it potentially disrupting and changing the # global variables. It may also raise. outer_trace_collection.onExceptionRaiseExit(BaseException) return self, None, None
def computeStatement(self, trace_collection): outer_trace_collection = trace_collection trace_collection = TraceCollectionBranch(parent=trace_collection, name="loop") loop_body, break_collections = self.computeLoopBody(trace_collection) # Consider trailing "continue" statements, these have no effect, so we # can remove them. if loop_body is not None: assert loop_body.isStatementsSequence() statements = loop_body.getStatements() assert statements # Cannot be empty # If the last statement is a "continue" statement, it can simply # be discarded. last_statement = statements[-1] if last_statement.isStatementLoopContinue(): if len(statements) == 1: self.subnode_body.finalize() self.setLoopBody(None) loop_body = None else: last_statement.parent.replaceChild(last_statement, None) last_statement.finalize() trace_collection.signalChange( "new_statements", last_statement.getSourceReference(), """\ Removed useless terminal 'continue' as last statement of loop.""") if break_collections: outer_trace_collection.mergeMultipleBranches(break_collections) # Consider leading "break" statements, they should be the only, and # should lead to removing the whole loop statement. Trailing "break" # statements could also be handled, but that would need to consider if # there are other "break" statements too. Numbering loop exits is # nothing we have yet. if loop_body is not None: assert loop_body.isStatementsSequence() statements = loop_body.getStatements() assert statements # Cannot be empty if len(statements) == 1 and statements[-1].isStatementLoopBreak(): return None, "new_statements", """\ Removed useless loop with immediate 'break' statement.""" # Also consider the threading intermission. TODO: We ought to make it # explicit, so we can see it potentially disrupting and changing the # global variables. It may also raise. outer_trace_collection.onExceptionRaiseExit(BaseException) return self, None, None
def computeExpressionRaw(self, trace_collection): # TODO: Use dictionary tracings for locals dict and then cut # to no branch variant if possible. possible = trace_collection.onLocalsDictGet(self.variable_name) if possible is not True: branch_fallback = TraceCollectionBranch(parent=trace_collection, name="fallback node usage") branch_fallback.computeBranch(self.subnode_fallback) trace_collection.mergeBranches(branch_fallback, None) if possible is False: return self.subnode_fallback, "new_expression", "Name '%s' cannot be in locals dict." % self.variable_name else: return self, None, None
def computeExpressionRaw(self, trace_collection): # Query the truth value after the expression is evaluated, once it is # evaluated in onExpression, it is known. trace_collection.onExpression( expression = self.getCondition() ) condition = self.getCondition() condition_may_raise = condition.mayRaiseException(BaseException) if condition_may_raise: trace_collection.onExceptionRaiseExit( BaseException ) # No need to look any further, if the condition raises, the branches do # not matter at all. if condition.willRaiseException(BaseException): return condition, "new_raise", """\ Conditional expression already raises implicitly in condition, removing \ branches.""" if not condition_may_raise and condition.mayRaiseExceptionBool(BaseException): trace_collection.onExceptionRaiseExit( BaseException ) # Decide this based on truth value of condition. truth_value = condition.getTruthValue() # TODO: We now know that condition evaluates to true for the yes branch # and to not true for no branch, the branch should know that. yes_branch = self.getExpressionYes() # Continue to execute for yes branch unless we know it's not going to be # relevant. if truth_value is not False: branch_yes_collection = TraceCollectionBranch( parent = trace_collection, name = "conditional expression yes branch" ) branch_yes_collection.computeBranch( branch = yes_branch ) # May have just gone away, so fetch it again. yes_branch = self.getExpressionYes() # If it's aborting, it doesn't contribute to merging. if yes_branch.willRaiseException(BaseException): branch_yes_collection = None else: branch_yes_collection = None no_branch = self.getExpressionNo() # Continue to execute for yes branch. if truth_value is not True: branch_no_collection = TraceCollectionBranch( parent = trace_collection, name = "conditional expression no branch" ) branch_no_collection.computeBranch( branch = no_branch ) # May have just gone away, so fetch it again. no_branch = self.getExpressionNo() # If it's aborting, it doesn't contribute to merging. if no_branch.willRaiseException(BaseException): branch_no_collection = None else: branch_no_collection = None # Merge into parent execution. self.merge_traces = trace_collection.mergeBranches( branch_yes_collection, branch_no_collection ) if truth_value is True: return ( wrapExpressionWithNodeSideEffects( new_node = self.getExpressionYes(), old_node = condition ), "new_expression", "Conditional expression predicted to yes case" ) elif truth_value is False: return ( wrapExpressionWithNodeSideEffects( new_node = self.getExpressionNo(), old_node = condition ), "new_expression", "Conditional expression predicted to no case" ) else: return self, None, None
def computeStatement(self, trace_collection): # This is rather complex stuff, pylint: disable=too-many-branches,too-many-statements trace_collection.onExpression( expression = self.getCondition() ) condition = self.getCondition() condition_may_raise = condition.mayRaiseException(BaseException) if condition_may_raise: trace_collection.onExceptionRaiseExit( BaseException ) # No need to look any further, if the condition raises, the branches do # not matter at all. if condition.willRaiseException(BaseException): result = makeStatementExpressionOnlyReplacementNode( expression = condition, node = self ) return result, "new_raise", """\ Conditional statements already raises implicitly in condition, removing \ branches.""" if not condition_may_raise and condition.mayRaiseExceptionBool(BaseException): trace_collection.onExceptionRaiseExit( BaseException ) # Query the truth value after the expression is evaluated, once it is # evaluated in onExpression, it is known. truth_value = condition.getTruthValue() # TODO: We now know that condition evaluates to true for the yes branch # and to not true for no branch, the branch collection should know that. yes_branch = self.getBranchYes() no_branch = self.getBranchNo() # Handle branches that became empty behind our back. if yes_branch is not None: if not yes_branch.getStatements(): yes_branch = None if no_branch is not None: if not no_branch.getStatements(): no_branch = None # Consider to not remove branches that we know won't be taken. if yes_branch is not None and truth_value is False: trace_collection.signalChange( tags = "new_statements", source_ref = yes_branch.source_ref, message = "Removed conditional branch not taken due to false condition value." ) self.setBranchYes(None) yes_branch = None if no_branch is not None and truth_value is True: trace_collection.signalChange( tags = "new_statements", source_ref = no_branch.source_ref, message = "Removed 'else' branch not taken due to true condition value." ) self.setBranchNo(None) no_branch = None # Continue to execute for yes branch unless we know it's not going to be # relevant. if yes_branch is not None: branch_yes_collection = TraceCollectionBranch( parent = trace_collection, name = "conditional yes branch", ) branch_yes_collection.computeBranch( branch = yes_branch ) # May have just gone away, so fetch it again. yes_branch = self.getBranchYes() # If it's aborting, it doesn't contribute to merging. if yes_branch is None or yes_branch.isStatementAborting(): branch_yes_collection = None else: branch_yes_collection = None # Continue to execute for yes branch. if no_branch is not None: branch_no_collection = TraceCollectionBranch( parent = trace_collection, name = "conditional no branch" ) branch_no_collection.computeBranch( branch = no_branch ) # May have just gone away, so fetch it again. no_branch = self.getBranchNo() # If it's aborting, it doesn't contribute to merging. if no_branch is None or no_branch.isStatementAborting(): branch_no_collection = None else: branch_no_collection = None # Merge into parent execution. self.merge_traces = trace_collection.mergeBranches( branch_yes_collection, branch_no_collection ) # Both branches may have become empty, which case, the statement needs # not remain. if yes_branch is None and no_branch is None: # Need to keep the boolean check. if truth_value is None: condition = ExpressionBuiltinBool( value = condition, source_ref = condition.getSourceReference() ) if condition.mayHaveSideEffects(): # With both branches eliminated, the condition remains as a side # effect. result = makeStatementExpressionOnlyReplacementNode( expression = condition, node = self ) return result, "new_statements", """\ Both branches have no effect, reduced to evaluate condition.""" else: return None, "new_statements", """\ Removed conditional statement without effect.""" # Note: Checking the condition late, so that the surviving branch got # processed already. Returning without doing that, will corrupt the SSA # results. TODO: Could pretend the other branch didn't exist to save # complexity the merging of processing. if truth_value is not None: if truth_value is True: choice = "true" new_statement = self.getBranchYes() else: choice = "false" new_statement = self.getBranchNo() new_statement = wrapStatementWithSideEffects( new_node = new_statement, old_node = condition, allow_none = True # surviving branch may empty ) return new_statement, "new_statements", """\ Condition for branch was predicted to be always %s.""" % choice # If there is no "yes" branch, remove that. Maybe a bad idea though. if yes_branch is None: # Would be eliminated already, if there wasn't any "no" branch # either. assert no_branch is not None from .OperatorNodes import ExpressionOperationNOT new_statement = StatementConditional( condition = ExpressionOperationNOT( operand = condition, source_ref = condition.getSourceReference() ), yes_branch = no_branch, no_branch = None, source_ref = self.getSourceReference() ) return new_statement, "new_statements", """\ Empty 'yes' branch for conditional statement treated with inverted condition check.""" return self, None, None
def computeExpressionRaw(self, trace_collection): # Query the truth value after the expression is evaluated, once it is # evaluated in onExpression, it is known. trace_collection.onExpression( expression = self.getLeft() ) left = self.getLeft() left_may_raise = left.mayRaiseException(BaseException) if left_may_raise: trace_collection.onExceptionRaiseExit( BaseException ) # No need to look any further, if the condition raises, the branches do # not matter at all. if left.willRaiseException(BaseException): return left, "new_raise", """\ Conditional %s statements already raises implicitly in condition, removing \ branches.""" % self.conditional_kind if not left_may_raise and left.mayRaiseExceptionBool(BaseException): trace_collection.onExceptionRaiseExit( BaseException ) # Decide this based on truth value of condition. truth_value = left.getTruthValue() truth_value_use_left = self.conditional_kind == "or" truth_value_use_right = not truth_value_use_left right = self.getRight() # Continue to execute for yes branch unless we know it's not going to be # relevant. if truth_value is not truth_value_use_left: # TODO: We now know that left evaluates and we should tell the # branch that. branch_yes_collection = TraceCollectionBranch( parent = trace_collection, name = "boolean %s right branch" % self.conditional_kind ) branch_yes_collection.computeBranch( branch = right ) # May have just gone away, so fetch it again. right = self.getRight() # If it's aborting, it doesn't contribute to merging. if right.willRaiseException(BaseException): branch_yes_collection = None else: branch_yes_collection = None if branch_yes_collection: # Merge into parent execution. self.merge_traces = trace_collection.mergeBranches( branch_yes_collection, None ) else: self.merge_traces = None if truth_value is truth_value_use_left: return ( left, "new_expression", "Conditional '%s' expression predicted to left value." % self.conditional_kind ) elif truth_value is truth_value_use_right: return ( wrapExpressionWithNodeSideEffects( new_node = right, old_node = left ), "new_expression", "Conditional '%s' expression predicted right value." % self.conditional_kind ) else: return self, None, None
def computeStatement(self, trace_collection): # This node has many children to handle, pylint: disable=R0912,R0914 tried = self.getBlockTry() except_handler = self.getBlockExceptHandler() break_handler = self.getBlockBreakHandler() continue_handler = self.getBlockContinueHandler() return_handler = self.getBlockReturnHandler() # The tried block must be considered as a branch, if it is not empty # already. collection_start = TraceCollectionBranch(parent=trace_collection, name="try start") abort_context = trace_collection.makeAbortStackContext( catch_breaks=break_handler is not None, catch_continues=continue_handler is not None, catch_returns=return_handler is not None, catch_exceptions=True, ) with abort_context: # As a branch point for the many types of handlers. result = tried.computeStatementsSequence( trace_collection=trace_collection) # We might be done entirely already. if result is None: return None, "new_statements", "Removed now empty try statement." # Might be changed. if result is not tried: self.setBlockTry(result) tried = result break_collections = trace_collection.getLoopBreakCollections() continue_collections = trace_collection.getLoopContinueCollections( ) return_collections = trace_collection.getFunctionReturnCollections( ) exception_collections = trace_collection.getExceptionRaiseCollections( ) tried_may_raise = tried.mayRaiseException(BaseException) # Exception handling is useless if no exception is to be raised. # TODO: signal the change. if not tried_may_raise: if except_handler is not None: self.setBlockExceptHandler(None) except_handler = None # If tried may raise, even empty exception handler has a meaning to # ignore that exception. if tried_may_raise: collection_exception_handling = TraceCollectionBranch( parent=collection_start, name="except handler") if not exception_collections: for statement in tried.getStatements(): if statement.mayRaiseException(BaseException): assert False, statement.asXmlText() assert False collection_exception_handling.mergeMultipleBranches( exception_collections) if except_handler is not None: result = except_handler.computeStatementsSequence( trace_collection=collection_exception_handling) # Might be changed. if result is not except_handler: self.setBlockExceptHandler(result) except_handler = result if break_handler is not None: if not tried.mayBreak(): self.setBlockBreakHandler(None) break_handler = None if break_handler is not None: collection_break = TraceCollectionBranch(parent=collection_start, name="break handler") collection_break.mergeMultipleBranches(break_collections) result = break_handler.computeStatementsSequence( trace_collection=collection_break) # Might be changed. if result is not break_handler: self.setBlockBreakHandler(result) break_handler = result if continue_handler is not None: if not tried.mayContinue(): self.setBlockContinueHandler(None) continue_handler = None if continue_handler is not None: collection_continue = TraceCollectionBranch( parent=collection_start, name="continue handler") collection_continue.mergeMultipleBranches(continue_collections) result = continue_handler.computeStatementsSequence( trace_collection=collection_continue) # Might be changed. if result is not continue_handler: self.setBlockContinueHandler(result) continue_handler = result if return_handler is not None: if not tried.mayReturn(): self.setBlockReturnHandler(None) return_handler = None if return_handler is not None: collection_return = TraceCollectionBranch(parent=collection_start, name="return handler") collection_return.mergeMultipleBranches(return_collections) result = return_handler.computeStatementsSequence( trace_collection=collection_return) # Might be changed. if result is not return_handler: self.setBlockReturnHandler(result) return_handler = result if return_handler is not None: if return_handler.getStatements()[0].isStatementReturn() and \ return_handler.getStatements()[0].getExpression().isExpressionReturnedValueRef(): self.setBlockReturnHandler(None) return_handler = None # Merge exception handler only if it is used. Empty means it is not # aborting, as it swallows the exception. if tried_may_raise and ( except_handler is None or \ not except_handler.isStatementAborting() ): trace_collection.mergeBranches( collection_yes=collection_exception_handling, collection_no=None) # An empty exception handler means we have to swallow exception. if not tried_may_raise and \ break_handler is None and \ continue_handler is None and \ return_handler is None: return tried, "new_statements", "Removed all try handlers." tried_statements = tried.getStatements() pre_statements = [] while tried_statements: tried_statement = tried_statements[0] if tried_statement.mayRaiseException(BaseException): break if break_handler is not None and \ tried_statement.mayBreak(): break if continue_handler is not None and \ tried_statement.mayContinue(): break if return_handler is not None and \ tried_statement.mayReturn(): break pre_statements.append(tried_statement) tried_statements = list(tried_statements) del tried_statements[0] post_statements = [] if except_handler is not None and except_handler.isStatementAborting(): while tried_statements: tried_statement = tried_statements[-1] if tried_statement.mayRaiseException(BaseException): break if break_handler is not None and \ tried_statement.mayBreak(): break if continue_handler is not None and \ tried_statement.mayContinue(): break if return_handler is not None and \ tried_statement.mayReturn(): break post_statements.insert(0, tried_statement) tried_statements = list(tried_statements) del tried_statements[-1] if pre_statements or post_statements: assert tried_statements # Should be dealt with already tried.setStatements(tried_statements) result = StatementsSequence(statements=pre_statements + [self] + post_statements, source_ref=self.getSourceReference()) def explain(): # TODO: We probably don't want to say this for re-formulation ones. result = "Reduced scope of tried block." if pre_statements: result += " Leading statements at %s." % (','.join( x.getSourceReference().getAsString() + '/' + str(x) for x in pre_statements)) if post_statements: result += " Trailing statements at %s." % (','.join( x.getSourceReference().getAsString() + '/' + str(x) for x in post_statements)) return result return (result, "new_statements", explain) return self, None, None
def computeExpressionRaw(self, trace_collection): # This is rather complex stuff, pylint: disable=too-many-branches # Query the truth value after the expression is evaluated, once it is # evaluated in onExpression, it is known. condition = trace_collection.onExpression( expression=self.subnode_condition) # No need to look any further, if the condition raises, the branches do # not matter at all. if condition.willRaiseException(BaseException): return ( condition, "new_raise", """\ Conditional expression already raises implicitly in condition, removing \ branches.""", ) # Tell it we are evaluation it for boolean value only, it may demote # itself possibly. condition.computeExpressionBool(trace_collection) condition = self.subnode_condition # Decide this based on truth value of condition. truth_value = condition.getTruthValue() # TODO: We now know that condition evaluates to true for the yes branch # and to not true for no branch, the branch should know that. yes_branch = self.subnode_expression_yes # Continue to execute for yes branch unless we know it's not going to be # relevant. if truth_value is not False: branch_yes_collection = TraceCollectionBranch( parent=trace_collection, name="conditional expression yes branch") branch_yes_collection.computeBranch(branch=yes_branch) # May have just gone away, so fetch it again. yes_branch = self.subnode_expression_yes # If it's aborting, it doesn't contribute to merging. if yes_branch.willRaiseException(BaseException): branch_yes_collection = None else: branch_yes_collection = None no_branch = self.subnode_expression_no # Continue to execute for yes branch. if truth_value is not True: branch_no_collection = TraceCollectionBranch( parent=trace_collection, name="conditional expression no branch") branch_no_collection.computeBranch(branch=no_branch) # May have just gone away, so fetch it again. no_branch = self.subnode_expression_no # If it's aborting, it doesn't contribute to merging. if no_branch.willRaiseException(BaseException): branch_no_collection = None else: branch_no_collection = None if truth_value is True: trace_collection.replaceBranch(branch_yes_collection) elif truth_value is False: trace_collection.replaceBranch(branch_no_collection) else: # Merge into parent execution. trace_collection.mergeBranches(branch_yes_collection, branch_no_collection) if truth_value is True: return ( wrapExpressionWithNodeSideEffects( new_node=self.subnode_expression_yes, old_node=condition), "new_expression", "Conditional expression predicted to yes case.", ) elif truth_value is False: return ( wrapExpressionWithNodeSideEffects( new_node=self.subnode_expression_no, old_node=condition), "new_expression", "Conditional expression predicted to no case.", ) else: return self, None, None
def computeStatement(self, trace_collection): # This node has many children to handle, pylint: disable=I0021,too-many-branches,too-many-locals,too-many-statements tried = self.getBlockTry() except_handler = self.getBlockExceptHandler() break_handler = self.getBlockBreakHandler() continue_handler = self.getBlockContinueHandler() return_handler = self.getBlockReturnHandler() # The tried block must be considered as a branch, if it is not empty # already. collection_start = TraceCollectionBranch(parent=trace_collection, name="try start") abort_context = trace_collection.makeAbortStackContext( catch_breaks=break_handler is not None, catch_continues=continue_handler is not None, catch_returns=return_handler is not None, catch_exceptions=True, ) with abort_context: # As a branch point for the many types of handlers. result = tried.computeStatementsSequence( trace_collection=trace_collection) # We might be done entirely already. if result is None: return None, "new_statements", "Removed now empty try statement." # Might be changed. if result is not tried: self.setBlockTry(result) tried = result break_collections = trace_collection.getLoopBreakCollections() continue_collections = trace_collection.getLoopContinueCollections( ) return_collections = trace_collection.getFunctionReturnCollections( ) exception_collections = trace_collection.getExceptionRaiseCollections( ) tried_may_raise = tried.mayRaiseException(BaseException) # Exception handling is useless if no exception is to be raised. if not tried_may_raise: if except_handler is not None: except_handler.finalize() self.setBlockExceptHandler(None) trace_collection.signalChange( tags="new_statements", message="Removed useless exception handler.", source_ref=except_handler.source_ref, ) except_handler = None # If tried may raise, even empty exception handler has a meaning to # ignore that exception. if tried_may_raise: collection_exception_handling = TraceCollectionBranch( parent=collection_start, name="except handler") # When no exception exits are there, this is a problem, we just # found an inconsistency that is a bug. if not exception_collections: for statement in tried.getStatements(): if statement.mayRaiseException(BaseException): raise NuitkaOptimizationError( "This statement does raise but didn't annotate an exception exit.", statement, ) raise NuitkaOptimizationError( "Falsely assuming tried block may raise, but no statement says so.", tried, ) collection_exception_handling.mergeMultipleBranches( exception_collections) if except_handler is not None: result = except_handler.computeStatementsSequence( trace_collection=collection_exception_handling) # Might be changed. if result is not except_handler: self.setBlockExceptHandler(result) except_handler = result if break_handler is not None: if not tried.mayBreak(): break_handler.finalize() self.setBlockBreakHandler(None) break_handler = None if break_handler is not None: collection_break = TraceCollectionBranch(parent=collection_start, name="break handler") collection_break.mergeMultipleBranches(break_collections) result = break_handler.computeStatementsSequence( trace_collection=collection_break) # Might be changed. if result is not break_handler: self.setBlockBreakHandler(result) break_handler = result if continue_handler is not None: if not tried.mayContinue(): continue_handler.finalize() self.setBlockContinueHandler(None) continue_handler = None if continue_handler is not None: collection_continue = TraceCollectionBranch( parent=collection_start, name="continue handler") collection_continue.mergeMultipleBranches(continue_collections) result = continue_handler.computeStatementsSequence( trace_collection=collection_continue) # Might be changed. if result is not continue_handler: self.setBlockContinueHandler(result) continue_handler = result if return_handler is not None: if not tried.mayReturn(): return_handler.finalize() self.setBlockReturnHandler(None) return_handler = None if return_handler is not None: collection_return = TraceCollectionBranch(parent=collection_start, name="return handler") collection_return.mergeMultipleBranches(return_collections) result = return_handler.computeStatementsSequence( trace_collection=collection_return) # Might be changed. if result is not return_handler: self.setBlockReturnHandler(result) return_handler = result # Check for trivial return handlers that immediately return, they can # just be removed. if return_handler is not None: if (return_handler.getStatements()[0].isStatementReturn() and return_handler.getStatements() [0].getExpression().isExpressionReturnedValueRef()): return_handler.finalize() self.setBlockReturnHandler(None) return_handler = None # Merge exception handler only if it is used. Empty means it is not # aborting, as it swallows the exception. if tried_may_raise and (except_handler is None or not except_handler.isStatementAborting()): trace_collection.mergeBranches( collection_yes=collection_exception_handling, collection_no=None) # An empty exception handler means we have to swallow exception. if ((not tried_may_raise or (except_handler is not None and except_handler.getStatements()[0].isStatementReraiseException())) and break_handler is None and continue_handler is None and return_handler is None): return tried, "new_statements", "Removed useless try, all handlers removed." tried_statements = tried.getStatements() pre_statements = [] while tried_statements: tried_statement = tried_statements[0] if tried_statement.mayRaiseException(BaseException): break if break_handler is not None and tried_statement.mayBreak(): break if continue_handler is not None and tried_statement.mayContinue(): break if return_handler is not None and tried_statement.mayReturn(): break pre_statements.append(tried_statement) tried_statements = list(tried_statements) del tried_statements[0] post_statements = [] if except_handler is not None and except_handler.isStatementAborting(): while tried_statements: tried_statement = tried_statements[-1] if tried_statement.mayRaiseException(BaseException): break if break_handler is not None and tried_statement.mayBreak(): break if continue_handler is not None and tried_statement.mayContinue( ): break if return_handler is not None and tried_statement.mayReturn(): break post_statements.insert(0, tried_statement) tried_statements = list(tried_statements) del tried_statements[-1] if pre_statements or post_statements: assert tried_statements # Should be dealt with already tried.setStatements(tried_statements) result = StatementsSequence( statements=pre_statements + [self] + post_statements, source_ref=self.source_ref, ) def explain(): # TODO: We probably don't want to say this for re-formulation ones. result = "Reduced scope of tried block." if pre_statements: result += " Leading statements at %s." % (",".join( x.getSourceReference().getAsString() + "/" + str(x) for x in pre_statements)) if post_statements: result += " Trailing statements at %s." % (",".join( x.getSourceReference().getAsString() + "/" + str(x) for x in post_statements)) return result return (result, "new_statements", explain) return self, None, None
def computeExpressionRaw(self, trace_collection): # This is rather complex stuff, pylint: disable=too-many-branches # Query the truth value after the expression is evaluated, once it is # evaluated in onExpression, it is known. trace_collection.onExpression(expression=self.getCondition()) condition = self.getCondition() # No need to look any further, if the condition raises, the branches do # not matter at all. if condition.willRaiseException(BaseException): return ( condition, "new_raise", """\ Conditional expression already raises implicitly in condition, removing \ branches.""", ) # Tell it we are evaluation it for boolean value only, it may demote # itself possibly. condition.computeExpressionBool(trace_collection) condition = self.getCondition() # Decide this based on truth value of condition. truth_value = condition.getTruthValue() # TODO: We now know that condition evaluates to true for the yes branch # and to not true for no branch, the branch should know that. yes_branch = self.getExpressionYes() # Continue to execute for yes branch unless we know it's not going to be # relevant. if truth_value is not False: branch_yes_collection = TraceCollectionBranch( parent=trace_collection, name="conditional expression yes branch" ) branch_yes_collection.computeBranch(branch=yes_branch) # May have just gone away, so fetch it again. yes_branch = self.getExpressionYes() # If it's aborting, it doesn't contribute to merging. if yes_branch.willRaiseException(BaseException): branch_yes_collection = None else: branch_yes_collection = None no_branch = self.getExpressionNo() # Continue to execute for yes branch. if truth_value is not True: branch_no_collection = TraceCollectionBranch( parent=trace_collection, name="conditional expression no branch" ) branch_no_collection.computeBranch(branch=no_branch) # May have just gone away, so fetch it again. no_branch = self.getExpressionNo() # If it's aborting, it doesn't contribute to merging. if no_branch.willRaiseException(BaseException): branch_no_collection = None else: branch_no_collection = None if truth_value is True: trace_collection.replaceBranch(branch_yes_collection) elif truth_value is False: trace_collection.replaceBranch(branch_no_collection) else: # Merge into parent execution. trace_collection.mergeBranches(branch_yes_collection, branch_no_collection) if truth_value is True: return ( wrapExpressionWithNodeSideEffects( new_node=self.getExpressionYes(), old_node=condition ), "new_expression", "Conditional expression predicted to yes case.", ) elif truth_value is False: return ( wrapExpressionWithNodeSideEffects( new_node=self.getExpressionNo(), old_node=condition ), "new_expression", "Conditional expression predicted to no case.", ) else: return self, None, None
def computeStatement(self, trace_collection): # This is rather complex stuff, pylint: disable=too-many-branches,too-many-statements trace_collection.onExpression(expression=self.getCondition()) condition = self.getCondition() # No need to look any further, if the condition raises, the branches do # not matter at all. if condition.willRaiseException(BaseException): result = makeStatementExpressionOnlyReplacementNode( expression=condition, node=self ) return ( result, "new_raise", """\ Conditional statements already raises implicitly in condition, removing \ branches.""", ) # Tell it we are evaluation it for boolean value only, it may demote # itself possibly. condition.computeExpressionBool(trace_collection) condition = self.getCondition() # Query the truth value after the expression is evaluated, once it is # evaluated in onExpression, it is known. truth_value = condition.getTruthValue() # TODO: We now know that condition evaluates to true for the yes branch # and to not true for no branch, the branch collection should know that. yes_branch = self.getBranchYes() no_branch = self.getBranchNo() # Handle branches that became empty behind our back. if yes_branch is not None: if not yes_branch.getStatements(): yes_branch.finalize() yes_branch = None self.setBranchYes(None) if no_branch is not None: if not no_branch.getStatements(): no_branch.finalize() no_branch = None self.setBranchNo(None) # Consider to not remove branches that we know won't be taken. if yes_branch is not None and truth_value is False: trace_collection.signalChange( tags="new_statements", source_ref=yes_branch.source_ref, message="Removed conditional branch not taken due to false condition value.", ) yes_branch.finalize() self.setBranchYes(None) yes_branch = None if no_branch is not None and truth_value is True: trace_collection.signalChange( tags="new_statements", source_ref=no_branch.source_ref, message="Removed 'else' branch not taken due to true condition value.", ) no_branch.finalize() self.setBranchNo(None) no_branch = None # Continue to execute for yes branch unless we know it's not going to be # relevant. if yes_branch is not None: branch_yes_collection = TraceCollectionBranch( parent=trace_collection, name="conditional yes branch" ) branch_yes_collection.computeBranch(branch=yes_branch) # May have just gone away, so fetch it again. yes_branch = self.getBranchYes() # If it's aborting, it doesn't contribute to merging. if yes_branch is None or yes_branch.isStatementAborting(): branch_yes_collection = None else: branch_yes_collection = None # Continue to execute for yes branch. if no_branch is not None: branch_no_collection = TraceCollectionBranch( parent=trace_collection, name="conditional no branch" ) branch_no_collection.computeBranch(branch=no_branch) # May have just gone away, so fetch it again. no_branch = self.getBranchNo() # If it's aborting, it doesn't contribute to merging. if no_branch is None or no_branch.isStatementAborting(): branch_no_collection = None else: branch_no_collection = None if truth_value is True: if branch_yes_collection is not None: trace_collection.replaceBranch(branch_yes_collection) elif truth_value is False: if branch_no_collection is not None: trace_collection.replaceBranch(branch_no_collection) else: trace_collection.mergeBranches(branch_yes_collection, branch_no_collection) # Both branches may have become empty, which case, the statement needs # not remain. if yes_branch is None and no_branch is None: # Need to keep the boolean check. if truth_value is None: condition = ExpressionBuiltinBool( value=condition, source_ref=condition.getSourceReference() ) if condition.mayHaveSideEffects(): # With both branches eliminated, the condition remains as a side # effect. result = makeStatementExpressionOnlyReplacementNode( expression=condition, node=self ) del self.parent return ( result, "new_statements", """\ Both branches have no effect, reduced to evaluate condition.""", ) else: self.finalize() return ( None, "new_statements", """\ Removed conditional statement without effect.""", ) # Note: Checking the condition late, so that the surviving branch got # processed already. Returning without doing that, will corrupt the SSA # results. TODO: Could pretend the other branch didn't exist to save # complexity the merging of processing. if truth_value is not None: if truth_value is True: choice = "true" new_statement = yes_branch if no_branch is not None: no_branch.finalize() else: choice = "false" new_statement = no_branch if yes_branch is not None: yes_branch.finalize() new_statement = wrapStatementWithSideEffects( new_node=new_statement, old_node=condition, allow_none=True, # surviving branch may empty ) del self.parent return ( new_statement, "new_statements", """\ Condition for branch statement was predicted to be always %s.""" % choice, ) # If there is no "yes" branch, remove that. Maybe a bad idea though. if yes_branch is None: # Would be eliminated already, if there wasn't any "no" branch # either. assert no_branch is not None new_statement = makeStatementConditional( condition=ExpressionOperationNOT( operand=condition, source_ref=condition.getSourceReference() ), yes_branch=no_branch, no_branch=None, source_ref=self.getSourceReference(), ) del self.parent return ( new_statement, "new_statements", """\ Empty 'yes' branch for conditional statement treated with inverted condition check.""", ) return self, None, None
def computeExpressionRaw(self, trace_collection): # Query the truth value after the expression is evaluated, once it is # evaluated in onExpression, it is known. trace_collection.onExpression(expression=self.getLeft()) left = self.getLeft() # No need to look any further, if the condition raises, the branches do # not matter at all. if left.willRaiseException(BaseException): return ( left, "new_raise", """\ Conditional %s statements already raises implicitly in condition, removing \ branches.""" % self.conditional_kind, ) if not left.mayRaiseException(BaseException) and left.mayRaiseExceptionBool( BaseException ): trace_collection.onExceptionRaiseExit(BaseException) # Decide this based on truth value of condition. truth_value = left.getTruthValue() truth_value_use_left = self.conditional_kind == "or" truth_value_use_right = not truth_value_use_left right = self.getRight() # Continue to execute for yes branch unless we know it's not going to be # relevant. if truth_value is not truth_value_use_left: # TODO: We now know that left evaluates and we should tell the # branch that. branch_yes_collection = TraceCollectionBranch( parent=trace_collection, name="boolean %s right branch" % self.conditional_kind, ) branch_yes_collection.computeBranch(branch=right) # May have just gone away, so fetch it again. right = self.getRight() # If it's aborting, it doesn't contribute to merging. if right.willRaiseException(BaseException): branch_yes_collection = None else: branch_yes_collection = None if branch_yes_collection: # Merge into parent execution. trace_collection.mergeBranches(branch_yes_collection, None) if truth_value is truth_value_use_left: return ( left, "new_expression", "Conditional '%s' expression predicted to left value." % self.conditional_kind, ) elif truth_value is truth_value_use_right: return ( wrapExpressionWithNodeSideEffects(new_node=right, old_node=left), "new_expression", "Conditional '%s' expression predicted right value." % self.conditional_kind, ) else: return self, None, None
def computeStatement(self, trace_collection): # This node has many children to handle, pylint: disable=R0912,R0914 tried = self.getBlockTry() except_handler = self.getBlockExceptHandler() break_handler = self.getBlockBreakHandler() continue_handler = self.getBlockContinueHandler() return_handler = self.getBlockReturnHandler() # The tried block must be considered as a branch, if it is not empty # already. collection_start = TraceCollectionBranch(parent=trace_collection, name="try start") abort_context = trace_collection.makeAbortStackContext( catch_breaks=break_handler is not None, catch_continues=continue_handler is not None, catch_returns=return_handler is not None, catch_exceptions=True, ) with abort_context: # As a branch point for the many types of handlers. result = tried.computeStatementsSequence(trace_collection=trace_collection) # We might be done entirely already. if result is None: return None, "new_statements", "Removed now empty try statement." # Might be changed. if result is not tried: self.setBlockTry(result) tried = result break_collections = trace_collection.getLoopBreakCollections() continue_collections = trace_collection.getLoopContinueCollections() return_collections = trace_collection.getFunctionReturnCollections() exception_collections = trace_collection.getExceptionRaiseCollections() tried_may_raise = tried.mayRaiseException(BaseException) # Exception handling is useless if no exception is to be raised. # TODO: signal the change. if not tried_may_raise: if except_handler is not None: self.setBlockExceptHandler(None) except_handler = None # If tried may raise, even empty exception handler has a meaning to # ignore that exception. if tried_may_raise: collection_exception_handling = TraceCollectionBranch(parent=collection_start, name="except handler") if not exception_collections: for statement in tried.getStatements(): if statement.mayRaiseException(BaseException): assert False, statement.asXmlText() assert False collection_exception_handling.mergeMultipleBranches(exception_collections) if except_handler is not None: result = except_handler.computeStatementsSequence(trace_collection=collection_exception_handling) # Might be changed. if result is not except_handler: self.setBlockExceptHandler(result) except_handler = result if break_handler is not None: if not tried.mayBreak(): self.setBlockBreakHandler(None) break_handler = None if break_handler is not None: collection_break = TraceCollectionBranch(parent=collection_start, name="break handler") collection_break.mergeMultipleBranches(break_collections) result = break_handler.computeStatementsSequence(trace_collection=collection_break) # Might be changed. if result is not break_handler: self.setBlockBreakHandler(result) break_handler = result if continue_handler is not None: if not tried.mayContinue(): self.setBlockContinueHandler(None) continue_handler = None if continue_handler is not None: collection_continue = TraceCollectionBranch(parent=collection_start, name="continue handler") collection_continue.mergeMultipleBranches(continue_collections) result = continue_handler.computeStatementsSequence(trace_collection=collection_continue) # Might be changed. if result is not continue_handler: self.setBlockContinueHandler(result) continue_handler = result if return_handler is not None: if not tried.mayReturn(): self.setBlockReturnHandler(None) return_handler = None if return_handler is not None: collection_return = TraceCollectionBranch(parent=collection_start, name="return handler") collection_return.mergeMultipleBranches(return_collections) result = return_handler.computeStatementsSequence(trace_collection=collection_return) # Might be changed. if result is not return_handler: self.setBlockReturnHandler(result) return_handler = result if return_handler is not None: if ( return_handler.getStatements()[0].isStatementReturn() and return_handler.getStatements()[0].getExpression().isExpressionReturnedValueRef() ): self.setBlockReturnHandler(None) return_handler = None # Merge exception handler only if it is used. Empty means it is not # aborting, as it swallows the exception. if tried_may_raise and (except_handler is None or not except_handler.isStatementAborting()): trace_collection.mergeBranches(collection_yes=collection_exception_handling, collection_no=None) # An empty exception handler means we have to swallow exception. if not tried_may_raise and break_handler is None and continue_handler is None and return_handler is None: return tried, "new_statements", "Removed all try handlers." tried_statements = tried.getStatements() pre_statements = [] while tried_statements: tried_statement = tried_statements[0] if tried_statement.mayRaiseException(BaseException): break if break_handler is not None and tried_statement.mayBreak(): break if continue_handler is not None and tried_statement.mayContinue(): break if return_handler is not None and tried_statement.mayReturn(): break pre_statements.append(tried_statement) tried_statements = list(tried_statements) del tried_statements[0] post_statements = [] if except_handler is not None and except_handler.isStatementAborting(): while tried_statements: tried_statement = tried_statements[-1] if tried_statement.mayRaiseException(BaseException): break if break_handler is not None and tried_statement.mayBreak(): break if continue_handler is not None and tried_statement.mayContinue(): break if return_handler is not None and tried_statement.mayReturn(): break post_statements.insert(0, tried_statement) tried_statements = list(tried_statements) del tried_statements[-1] if pre_statements or post_statements: assert tried_statements # Should be dealt with already tried.setStatements(tried_statements) result = StatementsSequence( statements=pre_statements + [self] + post_statements, source_ref=self.getSourceReference() ) def explain(): # TODO: We probably don't want to say this for re-formulation ones. result = "Reduced scope of tried block." if pre_statements: result += " Leading statements at %s." % ( ",".join(x.getSourceReference().getAsString() + "/" + str(x) for x in pre_statements) ) if post_statements: result += " Trailing statements at %s." % ( ",".join(x.getSourceReference().getAsString() + "/" + str(x) for x in post_statements) ) return result return (result, "new_statements", explain) return self, None, None