Example #1
0
    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
Example #2
0
    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
Example #3
0
    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
Example #4
0
    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
Example #5
0
    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
Example #6
0
    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
Example #7
0
    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
Example #8
0
    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
Example #9
0
    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
Example #10
0
    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
Example #11
0
    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
Example #12
0
    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
Example #13
0
    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
Example #14
0
    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