iflaststmt.py 8.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200
  1. # Copyright (c) 2020, 2022 Rocky Bernstein
  2. # This program is free software: you can redistribute it and/or modify
  3. # it under the terms of the GNU General Public License as published by
  4. # the Free Software Foundation, either version 3 of the License, or
  5. # (at your option) any later version.
  6. #
  7. # This program is distributed in the hope that it will be useful,
  8. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  9. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  10. # GNU General Public License for more details.
  11. #
  12. # You should have received a copy of the GNU General Public License
  13. # along with this program. If not, see <http://www.gnu.org/licenses/>.
  14. from decompyle3.scanners.tok import Token
  15. def iflaststmt(
  16. self, lhs: str, n: int, rule, tree, tokens: list, first: int, last: int
  17. ) -> bool:
  18. testexpr = tree[0]
  19. rhs = rule[1]
  20. # print("XXX", first, last, rule)
  21. # for t in range(first, last): print(tokens[t])
  22. # print("="*40)
  23. # FIXME: should this be done in the caller?
  24. if tokens[last] == "RETURN_LAST":
  25. last -= 1
  26. # If there is a fall-through it shouldn't be somewhere
  27. # inside iflaststmt, since the point of this is to handle
  28. # if statements that *don't* fall though.
  29. if tokens[last] == "COME_FROM":
  30. come_from_offset = tokens[last].attr
  31. if tokens[first].off2int() <= come_from_offset <= tokens[last].off2int():
  32. return True
  33. if rhs[0:2] in (
  34. ("testexpr", "stmts"),
  35. ("testexpr", "c_stmts"),
  36. ("testexprc", "c_stmts"),
  37. ):
  38. # "stmts" (end of then) should not end in a fallthrough instruction
  39. # other wise this is just a plain ol' stmt.
  40. ltm1 = tokens[last - 1]
  41. if ltm1 == "COME_FROM":
  42. return True
  43. then_end = self.off2inst(ltm1)
  44. # FIXME: fallthrough should be an xdis thing. Until then...
  45. if then_end.opcode not in self.opc.nofollow and tokens[last] != "JUMP_LOOP":
  46. return True
  47. # If there is a trailing if-jump (forward) at the end of "testexp", it should
  48. # to the end of "stmts".
  49. # If there was backward jump, the LHS would be "iflaststmtc".
  50. # Note that there might not be a COME_FROM before "stmts" because there can be a fall
  51. # through to it.
  52. stmt_offset = tree[1].first_child().off2int(prefer_last=False)
  53. inst_offset = self.offset2inst_index[stmt_offset]
  54. test_expr_offset = tree[0].first_child().off2int(prefer_last=False)
  55. test_inst_offset = self.offset2inst_index[test_expr_offset]
  56. last_offset = tokens[last].off2int(prefer_last=False)
  57. # Make sure there are *forward* jumps outside offset range of this construct.
  58. # This helps distinguish:
  59. # while True:
  60. # if testexpr
  61. # from:
  62. # while testexpr
  63. for i in range(test_inst_offset, inst_offset):
  64. inst = self.insts[i]
  65. if inst.is_jump() and inst.argval > last_offset:
  66. return True
  67. testexpr_last_inst = self.insts[inst_offset - 1]
  68. if testexpr_last_inst.is_jump():
  69. target_offset = testexpr_last_inst.argval
  70. if target_offset != last_offset:
  71. if target_offset < last_offset:
  72. # Look for this kind of situation from: iflaststmtc ::= testexprc c_stmts
  73. #
  74. # L. 13 78 LOAD_NAME ncols
  75. # 80 POP_JUMP_IF_FALSE_LOOP 40 'to 40'
  76. #
  77. # L. 14 82 POP_TOP
  78. # 84 CONTINUE 20 'to 20'
  79. # ^^^^^^^^^
  80. #
  81. # L. 15 86 JUMP_LOOP 40 'to 40'
  82. # COME_FROM ...
  83. # ^^^ last
  84. return tokens[last - 2] != "CONTINUE"
  85. # There is still this weird case:
  86. # if a:
  87. # if b:
  88. # x += 3
  89. # # jumps to same place as "if a then.." end jump.
  90. # else:
  91. # ...
  92. # we are going to hack this by looking for another jump to the same target. Sigh.
  93. i = inst_offset
  94. inst = self.insts[i]
  95. while inst.offset < target_offset:
  96. if inst.is_jump() and inst.argval == target_offset:
  97. return False
  98. i += 1
  99. inst = self.insts[i]
  100. pass
  101. last_index = self.offset2inst_index[last_offset]
  102. last_inst = self.insts[last_index]
  103. # Jumping beyond last_offset is okay since this may be the
  104. # inner "if" jumping around the "else" situation above.
  105. if last_inst.is_jump():
  106. return target_offset == last_offset
  107. else:
  108. # A fallthrough can't jump *beyond* the end in the nested
  109. # "if" around and outer "else"
  110. return True
  111. pass
  112. pass
  113. if testexpr[0] == "testexpr":
  114. testexpr = testexpr[0]
  115. if testexpr[0] in ("testtrue", "testtruec", "testfalse", "testfalsec"):
  116. if_condition = testexpr[0]
  117. if_condition_len = len(if_condition)
  118. if_bool = if_condition[0]
  119. if (
  120. if_condition_len == 1
  121. and if_bool in ("nand", "and")
  122. and rhs == ("testexpr", "stmts")
  123. ):
  124. # (n)and rules have precedence
  125. return True
  126. elif if_bool == "not_or":
  127. then_end = if_bool[-1]
  128. if isinstance(then_end, Token):
  129. then_end_come_from = then_end
  130. else:
  131. then_end_come_from = if_bool[-2].last_child()
  132. # If there jump location is right after then end of this rule, then we have
  133. # an "and", not a "not_or"
  134. if (
  135. then_end_come_from == "POP_JUMP_IF_FALSE"
  136. and then_end_come_from.attr == tokens[last].off2int()
  137. ):
  138. return True
  139. pass
  140. if if_condition_len > 1 and if_condition[1].kind.startswith("POP_JUMP_IF_"):
  141. if last == n:
  142. last -= 1
  143. jump_target = if_condition[1].attr
  144. first_offset = tokens[first].off2int()
  145. if first_offset <= jump_target < tokens[last].off2int():
  146. return True
  147. # jump_target less than tokens[first] is okay - is to a loop
  148. # jump_target equal tokens[last] is also okay: normal non-optimized non-loop jump
  149. if (last + 1) < n:
  150. if tokens[last - 1] == "JUMP_LOOP":
  151. if jump_target > first_offset:
  152. # The end of the iflaststmt if test jumps backward to a loop
  153. # but the false branch of the "if" doesn't also jump back.
  154. # No good. This is probably an if/else instead.
  155. return True
  156. pass
  157. elif (
  158. tokens[last + 1] == "COME_FROM_LOOP"
  159. and tokens[last] != "BREAK_LOOP"
  160. ):
  161. # iflastsmtc is not at the end of a loop, but jumped outside of loop. No good.
  162. # FIXME: check that tokens[last] == "POP_BLOCK"? Or allow for it not to appear?
  163. return True
  164. # If the instruction before "first" is a "POP_JUMP_IF_FALSE" which goes
  165. # to the same target as jump_target, then this not nested "if .. if .."
  166. # but rather "if ... and ..."
  167. if first > 0 and tokens[first - 1] == "POP_JUMP_IF_FALSE":
  168. return tokens[first - 1].attr == jump_target
  169. if jump_target > tokens[last].off2int():
  170. if jump_target == tokens[last - 1].attr:
  171. # if c1 [jump] jumps exactly the end of the iflaststmt...
  172. return False
  173. pass
  174. pass
  175. pass
  176. return False