ifelsestmt.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281
  1. # Copyright (c) 2020, 2022-2023 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. # When we use dominators, presumbaly this will be a lto cleaner
  16. def ifelsestmt(
  17. self, lhs: str, n: int, rule, tree, tokens: list, first: int, last: int
  18. ) -> bool:
  19. if (last + 1) < n and tokens[last + 1] == "COME_FROM_LOOP":
  20. # ifelsestmt jumped outside of loop. No good.
  21. return True
  22. # print("XXX", first, last, rule)
  23. # for t in range(first, last): print(tokens[t])
  24. # print("="*40)
  25. first_offset = tokens[first].off2int()
  26. last_offset = tokens[last].off2int(prefer_last=False)
  27. # FIXME: It is conceivable the below could be handled strictly in the grammar.
  28. # If we have an optional else, then we *must* have a COME_FROM for it.
  29. # Otherwise this is fine as an "if" without the "else"
  30. if rule[1][2] == "jf_cfs":
  31. jf_cfs = tree[2]
  32. jump = jf_cfs[0]
  33. # The jf_cfs should jump to the end of the ifelse, and not beyond it.
  34. # Think about: should we also check that it isn't to the
  35. # *interior* of the "else" part?
  36. jump = jf_cfs[0]
  37. if jump == "JUMP_FORWARD" and jump.attr > last_offset:
  38. # There is one situation where jf_cfs does not jump to the end of ifelse,
  39. # and that when this is inside *another* ifelse:
  40. # if x:
  41. # if y:
  42. # ...
  43. # else:
  44. # jumps to the end of the *enclosing* ifelse
  45. # else
  46. # ...
  47. #
  48. # We can detect this because there is a JUMP_FORWARD a the end of the ifelese
  49. # that also jumps to the same location
  50. if not (tokens[last] == "JUMP_FORWARD" and tokens[last].attr == jump.attr):
  51. return True
  52. if rule[1][2:4] == ("jf_cfs", "\\e_else_suite_opt"):
  53. come_froms = jf_cfs[1]
  54. if isinstance(come_froms, Token):
  55. come_from_target = come_froms.attr
  56. else:
  57. if len(come_froms) == 0:
  58. # We are seeing in optional else's:
  59. # XX JUMP_FORWARD XX+2
  60. # XX+2_00 COME_FROM XX
  61. # and these aren't caught by our "if/then" rules
  62. return tokens[last].off2int() != jf_cfs[0].attr
  63. come_from_target = come_froms[-1].attr
  64. if come_from_target < first_offset:
  65. return True
  66. # Make sure all the offsets from the "COME_FROMs" at the
  67. # end of the "if" come from somewhere inside the "if".
  68. # Since the come_froms are ordered so that lowest
  69. # offset COME_FROM is last, it is sufficient to test
  70. # just the last one.
  71. # Note: We may find Example A defeats this rule.
  72. if len(tree) == 5:
  73. end_come_froms = tree[-1]
  74. if end_come_froms == "opt_come_from_except" and len(end_come_froms) > 0:
  75. end_come_froms = end_come_froms[0]
  76. pass
  77. while not isinstance(end_come_froms, Token) and len(end_come_froms):
  78. end_come_froms = end_come_froms[-1]
  79. if isinstance(end_come_froms, Token):
  80. if first_offset > end_come_froms.attr:
  81. return True
  82. elif first_offset > end_come_froms.attr:
  83. return True
  84. testexpr = tree[0]
  85. if_condition = testexpr[0]
  86. # Check that the condition portion of the "if"
  87. # jumps to the "else" part, and that the
  88. # end of the "then" portion jumps to a reasonable
  89. # place, e.g. not somewhere in the middle of the "else"
  90. # portion.
  91. if if_condition in ("testtrue", "testfalse", "and_cond"):
  92. then_end = tree[2]
  93. else_suite = tree[3]
  94. if else_suite == "else_suite_opt" and len(else_suite):
  95. else_suite = else_suite[0]
  96. if else_suite not in ("else_suite", "else_suitec"):
  97. # May need to handle later.
  98. return False
  99. # We may need this later:
  100. # not_or = if_condition[0]
  101. # if not_or == "not_or":
  102. # # "if not_or" needs special attention to distinguish it from "if and".
  103. # # If the jump is to the beginning of the "else" part, this is an "and".
  104. # not_or_jump_expr = not_or[-1]
  105. # if not_or_jump_expr == "_come_froms":
  106. # not_or_jump_expr = not_or[-2]
  107. # not_or_jump_offset = not_or_jump_expr.last_child().attr
  108. # if not_or_jump_offset == else_suite.first_child().offset:
  109. # return True
  110. # If there is a COME_FROM at the end, it (the outermost COME_FROM) needs to come
  111. # from within the "if-then" part
  112. if isinstance(then_end, Token):
  113. then_end_come_from = then_end
  114. else:
  115. then_end_come_from = then_end.last_child()
  116. if then_end_come_from == "COME_FROM" and then_end_come_from.attr < first_offset:
  117. return True
  118. # If there any instructions in the "then" part that jump to the beginning of the
  119. # "else" then this is not a proper if/else. Note that we might generalize this
  120. # to jump *anywhere* in the else body instead of the first instruction.
  121. else_start_offset = else_suite.first_child().off2int(prefer_last=False)
  122. then_start = tree[1].first_child()
  123. if then_start is None:
  124. return False
  125. then_start_offset = tree[1].first_child().off2int(prefer_last=False)
  126. i = self.offset2inst_index[then_start_offset]
  127. inst = self.insts[i]
  128. while inst.offset < else_start_offset:
  129. if inst.is_jump() and inst.argval == else_start_offset:
  130. return True
  131. i += 1
  132. inst = self.insts[i]
  133. if last_offset == -1:
  134. last_offset = tokens[last - 1].off2int(prefer_last=False)
  135. if else_suite == "else_suitec" and then_end in (
  136. "jb_elsec",
  137. "jb_cfs",
  138. "jump_forward_else",
  139. ):
  140. stmts = tree[1]
  141. jb_else = then_end
  142. come_from = jb_else[-1]
  143. if come_from in ("come_froms", "_come_froms") and len(come_from):
  144. come_from = come_from[-1]
  145. if come_from == "COME_FROM":
  146. if come_from.attr > stmts.first_child().off2int():
  147. return True
  148. pass
  149. pass
  150. elif else_suite == "else_suite" and then_end == "jf_cfs":
  151. stmts = tree[1]
  152. jf_cfs = then_end
  153. if jf_cfs[0].attr < last_offset:
  154. return True
  155. if if_condition == "and_cond" and if_condition[1] == "expr_pjif":
  156. if_condition = if_condition[1]
  157. if_condition_last = if_condition.last_child()
  158. if if_condition_last.kind.startswith("POP_JUMP_IF_"):
  159. if last == n:
  160. last -= 1
  161. jmp = if_condition_last
  162. jump_target = jmp.attr
  163. # Below we check that jump_target is jumping to a feasible
  164. # location. It should be to the transition after the "then"
  165. # block and to the beginning of the "else" block.
  166. # However the "if/else" is inside a loop the false test can be
  167. # back to the loop.
  168. # FIXME: the below logic for jf_cfs could probably be
  169. # simplified.
  170. jump_else_end = tree[2]
  171. if jump_else_end == "jf_cf_pop":
  172. jump_else_end = jump_else_end[0]
  173. # jump_to_jump = False
  174. if jump_else_end == "JUMP_FORWARD":
  175. # jump_to_jump = True
  176. endif_target = int(jump_else_end.attr)
  177. if endif_target != last_offset:
  178. return True
  179. if jump_target == last_offset:
  180. # jump_target should be jumping to the end of the if/then/else
  181. # but is it jumping to the beginning of the "else"
  182. return True
  183. if (
  184. jump_else_end in ("jf_cfs", "jump_forward_else")
  185. and jump_else_end[0] == "JUMP_FORWARD"
  186. ):
  187. # If the "else" jump jumps before the end of the the "if .. else end",
  188. # then this is not this kind of "ifelsestmt".
  189. jump_else_forward = jump_else_end[0]
  190. jump_else_forward_target = jump_else_forward.attr
  191. if jump_else_forward_target < last_offset:
  192. return True
  193. pass
  194. if (
  195. jump_else_end in ("jf_cfs", "come_froms")
  196. and jump_else_end[-1] == "COME_FROM"
  197. ):
  198. if jump_else_end[-1].off2int() != jump_target:
  199. return True
  200. # If the end of the "then" jumps to back to a loop,
  201. # then the end of the "else" must jump somewhere too
  202. # and not fall through.
  203. if jump_else_end == "jb_cfs":
  204. i = self.offset2inst_index[last_offset]
  205. inst = self.insts[i]
  206. if not inst.is_jump():
  207. return True
  208. pass
  209. pass
  210. # If we have a jump_back, i.e we are in a loop, then a "end_then" of
  211. # the "else" can't be a fallthrough kind of instruction. In other
  212. # words, tokens[last] should have be a COME_FROM. Otherwise the
  213. # "else" suite should be extended to cover the next instruction at
  214. # tokens[last].
  215. if jump_else_end in ("jb_elsec", "jb_cfs") and tokens[last] not in (
  216. "COME_FROM",
  217. "JUMP_LOOP",
  218. "COME_FROM_LOOP",
  219. ):
  220. return True
  221. # If the part before the "else" statement doesn't have a JUMP in it,
  222. # i.e. is a "COME_FROM", then the statement before he COME_FROM should
  223. # not fallthrough. Otherwise we have an "if" statement, not "if/else".
  224. # if lhs == "ifelsestmtc":
  225. # print("XXX", first, last, tokens[first], tokens[last])
  226. # from trepan.api import debug; debug()
  227. if jump_else_end == "come_froms":
  228. jump_else_end = jump_else_end.last_child()
  229. if jump_else_end == "COME_FROM":
  230. come_from_offset = jump_else_end.off2int(prefer_last=False)
  231. before_come_from = self.insts[
  232. self.offset2inst_index[come_from_offset] - 1
  233. ]
  234. # FIXME: When xdis next changes, this will be a field in the instruction
  235. no_follow = before_come_from.opcode in self.opc.nofollow
  236. return not (before_come_from.is_jump() or no_follow)
  237. if first_offset > jump_target:
  238. return True
  239. return (jump_target > last_offset) and tokens[last] != "JUMP_FORWARD"
  240. return False