Files
codeql/python/ql/src/Variables/UndefinedPlaceHolder.ql
yoff f9398546a4 Python: remove getAFlowNode() — bridge AST→CFG only via CFG-side getNode()
Option 2: eliminates the AST→CFG bridge from the AST layer. Previously
'AstNode.getAFlowNode()' returned a 'ControlFlowNode' from the legacy
'Flow.qll' CFG via 'py_flow_bb_node' — this hardcoded the AST to know
about the legacy CFG, preventing files from cleanly switching to the
new shared CFG.

Removes:
  * 'AstNode.getAFlowNode()' from 'AstExtended.qll'
  * Type-narrowing overrides on 'Attribute' / 'Subscript' / 'Call' /
    'IfExp' / 'Name' / 'NameConstant' / 'ImportMember' (in Exprs.qll
    and Import.qll)

Rewrites ~130 call sites across 'python/ql/lib/' and 'python/ql/src/'
to bridge from the CFG side instead:

  Before:  node = expr.getAFlowNode()
  After:   node.getNode() = expr

  Before:  expr.getAFlowNode().(DefinitionNode).getValue()
  After:   exists(DefinitionNode d | d.getNode() = expr | d.getValue())

  Before:  cn.operands(const.getAFlowNode(), op, x)
  After:   exists(ControlFlowNode c | c.getNode() = const | cn.operands(c, op, x))

This is semantically a no-op — both forms are duals of the same predicate.
Verified by passing all library tests:
  * 64 dataflow tests
  * 28 ControlFlow + dataflow-new-ssa tests
  * 1 essa SSA-compute test
  * 93 tests total in the focused suite

Once committed, files that want to switch from the legacy 'Flow' CFG
to the new 'Cfg' facade only need to change their imports — the
bridge sites are CFG-side and respect whichever ControlFlowNode is in
scope.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-05-28 21:09:46 +00:00

52 lines
1.5 KiB
Plaintext

/**
* @name Use of an undefined placeholder variable
* @description Using a variable before it is initialized causes an exception.
* @kind problem
* @tags quality
* reliability
* correctness
* @problem.severity error
* @sub-severity low
* @precision medium
* @id py/undefined-placeholder-variable
*/
import python
import Variables.MonkeyPatched
private import LegacyPointsTo
private import semmle.python.types.ImportTime
/* Local variable part */
predicate initialized_as_local(PlaceHolder use) {
exists(SsaVariableWithPointsTo l, Function f, ControlFlowNode useCfg |
f = use.getScope() and useCfg.getNode() = use and l.getAUse() = useCfg
|
l.getVariable() instanceof LocalVariable and
not l.maybeUndefined()
)
}
/* Not a template member */
Class enclosing_class(PlaceHolder use) { result.getAMethod() = use.getScope() }
predicate template_attribute(PlaceHolder use) {
exists(ImportTimeScope cls | cls = enclosing_class(use) | cls.definesName(use.getId()))
}
/* Global Stuff */
predicate not_a_global(PlaceHolder use) {
not exists(PythonModuleObject mo |
mo.hasAttribute(use.getId()) and mo.getModule() = use.getEnclosingModule()
) and
not globallyDefinedName(use.getId()) and
not monkey_patched_builtin(use.getId()) and
not globallyDefinedName(use.getId())
}
from PlaceHolder p
where
not initialized_as_local(p) and
not template_attribute(p) and
not_a_global(p)
select p, "This use of place-holder variable '" + p.getId() + "' may be undefined."