mirror of
https://github.com/github/codeql.git
synced 2026-05-26 17:11:24 +02:00
Python: test dead bindings under no-raise CFG abstraction
Adds 'dead_under_no_raise.py' to the bindings test suite, capturing the
three CPython patterns where bindings legitimately have no CFG node
because the surrounding code is unreachable under the 'no expressions
raise' abstraction:
1. Statements after a 'try: return X; except: pass' block.
2. The 'else:' clause of a try whose body always raises.
3. Cache-lookup pattern 'try: return cache[k]; except: pass' followed
by computation and store.
These bindings intentionally carry no 'cfgdefines=' annotations. If
raise modelling is later added to the CFG, the BindingsTest will surface
the new CFG nodes as unexpected results and this file will need to be
revisited.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
@@ -0,0 +1,52 @@
|
||||
# Dead bindings under the "no expressions raise" CFG abstraction.
|
||||
#
|
||||
# The new CFG does not currently model raise edges from arbitrary
|
||||
# expressions. As a consequence, code that is only reachable through
|
||||
# exception flow is (correctly) classified as dead and has no CFG node.
|
||||
# Variable bindings in dead code do not need CFG nodes - SSA / dataflow
|
||||
# over dead code is moot.
|
||||
#
|
||||
# These tests act as a regression guard: the bindings below intentionally
|
||||
# have no `cfgdefines=` annotations. If raise modelling is later added,
|
||||
# the BindingsTest infrastructure will surface the new CFG nodes as
|
||||
# unexpected results, and this file will need to be revisited.
|
||||
|
||||
|
||||
def f(obj): # $ cfgdefines=f cfgdefines=obj
|
||||
try:
|
||||
return len(obj)
|
||||
except TypeError:
|
||||
pass
|
||||
|
||||
# The first try's body always returns; its except handler does not
|
||||
# raise or otherwise transfer control, so under "no expressions
|
||||
# raise" the only paths out of the try-statement are dead. Everything
|
||||
# below is unreachable.
|
||||
try:
|
||||
hint = type(obj).__length_hint__
|
||||
except AttributeError:
|
||||
return None
|
||||
return hint
|
||||
|
||||
|
||||
def g(): # $ cfgdefines=g
|
||||
try:
|
||||
raise Exception("inner")
|
||||
except:
|
||||
raise Exception("outer")
|
||||
else:
|
||||
# Unreachable: the inner try body always raises, so the `else:`
|
||||
# clause never runs.
|
||||
hit_inner_else = True
|
||||
|
||||
|
||||
def h(cache, key): # $ cfgdefines=h cfgdefines=cache cfgdefines=key
|
||||
try:
|
||||
return cache[key]
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
# Same pattern as `f`: dead under "no expressions raise".
|
||||
value = compute(key)
|
||||
cache[key] = value
|
||||
return value
|
||||
Reference in New Issue
Block a user