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 )