Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
78c33ab
Python: Remove points-to references from `python.qll`
tausbn Oct 30, 2025
f0465f4
Python: Get rid of some `get...Object` methods
tausbn Oct 30, 2025
0a4ec2c
Python: Move some non-points-to methods out of points-to
tausbn Oct 30, 2025
2732a65
Python: Fix example snippets
tausbn Oct 30, 2025
5b63b49
Python: Fix query tests
tausbn Oct 30, 2025
85029bd
Python: Fix Python 2 tests
tausbn Oct 30, 2025
665104e
Python: Fix Python 3 tests
tausbn Oct 30, 2025
b3b87c9
Python: Fix extractor/experimental tests
tausbn Oct 30, 2025
7176898
Python: Fix library tests
tausbn Oct 30, 2025
21e74a3
Python: Fully remove points-to from `Flow.qll`
tausbn Oct 31, 2025
7328f26
Python: Fix reachability-related test failures
tausbn Oct 31, 2025
e098404
Python: Get rid of points-to from `Definitions.qll`
tausbn Oct 31, 2025
9dc774a
Python: Remove points-to dependency from parts of SSA
tausbn Oct 31, 2025
5b47fcb
Python: Remove dependence on `Builtins` from attribute module
tausbn Oct 31, 2025
b9a5b3b
Python: Remove points-to from `SSA.ql`
tausbn Nov 26, 2025
cd1619b
Python: Fix queries and tests
tausbn Nov 26, 2025
c75329d
Python: Move metrics-related API to `LegacyPointsTo` module
tausbn Nov 26, 2025
24a29f4
Python: Fix all metrics-related compilation failures
tausbn Nov 26, 2025
c6ad438
Python: Add change note
tausbn Nov 26, 2025
a7458df
Python: Appease the QLDoc checker
tausbn Nov 26, 2025
bc8ed28
Python: Make some more points-to imports private
tausbn Nov 27, 2025
ec336a0
Python: Fix list bullets in change note
tausbn Nov 27, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions python/ql/examples/snippets/call.ql
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
*/

import python
private import LegacyPointsTo

from Value len, CallNode call
where len.getName() = "len" and len.getACall() = call
Expand Down
1 change: 1 addition & 0 deletions python/ql/examples/snippets/extend_class.ql
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
*/

import python
private import LegacyPointsTo

from ClassObject sub, ClassObject base
where
Expand Down
1 change: 1 addition & 0 deletions python/ql/examples/snippets/method_call.ql
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
*/

import python
private import LegacyPointsTo

from AstNode call, PythonFunctionValue method
where
Expand Down
1 change: 1 addition & 0 deletions python/ql/examples/snippets/mutualrecursion.ql
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
*/

import python
private import LegacyPointsTo

from FunctionObject m, FunctionObject n
where m != n and m.getACallee() = n and n.getACallee() = m
Expand Down
1 change: 1 addition & 0 deletions python/ql/examples/snippets/override_method.ql
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
*/

import python
private import LegacyPointsTo

from FunctionObject override, FunctionObject base
where
Expand Down
1 change: 1 addition & 0 deletions python/ql/examples/snippets/recursion.ql
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
*/

import python
private import LegacyPointsTo

from PythonFunctionValue f
where f.getACall().getScope() = f.getScope()
Expand Down
226 changes: 224 additions & 2 deletions python/ql/lib/LegacyPointsTo.qll
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,21 @@
*/

private import python
private import semmle.python.pointsto.PointsTo
private import semmle.python.objects.Modules
import semmle.python.pointsto.Base
import semmle.python.pointsto.Context
import semmle.python.pointsto.PointsTo
import semmle.python.pointsto.PointsToContext
import semmle.python.objects.ObjectAPI
import semmle.python.objects.ObjectInternal
import semmle.python.types.Object
import semmle.python.types.ClassObject
import semmle.python.types.FunctionObject
import semmle.python.types.ModuleObject
import semmle.python.types.Exceptions
import semmle.python.types.Properties
import semmle.python.types.Descriptors
import semmle.python.SelfAttribute
import semmle.python.Metrics

/**
* An extension of `ControlFlowNode` that provides points-to predicates.
Expand Down Expand Up @@ -93,6 +106,24 @@ class ControlFlowNodeWithPointsTo extends ControlFlowNode {
// for that variable.
exists(SsaVariable v | v.getAUse() = this | varHasCompletePointsToSet(v))
}

/** Whether it is unlikely that this ControlFlowNode can be reached */
predicate unlikelyReachable() {
not start_bb_likely_reachable(this.getBasicBlock())
or
exists(BasicBlock b |
start_bb_likely_reachable(b) and
not end_bb_likely_reachable(b) and
// If there is an unlikely successor edge earlier in the BB
// than this node, then this node must be unreachable.
exists(ControlFlowNode p, int i, int j |
p.(RaisingNode).unlikelySuccessor(_) and
p = b.getNode(i) and
this = b.getNode(j) and
i < j
)
)
}
}

/**
Expand Down Expand Up @@ -121,6 +152,45 @@ private predicate varHasCompletePointsToSet(SsaVariable var) {
)
}

private predicate start_bb_likely_reachable(BasicBlock b) {
exists(Scope s | s.getEntryNode() = b.getNode(_))
or
exists(BasicBlock pred |
pred = b.getAPredecessor() and
end_bb_likely_reachable(pred) and
not pred.getLastNode().(RaisingNode).unlikelySuccessor(b)
)
}

private predicate end_bb_likely_reachable(BasicBlock b) {
start_bb_likely_reachable(b) and
not exists(ControlFlowNode p, ControlFlowNode s |
p.(RaisingNode).unlikelySuccessor(s) and
p = b.getNode(_) and
s = b.getNode(_) and
not p = b.getLastNode()
)
}

/**
* An extension of `BasicBlock` that provides points-to related methods.
*/
class BasicBlockWithPointsTo extends BasicBlock {
/**
* Whether (as inferred by type inference) it is highly unlikely (or impossible) for control to flow from this to succ.
*/
predicate unlikelySuccessor(BasicBlockWithPointsTo succ) {
this.getLastNode().(RaisingNode).unlikelySuccessor(succ.firstNode())
or
not end_bb_likely_reachable(this) and succ = this.getASuccessor()
}

/**
* Whether (as inferred by type inference) this basic block is likely to be reachable.
*/
predicate likelyReachable() { start_bb_likely_reachable(this) }
}

/**
* An extension of `Expr` that provides points-to predicates.
*/
Expand Down Expand Up @@ -208,3 +278,155 @@ class ModuleWithPointsTo extends Module {

override string getAQlClass() { none() }
}

/**
* An extension of `Function` that provides points-to related methods.
*/
class FunctionWithPointsTo extends Function {
/** Gets the FunctionObject corresponding to this function */
FunctionObject getFunctionObject() { result.getOrigin() = this.getDefinition() }

override string getAQlClass() { none() }
}

/**
* An extension of `Class` that provides points-to related methods.
*/
class ClassWithPointsTo extends Class {
/** Gets the ClassObject corresponding to this class */
ClassObject getClassObject() { result.getOrigin() = this.getParent() }

override string getAQlClass() { none() }
}

/** Gets the `Object` corresponding to the immutable literal `l`. */
Object getLiteralObject(ImmutableLiteral l) {
l instanceof IntegerLiteral and
(
py_cobjecttypes(result, theIntType()) and py_cobjectnames(result, l.(Num).getN())
or
py_cobjecttypes(result, theLongType()) and py_cobjectnames(result, l.(Num).getN())
)
or
l instanceof FloatLiteral and
py_cobjecttypes(result, theFloatType()) and
py_cobjectnames(result, l.(Num).getN())
or
l instanceof ImaginaryLiteral and
py_cobjecttypes(result, theComplexType()) and
py_cobjectnames(result, l.(Num).getN())
or
l instanceof NegativeIntegerLiteral and
(
(py_cobjecttypes(result, theIntType()) or py_cobjecttypes(result, theLongType())) and
py_cobjectnames(result, "-" + l.(UnaryExpr).getOperand().(IntegerLiteral).getN())
)
or
l instanceof Bytes and
py_cobjecttypes(result, theBytesType()) and
py_cobjectnames(result, l.(Bytes).quotedString())
or
l instanceof Unicode and
py_cobjecttypes(result, theUnicodeType()) and
py_cobjectnames(result, l.(Unicode).quotedString())
or
l instanceof True and
name_consts(l, "True") and
result = theTrueObject()
or
l instanceof False and
name_consts(l, "False") and
result = theFalseObject()
or
l instanceof None and
name_consts(l, "None") and
result = theNoneObject()
}

private predicate gettext_installed() {
// Good enough (and fast) approximation
exists(Module m | m.getName() = "gettext")
}

private predicate builtin_constant(string name) {
exists(Object::builtin(name))
or
name = "WindowsError"
or
name = "_" and gettext_installed()
}

/** Whether this name is (almost) always defined, ie. it is a builtin or VM defined name */
predicate globallyDefinedName(string name) { builtin_constant(name) or auto_name(name) }

private predicate auto_name(string name) {
name = "__file__" or name = "__builtins__" or name = "__name__"
}

/** An extension of `SsaVariable` that provides points-to related methods. */
class SsaVariableWithPointsTo extends SsaVariable {
/** Gets an argument of the phi function defining this variable, pruned of unlikely edges. */
SsaVariable getAPrunedPhiInput() {
result = this.getAPhiInput() and
exists(BasicBlock incoming | incoming = this.getPredecessorBlockForPhiArgument(result) |
not incoming.getLastNode().(RaisingNode).unlikelySuccessor(this.getDefinition())
)
}

/** Gets the incoming edges for a Phi node, pruned of unlikely edges. */
private BasicBlockWithPointsTo getAPrunedPredecessorBlockForPhi() {
result = this.getAPredecessorBlockForPhi() and
not result.unlikelySuccessor(this.getDefinition().getBasicBlock())
}

private predicate implicitlyDefined() {
not exists(this.getDefinition()) and
not py_ssa_phi(this, _) and
exists(GlobalVariable var | this.getVariable() = var |
globallyDefinedName(var.getId())
or
var.getId() = "__path__" and var.getScope().(Module).isPackageInit()
)
}

/** Whether this variable may be undefined */
predicate maybeUndefined() {
not exists(this.getDefinition()) and not py_ssa_phi(this, _) and not this.implicitlyDefined()
or
this.getDefinition().isDelete()
or
exists(SsaVariableWithPointsTo var | var = this.getAPrunedPhiInput() | var.maybeUndefined())
or
/*
* For phi-nodes, there must be a corresponding phi-input for each control-flow
* predecessor. Otherwise, the variable will be undefined on that incoming edge.
* WARNING: the same phi-input may cover multiple predecessors, so this check
* cannot be done by counting.
*/

exists(BasicBlock incoming |
reaches_end(incoming) and
incoming = this.getAPrunedPredecessorBlockForPhi() and
not this.getAPhiInput().getDefinition().getBasicBlock().dominates(incoming)
)
}

override string getAQlClass() { none() }
}

private predicate reaches_end(BasicBlock b) {
not exits_early(b) and
(
/* Entry point */
not exists(BasicBlock prev | prev.getASuccessor() = b)
or
exists(BasicBlock prev | prev.getASuccessor() = b | reaches_end(prev))
)
}

private predicate exits_early(BasicBlock b) {
exists(FunctionObject f |
f.neverReturns() and
f.getACall().getBasicBlock() = b
)
}
2 changes: 1 addition & 1 deletion python/ql/lib/analysis/DefinitionTracking.qll
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

import python
private import LegacyPointsTo
import semmle.python.pointsto.PointsTo
private import semmle.python.types.ImportTime
import IDEContextual

private newtype TDefinition =
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
category: breaking
---

* All modules that depend on the points-to analysis have now been removed from the top level `python.qll` module. To access the points-to functionality, import the new `LegacyPointsTo` module. This also means that some predicates have been removed from various classes, for instance `Function.getFunctionObject()`. To access these predicates, import the `LegacyPointsTo` module and use the `FunctionWithPointsTo` class instead. Most cases follow this pattern, but there are a few exceptions:
* The `getLiteralObject` method on `ImmutableLiteral` subclasses has been replaced with a predicate `getLiteralObject(ImmutableLiteral l)` in the `LegacyPointsTo` module.
* The `getMetrics` method on `Function`, `Class`, and `Module` has been removed. To access metrics, import `LegacyPointsTo` and use the classes `FunctionMetrics`, etc. instead.
28 changes: 14 additions & 14 deletions python/ql/lib/python.qll
Original file line number Diff line number Diff line change
Expand Up @@ -14,27 +14,27 @@ import semmle.python.Patterns
import semmle.python.Keywords
import semmle.python.Comprehensions
import semmle.python.Flow
import semmle.python.Metrics
private import semmle.python.Metrics
import semmle.python.Constants
import semmle.python.Scope
import semmle.python.Comment
import semmle.python.GuardedControlFlow
import semmle.python.types.ImportTime
import semmle.python.types.Object
import semmle.python.types.ClassObject
import semmle.python.types.FunctionObject
import semmle.python.types.ModuleObject
import semmle.python.types.Version
import semmle.python.types.Descriptors
private import semmle.python.types.ImportTime
private import semmle.python.types.Object
private import semmle.python.types.ClassObject
private import semmle.python.types.FunctionObject
private import semmle.python.types.ModuleObject
private import semmle.python.types.Version
private import semmle.python.types.Descriptors
import semmle.python.SSA
import semmle.python.SelfAttribute
import semmle.python.types.Properties
private import semmle.python.SelfAttribute
private import semmle.python.types.Properties
import semmle.python.xml.XML
import semmle.python.essa.Essa
import semmle.python.pointsto.Base
import semmle.python.pointsto.Context
import semmle.python.pointsto.CallGraph
import semmle.python.objects.ObjectAPI
private import semmle.python.pointsto.Base
private import semmle.python.pointsto.Context
private import semmle.python.pointsto.CallGraph
private import semmle.python.objects.ObjectAPI
import semmle.python.Unit
import site
private import semmle.python.Overlay
Expand Down
6 changes: 0 additions & 6 deletions python/ql/lib/semmle/python/Class.qll
Original file line number Diff line number Diff line change
Expand Up @@ -141,18 +141,12 @@ class Class extends Class_, Scope, AstNode {
/** Gets the metaclass expression */
Expr getMetaClass() { result = this.getParent().getMetaClass() }

/** Gets the ClassObject corresponding to this class */
ClassObject getClassObject() { result.getOrigin() = this.getParent() }

/** Gets the nth base of this class definition. */
Expr getBase(int index) { result = this.getParent().getBase(index) }

/** Gets a base of this class definition. */
Expr getABase() { result = this.getParent().getABase() }

/** Gets the metrics for this class */
ClassMetrics getMetrics() { result = this }

/**
* Gets the qualified name for this class.
* Should return the same name as the `__qualname__` attribute on classes in Python 3.
Expand Down
Loading