Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
3 changes: 2 additions & 1 deletion python/ql/examples/snippets/builtin_object.ql
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@
*/

import python
private import LegacyPointsTo

from Expr e, string name
from ExprWithPointsTo e, string name
where e.pointsTo(Value::named(name)) and not name.charAt(_) = "."
select e
3 changes: 2 additions & 1 deletion python/ql/examples/snippets/catch_exception.ql
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,10 @@
*/

import python
private import LegacyPointsTo

from ExceptStmt ex, ClassValue cls
where
cls.getName() = "MyExceptionClass" and
ex.getType().pointsTo(cls)
ex.getType().(ExprWithPointsTo).pointsTo(cls)
select ex
5 changes: 3 additions & 2 deletions python/ql/examples/snippets/conditional_expression.ql
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,11 @@
*/

import python
private import LegacyPointsTo

from IfExp e, ClassObject cls1, ClassObject cls2
where
e.getBody().refersTo(_, cls1, _) and
e.getOrelse().refersTo(_, cls2, _) and
e.getBody().(ExprWithPointsTo).refersTo(_, cls1, _) and
e.getOrelse().(ExprWithPointsTo).refersTo(_, cls2, _) and
cls1 != cls2
select e
3 changes: 2 additions & 1 deletion python/ql/examples/snippets/new_instance.ql
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,10 @@
*/

import python
private import LegacyPointsTo

from Call new, ClassValue cls
where
cls.getName() = "MyClass" and
new.getFunc().pointsTo(cls)
new.getFunc().(ExprWithPointsTo).pointsTo(cls)
select new
3 changes: 2 additions & 1 deletion python/ql/examples/snippets/print.ql
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,13 @@
*/

import python
private import LegacyPointsTo

from AstNode print
where
/* Python 2 without `from __future__ import print_function` */
print instanceof Print
or
/* Python 3 or with `from __future__ import print_function` */
print.(Call).getFunc().pointsTo(Value::named("print"))
print.(Call).getFunc().(ExprWithPointsTo).pointsTo(Value::named("print"))
select print
3 changes: 2 additions & 1 deletion python/ql/examples/snippets/raise_exception.ql
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,10 @@
*/

import python
private import LegacyPointsTo

from Raise raise, ClassValue ex
where
ex.getName() = "AnException" and
raise.getException().pointsTo(ex.getASuperType())
raise.getException().(ExprWithPointsTo).pointsTo(ex.getASuperType())
select raise, "Don't raise instances of 'AnException'"
3 changes: 2 additions & 1 deletion python/ql/examples/snippets/store_none.ql
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,10 @@
*/

import python
private import LegacyPointsTo

from SubscriptNode store
where
store.isStore() and
store.getIndex().pointsTo(Value::named("None"))
store.getIndex().(ControlFlowNodeWithPointsTo).pointsTo(Value::named("None"))
select store
210 changes: 210 additions & 0 deletions python/ql/lib/LegacyPointsTo.qll
Original file line number Diff line number Diff line change
@@ -0,0 +1,210 @@
/**
* DEPRECATED: Using the methods in this module may lead to a degradation of performance. Use at
* your own peril.
*
* This module contains legacy points-to predicates and methods for various classes in the
* points-to analysis.
*
* Existing code that depends on, say, points-to predicates on `ControlFlowNode` should be modified
* to use `ControlFlowNodeWithPointsTo` instead. In particular, if inside a method call chain such
* as
*
* `someCallNode.getFunction().pointsTo(...)`
*
* an explicit cast should be added as follows
*
* `someCallNode.getFunction().(ControlFlowNodeWithPointsTo).pointsTo(...)`
*
* Similarly, if a bound variable has type `ControlFlowNode`, and a points-to method is called on
* it, the type should be changed to `ControlFlowNodeWithPointsTo`.
*/

private import python
private import semmle.python.pointsto.PointsTo
private import semmle.python.objects.Modules

/**
* An extension of `ControlFlowNode` that provides points-to predicates.
*/
class ControlFlowNodeWithPointsTo extends ControlFlowNode {
/** Gets the value that this ControlFlowNode points-to. */
predicate pointsTo(Value value) { this.pointsTo(_, value, _) }

/** Gets the value that this ControlFlowNode points-to. */
Value pointsTo() { this.pointsTo(_, result, _) }

/** Gets a value that this ControlFlowNode may points-to. */
Value inferredValue() { this.pointsTo(_, result, _) }

/** Gets the value and origin that this ControlFlowNode points-to. */
predicate pointsTo(Value value, ControlFlowNode origin) { this.pointsTo(_, value, origin) }

/** Gets the value and origin that this ControlFlowNode points-to, given the context. */
predicate pointsTo(Context context, Value value, ControlFlowNode origin) {
PointsTo::pointsTo(this, context, value, origin)
}

/**
* Gets what this flow node might "refer-to". Performs a combination of localized (intra-procedural) points-to
* analysis and global module-level analysis. This points-to analysis favours precision over recall. It is highly
* precise, but may not provide information for a significant number of flow-nodes.
* If the class is unimportant then use `refersTo(value)` or `refersTo(value, origin)` instead.
*/
pragma[nomagic]
predicate refersTo(Object obj, ClassObject cls, ControlFlowNode origin) {
this.refersTo(_, obj, cls, origin)
}

/** Gets what this expression might "refer-to" in the given `context`. */
pragma[nomagic]
predicate refersTo(Context context, Object obj, ClassObject cls, ControlFlowNode origin) {
not obj = unknownValue() and
not cls = theUnknownType() and
PointsTo::points_to(this, context, obj, cls, origin)
}

/**
* Whether this flow node might "refer-to" to `value` which is from `origin`
* Unlike `this.refersTo(value, _, origin)` this predicate includes results
* where the class cannot be inferred.
*/
pragma[nomagic]
predicate refersTo(Object obj, ControlFlowNode origin) {
not obj = unknownValue() and
PointsTo::points_to(this, _, obj, _, origin)
}

/** Equivalent to `this.refersTo(value, _)` */
predicate refersTo(Object obj) { this.refersTo(obj, _) }

/**
* Check whether this control-flow node has complete points-to information.
* This would mean that the analysis managed to infer an over approximation
* of possible values at runtime.
*/
predicate hasCompletePointsToSet() {
// If the tracking failed, then `this` will be its own "origin". In that
// case, we want to exclude nodes for which there is also a different
// origin, as that would indicate that some paths failed and some did not.
this.refersTo(_, _, this) and
not exists(ControlFlowNode other | other != this and this.refersTo(_, _, other))
or
// If `this` is a use of a variable, then we must have complete points-to
// for that variable.
exists(SsaVariable v | v.getAUse() = this | varHasCompletePointsToSet(v))
}
}

/**
* Check whether a SSA variable has complete points-to information.
* This would mean that the analysis managed to infer an overapproximation
* of possible values at runtime.
*/
private predicate varHasCompletePointsToSet(SsaVariable var) {
// Global variables may be modified non-locally or concurrently.
not var.getVariable() instanceof GlobalVariable and
(
// If we have complete points-to information on the definition of
// this variable, then the variable has complete information.
var.getDefinition()
.(DefinitionNode)
.getValue()
.(ControlFlowNodeWithPointsTo)
.hasCompletePointsToSet()
or
// If this variable is a phi output, then we have complete
// points-to information about it if all phi inputs had complete
// information.
forex(SsaVariable phiInput | phiInput = var.getAPhiInput() |
varHasCompletePointsToSet(phiInput)
)
)
}

/**
* An extension of `Expr` that provides points-to predicates.
*/
class ExprWithPointsTo extends Expr {
/**
* NOTE: `refersTo` will be deprecated in 2019. Use `pointsTo` instead.
* Gets what this expression might "refer-to". Performs a combination of localized (intra-procedural) points-to
* analysis and global module-level analysis. This points-to analysis favours precision over recall. It is highly
* precise, but may not provide information for a significant number of flow-nodes.
* If the class is unimportant then use `refersTo(value)` or `refersTo(value, origin)` instead.
* NOTE: For complex dataflow, involving multiple stages of points-to analysis, it may be more precise to use
* `ControlFlowNode.refersTo(...)` instead.
*/
predicate refersTo(Object obj, ClassObject cls, AstNode origin) {
this.refersTo(_, obj, cls, origin)
}

/**
* NOTE: `refersTo` will be deprecated in 2019. Use `pointsTo` instead.
* Gets what this expression might "refer-to" in the given `context`.
*/
predicate refersTo(Context context, Object obj, ClassObject cls, AstNode origin) {
this.getAFlowNode()
.(ControlFlowNodeWithPointsTo)
.refersTo(context, obj, cls, origin.getAFlowNode())
}

/**
* NOTE: `refersTo` will be deprecated in 2019. Use `pointsTo` instead.
* Holds if this expression might "refer-to" to `value` which is from `origin`
* Unlike `this.refersTo(value, _, origin)`, this predicate includes results
* where the class cannot be inferred.
*/
pragma[nomagic]
predicate refersTo(Object obj, AstNode origin) {
this.getAFlowNode().(ControlFlowNodeWithPointsTo).refersTo(obj, origin.getAFlowNode())
}

/**
* NOTE: `refersTo` will be deprecated in 2019. Use `pointsTo` instead.
* Equivalent to `this.refersTo(value, _)`
*/
predicate refersTo(Object obj) { this.refersTo(obj, _) }

/**
* Holds if this expression might "point-to" to `value` which is from `origin`
* in the given `context`.
*/
predicate pointsTo(Context context, Value value, AstNode origin) {
this.getAFlowNode()
.(ControlFlowNodeWithPointsTo)
.pointsTo(context, value, origin.getAFlowNode())
}

/**
* Holds if this expression might "point-to" to `value` which is from `origin`.
*/
predicate pointsTo(Value value, AstNode origin) {
this.getAFlowNode().(ControlFlowNodeWithPointsTo).pointsTo(value, origin.getAFlowNode())
}

/**
* Holds if this expression might "point-to" to `value`.
*/
predicate pointsTo(Value value) { this.pointsTo(value, _) }

/** Gets a value that this expression might "point-to". */
Value pointsTo() { this.pointsTo(result) }

override string getAQlClass() { none() }
}

/**
* An extension of `Module` that provides points-to related methods.
*/
class ModuleWithPointsTo extends Module {
/** Gets a name exported by this module, that is the names that will be added to a namespace by 'from this-module import *' */
string getAnExport() {
py_exports(this, result)
or
exists(ModuleObjectInternal mod | mod.getSource() = this.getEntryNode() |
mod.(ModuleValue).exports(result)
)
}

override string getAQlClass() { none() }
}
21 changes: 12 additions & 9 deletions python/ql/lib/analysis/DefinitionTracking.qll
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
*/

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

Expand Down Expand Up @@ -36,22 +37,22 @@ private predicate jump_to_defn(ControlFlowNode use, Definition defn) {
)
or
exists(PythonModuleObject mod |
use.(ImportExprNode).refersTo(mod) and
use.(ImportExprNode).(ControlFlowNodeWithPointsTo).refersTo(mod) and
defn.getAstNode() = mod.getModule()
)
or
exists(PythonModuleObject mod, string name |
use.(ImportMemberNode).getModule(name).refersTo(mod) and
use.(ImportMemberNode).getModule(name).(ControlFlowNodeWithPointsTo).refersTo(mod) and
scope_jump_to_defn_attribute(mod.getModule(), name, defn)
)
or
exists(PackageObject package |
use.(ImportExprNode).refersTo(package) and
use.(ImportExprNode).(ControlFlowNodeWithPointsTo).refersTo(package) and
defn.getAstNode() = package.getInitModule().getModule()
)
or
exists(PackageObject package, string name |
use.(ImportMemberNode).getModule(name).refersTo(package) and
use.(ImportMemberNode).getModule(name).(ControlFlowNodeWithPointsTo).refersTo(package) and
scope_jump_to_defn_attribute(package.getInitModule().getModule(), name, defn)
)
or
Expand Down Expand Up @@ -230,7 +231,7 @@ private predicate module_and_name_for_import_star_helper(
ModuleObject mod, string name, ImportStarNode im_star, ImportStarRefinement def
) {
im_star = def.getDefiningNode() and
im_star.getModule().refersTo(mod) and
im_star.getModule().(ControlFlowNodeWithPointsTo).refersTo(mod) and
name = def.getSourceVariable().getName()
}

Expand All @@ -239,7 +240,7 @@ pragma[noinline]
private predicate variable_not_redefined_by_import_star(EssaVariable var, ImportStarRefinement def) {
var = def.getInput() and
exists(ModuleObject mod |
def.getDefiningNode().(ImportStarNode).getModule().refersTo(mod) and
def.getDefiningNode().(ImportStarNode).getModule().(ControlFlowNodeWithPointsTo).refersTo(mod) and
not mod.exports(var.getSourceVariable().getName())
)
}
Expand Down Expand Up @@ -352,7 +353,9 @@ private predicate scope_jump_to_defn_attribute(ImportTimeScope s, string name, D
)
}

private predicate jump_to_defn_attribute(ControlFlowNode use, string name, Definition defn) {
private predicate jump_to_defn_attribute(
ControlFlowNodeWithPointsTo use, string name, Definition defn
) {
/* Local attribute */
exists(EssaVariable var |
use = var.getASourceUse() and
Expand All @@ -367,7 +370,7 @@ private predicate jump_to_defn_attribute(ControlFlowNode use, string name, Defin
/* Super attributes */
exists(AttrNode f, SuperBoundMethod sbm, Object function |
use = f.getObject(name) and
f.refersTo(sbm) and
f.(ControlFlowNodeWithPointsTo).refersTo(sbm) and
function = sbm.getFunction(_) and
function.getOrigin() = defn.getAstNode()
)
Expand Down Expand Up @@ -408,7 +411,7 @@ private predicate attribute_assignment_jump_to_defn_attribute(
private predicate sets_attribute(ArgumentRefinement def, string name) {
exists(CallNode call |
call = def.getDefiningNode() and
call.getFunction().refersTo(Object::builtin("setattr")) and
call.getFunction().(ControlFlowNodeWithPointsTo).refersTo(Object::builtin("setattr")) and
def.getInput().getAUse() = call.getArg(0) and
call.getArg(1).getNode().(StringLiteral).getText() = name
)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
category: breaking
---

- The classes `ControlFlowNode`, `Expr`, and `Module` no longer expose predicates that invoke the points-to analysis. To access these predicates, import the module `LegacyPointsTo` and follow the instructions given therein.
Loading