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

324 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 

8 

9from functools import lru_cache 

10 

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

12 

13 

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

15 while True: 

16 if hasattr(node, "parent"): 

17 node = node.parent 

18 yield node 

19 else: 

20 break # pragma: no mutate 

21 

22 

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

24 yield node 

25 yield from parents(node) 

26 

27 

28def mangled_name(node: EnhancedAST) -> str: 

29 """ 

30 

31 Parameters: 

32 node: the node which should be mangled 

33 name: the name of the node 

34 

35 Returns: 

36 The mangled name of `node` 

37 """ 

38 if isinstance(node, ast.Attribute): 

39 name = node.attr 

40 elif isinstance(node, ast.Name): 

41 name = node.id 

42 elif isinstance(node, (ast.alias)): 

43 name = node.asname or node.name.split(".")[0] 

44 elif isinstance(node, (ast.FunctionDef, ast.ClassDef, ast.AsyncFunctionDef)): 

45 name = node.name 

46 elif isinstance(node, ast.ExceptHandler): 

47 assert node.name 

48 name = node.name 

49 elif sys.version_info >= (3,12) and isinstance(node,ast.TypeVar): 

50 name=node.name 

51 else: 

52 raise TypeError("no node to mangle for type "+repr(type(node))) 

53 

54 if name.startswith("__") and not name.endswith("__"): 

55 

56 parent,child=node.parent,node 

57 

58 while not (isinstance(parent,ast.ClassDef) and child not in parent.bases): 

59 if not hasattr(parent,"parent"): 

60 break # pragma: no mutate 

61 

62 parent,child=parent.parent,parent 

63 else: 

64 class_name=parent.name.lstrip("_") 

65 if class_name!="": 

66 return "_" + class_name + name 

67 

68 

69 

70 return name 

71 

72 

73@lru_cache(128) # pragma: no mutate 

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

75 return list(dis.get_instructions(code)) 

76 

77 

78types_cmp_issue_fix = ( 

79 ast.IfExp, 

80 ast.If, 

81 ast.Assert, 

82 ast.While, 

83) 

84 

85types_cmp_issue = types_cmp_issue_fix + ( 

86 ast.ListComp, 

87 ast.SetComp, 

88 ast.DictComp, 

89 ast.GeneratorExp, 

90) 

91 

92op_type_map = { 

93 "**": ast.Pow, 

94 "*": ast.Mult, 

95 "@": ast.MatMult, 

96 "//": ast.FloorDiv, 

97 "/": ast.Div, 

98 "%": ast.Mod, 

99 "+": ast.Add, 

100 "-": ast.Sub, 

101 "<<": ast.LShift, 

102 ">>": ast.RShift, 

103 "&": ast.BitAnd, 

104 "^": ast.BitXor, 

105 "|": ast.BitOr, 

106} 

107 

108 

109class PositionNodeFinder(object): 

110 """ 

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

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

113 There are only some exceptions for methods and attributes. 

114 """ 

115 

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

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

118 

119 self.source = source 

120 self.decorator: Optional[EnhancedAST] = None 

121 

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

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

124 lasti -= 2 

125 

126 try: 

127 # try to map with all match_positions 

128 self.result = self.find_node(lasti) 

129 except NotOneValueFound: 

130 typ: tuple[Type] 

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

132 # this can only be associated by using all positions 

133 if self.opname(lasti) in ( 

134 "LOAD_METHOD", 

135 "LOAD_ATTR", 

136 "STORE_ATTR", 

137 "DELETE_ATTR", 

138 ): 

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

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

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

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

143 typ = (ast.Attribute,) 

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

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

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

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

148 # One closing ) only belongs to one method. 

149 typ = (ast.Call,) 

150 else: 

151 raise 

152 

153 self.result = self.find_node( 

154 lasti, 

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

156 typ=typ, 

157 ) 

158 

159 instruction = self.instruction(lasti) 

160 assert instruction is not None 

161 

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

163 

164 self.known_issues(self.result, instruction) 

165 

166 self.test_for_decorator(self.result, lasti) 

167 

168 # verify 

169 if self.decorator is None: 

170 self.verify(self.result, instruction) 

171 else: 

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

173 

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

175 if ( 

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

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

178 ): 

179 node_func = node.parent 

180 

181 while True: 

182 # the generated bytecode looks like follow: 

183 

184 # index opname 

185 # ------------------ 

186 # index-4 PRECALL (only in 3.11) 

187 # index-2 CACHE 

188 # index CALL <- the call instruction 

189 # ... CACHE some CACHE instructions 

190 

191 # maybe multiple other bytecode blocks for other decorators 

192 # index-4 PRECALL (only in 3.11) 

193 # index-2 CACHE 

194 # index CALL <- index of the next loop 

195 # ... CACHE some CACHE instructions 

196 

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

198 

199 if not ( 

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

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

202 ): # pragma: no mutate 

203 break # pragma: no mutate 

204 

205 index += 2 

206 

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

208 index += 2 

209 

210 if ( 

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

212 and self.find_node(index) == node_func 

213 ): 

214 self.result = node_func 

215 self.decorator = node 

216 return 

217 

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

219 index += 4 

220 

221 def fix_result( 

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

223 ) -> EnhancedAST: 

224 if ( 

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

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

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

228 and node is node.parent.iter 

229 ): 

230 # node positions have changed in 3.12.5 

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

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

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

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

235 # This keeps backward compatibility with older executing versions. 

236 

237 # there are also cases like: 

238 # 

239 # for a in iter(l): pass 

240 # 

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

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

243 

244 return node.parent 

245 

246 if ( 

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

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

249 and isinstance( 

250 node.parent.parent, 

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

252 ) 

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

254 and node is node.parent.iter 

255 ): 

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

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

258 

259 return node.parent.parent 

260 

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

262 before = self.instruction_before(instruction) 

263 if ( 

264 before is not None 

265 and before.opname == "LOAD_CONST" 

266 and before.positions == instruction.positions 

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

268 and node is node.parent.context_expr 

269 ): 

270 # node positions for with-statements have change 

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

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

273 

274 # with context_manager: 

275 # ... 

276 

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

278 

279 # with context_manager(): 

280 # ... 

281 

282 # the call for __exit__ 

283 

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

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

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

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

288 

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

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

291 

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

293 

294 return node.parent.parent 

295 

296 if ( 

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

298 and instruction.opname == "BEFORE_WITH" 

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

300 and node is node.parent.context_expr 

301 ): 

302 # handle positions changes for __enter__ 

303 return node.parent.parent 

304 

305 return node 

306 

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

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

309 node, types_cmp_issue 

310 ): 

311 if isinstance(node, types_cmp_issue_fix): 

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

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

314 # 

315 # we can not fix cases like: 

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

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

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

319 

320 comparisons = [ 

321 n 

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

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

324 ] 

325 

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

327 

328 if len(comparisons) == 1: 

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

330 else: 

331 raise KnownIssue( 

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

333 ) 

334 

335 else: 

336 # Comprehension and generators get not fixed for now. 

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

338 

339 if ( 

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

341 and isinstance(node, ast.Compare) 

342 and instruction.opname == "CALL" 

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

344 ): 

345 raise KnownIssue( 

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

347 ) 

348 

349 if isinstance(node, ast.Assert): 

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

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

352 # We don't report this wrong result. 

353 raise KnownIssue("assert") 

354 

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

356 # TODO: investigate 

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

358 

359 if ( 

360 sys.version_info >= (3, 12) 

361 and isinstance(node, ast.Call) 

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

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

364 ): 

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

366 

367 # find the enclosing function 

368 func = node.parent 

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

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

371 ): 

372 

373 func = func.parent 

374 

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

376 first_arg = None 

377 

378 if hasattr(func, "args"): 

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

380 if args: 

381 first_arg = args[0].arg 

382 

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

384 ("LOAD_DEREF", "__class__"), 

385 ("LOAD_FAST", first_arg), 

386 ("LOAD_DEREF", first_arg), 

387 ]: 

388 raise KnownIssue("super optimization") 

389 

390 if self.is_except_cleanup(instruction, node): 

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

392 

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

394 # handle stores to __classcell__ as KnownIssue, 

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

396 # example: 

397 # 

398 # class X: 

399 # # ... something 

400 # if some_condition: 

401 # def method(self): 

402 # super() 

403 # 

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

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

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

407 

408 raise KnownIssue("store __classcell__") 

409 

410 if ( 

411 instruction.opname == "CALL" 

412 and not isinstance(node,ast.Call) 

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

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

415 ): 

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

417 

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

419 if instruction.opname in ( 

420 "STORE_FAST_STORE_FAST", 

421 "STORE_FAST_LOAD_FAST", 

422 "LOAD_FAST_LOAD_FAST", 

423 ): 

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

425 

426 if instruction.opname == "LOAD_FAST" and instruction.argval == "__class__": 

427 # example: 

428 # class T: 

429 # def a(): 

430 # super() 

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

432 

433 raise KnownIssue( 

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

435 ) 

436 

437 if ( 

438 instruction.opname == "COMPARE_OP" 

439 and isinstance(node, ast.UnaryOp) 

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

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

442 ): 

443 # work around for  

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

445 self.result = node.operand 

446 

447 @staticmethod 

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

449 if inst.opname not in ( 

450 "STORE_NAME", 

451 "STORE_FAST", 

452 "STORE_DEREF", 

453 "STORE_GLOBAL", 

454 "DELETE_NAME", 

455 "DELETE_FAST", 

456 "DELETE_DEREF", 

457 "DELETE_GLOBAL", 

458 ): 

459 return False 

460 

461 # This bytecode does something exception cleanup related. 

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

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

464 

465 # example: 

466 # except Exception as exc: 

467 # enum_member._value_ = value 

468 

469 # other example: 

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

471 # except OSError as e: 

472 # if not _ignore_error(e): 

473 # raise 

474 # return False 

475 

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

477 # except TypeError as msg: 

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

479 

480 if ( 

481 isinstance(node, ast.Name) 

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

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

484 and mangled_name(node) == inst.argval 

485 ): 

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

487 return False 

488 

489 if ( 

490 isinstance(node, ast.Name) 

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

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

493 and mangled_name(node) == inst.argval 

494 ): 

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

496 return False 

497 

498 return any( 

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

500 for n in parents(node) 

501 ) 

502 

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

504 """ 

505 checks if this node could gererate this instruction 

506 """ 

507 

508 op_name = instruction.opname 

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

510 ctx: Type = type(None) 

511 

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

513 """ 

514 match instruction 

515 

516 Parameters: 

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

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

519 

520 Returns: 

521 True if all conditions match the instruction 

522 

523 """ 

524 

525 if isinstance(opnames, str): 

526 opnames = [opnames] 

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

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

529 } 

530 

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

532 """ 

533 match the ast-node 

534 

535 Parameters: 

536 node_type: type of the node 

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

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

539 """ 

540 return isinstance(node, node_type) and all( 

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

542 if isinstance(v, type) 

543 else getattr(node, k) == v 

544 for k, v in kwargs.items() 

545 ) 

546 

547 if op_name == "CACHE": 

548 return 

549 

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

551 # call to context.__exit__ 

552 return 

553 

554 if inst_match(("CALL", "LOAD_FAST")) and node_match( 

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

556 ): 

557 # call to the generator function 

558 return 

559 

560 if ( 

561 sys.version_info >= (3, 12) 

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

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

564 ): 

565 return 

566 

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

568 (ast.ClassDef, ast.Call) 

569 ): 

570 return 

571 

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

573 ast.Compare 

574 ): 

575 return 

576 

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

578 ast.AnnAssign 

579 ): 

580 return 

581 

582 if ( 

583 ( 

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

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

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

587 ) 

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

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

590 ): 

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

592 return 

593 

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

595 # data: int 

596 return 

597 

598 

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

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

601 ): 

602 return 

603 

604 if inst_match("BUILD_STRING") and ( 

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

606 ): 

607 return 

608 

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

610 return 

611 

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

613 ast.Constant 

614 ): 

615 # store docstrings 

616 return 

617 

618 if ( 

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

620 and node_match(ast.ExceptHandler) 

621 and instruction.argval == mangled_name(node) 

622 ): 

623 # store exception in variable 

624 return 

625 

626 if ( 

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

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

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

630 ): 

631 # store imported module in variable 

632 return 

633 

634 if ( 

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

636 and ( 

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

638 or node_match( 

639 ast.Name, 

640 ctx=ast.Store, 

641 ) 

642 ) 

643 and instruction.argval == mangled_name(node) 

644 ): 

645 return 

646 

647 if False: 

648 # TODO: match expressions are not supported for now 

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

650 ast.MatchAs, name=instruction.argval 

651 ): 

652 return 

653 

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

655 return 

656 

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

658 return 

659 

660 if inst_match("BINARY_OP") and node_match( 

661 ast.AugAssign, op=op_type_map[instruction.argrepr.removesuffix("=")] 

662 ): 

663 # a+=5 

664 return 

665 

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

667 "DELETE_ATTR", argval=mangled_name(node) 

668 ): 

669 return 

670 

671 if inst_match( 

672 ( 

673 "JUMP_IF_TRUE_OR_POP", 

674 "JUMP_IF_FALSE_OR_POP", 

675 "POP_JUMP_IF_TRUE", 

676 "POP_JUMP_IF_FALSE", 

677 ) 

678 ) and node_match(ast.BoolOp): 

679 # and/or short circuit 

680 return 

681 

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

683 return 

684 

685 if ( 

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

687 or ( 

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

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

690 ) 

691 ) and inst_match( 

692 ( 

693 "LOAD_NAME", 

694 "LOAD_FAST", 

695 "LOAD_FAST_CHECK", 

696 "LOAD_GLOBAL", 

697 "LOAD_DEREF", 

698 "LOAD_FROM_DICT_OR_DEREF", 

699 ), 

700 argval=mangled_name(node), 

701 ): 

702 return 

703 

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

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

706 ): 

707 return 

708 

709 if node_match(ast.Constant) and inst_match( 

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

711 ): 

712 return 

713 

714 if node_match( 

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

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

717 return 

718 

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

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

721 "CALL_INTRINSIC_1", argrepr="INTRINSIC_UNARY_POSITIVE" 

722 ): 

723 return 

724 

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

726 return 

727 

728 if node_match(ast.ImportFrom) and inst_match( 

729 "CALL_INTRINSIC_1", argrepr="INTRINSIC_IMPORT_STAR" 

730 ): 

731 return 

732 

733 if ( 

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

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

736 return 

737 

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

739 return 

740 

741 if node_match(ast.TypeVar) and ( 

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

743 or inst_match( 

744 "CALL_INTRINSIC_2", argrepr="INTRINSIC_TYPEVAR_WITH_BOUND" 

745 ) 

746 or inst_match( 

747 "CALL_INTRINSIC_2", argrepr="INTRINSIC_TYPEVAR_WITH_CONSTRAINTS" 

748 ) 

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

750 ): 

751 return 

752 

753 if node_match(ast.TypeVarTuple) and ( 

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

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

756 ): 

757 return 

758 

759 if node_match(ast.ParamSpec) and ( 

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

761 

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

763 return 

764 

765 

766 if node_match(ast.TypeAlias): 

767 if( 

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

769 or inst_match( 

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

771 ) 

772 or inst_match("CALL") 

773 ): 

774 return 

775 

776 

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

778 if inst_match( 

779 ("STORE_DEREF", "LOAD_DEREF", "LOAD_FROM_DICT_OR_DEREF"), 

780 argrepr=".type_params", 

781 ): 

782 return 

783 

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

785 return 

786 

787 if inst_match( 

788 "CALL_INTRINSIC_1", argrepr="INTRINSIC_SUBSCRIPT_GENERIC" 

789 ): 

790 return 

791 

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

793 return 

794 

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

796 if inst_match("CALL"): 

797 return 

798 

799 if inst_match( 

800 "CALL_INTRINSIC_2", argrepr="INTRINSIC_SET_FUNCTION_TYPE_PARAMS" 

801 ): 

802 return 

803 

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

805 return 

806 

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

808 return 

809 

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

811 # this is a general thing 

812 return 

813 

814 

815 # f-strings 

816 

817 if node_match(ast.JoinedStr) and ( 

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

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

820 ): 

821 return 

822 

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

824 return 

825 

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

827 

828 if inst_match("NOP"): 

829 return 

830 

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

832 return 

833 

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

835 return 

836 

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

838 return 

839 

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

841 return 

842 

843 if inst_match("LOAD_FAST") and node_match( 

844 ( 

845 ast.FunctionDef, 

846 ast.ClassDef, 

847 ast.TypeAlias, 

848 ast.TypeVar, 

849 ast.Lambda, 

850 ast.AsyncFunctionDef, 

851 ) 

852 ): 

853 # These are loads for closure variables. 

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

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

856 return 

857 

858 if ( 

859 inst_match("LOAD_FAST") 

860 and node_match(ast.TypeAlias) 

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

862 ): 

863 return 

864 

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

866 # the node is the first node in the body 

867 return 

868 

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

870 return 

871 

872 

873 # old verifier 

874 

875 typ: Type = type(None) 

876 op_type: Type = type(None) 

877 

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

879 typ = ast.Subscript 

880 ctx = ast.Load 

881 elif op_name.startswith("BINARY_"): 

882 typ = ast.BinOp 

883 op_type = op_type_map[instruction.argrepr] 

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

885 elif op_name.startswith("UNARY_"): 

886 typ = ast.UnaryOp 

887 op_type = dict( 

888 UNARY_POSITIVE=ast.UAdd, 

889 UNARY_NEGATIVE=ast.USub, 

890 UNARY_NOT=ast.Not, 

891 UNARY_INVERT=ast.Invert, 

892 )[op_name] 

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

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

895 typ = ast.Attribute 

896 ctx = ast.Load 

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

898 elif op_name in ( 

899 "LOAD_NAME", 

900 "LOAD_GLOBAL", 

901 "LOAD_FAST", 

902 "LOAD_DEREF", 

903 "LOAD_CLASSDEREF", 

904 ): 

905 typ = ast.Name 

906 ctx = ast.Load 

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

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

909 typ = ast.Compare 

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

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

912 ctx = ast.Store 

913 typ = ast.Subscript 

914 elif op_name.startswith("STORE_ATTR"): 

915 ctx = ast.Store 

916 typ = ast.Attribute 

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

918 

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

920 

921 ctx_match = ( 

922 ctx is not type(None) 

923 or not hasattr(node, "ctx") 

924 or isinstance(node_ctx, ctx) 

925 ) 

926 

927 # check for old verifier 

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

929 return 

930 

931 # generate error 

932 

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

934 type(node).__name__, 

935 instruction.opname, 

936 ) 

937 

938 raise VerifierFailure(title, node, instruction) 

939 

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

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

942 

943 def instruction_before( 

944 self, instruction: dis.Instruction 

945 ) -> Optional[dis.Instruction]: 

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

947 

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

949 i=self.instruction(index) 

950 if i is None: 

951 return "CACHE" 

952 return i.opname 

953 

954 extra_node_types=() 

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

956 extra_node_types = (ast.type_param,) 

957 

958 def find_node( 

959 self, 

960 index: int, 

961 match_positions: Sequence[str] = ( 

962 "lineno", 

963 "end_lineno", 

964 "col_offset", 

965 "end_col_offset", 

966 ), 

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

968 ast.expr, 

969 ast.stmt, 

970 ast.excepthandler, 

971 ast.pattern, 

972 *extra_node_types, 

973 ), 

974 ) -> EnhancedAST: 

975 instruction = self.instruction(index) 

976 assert instruction is not None 

977 

978 position = instruction.positions 

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

980 

981 return only( 

982 cast(EnhancedAST, node) 

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

984 if isinstance(node, typ) 

985 if not isinstance(node, ast.Expr) 

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

987 if not isinstance(node, ast.MatchValue) 

988 if all( 

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

990 for attr in match_positions 

991 ) 

992 )