Files
codeql/python/ql/src/analysis/CrossProjectDefinitions.qll
yoff 3b3bec8825 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-26 16:32:44 +00:00

106 lines
2.9 KiB
Plaintext

/**
* Symbols for cross-project jump-to-definition resolution.
*/
import python
private import LegacyPointsTo
private newtype TSymbol =
TModule(Module m) or
TMember(Symbol outer, string part) {
exists(Object o | outer.resolvesTo() = o |
o.(ModuleObject).hasAttribute(part)
or
o.(ClassObject).hasAttribute(part)
)
}
/**
* A "symbol" referencing an object in another module
* Symbols are represented by the module name and the dotted name by which the
* object would be referred to in that module.
* For example for the code:
* ```
* class C:
* def m(self): pass
* ```
* If the code were in a module `mod`,
* then symbol for the method `m` would be "mod/C.m"
*/
class Symbol extends TSymbol {
string toString() {
exists(Module m | this = TModule(m) and result = m.getName())
or
exists(TModule outer, string part |
this = TMember(outer, part) and
outer = TModule(_) and
result = outer.(Symbol).toString() + "/" + part
)
or
exists(TMember outer, string part |
this = TMember(outer, part) and
outer = TMember(_, _) and
result = outer.(Symbol).toString() + "." + part
)
}
/** Finds the `AstNode` that this `Symbol` refers to. */
AstNode find() {
this = TModule(result)
or
exists(Symbol s, string name, ControlFlowNode resultCfg |
this = TMember(s, name) and resultCfg.getNode() = result
|
exists(ClassObject cls |
s.resolvesTo() = cls and
cls.attributeRefersTo(name, _, resultCfg)
)
or
exists(ModuleObject m |
s.resolvesTo() = m and
m.attributeRefersTo(name, _, resultCfg)
)
)
}
/**
* Find the class or module `Object` that this `Symbol` refers to, if
* this `Symbol` refers to a class or module.
*/
Object resolvesTo() {
this = TModule(result.(ModuleObject).getModule())
or
exists(Symbol s, string name, Object o |
this = TMember(s, name) and
o = s.resolvesTo() and
result = attribute_in_scope(o, name)
)
}
/**
* Gets the `Module` for the module part of this `Symbol`.
* For example, this would return the `os` module for the `Symbol` "os/environ".
*/
Module getModule() {
this = TModule(result)
or
exists(Symbol outer | this = TMember(outer, _) and result = outer.getModule())
}
/** Gets the `Symbol` that is the named member of this `Symbol`. */
Symbol getMember(string name) { result = TMember(this, name) }
}
/* Helper for `Symbol`.resolvesTo() */
private Object attribute_in_scope(Object obj, string name) {
exists(ClassObject cls | cls = obj |
cls.lookupAttribute(name) = result and result.(ControlFlowNode).getScope() = cls.getPyClass()
)
or
exists(ModuleObject mod | mod = obj |
mod.attr(name) = result and
result.(ControlFlowNode).getScope() = mod.getModule() and
not result.(ControlFlowNode).isEntryNode()
)
}