| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281 |
- # Copyright (c) 2020, 2022-2023 Rocky Bernstein
- # This program is free software: you can redistribute it and/or modify
- # it under the terms of the GNU General Public License as published by
- # the Free Software Foundation, either version 3 of the License, or
- # (at your option) any later version.
- #
- # This program is distributed in the hope that it will be useful,
- # but WITHOUT ANY WARRANTY; without even the implied warranty of
- # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- # GNU General Public License for more details.
- #
- # You should have received a copy of the GNU General Public License
- # along with this program. If not, see <http://www.gnu.org/licenses/>.
- from decompyle3.scanners.tok import Token
- # When we use dominators, presumbaly this will be a lto cleaner
- def ifelsestmt(
- self, lhs: str, n: int, rule, tree, tokens: list, first: int, last: int
- ) -> bool:
- if (last + 1) < n and tokens[last + 1] == "COME_FROM_LOOP":
- # ifelsestmt jumped outside of loop. No good.
- return True
- # print("XXX", first, last, rule)
- # for t in range(first, last): print(tokens[t])
- # print("="*40)
- first_offset = tokens[first].off2int()
- last_offset = tokens[last].off2int(prefer_last=False)
- # FIXME: It is conceivable the below could be handled strictly in the grammar.
- # If we have an optional else, then we *must* have a COME_FROM for it.
- # Otherwise this is fine as an "if" without the "else"
- if rule[1][2] == "jf_cfs":
- jf_cfs = tree[2]
- jump = jf_cfs[0]
- # The jf_cfs should jump to the end of the ifelse, and not beyond it.
- # Think about: should we also check that it isn't to the
- # *interior* of the "else" part?
- jump = jf_cfs[0]
- if jump == "JUMP_FORWARD" and jump.attr > last_offset:
- # There is one situation where jf_cfs does not jump to the end of ifelse,
- # and that when this is inside *another* ifelse:
- # if x:
- # if y:
- # ...
- # else:
- # jumps to the end of the *enclosing* ifelse
- # else
- # ...
- #
- # We can detect this because there is a JUMP_FORWARD a the end of the ifelese
- # that also jumps to the same location
- if not (tokens[last] == "JUMP_FORWARD" and tokens[last].attr == jump.attr):
- return True
- if rule[1][2:4] == ("jf_cfs", "\\e_else_suite_opt"):
- come_froms = jf_cfs[1]
- if isinstance(come_froms, Token):
- come_from_target = come_froms.attr
- else:
- if len(come_froms) == 0:
- # We are seeing in optional else's:
- # XX JUMP_FORWARD XX+2
- # XX+2_00 COME_FROM XX
- # and these aren't caught by our "if/then" rules
- return tokens[last].off2int() != jf_cfs[0].attr
- come_from_target = come_froms[-1].attr
- if come_from_target < first_offset:
- return True
- # Make sure all the offsets from the "COME_FROMs" at the
- # end of the "if" come from somewhere inside the "if".
- # Since the come_froms are ordered so that lowest
- # offset COME_FROM is last, it is sufficient to test
- # just the last one.
- # Note: We may find Example A defeats this rule.
- if len(tree) == 5:
- end_come_froms = tree[-1]
- if end_come_froms == "opt_come_from_except" and len(end_come_froms) > 0:
- end_come_froms = end_come_froms[0]
- pass
- while not isinstance(end_come_froms, Token) and len(end_come_froms):
- end_come_froms = end_come_froms[-1]
- if isinstance(end_come_froms, Token):
- if first_offset > end_come_froms.attr:
- return True
- elif first_offset > end_come_froms.attr:
- return True
- testexpr = tree[0]
- if_condition = testexpr[0]
- # Check that the condition portion of the "if"
- # jumps to the "else" part, and that the
- # end of the "then" portion jumps to a reasonable
- # place, e.g. not somewhere in the middle of the "else"
- # portion.
- if if_condition in ("testtrue", "testfalse", "and_cond"):
- then_end = tree[2]
- else_suite = tree[3]
- if else_suite == "else_suite_opt" and len(else_suite):
- else_suite = else_suite[0]
- if else_suite not in ("else_suite", "else_suitec"):
- # May need to handle later.
- return False
- # We may need this later:
- # not_or = if_condition[0]
- # if not_or == "not_or":
- # # "if not_or" needs special attention to distinguish it from "if and".
- # # If the jump is to the beginning of the "else" part, this is an "and".
- # not_or_jump_expr = not_or[-1]
- # if not_or_jump_expr == "_come_froms":
- # not_or_jump_expr = not_or[-2]
- # not_or_jump_offset = not_or_jump_expr.last_child().attr
- # if not_or_jump_offset == else_suite.first_child().offset:
- # return True
- # If there is a COME_FROM at the end, it (the outermost COME_FROM) needs to come
- # from within the "if-then" part
- if isinstance(then_end, Token):
- then_end_come_from = then_end
- else:
- then_end_come_from = then_end.last_child()
- if then_end_come_from == "COME_FROM" and then_end_come_from.attr < first_offset:
- return True
- # If there any instructions in the "then" part that jump to the beginning of the
- # "else" then this is not a proper if/else. Note that we might generalize this
- # to jump *anywhere* in the else body instead of the first instruction.
- else_start_offset = else_suite.first_child().off2int(prefer_last=False)
- then_start = tree[1].first_child()
- if then_start is None:
- return False
- then_start_offset = tree[1].first_child().off2int(prefer_last=False)
- i = self.offset2inst_index[then_start_offset]
- inst = self.insts[i]
- while inst.offset < else_start_offset:
- if inst.is_jump() and inst.argval == else_start_offset:
- return True
- i += 1
- inst = self.insts[i]
- if last_offset == -1:
- last_offset = tokens[last - 1].off2int(prefer_last=False)
- if else_suite == "else_suitec" and then_end in (
- "jb_elsec",
- "jb_cfs",
- "jump_forward_else",
- ):
- stmts = tree[1]
- jb_else = then_end
- come_from = jb_else[-1]
- if come_from in ("come_froms", "_come_froms") and len(come_from):
- come_from = come_from[-1]
- if come_from == "COME_FROM":
- if come_from.attr > stmts.first_child().off2int():
- return True
- pass
- pass
- elif else_suite == "else_suite" and then_end == "jf_cfs":
- stmts = tree[1]
- jf_cfs = then_end
- if jf_cfs[0].attr < last_offset:
- return True
- if if_condition == "and_cond" and if_condition[1] == "expr_pjif":
- if_condition = if_condition[1]
- if_condition_last = if_condition.last_child()
- if if_condition_last.kind.startswith("POP_JUMP_IF_"):
- if last == n:
- last -= 1
- jmp = if_condition_last
- jump_target = jmp.attr
- # Below we check that jump_target is jumping to a feasible
- # location. It should be to the transition after the "then"
- # block and to the beginning of the "else" block.
- # However the "if/else" is inside a loop the false test can be
- # back to the loop.
- # FIXME: the below logic for jf_cfs could probably be
- # simplified.
- jump_else_end = tree[2]
- if jump_else_end == "jf_cf_pop":
- jump_else_end = jump_else_end[0]
- # jump_to_jump = False
- if jump_else_end == "JUMP_FORWARD":
- # jump_to_jump = True
- endif_target = int(jump_else_end.attr)
- if endif_target != last_offset:
- return True
- if jump_target == last_offset:
- # jump_target should be jumping to the end of the if/then/else
- # but is it jumping to the beginning of the "else"
- return True
- if (
- jump_else_end in ("jf_cfs", "jump_forward_else")
- and jump_else_end[0] == "JUMP_FORWARD"
- ):
- # If the "else" jump jumps before the end of the the "if .. else end",
- # then this is not this kind of "ifelsestmt".
- jump_else_forward = jump_else_end[0]
- jump_else_forward_target = jump_else_forward.attr
- if jump_else_forward_target < last_offset:
- return True
- pass
- if (
- jump_else_end in ("jf_cfs", "come_froms")
- and jump_else_end[-1] == "COME_FROM"
- ):
- if jump_else_end[-1].off2int() != jump_target:
- return True
- # If the end of the "then" jumps to back to a loop,
- # then the end of the "else" must jump somewhere too
- # and not fall through.
- if jump_else_end == "jb_cfs":
- i = self.offset2inst_index[last_offset]
- inst = self.insts[i]
- if not inst.is_jump():
- return True
- pass
- pass
- # If we have a jump_back, i.e we are in a loop, then a "end_then" of
- # the "else" can't be a fallthrough kind of instruction. In other
- # words, tokens[last] should have be a COME_FROM. Otherwise the
- # "else" suite should be extended to cover the next instruction at
- # tokens[last].
- if jump_else_end in ("jb_elsec", "jb_cfs") and tokens[last] not in (
- "COME_FROM",
- "JUMP_LOOP",
- "COME_FROM_LOOP",
- ):
- return True
- # If the part before the "else" statement doesn't have a JUMP in it,
- # i.e. is a "COME_FROM", then the statement before he COME_FROM should
- # not fallthrough. Otherwise we have an "if" statement, not "if/else".
- # if lhs == "ifelsestmtc":
- # print("XXX", first, last, tokens[first], tokens[last])
- # from trepan.api import debug; debug()
- if jump_else_end == "come_froms":
- jump_else_end = jump_else_end.last_child()
- if jump_else_end == "COME_FROM":
- come_from_offset = jump_else_end.off2int(prefer_last=False)
- before_come_from = self.insts[
- self.offset2inst_index[come_from_offset] - 1
- ]
- # FIXME: When xdis next changes, this will be a field in the instruction
- no_follow = before_come_from.opcode in self.opc.nofollow
- return not (before_come_from.is_jump() or no_follow)
- if first_offset > jump_target:
- return True
- return (jump_target > last_offset) and tokens[last] != "JUMP_FORWARD"
- return False
|