def degradePartiallyFromCode(self, statement_sequence): from nuitka.tree.Extractions import getVariablesWritten variable_writes = getVariablesWritten( statement_sequence ) # Mark all variables as unknown that are written in the statement # sequence, so it destroys the assumptions for final block. TODO: To # unknown is a bit harsh, in case it is known assigned before and # not deleted. for variable, _variable_version in variable_writes: self.markActiveVariableAsUnknown( variable = variable )
def computeLoopBody(self, trace_collection): # Rather complex stuff, pylint: disable=too-many-branches,too-many-locals,too-many-statements abort_context = trace_collection.makeAbortStackContext( catch_breaks=True, catch_continues=True, catch_returns=False, catch_exceptions=False, ) has_initial = False with abort_context: loop_body = self.getLoopBody() if loop_body is not None: # Look ahead. what will be written and degrade to initial loop # traces about that if we are in the first iteration, later we # will have more precise knowledge. if self.loop_variables is None: early = True loop_variables = getVariablesWritten(loop_body) self.loop_variables = {} self.loop_memory = {} for loop_variable in loop_variables: self.loop_variables[loop_variable] = set() self.loop_memory[loop_variable] = None else: early = False loop_entry_traces = set() # List of variables to remove. TODO: Benchmark if it has any # value to avoid creating the normally empty list. to_remove = None # Mark all variables as loop wrap around that are written in # the loop and hit a 'continue'. for loop_variable, current in self.loop_variables.items(): # Loop variable became unused. if not current and not early: if to_remove is None: to_remove = [] to_remove.append(loop_variable) continue last_ones = self.loop_memory[loop_variable] if last_ones is not True: last_ones = ( self.loop_memory[loop_variable] == self.loop_variables[loop_variable] ) loop_entry_traces.add( ( loop_variable, trace_collection.markActiveVariableAsLoopMerge( variable=loop_variable, shapes=self.loop_variables[loop_variable], initial=last_ones is not True, ), ) ) has_initial = has_initial or last_ones is not True if last_ones is not True: self.loop_memory[loop_variable] = set( self.loop_variables[loop_variable] ) if to_remove is not None: for loop_variable in to_remove: del self.loop_memory[loop_variable] del self.loop_variables[loop_variable] # Forget all iterator and other value status. trace_collection.resetValueStates() result = loop_body.computeStatementsSequence( trace_collection=trace_collection ) # Might be changed. if result is not loop_body: self.setLoopBody(result) loop_body = result if loop_body is not None: # Emulate terminal continue if not aborting. if not loop_body.isStatementAborting(): trace_collection.onLoopContinue() continue_collections = trace_collection.getLoopContinueCollections() for variable, loop_entry_trace in loop_entry_traces: loop_end_traces = set() for continue_collection in continue_collections: loop_end_trace = continue_collection.getVariableCurrentTrace( variable ) if loop_end_trace is not loop_entry_trace: loop_end_trace.getTypeShape().emitAlternatives( self.loop_variables[variable].add ) loop_end_traces.add(loop_end_trace) if loop_end_traces: loop_entry_trace.addLoopContinueTraces(loop_end_traces) else: loop_entry_trace.markLoopTraceComplete() # If we break, the outer collections becomes a merge of all those breaks # or just the one, if there is only one. break_collections = trace_collection.getLoopBreakCollections() if has_initial: trace_collection.signalChange( "new_expression", self.source_ref, "Loop has incomplete variable types." ) return loop_body, break_collections
def computeLoopBody(self, constraint_collection): abort_context = constraint_collection.makeAbortStackContext( catch_breaks = True, catch_continues = True, catch_returns = False, catch_exceptions = False, ) with abort_context: loop_body = self.getLoopBody() if loop_body is not None: # Look ahead. what will be written and degrade about that if we # are in the first iteration, later we will have more precise # knowledge. if self.loop_variables is None: self.loop_variables = getVariablesWritten( loop_body ) loop_entry_traces = set() # Mark all variables as loop wrap around that are written in # the loop and hit a 'continue'. for variable in self.loop_variables: loop_entry_traces.add( constraint_collection.markActiveVariableAsLoopMerge( variable = variable ) ) result = loop_body.computeStatementsSequence( constraint_collection = constraint_collection ) # Might be changed. if result is not loop_body: self.setLoopBody(result) loop_body = result if loop_body is not None: # Emulate terminal continue if not aborting. if not loop_body.isStatementAborting(): constraint_collection.onLoopContinue() continue_collections = constraint_collection.getLoopContinueCollections() self.loop_variables = set() for loop_entry_trace in loop_entry_traces: variable = loop_entry_trace.getVariable() loop_end_traces = set() for continue_collection in continue_collections: loop_end_trace = continue_collection.getVariableCurrentTrace(variable) if loop_end_trace is not loop_entry_trace: loop_end_traces.add(loop_end_trace) if loop_end_traces: loop_entry_trace.addLoopContinueTraces(loop_end_traces) self.loop_variables.add(variable) # If we break, the outer collections becomes a merge of all those breaks # or just the one, if there is only one. break_collections = constraint_collection.getLoopBreakCollections() return loop_body, break_collections
def computeStatement(self, constraint_collection): loop_body = self.getLoopBody() if loop_body is not None: # Look ahead. what will be written. variable_writes = getVariablesWritten(loop_body) # Mark all variables as unknown that are written in the loop body, # so it destroys the assumptions for loop turn around. for variable, _variable_version in variable_writes: constraint_collection.markActiveVariableAsUnknown( variable = variable ) result = loop_body.computeStatementsSequence( constraint_collection = constraint_collection ) # Might be changed. if result is not loop_body: self.setLoopBody(result) loop_body = result # 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.isStatementContinueLoop(): if len(statements) == 1: self.setLoopBody(None) loop_body = None else: last_statement.replaceWith(None) constraint_collection.signalChange( "new_statements", last_statement.getSourceReference(), """\ Removed useless terminal 'continue' as last statement of loop.""" ) # 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].isStatementBreakLoop(): return None, "new_statements", """\ Removed useless loop with immediate 'break' statement.""" return self, None, None
def computeExpressionRaw(self, constraint_collection): # The tried block must be considered as a branch, if it is not empty # already. tried_statement_sequence = self.getBlockTry() # May be "None" from the outset, so guard against that, later in this # function we are going to remove it. if tried_statement_sequence is not None: result = tried_statement_sequence.computeStatementsSequence( constraint_collection = constraint_collection ) # Might be changed. if result is not tried_statement_sequence: tried_statement_sequence.replaceWith(result) tried_statement_sequence = result # The main expression itself. constraint_collection.onExpression(self.getExpression()) final_statement_sequence = self.getBlockFinal() # TODO: The final must not assume that all of tried was executed, # instead it may have aborted after any part of it, which is a rather # complex definition. if final_statement_sequence is not None: if tried_statement_sequence is not None: from nuitka.tree.Extractions import getVariablesWritten variable_writes = getVariablesWritten( tried_statement_sequence ) # Mark all variables as unknown that are written in the tried # block, so it destroys the assumptions for loop turn around. for variable, _variable_version in variable_writes: constraint_collection.markActiveVariableAsUnknown( variable = variable ) # Then assuming no exception, the no raise block if present. result = final_statement_sequence.computeStatementsSequence( constraint_collection = constraint_collection ) if result is not final_statement_sequence: self.setBlockFinal(result) final_statement_sequence = result if tried_statement_sequence is None and \ final_statement_sequence is None: # If the tried and final block is empty, go to the expression # directly. return self.getExpression(), "new_expression", """\ Removed try/finally expression with empty tried and final block.""" else: # TODO: Can't really merge it yet. constraint_collection.removeAllKnowledge() # Otherwise keep it as it. return self, None, None
def _computeLoopBody(self, trace_collection): # Rather complex stuff, pylint: disable=too-many-branches,too-many-locals loop_body = self.getLoopBody() if loop_body is None: return None, None, None # Track if we got incomplete knowledge due to loop. If so, we are not done, even # if no was optimization done, once we are complete, they can come. has_incomplete = False # Look ahead. what will be written and degrade to initial loop # traces about that if we are in the first iteration, later we # will have more precise knowledge. if self.loop_variables is None: self.loop_variables = getVariablesWritten(loop_body) # Only important to mark these states as different, so we start with # initial loop traces. for loop_variable in self.loop_variables: self.loop_start[loop_variable] = None self.loop_previous_end[loop_variable] = None self.loop_end[loop_variable] = set() first_pass = True else: first_pass = False # Mark all variables as loop wrap around that are written in the loop and # hit a 'continue' and make them become loop merges. We will strive to # reduce self.loop_variables if we find ones that have no change in all # 'continue' exits. loop_entry_traces = set() for loop_variable in self.loop_variables: current = trace_collection.getVariableCurrentTrace(loop_variable) if first_pass: incomplete = True has_incomplete = True else: incomplete = ( self.loop_start[loop_variable].getLoopTypeShapes() != current.getLoopTypeShapes() or self.loop_end[loop_variable] != self.loop_previous_end[loop_variable] ) if incomplete: has_incomplete = True # TODO: We should be able to avoid these, but it breaks assumptions for assertions # of asssigned and deleted values in the loop. # if ( # incomplete # and not current.isUninitTrace() # or self.loop_end[loop_variable] # ): loop_entry_traces.add( ( loop_variable, trace_collection.markActiveVariableAsLoopMerge( variable=loop_variable, shapes=self.loop_end[loop_variable], incomplete=incomplete, first_pass=first_pass, ), ) ) # Remember what we started with, so we can detect changes from outside the # loop and make them restart the collection process. self.loop_start[loop_variable] = current abort_context = trace_collection.makeAbortStackContext( catch_breaks=True, catch_continues=True, catch_returns=False, catch_exceptions=False, ) with abort_context: # Forget all iterator and other value status. TODO: These should be using # more proper tracing to benefit. trace_collection.resetValueStates() result = loop_body.computeStatementsSequence( trace_collection=trace_collection ) # Might be changed. if result is not loop_body: self.setLoopBody(result) loop_body = result if loop_body is not None: # Emulate terminal continue if not aborting. if not loop_body.isStatementAborting(): trace_collection.onLoopContinue() continue_collections = trace_collection.getLoopContinueCollections() # Rebuild this with only the ones that actually changed in the loop. self.loop_variables = set() for loop_variable, loop_entry_trace in loop_entry_traces: loop_end_traces = set() if not first_pass: self.loop_previous_end[loop_variable] = self.loop_end[loop_variable] self.loop_end[loop_variable] = set() for continue_collection in continue_collections: loop_end_trace = continue_collection.getVariableCurrentTrace( loop_variable ) if loop_end_trace is not loop_entry_trace: if not first_pass: loop_end_trace.getTypeShape().emitAlternatives( self.loop_end[loop_variable].add ) loop_end_traces.add(loop_end_trace) if loop_end_traces: loop_entry_trace.addLoopContinueTraces(loop_end_traces) self.loop_variables.add(loop_variable) # If we break, the outer collections becomes a merge of all those breaks # or just the one, if there is only one. break_collections = trace_collection.getLoopBreakCollections() if has_incomplete: trace_collection.signalChange( "new_expression", self.source_ref, "Loop has incomplete variable types." ) return loop_body, break_collections, continue_collections