Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/executing/_position_node_finder.py: 9%

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

351 statements  

1import ast 

2import sys 

3import dis 

4from types import CodeType, FrameType 

5from typing import Any, Callable, Iterator, Optional, Sequence, Set, Tuple, Type, Union, cast 

6from .executing import EnhancedAST, NotOneValueFound, Source, only, function_node_types, assert_ 

7from ._exceptions import KnownIssue, VerifierFailure 

8from ._utils import mangled_name 

9 

10from functools import lru_cache 

11import itertools 

12 

13# the code in this module can use all python>=3.11 features 

14 

15 

16def parents(node: EnhancedAST) -> Iterator[EnhancedAST]: 

17 while True: 

18 if hasattr(node, "parent"): 

19 node = node.parent 

20 yield node 

21 else: 

22 break # pragma: no mutate 

23 

24 

25def node_and_parents(node: EnhancedAST) -> Iterator[EnhancedAST]: 

26 yield node 

27 yield from parents(node) 

28 

29 

30@lru_cache(128) # pragma: no mutate 

31def get_instructions(code: CodeType) -> list[dis.Instruction]: 

32 return list(dis.get_instructions(code)) 

33 

34 

35types_cmp_issue_fix = ( 

36 ast.IfExp, 

37 ast.If, 

38 ast.Assert, 

39 ast.While, 

40) 

41 

42types_cmp_issue = types_cmp_issue_fix + ( 

43 ast.ListComp, 

44 ast.SetComp, 

45 ast.DictComp, 

46 ast.GeneratorExp, 

47) 

48 

49op_type_map = { 

50 "**": ast.Pow, 

51 "*": ast.Mult, 

52 "@": ast.MatMult, 

53 "//": ast.FloorDiv, 

54 "/": ast.Div, 

55 "%": ast.Mod, 

56 "+": ast.Add, 

57 "-": ast.Sub, 

58 "<<": ast.LShift, 

59 ">>": ast.RShift, 

60 "&": ast.BitAnd, 

61 "^": ast.BitXor, 

62 "|": ast.BitOr, 

63} 

64 

65 

66class PositionNodeFinder(object): 

67 """ 

68 Mapping bytecode to ast-node based on the source positions, which where introduced in pyhon 3.11. 

69 In general every ast-node can be exactly referenced by its begin/end line/col_offset, which is stored in the bytecode. 

70 There are only some exceptions for methods and attributes. 

71 """ 

72 

73 def __init__(self, frame: FrameType, stmts: Set[EnhancedAST], tree: ast.Module, lasti: int, source: Source): 

74 self.bc_dict={bc.offset:bc for bc in get_instructions(frame.f_code) } 

75 self.frame=frame 

76 

77 self.source = source 

78 self.decorator: Optional[EnhancedAST] = None 

79 

80 # work around for https://github.com/python/cpython/issues/96970 

81 while self.opname(lasti) == "CACHE": 

82 lasti -= 2 

83 

84 try: 

85 # try to map with all match_positions 

86 self.result = self.find_node(lasti) 

87 except NotOneValueFound: 

88 typ: tuple[Type] 

89 # LOAD_METHOD could load "".join for long "..."%(...) BinOps 

90 # this can only be associated by using all positions 

91 if self.opname(lasti) in ( 

92 "LOAD_METHOD", 

93 "LOAD_ATTR", 

94 "STORE_ATTR", 

95 "DELETE_ATTR", 

96 ): 

97 # lineno and col_offset of LOAD_METHOD and *_ATTR instructions get set to the beginning of 

98 # the attribute by the python compiler to improved error messages (PEP-657) 

99 # we ignore here the start position and try to find the ast-node just by end position and expected node type 

100 # This is save, because there can only be one attribute ending at a specific point in the source code. 

101 typ = (ast.Attribute,) 

102 elif self.opname(lasti) in ("CALL", "CALL_KW"): 

103 # A CALL instruction can be a method call, in which case the lineno and col_offset gets changed by the compiler. 

104 # Therefore we ignoring here this attributes and searchnig for a Call-node only by end_col_offset and end_lineno. 

105 # This is save, because there can only be one method ending at a specific point in the source code. 

106 # One closing ) only belongs to one method. 

107 typ = (ast.Call,) 

108 else: 

109 raise 

110 

111 self.result = self.find_node( 

112 lasti, 

113 match_positions=("end_col_offset", "end_lineno"), 

114 typ=typ, 

115 ) 

116 

117 instruction = self.instruction(lasti) 

118 assert instruction is not None 

119 

120 self.result = self.fix_result(self.result, instruction) 

121 

122 self.known_issues(self.result, instruction) 

123 

124 self.test_for_decorator(self.result, lasti) 

125 

126 # verify 

127 if self.decorator is None: 

128 self.verify(self.result, instruction) 

129 else: 

130 assert_(self.decorator in self.result.decorator_list) 

131 

132 def test_for_decorator(self, node: EnhancedAST, index: int) -> None: 

133 if ( 

134 isinstance(node.parent, (ast.ClassDef, function_node_types)) 

135 and node in node.parent.decorator_list # type: ignore[attr-defined] 

136 ): 

137 node_func = node.parent 

138 

139 while True: 

140 # the generated bytecode looks like follow: 

141 

142 # index opname 

143 # ------------------ 

144 # index-4 PRECALL (only in 3.11) 

145 # index-2 CACHE 

146 # index CALL <- the call instruction 

147 # ... CACHE some CACHE instructions 

148 

149 # maybe multiple other bytecode blocks for other decorators 

150 # index-4 PRECALL (only in 3.11) 

151 # index-2 CACHE 

152 # index CALL <- index of the next loop 

153 # ... CACHE some CACHE instructions 

154 

155 # index+x STORE_* the ast-node of this instruction points to the decorated thing 

156 

157 if not ( 

158 (self.opname(index - 4) == "PRECALL" or sys.version_info >= (3, 12)) 

159 and self.opname(index) == "CALL" 

160 ): # pragma: no mutate 

161 break # pragma: no mutate 

162 

163 index += 2 

164 

165 while self.opname(index) in ("CACHE", "EXTENDED_ARG"): 

166 index += 2 

167 

168 if ( 

169 self.opname(index).startswith("STORE_") 

170 and self.find_node(index) == node_func 

171 ): 

172 self.result = node_func 

173 self.decorator = node 

174 return 

175 

176 if sys.version_info < (3, 12): 

177 index += 4 

178 

179 def fix_result( 

180 self, node: EnhancedAST, instruction: dis.Instruction 

181 ) -> EnhancedAST: 

182 if ( 

183 sys.version_info >= (3, 12, 5) 

184 and instruction.opname in ("GET_ITER", "FOR_ITER") 

185 and isinstance(node.parent, ast.For) 

186 and node is node.parent.iter 

187 ): 

188 # node positions have changed in 3.12.5 

189 # https://github.com/python/cpython/issues/93691 

190 # `for` calls __iter__ and __next__ during execution, the calling 

191 # expression of these calls was the ast.For node since cpython 3.11 (see test_iter). 

192 # cpython 3.12.5 changed this to the `iter` node of the loop, to make tracebacks easier to read. 

193 # This keeps backward compatibility with older executing versions. 

194 

195 # there are also cases like: 

196 # 

197 # for a in iter(l): pass 

198 # 

199 # where `iter(l)` would be otherwise the resulting node for the `iter()` call and the __iter__ call of the for implementation. 

200 # keeping the old behaviour makes it possible to distinguish both cases. 

201 

202 return node.parent 

203 

204 if ( 

205 sys.version_info >= (3, 12, 6) 

206 and instruction.opname in ("GET_ITER", "FOR_ITER") 

207 and isinstance( 

208 node.parent.parent, 

209 (ast.ListComp, ast.SetComp, ast.DictComp, ast.GeneratorExp), 

210 ) 

211 and isinstance(node.parent,ast.comprehension) 

212 and node is node.parent.iter 

213 ): 

214 # same as above but only for comprehensions, see: 

215 # https://github.com/python/cpython/issues/123142 

216 

217 return node.parent.parent 

218 

219 if sys.version_info >= (3, 12,6) and instruction.opname == "CALL": 

220 before = self.instruction_before(instruction) 

221 if ( 

222 before is not None 

223 and before.opname == "LOAD_CONST" 

224 and before.positions == instruction.positions 

225 and isinstance(node.parent, ast.withitem) 

226 and node is node.parent.context_expr 

227 ): 

228 # node positions for with-statements have change 

229 # and is now equal to the expression which created the context-manager 

230 # https://github.com/python/cpython/pull/120763 

231 

232 # with context_manager: 

233 # ... 

234 

235 # but there is one problem to distinguish call-expressions from __exit__() 

236 

237 # with context_manager(): 

238 # ... 

239 

240 # the call for __exit__ 

241 

242 # 20 1:5 1:22 LOAD_CONST(None) 

243 # 22 1:5 1:22 LOAD_CONST(None) 

244 # 24 1:5 1:22 LOAD_CONST(None) 

245 # 26 1:5 1:22 CALL() # <-- same source range as context_manager() 

246 

247 # but we can use the fact that the previous load for None 

248 # has the same source range as the call, wich can not happen for normal calls 

249 

250 # we return the same ast.With statement at the and to preserve backward compatibility 

251 

252 return node.parent.parent 

253 

254 if ( 

255 sys.version_info >= (3, 12,6) 

256 and instruction.opname == "BEFORE_WITH" 

257 and isinstance(node.parent, ast.withitem) 

258 and node is node.parent.context_expr 

259 ): 

260 # handle positions changes for __enter__ 

261 return node.parent.parent 

262 

263 if sys.version_info >= (3, 14) and instruction.opname == "CALL": 

264 before = self.instruction_before(instruction) 

265 if ( 

266 before is not None 

267 and before.opname == "LOAD_SPECIAL" 

268 and before.argrepr in ("__enter__","__aenter__") 

269 and before.positions == instruction.positions 

270 and isinstance(node.parent, ast.withitem) 

271 and node is node.parent.context_expr 

272 ): 

273 return node.parent.parent 

274 

275 if sys.version_info >= (3, 14) and isinstance(node, ast.UnaryOp) and isinstance(node.op,ast.Not) and instruction.opname !="UNARY_NOT": 

276 # fix for https://github.com/python/cpython/issues/137843 

277 return node.operand 

278 

279 

280 return node 

281 

282 def known_issues(self, node: EnhancedAST, instruction: dis.Instruction) -> None: 

283 if instruction.opname in ("COMPARE_OP", "IS_OP", "CONTAINS_OP") and isinstance( 

284 node, types_cmp_issue 

285 ): 

286 if isinstance(node, types_cmp_issue_fix): 

287 # this is a workaround for https://github.com/python/cpython/issues/95921 

288 # we can fix cases with only on comparison inside the test condition 

289 # 

290 # we can not fix cases like: 

291 # if a<b<c and d<e<f: pass 

292 # if (a<b<c)!=d!=e: pass 

293 # because we don't know which comparison caused the problem 

294 

295 comparisons = [ 

296 n 

297 for n in ast.walk(node.test) # type: ignore[attr-defined] 

298 if isinstance(n, ast.Compare) and len(n.ops) > 1 

299 ] 

300 

301 assert_(comparisons, "expected at least one comparison") 

302 

303 if len(comparisons) == 1: 

304 node = self.result = cast(EnhancedAST, comparisons[0]) 

305 else: 

306 raise KnownIssue( 

307 "multiple chain comparison inside %s can not be fixed" % (node) 

308 ) 

309 

310 else: 

311 # Comprehension and generators get not fixed for now. 

312 raise KnownIssue("chain comparison inside %s can not be fixed" % (node)) 

313 

314 if ( 

315 sys.version_info[:3] == (3, 11, 1) 

316 and isinstance(node, ast.Compare) 

317 and instruction.opname == "CALL" 

318 and any(isinstance(n, ast.Assert) for n in node_and_parents(node)) 

319 ): 

320 raise KnownIssue( 

321 "known bug in 3.11.1 https://github.com/python/cpython/issues/95921" 

322 ) 

323 

324 if isinstance(node, ast.Assert): 

325 # pytest assigns the position of the assertion to all expressions of the rewritten assertion. 

326 # All the rewritten expressions get mapped to ast.Assert, which is the wrong ast-node. 

327 # We don't report this wrong result. 

328 raise KnownIssue("assert") 

329 

330 if any(isinstance(n, ast.pattern) for n in node_and_parents(node)): 

331 # TODO: investigate 

332 raise KnownIssue("pattern matching ranges seems to be wrong") 

333 

334 if ( 

335 sys.version_info >= (3, 12) 

336 and isinstance(node, ast.Call) 

337 and isinstance(node.func, ast.Name) 

338 and node.func.id == "super" 

339 ): 

340 # super is optimized to some instructions which do not map nicely to a Call 

341 

342 # find the enclosing function 

343 func = node.parent 

344 while hasattr(func, "parent") and not isinstance( 

345 func, (ast.AsyncFunctionDef, ast.FunctionDef) 

346 ): 

347 

348 func = func.parent 

349 

350 # get the first function argument (self/cls) 

351 first_arg = None 

352 

353 if hasattr(func, "args"): 

354 args = [*func.args.posonlyargs, *func.args.args] 

355 if args: 

356 first_arg = args[0].arg 

357 

358 if (instruction.opname, instruction.argval) in [ 

359 ("LOAD_DEREF", "__class__"), 

360 ("LOAD_FAST", first_arg), 

361 ("LOAD_FAST_BORROW", first_arg), 

362 ("LOAD_DEREF", first_arg), 

363 ]: 

364 raise KnownIssue("super optimization") 

365 

366 if self.is_except_cleanup(instruction, node): 

367 raise KnownIssue("exeption cleanup does not belong to the last node in a except block") 

368 

369 if instruction.opname == "STORE_NAME" and instruction.argval == "__classcell__": 

370 # handle stores to __classcell__ as KnownIssue, 

371 # because they get complicated if they are used in `if` or `for` loops 

372 # example: 

373 # 

374 # class X: 

375 # # ... something 

376 # if some_condition: 

377 # def method(self): 

378 # super() 

379 # 

380 # The `STORE_NAME` instruction gets mapped to the `ast.If` node, 

381 # because it is the last element in the class. 

382 # This last element could be anything and gets dificult to verify. 

383 

384 raise KnownIssue("store __classcell__") 

385 

386 if ( 

387 instruction.opname == "CALL" 

388 and not isinstance(node,ast.Call) 

389 and any(isinstance(p, ast.Assert) for p in parents(node)) 

390 and sys.version_info >= (3, 11, 2) 

391 ): 

392 raise KnownIssue("exception generation maps to condition") 

393 

394 if sys.version_info >= (3, 13): 

395 if instruction.opname in ( 

396 "STORE_FAST_STORE_FAST", 

397 "STORE_FAST_LOAD_FAST", 

398 "LOAD_FAST_LOAD_FAST", 

399 ): 

400 raise KnownIssue(f"can not map {instruction.opname} to two ast nodes") 

401 

402 if instruction.opname in ("LOAD_FAST","LOAD_FAST_BORROW") and instruction.argval == "__class__": 

403 # example: 

404 # class T: 

405 # def a(): 

406 # super() 

407 # some_node # <- there is a LOAD_FAST for this node because we use super() 

408 

409 raise KnownIssue( 

410 f"loading of __class__ is accociated with a random node at the end of a class if you use super()" 

411 ) 

412 

413 if ( 

414 instruction.opname == "COMPARE_OP" 

415 and isinstance(node, ast.UnaryOp) 

416 and isinstance(node.operand,ast.Compare) 

417 and isinstance(node.op, ast.Not) 

418 ): 

419 # work around for  

420 # https://github.com/python/cpython/issues/114671 

421 self.result = node.operand 

422 

423 if sys.version_info >= (3,14): 

424 

425 

426 if header_length := self.annotation_header_size(): 

427 

428 last_offset=list(self.bc_dict.keys())[-1] 

429 if ( 

430 not (header_length*2 < instruction.offset <last_offset-4) 

431 ): 

432 # https://github.com/python/cpython/issues/135700 

433 raise KnownIssue("synthetic opcodes in annotations are just bound to the first node") 

434 

435 if self.frame.f_code.co_name=="__annotate__" and instruction.opname=="STORE_SUBSCR": 

436 raise KnownIssue("synthetic code to store annotation") 

437 

438 if self.frame.f_code.co_name=="__annotate__" and isinstance(node,ast.AnnAssign): 

439 raise KnownIssue("some opcodes in the annotation are just bound specific nodes") 

440 

441 if isinstance(node,(ast.TypeAlias)) and self.frame.f_code.co_name==node.name.id : 

442 raise KnownIssue("some opcodes in the annotation are just bound TypeAlias") 

443 

444 if instruction.opname == "STORE_NAME" and instruction.argrepr == "__annotate__": 

445 raise KnownIssue("just a store of the annotation") 

446 

447 if instruction.opname == "IS_OP" and isinstance(node,ast.Name): 

448 raise KnownIssue("part of a check that a name like `all` is a builtin") 

449 

450 

451 

452 def annotation_header_size(self)->int: 

453 if sys.version_info >=(3,14): 

454 header=[inst.opname for inst in itertools.islice(self.bc_dict.values(),8)] 

455 

456 if len(header)==8: 

457 if header[0] in ("COPY_FREE_VARS","MAKE_CELL"): 

458 del header[0] 

459 header_size=8 

460 else: 

461 del header[7] 

462 header_size=7 

463 

464 if header==[ 

465 "RESUME", 

466 "LOAD_FAST_BORROW", 

467 "LOAD_SMALL_INT", 

468 "COMPARE_OP", 

469 "POP_JUMP_IF_FALSE", 

470 "NOT_TAKEN", 

471 "LOAD_COMMON_CONSTANT", 

472 ]: 

473 return header_size 

474 

475 return 0 

476 

477 @staticmethod 

478 def is_except_cleanup(inst: dis.Instruction, node: EnhancedAST) -> bool: 

479 if inst.opname not in ( 

480 "STORE_NAME", 

481 "STORE_FAST", 

482 "STORE_DEREF", 

483 "STORE_GLOBAL", 

484 "DELETE_NAME", 

485 "DELETE_FAST", 

486 "DELETE_DEREF", 

487 "DELETE_GLOBAL", 

488 ): 

489 return False 

490 

491 # This bytecode does something exception cleanup related. 

492 # The position of the instruciton seems to be something in the last ast-node of the ExceptHandler 

493 # this could be a bug, but it might not be observable in normal python code. 

494 

495 # example: 

496 # except Exception as exc: 

497 # enum_member._value_ = value 

498 

499 # other example: 

500 # STORE_FAST of e was mapped to Constant(value=False) 

501 # except OSError as e: 

502 # if not _ignore_error(e): 

503 # raise 

504 # return False 

505 

506 # STORE_FAST of msg was mapped to print(...) 

507 # except TypeError as msg: 

508 # print("Sorry:", msg, file=file) 

509 

510 if ( 

511 isinstance(node, ast.Name) 

512 and isinstance(node.ctx,ast.Store) 

513 and inst.opname.startswith("STORE_") 

514 and mangled_name(node) == inst.argval 

515 ): 

516 # Storing the variable is valid and no exception cleanup, if the name is correct 

517 return False 

518 

519 if ( 

520 isinstance(node, ast.Name) 

521 and isinstance(node.ctx,ast.Del) 

522 and inst.opname.startswith("DELETE_") 

523 and mangled_name(node) == inst.argval 

524 ): 

525 # Deleting the variable is valid and no exception cleanup, if the name is correct 

526 return False 

527 

528 return any( 

529 isinstance(n, ast.ExceptHandler) and n.name and mangled_name(n) == inst.argval 

530 for n in parents(node) 

531 ) 

532 

533 def verify(self, node: EnhancedAST, instruction: dis.Instruction) -> None: 

534 """ 

535 checks if this node could gererate this instruction 

536 """ 

537 

538 op_name = instruction.opname 

539 extra_filter: Callable[[EnhancedAST], bool] = lambda e: True 

540 ctx: Type = type(None) 

541 

542 def inst_match(opnames: Union[str, Sequence[str]], **kwargs: Any) -> bool: 

543 """ 

544 match instruction 

545 

546 Parameters: 

547 opnames: (str|Seq[str]): inst.opname has to be equal to or in `opname` 

548 **kwargs: every arg has to match inst.arg 

549 

550 Returns: 

551 True if all conditions match the instruction 

552 

553 """ 

554 

555 if isinstance(opnames, str): 

556 opnames = [opnames] 

557 return instruction.opname in opnames and kwargs == { 

558 k: getattr(instruction, k) for k in kwargs 

559 } 

560 

561 def node_match(node_type: Union[Type, Tuple[Type, ...]], **kwargs: Any) -> bool: 

562 """ 

563 match the ast-node 

564 

565 Parameters: 

566 node_type: type of the node 

567 **kwargs: every `arg` has to be equal `node.arg` 

568 or `node.arg` has to be an instance of `arg` if it is a type. 

569 """ 

570 return isinstance(node, node_type) and all( 

571 isinstance(getattr(node, k), v) 

572 if isinstance(v, type) 

573 else getattr(node, k) == v 

574 for k, v in kwargs.items() 

575 ) 

576 

577 if op_name == "CACHE": 

578 return 

579 

580 if inst_match("CALL") and node_match((ast.With, ast.AsyncWith)): 

581 # call to context.__exit__ 

582 return 

583 

584 if inst_match(("CALL", "LOAD_FAST","LOAD_FAST_BORROW")) and node_match( 

585 (ast.ListComp, ast.GeneratorExp, ast.SetComp, ast.DictComp) 

586 ): 

587 # call to the generator function 

588 return 

589 

590 if ( 

591 sys.version_info >= (3, 12) 

592 and inst_match(("LOAD_FAST_AND_CLEAR", "STORE_FAST")) 

593 and node_match((ast.ListComp, ast.SetComp, ast.DictComp)) 

594 ): 

595 return 

596 

597 if inst_match(("CALL", "CALL_FUNCTION_EX")) and node_match( 

598 (ast.ClassDef, ast.Call) 

599 ): 

600 return 

601 

602 if inst_match(("COMPARE_OP", "IS_OP", "CONTAINS_OP")) and node_match( 

603 ast.Compare 

604 ): 

605 return 

606 

607 if inst_match("LOAD_NAME", argval="__annotations__") and node_match( 

608 ast.AnnAssign 

609 ): 

610 return 

611 

612 if ( 

613 ( 

614 inst_match("LOAD_METHOD", argval="join") 

615 or inst_match("LOAD_ATTR", argval="join") # 3.12 

616 or inst_match(("CALL", "BUILD_STRING")) 

617 ) 

618 and node_match(ast.BinOp, left=ast.Constant, op=ast.Mod) 

619 and isinstance(cast(ast.Constant, cast(ast.BinOp, node).left).value, str) 

620 ): 

621 # "..."%(...) uses "".join 

622 return 

623 

624 if inst_match("STORE_SUBSCR") and node_match(ast.AnnAssign): 

625 # data: int 

626 return 

627 

628 

629 if inst_match(("DELETE_NAME", "DELETE_FAST")) and node_match( 

630 ast.Name, id=instruction.argval, ctx=ast.Del 

631 ): 

632 return 

633 

634 if inst_match("BUILD_STRING") and ( 

635 node_match(ast.JoinedStr) or node_match(ast.BinOp, op=ast.Mod) 

636 ): 

637 return 

638 

639 if inst_match(("BEFORE_WITH","WITH_EXCEPT_START")) and node_match(ast.With): 

640 return 

641 

642 if inst_match(("STORE_NAME", "STORE_GLOBAL"), argval="__doc__") and node_match( 

643 ast.Constant 

644 ): 

645 # store docstrings 

646 return 

647 

648 if ( 

649 inst_match(("STORE_NAME", "STORE_FAST", "STORE_GLOBAL", "STORE_DEREF")) 

650 and node_match(ast.ExceptHandler) 

651 and instruction.argval == mangled_name(node) 

652 ): 

653 # store exception in variable 

654 return 

655 

656 if ( 

657 inst_match(("STORE_NAME", "STORE_FAST", "STORE_DEREF", "STORE_GLOBAL")) 

658 and node_match((ast.Import, ast.ImportFrom)) 

659 and any(mangled_name(cast(EnhancedAST, alias)) == instruction.argval for alias in cast(ast.Import, node).names) 

660 ): 

661 # store imported module in variable 

662 return 

663 

664 if ( 

665 inst_match(("STORE_FAST", "STORE_DEREF", "STORE_NAME", "STORE_GLOBAL")) 

666 and ( 

667 node_match((ast.FunctionDef, ast.ClassDef, ast.AsyncFunctionDef)) 

668 or node_match( 

669 ast.Name, 

670 ctx=ast.Store, 

671 ) 

672 ) 

673 and instruction.argval == mangled_name(node) 

674 ): 

675 return 

676 

677 if False: 

678 # TODO: match expressions are not supported for now 

679 if inst_match(("STORE_FAST", "STORE_NAME")) and node_match( 

680 ast.MatchAs, name=instruction.argval 

681 ): 

682 return 

683 

684 if inst_match("COMPARE_OP", argval="==") and node_match(ast.MatchSequence): 

685 return 

686 

687 if inst_match("COMPARE_OP", argval="==") and node_match(ast.MatchValue): 

688 return 

689 

690 if inst_match("BINARY_OP"): 

691 arg=instruction.argrepr.removesuffix("=") 

692 

693 if arg!="[]" and node_match( ast.AugAssign, op=op_type_map[arg]): 

694 # a+=5 

695 return 

696 

697 if node_match(ast.Attribute, ctx=ast.Del) and inst_match( 

698 "DELETE_ATTR", argval=mangled_name(node) 

699 ): 

700 return 

701 

702 if inst_match( 

703 ( 

704 "JUMP_IF_TRUE_OR_POP", 

705 "JUMP_IF_FALSE_OR_POP", 

706 "POP_JUMP_IF_TRUE", 

707 "POP_JUMP_IF_FALSE", 

708 ) 

709 ) and node_match(ast.BoolOp): 

710 # and/or short circuit 

711 return 

712 

713 if inst_match("DELETE_SUBSCR") and node_match(ast.Subscript, ctx=ast.Del): 

714 return 

715 

716 if ( 

717 node_match(ast.Name, ctx=ast.Load) 

718 or ( 

719 node_match(ast.Name, ctx=ast.Store) 

720 and isinstance(node.parent, ast.AugAssign) 

721 ) 

722 ) and inst_match( 

723 ( 

724 "LOAD_NAME", 

725 "LOAD_FAST", 

726 "LOAD_FAST_CHECK", 

727 "LOAD_FAST_BORROW", 

728 "LOAD_GLOBAL", 

729 "LOAD_DEREF", 

730 "LOAD_FROM_DICT_OR_DEREF", 

731 "LOAD_FAST_BORROW_LOAD_FAST_BORROW", 

732 ), 

733 ) and ( 

734 mangled_name(node) in instruction.argval if isinstance(instruction.argval,tuple) 

735 else instruction.argval == mangled_name(node) 

736 ): 

737 return 

738 

739 if node_match(ast.Name, ctx=ast.Del) and inst_match( 

740 ("DELETE_NAME", "DELETE_GLOBAL", "DELETE_DEREF"), argval=mangled_name(node) 

741 ): 

742 return 

743 

744 if node_match(ast.Constant) and inst_match( 

745 ("LOAD_CONST","LOAD_SMALL_INT"), argval=cast(ast.Constant, node).value 

746 ): 

747 return 

748 

749 if node_match( 

750 (ast.ListComp, ast.SetComp, ast.DictComp, ast.GeneratorExp, ast.For) 

751 ) and inst_match(("GET_ITER", "FOR_ITER")): 

752 return 

753 

754 if sys.version_info >= (3, 12): 

755 if node_match(ast.UnaryOp, op=ast.UAdd) and inst_match( 

756 "CALL_INTRINSIC_1", argrepr="INTRINSIC_UNARY_POSITIVE" 

757 ): 

758 return 

759 

760 if node_match(ast.Subscript) and inst_match("BINARY_SLICE"): 

761 return 

762 

763 if node_match(ast.ImportFrom) and inst_match( 

764 "CALL_INTRINSIC_1", argrepr="INTRINSIC_IMPORT_STAR" 

765 ): 

766 return 

767 

768 if ( 

769 node_match(ast.Yield) or isinstance(node.parent, ast.GeneratorExp) 

770 ) and inst_match("CALL_INTRINSIC_1", argrepr="INTRINSIC_ASYNC_GEN_WRAP"): 

771 return 

772 

773 if node_match(ast.Name) and inst_match("LOAD_DEREF",argval="__classdict__"): 

774 return 

775 

776 if node_match(ast.TypeVar) and ( 

777 inst_match("CALL_INTRINSIC_1", argrepr="INTRINSIC_TYPEVAR") 

778 or inst_match( 

779 "CALL_INTRINSIC_2", argrepr="INTRINSIC_TYPEVAR_WITH_BOUND" 

780 ) 

781 or inst_match( 

782 "CALL_INTRINSIC_2", argrepr="INTRINSIC_TYPEVAR_WITH_CONSTRAINTS" 

783 ) 

784 or inst_match(("STORE_FAST", "STORE_DEREF"), argrepr=mangled_name(node)) 

785 ): 

786 return 

787 

788 if node_match(ast.TypeVarTuple) and ( 

789 inst_match("CALL_INTRINSIC_1", argrepr="INTRINSIC_TYPEVARTUPLE") 

790 or inst_match(("STORE_FAST", "STORE_DEREF"), argrepr=node.name) 

791 ): 

792 return 

793 

794 if node_match(ast.ParamSpec) and ( 

795 inst_match("CALL_INTRINSIC_1", argrepr="INTRINSIC_PARAMSPEC") 

796 

797 or inst_match(("STORE_FAST", "STORE_DEREF"), argrepr=node.name)): 

798 return 

799 

800 

801 if node_match(ast.TypeAlias): 

802 if( 

803 inst_match("CALL_INTRINSIC_1", argrepr="INTRINSIC_TYPEALIAS") 

804 or inst_match( 

805 ("STORE_NAME", "STORE_FAST", "STORE_DEREF","STORE_GLOBAL"), argrepr=node.name.id 

806 ) 

807 or inst_match("CALL") 

808 ): 

809 return 

810 

811 

812 if node_match(ast.ClassDef) and node.type_params: 

813 if inst_match( 

814 ("STORE_DEREF", "LOAD_DEREF", "LOAD_FROM_DICT_OR_DEREF"), 

815 argrepr=".type_params", 

816 ): 

817 return 

818 

819 if inst_match(("STORE_FAST", "LOAD_FAST"), argrepr=".generic_base"): 

820 return 

821 

822 if inst_match( 

823 "CALL_INTRINSIC_1", argrepr="INTRINSIC_SUBSCRIPT_GENERIC" 

824 ): 

825 return 

826 

827 if inst_match("LOAD_DEREF",argval="__classdict__"): 

828 return 

829 

830 if node_match((ast.FunctionDef,ast.AsyncFunctionDef)) and node.type_params: 

831 if inst_match("CALL"): 

832 return 

833 

834 if inst_match( 

835 "CALL_INTRINSIC_2", argrepr="INTRINSIC_SET_FUNCTION_TYPE_PARAMS" 

836 ): 

837 return 

838 

839 if inst_match("LOAD_FAST",argval=".defaults"): 

840 return 

841 

842 if inst_match("LOAD_FAST",argval=".kwdefaults"): 

843 return 

844 

845 if sys.version_info >= (3, 14): 

846 if inst_match("LOAD_FAST_BORROW_LOAD_FAST_BORROW",argval=(".defaults",".kwdefaults")): 

847 return 

848 

849 if inst_match("STORE_NAME", argval="__classdictcell__"): 

850 # this is a general thing 

851 return 

852 

853 

854 # f-strings 

855 

856 if node_match(ast.JoinedStr) and ( 

857 inst_match("LOAD_ATTR", argval="join") 

858 or inst_match(("LIST_APPEND", "CALL")) 

859 ): 

860 return 

861 

862 if node_match(ast.FormattedValue) and inst_match("FORMAT_VALUE"): 

863 return 

864 

865 if sys.version_info >= (3, 13): 

866 

867 if inst_match("NOP"): 

868 return 

869 

870 if inst_match("TO_BOOL") and node_match(ast.BoolOp): 

871 return 

872 

873 if inst_match("CALL_KW") and node_match((ast.Call, ast.ClassDef)): 

874 return 

875 

876 if inst_match("LOAD_FAST", argval=".type_params"): 

877 return 

878 

879 if inst_match("LOAD_FAST", argval="__classdict__"): 

880 return 

881 

882 if inst_match(("LOAD_FAST","LOAD_FAST_BORROW")) and node_match( 

883 ( 

884 ast.FunctionDef, 

885 ast.ClassDef, 

886 ast.TypeAlias, 

887 ast.TypeVar, 

888 ast.Lambda, 

889 ast.AsyncFunctionDef, 

890 ) 

891 ): 

892 # These are loads for closure variables. 

893 # It is difficult to check that this is actually closure variable, see: 

894 # https://github.com/alexmojaki/executing/pull/80#discussion_r1716027317 

895 return 

896 

897 if ( 

898 inst_match("LOAD_FAST") 

899 and node_match(ast.TypeAlias) 

900 and node.name.id == instruction.argval 

901 ): 

902 return 

903 

904 if inst_match("STORE_NAME",argval="__static_attributes__"): 

905 # the node is the first node in the body 

906 return 

907 

908 if inst_match(("LOAD_FAST","LOAD_FAST_BORROW")) and isinstance(node.parent,ast.TypeVar): 

909 return 

910 

911 if inst_match("CALL_INTRINSIC_2",argrepr="INTRINSIC_SET_TYPEPARAM_DEFAULT") and node_match((ast.TypeVar,ast.ParamSpec,ast.TypeVarTuple)): 

912 return 

913 

914 if sys.version_info >= (3, 14): 

915 if inst_match("BINARY_OP",argrepr="[]") and node_match(ast.Subscript): 

916 return 

917 if inst_match("LOAD_FAST_BORROW", argval="__classdict__"): 

918 return 

919 if inst_match(("STORE_NAME","LOAD_NAME"), argval="__conditional_annotations__"): 

920 return 

921 

922 if inst_match("LOAD_FAST_BORROW_LOAD_FAST_BORROW") and node_match(ast.Name) and node.id in instruction.argval: 

923 return 

924 

925 

926 # old verifier 

927 

928 typ: Type = type(None) 

929 op_type: Type = type(None) 

930 

931 if op_name.startswith(("BINARY_SUBSCR", "SLICE+")): 

932 typ = ast.Subscript 

933 ctx = ast.Load 

934 elif op_name.startswith("BINARY_"): 

935 typ = ast.BinOp 

936 op_type = op_type_map[instruction.argrepr] 

937 extra_filter = lambda e: isinstance(cast(ast.BinOp, e).op, op_type) 

938 elif op_name.startswith("UNARY_"): 

939 typ = ast.UnaryOp 

940 op_type = dict( 

941 UNARY_POSITIVE=ast.UAdd, 

942 UNARY_NEGATIVE=ast.USub, 

943 UNARY_NOT=ast.Not, 

944 UNARY_INVERT=ast.Invert, 

945 )[op_name] 

946 extra_filter = lambda e: isinstance(cast(ast.UnaryOp, e).op, op_type) 

947 elif op_name in ("LOAD_ATTR", "LOAD_METHOD", "LOOKUP_METHOD","LOAD_SUPER_ATTR"): 

948 typ = ast.Attribute 

949 ctx = ast.Load 

950 extra_filter = lambda e: mangled_name(e) == instruction.argval 

951 elif op_name in ( 

952 "LOAD_NAME", 

953 "LOAD_GLOBAL", 

954 "LOAD_FAST", 

955 "LOAD_DEREF", 

956 "LOAD_CLASSDEREF", 

957 ): 

958 typ = ast.Name 

959 ctx = ast.Load 

960 extra_filter = lambda e: cast(ast.Name, e).id == instruction.argval 

961 elif op_name in ("COMPARE_OP", "IS_OP", "CONTAINS_OP"): 

962 typ = ast.Compare 

963 extra_filter = lambda e: len(cast(ast.Compare, e).ops) == 1 

964 elif op_name.startswith(("STORE_SLICE", "STORE_SUBSCR")): 

965 ctx = ast.Store 

966 typ = ast.Subscript 

967 elif op_name.startswith("STORE_ATTR"): 

968 ctx = ast.Store 

969 typ = ast.Attribute 

970 extra_filter = lambda e: mangled_name(e) == instruction.argval 

971 

972 node_ctx = getattr(node, "ctx", None) 

973 

974 ctx_match = ( 

975 ctx is not type(None) 

976 or not hasattr(node, "ctx") 

977 or isinstance(node_ctx, ctx) 

978 ) 

979 

980 # check for old verifier 

981 if isinstance(node, typ) and ctx_match and extra_filter(node): 

982 return 

983 

984 # generate error 

985 

986 title = "ast.%s is not created from %s" % ( 

987 type(node).__name__, 

988 instruction.opname, 

989 ) 

990 

991 raise VerifierFailure(title, node, instruction) 

992 

993 def instruction(self, index: int) -> Optional[dis.Instruction]: 

994 return self.bc_dict.get(index,None) 

995 

996 def instruction_before( 

997 self, instruction: dis.Instruction 

998 ) -> Optional[dis.Instruction]: 

999 return self.bc_dict.get(instruction.offset - 2, None) 

1000 

1001 def opname(self, index: int) -> str: 

1002 i=self.instruction(index) 

1003 if i is None: 

1004 return "CACHE" 

1005 return i.opname 

1006 

1007 extra_node_types=() 

1008 if sys.version_info >= (3,12): 

1009 extra_node_types = (ast.type_param,) 

1010 

1011 def find_node( 

1012 self, 

1013 index: int, 

1014 match_positions: Sequence[str] = ( 

1015 "lineno", 

1016 "end_lineno", 

1017 "col_offset", 

1018 "end_col_offset", 

1019 ), 

1020 typ: tuple[Type, ...] = ( 

1021 ast.expr, 

1022 ast.stmt, 

1023 ast.excepthandler, 

1024 ast.pattern, 

1025 *extra_node_types, 

1026 ), 

1027 ) -> EnhancedAST: 

1028 instruction = self.instruction(index) 

1029 assert instruction is not None 

1030 

1031 position = instruction.positions 

1032 assert position is not None and position.lineno is not None 

1033 

1034 return only( 

1035 cast(EnhancedAST, node) 

1036 for node in self.source._nodes_by_line[position.lineno] 

1037 if isinstance(node, typ) 

1038 if not isinstance(node, ast.Expr) 

1039 # matchvalue.value has the same positions as matchvalue themself, so we exclude ast.MatchValue 

1040 if not isinstance(node, ast.MatchValue) 

1041 if all( 

1042 getattr(position, attr) == getattr(node, attr) 

1043 for attr in match_positions 

1044 ) 

1045 )