Skip to content

Commit 1b51938

Browse files
authored
Merge pull request #20739 from github/tausbn/python-remove-top-level-points-to-imports
Python: Hide points-to imports in `python.qll`
2 parents 86962c6 + ec336a0 commit 1b51938

File tree

284 files changed

+690
-430
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

284 files changed

+690
-430
lines changed

python/ql/examples/snippets/call.ql

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
*/
88

99
import python
10+
private import LegacyPointsTo
1011

1112
from Value len, CallNode call
1213
where len.getName() = "len" and len.getACall() = call

python/ql/examples/snippets/extend_class.ql

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
*/
1212

1313
import python
14+
private import LegacyPointsTo
1415

1516
from ClassObject sub, ClassObject base
1617
where

python/ql/examples/snippets/method_call.ql

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
*/
88

99
import python
10+
private import LegacyPointsTo
1011

1112
from AstNode call, PythonFunctionValue method
1213
where

python/ql/examples/snippets/mutualrecursion.ql

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
*/
88

99
import python
10+
private import LegacyPointsTo
1011

1112
from FunctionObject m, FunctionObject n
1213
where m != n and m.getACallee() = n and n.getACallee() = m

python/ql/examples/snippets/override_method.ql

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
*/
88

99
import python
10+
private import LegacyPointsTo
1011

1112
from FunctionObject override, FunctionObject base
1213
where

python/ql/examples/snippets/recursion.ql

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
*/
88

99
import python
10+
private import LegacyPointsTo
1011

1112
from PythonFunctionValue f
1213
where f.getACall().getScope() = f.getScope()

python/ql/lib/LegacyPointsTo.qll

Lines changed: 224 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,21 @@
2020
*/
2121

2222
private import python
23-
private import semmle.python.pointsto.PointsTo
24-
private import semmle.python.objects.Modules
23+
import semmle.python.pointsto.Base
24+
import semmle.python.pointsto.Context
25+
import semmle.python.pointsto.PointsTo
26+
import semmle.python.pointsto.PointsToContext
27+
import semmle.python.objects.ObjectAPI
28+
import semmle.python.objects.ObjectInternal
29+
import semmle.python.types.Object
30+
import semmle.python.types.ClassObject
31+
import semmle.python.types.FunctionObject
32+
import semmle.python.types.ModuleObject
33+
import semmle.python.types.Exceptions
34+
import semmle.python.types.Properties
35+
import semmle.python.types.Descriptors
36+
import semmle.python.SelfAttribute
37+
import semmle.python.Metrics
2538

2639
/**
2740
* An extension of `ControlFlowNode` that provides points-to predicates.
@@ -93,6 +106,24 @@ class ControlFlowNodeWithPointsTo extends ControlFlowNode {
93106
// for that variable.
94107
exists(SsaVariable v | v.getAUse() = this | varHasCompletePointsToSet(v))
95108
}
109+
110+
/** Whether it is unlikely that this ControlFlowNode can be reached */
111+
predicate unlikelyReachable() {
112+
not start_bb_likely_reachable(this.getBasicBlock())
113+
or
114+
exists(BasicBlock b |
115+
start_bb_likely_reachable(b) and
116+
not end_bb_likely_reachable(b) and
117+
// If there is an unlikely successor edge earlier in the BB
118+
// than this node, then this node must be unreachable.
119+
exists(ControlFlowNode p, int i, int j |
120+
p.(RaisingNode).unlikelySuccessor(_) and
121+
p = b.getNode(i) and
122+
this = b.getNode(j) and
123+
i < j
124+
)
125+
)
126+
}
96127
}
97128

98129
/**
@@ -121,6 +152,45 @@ private predicate varHasCompletePointsToSet(SsaVariable var) {
121152
)
122153
}
123154

155+
private predicate start_bb_likely_reachable(BasicBlock b) {
156+
exists(Scope s | s.getEntryNode() = b.getNode(_))
157+
or
158+
exists(BasicBlock pred |
159+
pred = b.getAPredecessor() and
160+
end_bb_likely_reachable(pred) and
161+
not pred.getLastNode().(RaisingNode).unlikelySuccessor(b)
162+
)
163+
}
164+
165+
private predicate end_bb_likely_reachable(BasicBlock b) {
166+
start_bb_likely_reachable(b) and
167+
not exists(ControlFlowNode p, ControlFlowNode s |
168+
p.(RaisingNode).unlikelySuccessor(s) and
169+
p = b.getNode(_) and
170+
s = b.getNode(_) and
171+
not p = b.getLastNode()
172+
)
173+
}
174+
175+
/**
176+
* An extension of `BasicBlock` that provides points-to related methods.
177+
*/
178+
class BasicBlockWithPointsTo extends BasicBlock {
179+
/**
180+
* Whether (as inferred by type inference) it is highly unlikely (or impossible) for control to flow from this to succ.
181+
*/
182+
predicate unlikelySuccessor(BasicBlockWithPointsTo succ) {
183+
this.getLastNode().(RaisingNode).unlikelySuccessor(succ.firstNode())
184+
or
185+
not end_bb_likely_reachable(this) and succ = this.getASuccessor()
186+
}
187+
188+
/**
189+
* Whether (as inferred by type inference) this basic block is likely to be reachable.
190+
*/
191+
predicate likelyReachable() { start_bb_likely_reachable(this) }
192+
}
193+
124194
/**
125195
* An extension of `Expr` that provides points-to predicates.
126196
*/
@@ -208,3 +278,155 @@ class ModuleWithPointsTo extends Module {
208278

209279
override string getAQlClass() { none() }
210280
}
281+
282+
/**
283+
* An extension of `Function` that provides points-to related methods.
284+
*/
285+
class FunctionWithPointsTo extends Function {
286+
/** Gets the FunctionObject corresponding to this function */
287+
FunctionObject getFunctionObject() { result.getOrigin() = this.getDefinition() }
288+
289+
override string getAQlClass() { none() }
290+
}
291+
292+
/**
293+
* An extension of `Class` that provides points-to related methods.
294+
*/
295+
class ClassWithPointsTo extends Class {
296+
/** Gets the ClassObject corresponding to this class */
297+
ClassObject getClassObject() { result.getOrigin() = this.getParent() }
298+
299+
override string getAQlClass() { none() }
300+
}
301+
302+
/** Gets the `Object` corresponding to the immutable literal `l`. */
303+
Object getLiteralObject(ImmutableLiteral l) {
304+
l instanceof IntegerLiteral and
305+
(
306+
py_cobjecttypes(result, theIntType()) and py_cobjectnames(result, l.(Num).getN())
307+
or
308+
py_cobjecttypes(result, theLongType()) and py_cobjectnames(result, l.(Num).getN())
309+
)
310+
or
311+
l instanceof FloatLiteral and
312+
py_cobjecttypes(result, theFloatType()) and
313+
py_cobjectnames(result, l.(Num).getN())
314+
or
315+
l instanceof ImaginaryLiteral and
316+
py_cobjecttypes(result, theComplexType()) and
317+
py_cobjectnames(result, l.(Num).getN())
318+
or
319+
l instanceof NegativeIntegerLiteral and
320+
(
321+
(py_cobjecttypes(result, theIntType()) or py_cobjecttypes(result, theLongType())) and
322+
py_cobjectnames(result, "-" + l.(UnaryExpr).getOperand().(IntegerLiteral).getN())
323+
)
324+
or
325+
l instanceof Bytes and
326+
py_cobjecttypes(result, theBytesType()) and
327+
py_cobjectnames(result, l.(Bytes).quotedString())
328+
or
329+
l instanceof Unicode and
330+
py_cobjecttypes(result, theUnicodeType()) and
331+
py_cobjectnames(result, l.(Unicode).quotedString())
332+
or
333+
l instanceof True and
334+
name_consts(l, "True") and
335+
result = theTrueObject()
336+
or
337+
l instanceof False and
338+
name_consts(l, "False") and
339+
result = theFalseObject()
340+
or
341+
l instanceof None and
342+
name_consts(l, "None") and
343+
result = theNoneObject()
344+
}
345+
346+
private predicate gettext_installed() {
347+
// Good enough (and fast) approximation
348+
exists(Module m | m.getName() = "gettext")
349+
}
350+
351+
private predicate builtin_constant(string name) {
352+
exists(Object::builtin(name))
353+
or
354+
name = "WindowsError"
355+
or
356+
name = "_" and gettext_installed()
357+
}
358+
359+
/** Whether this name is (almost) always defined, ie. it is a builtin or VM defined name */
360+
predicate globallyDefinedName(string name) { builtin_constant(name) or auto_name(name) }
361+
362+
private predicate auto_name(string name) {
363+
name = "__file__" or name = "__builtins__" or name = "__name__"
364+
}
365+
366+
/** An extension of `SsaVariable` that provides points-to related methods. */
367+
class SsaVariableWithPointsTo extends SsaVariable {
368+
/** Gets an argument of the phi function defining this variable, pruned of unlikely edges. */
369+
SsaVariable getAPrunedPhiInput() {
370+
result = this.getAPhiInput() and
371+
exists(BasicBlock incoming | incoming = this.getPredecessorBlockForPhiArgument(result) |
372+
not incoming.getLastNode().(RaisingNode).unlikelySuccessor(this.getDefinition())
373+
)
374+
}
375+
376+
/** Gets the incoming edges for a Phi node, pruned of unlikely edges. */
377+
private BasicBlockWithPointsTo getAPrunedPredecessorBlockForPhi() {
378+
result = this.getAPredecessorBlockForPhi() and
379+
not result.unlikelySuccessor(this.getDefinition().getBasicBlock())
380+
}
381+
382+
private predicate implicitlyDefined() {
383+
not exists(this.getDefinition()) and
384+
not py_ssa_phi(this, _) and
385+
exists(GlobalVariable var | this.getVariable() = var |
386+
globallyDefinedName(var.getId())
387+
or
388+
var.getId() = "__path__" and var.getScope().(Module).isPackageInit()
389+
)
390+
}
391+
392+
/** Whether this variable may be undefined */
393+
predicate maybeUndefined() {
394+
not exists(this.getDefinition()) and not py_ssa_phi(this, _) and not this.implicitlyDefined()
395+
or
396+
this.getDefinition().isDelete()
397+
or
398+
exists(SsaVariableWithPointsTo var | var = this.getAPrunedPhiInput() | var.maybeUndefined())
399+
or
400+
/*
401+
* For phi-nodes, there must be a corresponding phi-input for each control-flow
402+
* predecessor. Otherwise, the variable will be undefined on that incoming edge.
403+
* WARNING: the same phi-input may cover multiple predecessors, so this check
404+
* cannot be done by counting.
405+
*/
406+
407+
exists(BasicBlock incoming |
408+
reaches_end(incoming) and
409+
incoming = this.getAPrunedPredecessorBlockForPhi() and
410+
not this.getAPhiInput().getDefinition().getBasicBlock().dominates(incoming)
411+
)
412+
}
413+
414+
override string getAQlClass() { none() }
415+
}
416+
417+
private predicate reaches_end(BasicBlock b) {
418+
not exits_early(b) and
419+
(
420+
/* Entry point */
421+
not exists(BasicBlock prev | prev.getASuccessor() = b)
422+
or
423+
exists(BasicBlock prev | prev.getASuccessor() = b | reaches_end(prev))
424+
)
425+
}
426+
427+
private predicate exits_early(BasicBlock b) {
428+
exists(FunctionObject f |
429+
f.neverReturns() and
430+
f.getACall().getBasicBlock() = b
431+
)
432+
}

python/ql/lib/analysis/DefinitionTracking.qll

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
import python
66
private import LegacyPointsTo
7-
import semmle.python.pointsto.PointsTo
7+
private import semmle.python.types.ImportTime
88
import IDEContextual
99

1010
private newtype TDefinition =
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
category: breaking
3+
---
4+
5+
* 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:
6+
* The `getLiteralObject` method on `ImmutableLiteral` subclasses has been replaced with a predicate `getLiteralObject(ImmutableLiteral l)` in the `LegacyPointsTo` module.
7+
* The `getMetrics` method on `Function`, `Class`, and `Module` has been removed. To access metrics, import `LegacyPointsTo` and use the classes `FunctionMetrics`, etc. instead.

python/ql/lib/python.qll

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -14,27 +14,27 @@ import semmle.python.Patterns
1414
import semmle.python.Keywords
1515
import semmle.python.Comprehensions
1616
import semmle.python.Flow
17-
import semmle.python.Metrics
17+
private import semmle.python.Metrics
1818
import semmle.python.Constants
1919
import semmle.python.Scope
2020
import semmle.python.Comment
2121
import semmle.python.GuardedControlFlow
22-
import semmle.python.types.ImportTime
23-
import semmle.python.types.Object
24-
import semmle.python.types.ClassObject
25-
import semmle.python.types.FunctionObject
26-
import semmle.python.types.ModuleObject
27-
import semmle.python.types.Version
28-
import semmle.python.types.Descriptors
22+
private import semmle.python.types.ImportTime
23+
private import semmle.python.types.Object
24+
private import semmle.python.types.ClassObject
25+
private import semmle.python.types.FunctionObject
26+
private import semmle.python.types.ModuleObject
27+
private import semmle.python.types.Version
28+
private import semmle.python.types.Descriptors
2929
import semmle.python.SSA
30-
import semmle.python.SelfAttribute
31-
import semmle.python.types.Properties
30+
private import semmle.python.SelfAttribute
31+
private import semmle.python.types.Properties
3232
import semmle.python.xml.XML
3333
import semmle.python.essa.Essa
34-
import semmle.python.pointsto.Base
35-
import semmle.python.pointsto.Context
36-
import semmle.python.pointsto.CallGraph
37-
import semmle.python.objects.ObjectAPI
34+
private import semmle.python.pointsto.Base
35+
private import semmle.python.pointsto.Context
36+
private import semmle.python.pointsto.CallGraph
37+
private import semmle.python.objects.ObjectAPI
3838
import semmle.python.Unit
3939
import site
4040
private import semmle.python.Overlay

0 commit comments

Comments
 (0)