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 )