1
0
mirror of synced 2024-12-01 00:57:18 +01:00

Eliminate some more gotos, enable finding more types of compound if.

This commit is contained in:
Jennifer Taylor 2021-07-15 00:15:52 +00:00
parent 2895bfc050
commit c8be6aef23

View File

@ -2854,6 +2854,53 @@ class ByteCodeDecompiler(VerboseOutput):
statements = self.__walk(statements, remove_continues)
return statements, updated
def __eliminate_useless_breaks(self, statements: Sequence[Statement]) -> Tuple[List[Statement], bool]:
# Go through and find breaks that show up just before another break logically.
def find_breaks(statements: Sequence[Statement], parent_next_statement: Statement) -> Set[BreakStatement]:
breaks: Set[BreakStatement] = set()
for i in range(len(statements)):
cur_statement = statements[i]
next_statement = statements[i + 1] if (i < len(statements) - 1) else parent_next_statement
if (
isinstance(cur_statement, BreakStatement) and
isinstance(next_statement, BreakStatement)
):
breaks.add(cur_statement)
elif isinstance(cur_statement, DoWhileStatement):
# The next entry after a loop can be a break, as it applies to a different statement.
breaks.update(find_breaks(cur_statement.body, NopStatement()))
elif isinstance(cur_statement, IfStatement):
breaks.update(find_breaks(cur_statement.true_statements, next_statement))
breaks.update(find_breaks(cur_statement.false_statements, next_statement))
elif isinstance(cur_statement, SwitchStatement):
# The next entry after a switch can be a break, as it applies to a different statement.
for case in cur_statement.cases:
breaks.update(find_breaks(case.statements, NopStatement()))
return breaks
# Instead of an empty next statement, make up a return because that's what
# falling off the end of execution means.
breaks = find_breaks(statements, NullReturnStatement())
updated: bool = False
def remove_breaks(statement: Statement) -> Optional[Statement]:
nonlocal updated
for removable in breaks:
if removable is statement:
updated = True
return None
return statement
statements = self.__walk(statements, remove_breaks)
return statements, updated
def __is_math(self, expression: Expression, variable: str) -> bool:
if isinstance(expression, ArithmeticExpression):
# Okay, let's see if it is any sort of math.
@ -3255,7 +3302,7 @@ class ByteCodeDecompiler(VerboseOutput):
running_conditional = OrIf(AndIf(true_cond, running_conditional), AndIf(false_cond, running_conditional)).simplify()
else:
flowed_statements.append((running_conditional, statement))
if isinstance(statement, (NullReturnStatement, ReturnStatement, ThrowStatement)):
if isinstance(statement, (NullReturnStatement, ReturnStatement, ThrowStatement, BreakStatement, ContinueStatement)):
# We shouldn't find any more statements after this, unless there's a label.
running_conditional = IsBooleanIf(False)
elif isinstance(statement, GotoStatement):
@ -3274,7 +3321,7 @@ class ByteCodeDecompiler(VerboseOutput):
continue
goto_conditional = OrIf(gotos[stmt.location], goto_conditional).simplify()
flowed_statements[i] = (OrIf(cond, goto_conditional).simplify(), stmt)
if isinstance(stmt, (NullReturnStatement, ReturnStatement, ThrowStatement, GotoStatement)):
if isinstance(stmt, (NullReturnStatement, ReturnStatement, ThrowStatement, GotoStatement, BreakStatement, ContinueStatement)):
# The current running conditional no longer applies after this statement.
goto_conditional = IsBooleanIf(False)
@ -3433,7 +3480,7 @@ class ByteCodeDecompiler(VerboseOutput):
false_statements.append(statement)
i += 1
if isinstance(statement, (NullReturnStatement, ReturnStatement, ThrowStatement, GotoStatement)):
if isinstance(statement, (NullReturnStatement, ReturnStatement, ThrowStatement, GotoStatement, BreakStatement, ContinueStatement)):
break
# Now, add this new if statement, but make sure to gather up
@ -3841,6 +3888,61 @@ class ByteCodeDecompiler(VerboseOutput):
return new_statements, changed
def __convert_switch_gotos(self, statements: Sequence[Statement]) -> Tuple[List[Statement], bool]:
# Go through and find switch cases that goto the next line in the switch, replacing those
# with break statements.
def find_gotos(statements: Sequence[Statement], parent_next_statement: Statement, goto_next_statement: Statement) -> Set[GotoStatement]:
gotos: Set[GotoStatement] = set()
for i in range(len(statements)):
cur_statement = statements[i]
next_statement = statements[i + 1] if (i < len(statements) - 1) else parent_next_statement
if (
isinstance(cur_statement, GotoStatement) and
isinstance(goto_next_statement, DefineLabelStatement) and
cur_statement.location == goto_next_statement.location
):
# We are jumping to a location where we could insert a break.
gotos.add(cur_statement)
elif isinstance(cur_statement, DoWhileStatement):
# We don't want to track gotos into while loops because replacing one of
# these with a break would change the flow.
gotos.update(find_gotos(cur_statement.body, next_statement, NopStatement()))
elif isinstance(cur_statement, IfStatement):
gotos.update(find_gotos(cur_statement.true_statements, next_statement, goto_next_statement))
gotos.update(find_gotos(cur_statement.false_statements, next_statement, goto_next_statement))
elif isinstance(cur_statement, SwitchStatement):
# The next entry after this switch is what we're interested in, so pass it
# as the goto next statement. This is the only reason we need to track this.
# We don't care about the semantic next statement for the purposes of this
# call, so just set it as a NOP.
for case in cur_statement.cases:
gotos.update(find_gotos(case.statements, NopStatement(), next_statement))
return gotos
# Instead of an empty next statement, make up a return because that's what
# falling off the end of execution means.
gotos = find_gotos(statements, NullReturnStatement(), NullReturnStatement())
updated: bool = False
def remove_gotos(statement: Statement) -> Optional[Statement]:
nonlocal updated
for removable in gotos:
if removable is statement:
updated = True
return BreakStatement()
return statement
statements = self.__walk(statements, remove_gotos)
return statements, updated
def _optimize_code(self, statements: Sequence[Statement]) -> List[Statement]:
statements = list(statements)
@ -3848,6 +3950,7 @@ class ByteCodeDecompiler(VerboseOutput):
funcs = [
self.__collapse_identical_labels,
self.__eliminate_useless_continues,
self.__eliminate_useless_breaks,
self.__eliminate_unused_labels,
self.__remove_useless_gotos,
self.__remove_goto_return,
@ -3859,6 +3962,7 @@ class ByteCodeDecompiler(VerboseOutput):
self.__swap_ugly_ifexprs,
self.__rearrange_compound_ifs,
self.__convert_switches,
self.__convert_switch_gotos,
]
else:
# These are required for some sanity checks to pass.