Python points-to: Use objects, not booleans when doing evaluation of tests.

This commit is contained in:
Mark Shannon
2019-03-28 13:24:00 +00:00
parent 610a35c187
commit 8af6cb6644
5 changed files with 145 additions and 86 deletions

View File

@@ -87,13 +87,9 @@ class PythonFunctionObjectInternal extends CallableObjectInternal, TPythonFuncti
rval = func.getAReturnValueFlowNode() and
PointsToInternal::pointsTo(rval, callee, obj, origin)
or
exists(Return ret |
ret.getScope() = func and
PointsToInternal::reachableBlock(ret.getAFlowNode().getBasicBlock(), callee) and
not exists(ret.getValue()) and
obj = ObjectInternal::none_() and
origin = CfgOrigin::unknown()
)
PointsToInternal::reachableBlock(blockReturningNone(func), callee) and
obj = ObjectInternal::none_() and
origin = CfgOrigin::unknown()
)
}
@@ -121,12 +117,22 @@ class PythonFunctionObjectInternal extends CallableObjectInternal, TPythonFuncti
PointsTo::pointsTo(result.getFunction(), ctx, this, _)
or
exists(BoundMethodObjectInternal bm |
bm.getACall() = result and this = bm.getFunction()
bm.getACall(ctx) = result and this = bm.getFunction()
)
}
}
private BasicBlock blockReturningNone(Function func) {
exists(Return ret |
not exists(ret.getValue()) and
ret.getScope() = func and
result = ret.getAFlowNode().getBasicBlock()
)
}
class BuiltinFunctionObjectInternal extends CallableObjectInternal, TBuiltinFunctionObject {
override Builtin getBuiltin() {

View File

@@ -214,7 +214,7 @@ class UnknownClassInternal extends ClassObjectInternal, TUnknownClass {
class TypeInternal extends ClassObjectInternal, TType {
override string toString() {
result = "type"
result = "builtin-class type"
}
override ClassDecl getClassDeclaration() {
@@ -229,7 +229,7 @@ class TypeInternal extends ClassObjectInternal, TType {
none()
}
override boolean isComparable() { result = false }
override boolean isComparable() { result = true }
override Builtin getBuiltin() {
result = Builtin::special("type")

View File

@@ -395,6 +395,10 @@ class SingleSuccessorGuard extends PyNodeRefinement {
not exists(this.getSense()) and
result = PyNodeRefinement.super.getRepresentation() + " [??]"
}
ControlFlowNode getTest() {
result = this.getDefiningNode()
}
}
/** Implicit definition of the names of sub-modules in a package.

View File

@@ -25,20 +25,6 @@ predicate isinstance(CallNode fc, ControlFlowNode cls, ControlFlowNode use) {
cls = fc.getArg(1) and fc.getArg(0) = use
}
/** Holds if `c` is a test comparing `x` and `y`. `is` is true if the operator is `is` or `==`, it is false if the operator is `is not` or `!=`. */
predicate equality_test(CompareNode c, ControlFlowNode x, boolean is, ControlFlowNode y) {
exists(Cmpop op |
c.operands(x, op, y) or
c.operands(y, op, x)
|
(is = true and op instanceof Is or
is = false and op instanceof IsNot or
is = true and op instanceof Eq or
is = false and op instanceof NotEq
)
)
}
/** Holds if `c` is a call to `issubclass(use, cls)`. */
predicate issubclass(CallNode fc, ControlFlowNode cls, ControlFlowNode use) {
fc.getFunction().(NameNode).getId() = "issubclass" and

View File

@@ -319,10 +319,13 @@ cached module PointsToInternal {
pragma [noinline]
private predicate attribute_load_points_to(AttrNode f, PointsToContext context, ObjectInternal value, ControlFlowNode origin) {
f.isLoad() and
exists(ObjectInternal object, string name, CfgOrigin orig |
pointsTo(f.getObject(name), context, object, _) |
object.attribute(name, value, orig) and
origin = orig.fix(f)
exists(ObjectInternal object, string name |
pointsTo(f.getObject(name), context, object, _)
|
exists(CfgOrigin orig |
object.attribute(name, value, orig) and
origin = orig.fix(f)
)
or
object.attributesUnknown() and
origin = f and value = ObjectInternal::unknown()
@@ -401,7 +404,8 @@ cached module PointsToInternal {
if def.getName() = "__class__" then
exists(ObjectInternal cls |
pointsTo(def.getValue(), context, cls, _) and
value = TUnknownInstance(cls)
value = TUnknownInstance(cls) and
origin = CfgOrigin::fromCfgNode(def.getDefiningNode())
)
else
variablePointsTo(def.getInput(), context, value, origin)
@@ -419,9 +423,10 @@ cached module PointsToInternal {
/** Holds if ESSA edge refinement, `def`, refers to `(value, cls, origin)`. */
private predicate ssa_filter_definition_points_to(PyEdgeRefinement def, PointsToContext context, ObjectInternal value, CfgOrigin origin) {
exists(ControlFlowNode test, ControlFlowNode use |
refinement_test(test, use, Conditionals::branchEvaluatesTo(test, use, context, value, origin.toCfgNode()), def)
)
def.getSense() = Conditionals::testEvaluates(def.getTest(), def.getInput().getASourceUse(), context, value, origin)
//exists(ControlFlowNode test, ControlFlowNode use |
// refinement_test(test, use, Conditionals::branchEvaluatesTo(test, use, context, value, origin.toCfgNode()), def)
//)
}
/** Holds if ESSA definition, `uniphi`, refers to `(value, origin)`. */
@@ -431,9 +436,9 @@ cached module PointsToInternal {
/* Because calls such as `len` may create a new variable, we need to go via the source variable
* That is perfectly safe as we are only dealing with calls that do not mutate their arguments.
*/
use = uniphi.getInput().getSourceVariable().(Variable).getAUse() and
test = uniphi.getDefiningNode() and
uniphi.getSense() = Conditionals::branchEvaluatesTo(test, use, context, value, origin.toCfgNode())
use = uniphi.getInput().getASourceUse() and
test = uniphi.getTest() and
uniphi.getSense() = Conditionals::testEvaluates(test, use, context, value, origin.toCfgNode())
)
}
@@ -535,7 +540,10 @@ cached module PointsToInternal {
pragma [noinline]
private predicate test_expr_points_to(ControlFlowNode cmp, PointsToContext context, ObjectInternal value) {
value = ObjectInternal::bool(Conditionals::testEvaluatesTo(cmp, _, context, _, _))
exists(ControlFlowNode use |
value = Conditionals::evaluates(cmp, use, context, _, _) and
use != cmp
)
// or
// value = version_tuple_compare(cmp, context)
}
@@ -969,70 +977,99 @@ private predicate potential_builtin_points_to(NameNode f, ObjectInternal value,
module Conditionals {
boolean testEvaluates(ControlFlowNode expr, ControlFlowNode use, PointsToContext context, ObjectInternal value, ControlFlowNode origin) {
//pinode_test(expr, use) and
result = evaluates(expr, use, context, value, origin).booleanValue()
}
ObjectInternal evaluates(ControlFlowNode expr, ControlFlowNode use, PointsToContext context, ObjectInternal val, ControlFlowNode origin) {
PointsToInternal::pointsTo(use, context, val, origin) and
pinode_test(_, use) and expr = use and result = val
or
exists(ControlFlowNode part, ObjectInternal partval |
pinode_test_part(expr, part) and
partval = evaluates(part, use, context, val, origin)
|
result = equalityEvaluates(expr, part, context, partval)
or
result = ObjectInternal::bool(inequalityEvaluatesBoolean(expr, part, context, partval))
or
result = ObjectInternal::bool(isinstance_test_evaluates_boolean(expr, part, context, partval))
or
result = ObjectInternal::bool(issubclass_test_evaluates_boolean(expr, part, context, partval))
or
exists(string attr |
expr.(AttrNode).getObject(attr) = use |
val.attribute(attr, result, _)
or
val.attributesUnknown() and result = ObjectInternal::unknown()
)
or
expr instanceof BinaryExprNode and result = ObjectInternal::unknown()
or
part = not_operand(expr) and result = ObjectInternal::bool(partval.booleanValue().booleanNot())
or
result = evaluatesLen(expr, part, context, partval)
//or
//result = callable_test_evaluates_boolean(expr, use, context, val, origin)
//or
//result = hasattr_test_evaluates_boolean(expr, use, context, val, origin)
)
}
/** Holds if `expr` is the operand of a unary `not` expression. */
private ControlFlowNode not_operand(ControlFlowNode expr) {
expr.(UnaryExprNode).getNode().getOp() instanceof Not and
result = expr.(UnaryExprNode).getOperand()
}
boolean branchEvaluatesTo(ControlFlowNode expr, ControlFlowNode use, PointsToContext context, ObjectInternal val, ControlFlowNode origin) {
contains_interesting_expression_within_test(expr, use) and
PointsToInternal::pointsTo(use, context, val, origin) and
(
expr = use and
val.booleanValue() = result
or
exists(string name, ObjectInternal attr |
expr.(AttrNode).getObject(name) = use |
val.attribute(name, attr, _) and
result = attr.booleanValue()
or
val.attributesUnknown() and
result = maybe()
)
)
or
result = testEvaluatesTo(expr, use, context, val, origin)
or
result = branchEvaluatesTo(not_operand(expr), use, context, val, origin).booleanNot()
}
boolean testEvaluatesTo(ControlFlowNode expr, ControlFlowNode use, PointsToContext context, ObjectInternal val, ControlFlowNode origin) {
result = equalityEvaluatesTo(expr, use, context, val, origin)
or
result = inequalityEvaluatesTo(expr, use, context, val, origin)
or
result = isinstance_test_evaluates_boolean(expr, use, context, val, origin)
or
result = issubclass_test_evaluates_boolean(expr, use, context, val, origin)
//or
//result = callable_test_evaluates_boolean(expr, use, context, val, origin)
//or
//result = hasattr_test_evaluates_boolean(expr, use, context, val, origin)
}
pragma [noinline]
private boolean equalityEvaluatesTo(ControlFlowNode expr, ControlFlowNode use, PointsToContext context, ObjectInternal val, ControlFlowNode origin) {
private ObjectInternal equalityEvaluates(ControlFlowNode expr, ControlFlowNode use, PointsToContext context, ObjectInternal val) {
exists(ControlFlowNode r, boolean sense |
equality_test(expr, use, sense, r) and
pinode_test_part(expr, use) and equality_test(expr, use, sense, r) and
exists(ObjectInternal other |
PointsToInternal::pointsTo(use, context, val, origin) and
PointsToInternal::pointsTo(use, context, val, _) and
PointsToInternal::pointsTo(r, context, other, _) |
val.isComparable() = true and other.isComparable() = true and
(
other = val and result = sense
other = val and result = ObjectInternal::bool(sense)
or
other != val and result = sense.booleanNot()
other != val and result = ObjectInternal::bool(sense.booleanNot())
)
or
val.isComparable() = false and result = maybe()
val.isComparable() = false and result = ObjectInternal::bool(_)
or
other.isComparable() = false and result = maybe()
other.isComparable() = false and result = ObjectInternal::bool(_)
)
)
}
private predicate isinstance_call(CallNode call, ControlFlowNode use, PointsToContext context, ObjectInternal val, ObjectInternal cls, ControlFlowNode origin) {
/** Holds if `c` is a test comparing `x` and `y`. `is` is true if the operator is `is` or `==`, it is false if the operator is `is not` or `!=`. */
private predicate equality_test(CompareNode c, ControlFlowNode x, boolean is, ControlFlowNode y) {
exists(Cmpop op |
c.operands(x, op, y) or
c.operands(y, op, x)
|
(is = true and op instanceof Is or
is = false and op instanceof IsNot or
is = true and op instanceof Eq or
is = false and op instanceof NotEq
)
)
}
pragma [noinline]
private ObjectInternal evaluatesLen(CallNode call, ControlFlowNode use, PointsToContext context, ObjectInternal val) {
pinode_test_part(call, use) and
PointsToInternal::pointsTo(use, context, val, _) and
PointsToInternal::pointsTo(call.getFunction(), context, ObjectInternal::builtin("len"), _) and
result = TInt(val.(SequenceObjectInternal).length())
}
//private
predicate isinstance_call(CallNode call, ControlFlowNode use, PointsToContext context, ObjectInternal val, ObjectInternal cls, ControlFlowNode origin) {
pinode_test_part(call, use) and
PointsToInternal::pointsTo(call.getFunction(), context, ObjectInternal::builtin("isinstance"), _) and
use = call.getArg(0) and
PointsToInternal::pointsTo(use, context, val, origin) and
@@ -1040,9 +1077,10 @@ module Conditionals {
}
pragma [nomagic]
private boolean isinstance_test_evaluates_boolean(CallNode call, ControlFlowNode use, PointsToContext context, ObjectInternal val, ControlFlowNode origin) {
//private
boolean isinstance_test_evaluates_boolean(CallNode call, ControlFlowNode use, PointsToContext context, ObjectInternal val) {
exists(ObjectInternal cls |
isinstance_call(call, use, context, val, cls, origin) |
isinstance_call(call, use, context, val, cls, _) |
result = Types::improperSubclass(val.getClass(), cls)
or
val = ObjectInternal::unknown() and result = maybe()
@@ -1061,9 +1099,10 @@ module Conditionals {
}
pragma [nomagic]
private boolean issubclass_test_evaluates_boolean(CallNode call, ControlFlowNode use, PointsToContext context, ObjectInternal val, ControlFlowNode origin) {
private boolean issubclass_test_evaluates_boolean(CallNode call, ControlFlowNode use, PointsToContext context, ObjectInternal val) {
pinode_test_part(call, use) and
exists(ObjectInternal cls |
issubclass_call(call, use, context, val, cls, origin) |
issubclass_call(call, use, context, val, cls, _) |
result = Types::improperSubclass(val, cls)
or
val = ObjectInternal::unknownClass() and result = maybe()
@@ -1091,7 +1130,8 @@ module Conditionals {
}
pragma [noinline]
private boolean inequalityEvaluatesTo(ControlFlowNode expr, ControlFlowNode use, PointsToContext context, ObjectInternal val, ControlFlowNode origin) {
private boolean inequalityEvaluatesBoolean(ControlFlowNode expr, ControlFlowNode use, PointsToContext context, ObjectInternal val) {
pinode_test_part(expr, use) and
exists(ControlFlowNode r, boolean sense |
exists(boolean strict, ObjectInternal other |
(
@@ -1099,7 +1139,7 @@ module Conditionals {
or
inequality(expr, r, use, strict) and sense = false
) and
PointsToInternal::pointsTo(use, context, val, origin) and
PointsToInternal::pointsTo(use, context, val, _) and
PointsToInternal::pointsTo(r, context, other, _)
|
val.intValue() < other.intValue() and result = sense
@@ -1135,6 +1175,29 @@ module Conditionals {
)
}
private predicate pinode_test(ControlFlowNode test, NameNode use) {
exists(PyEdgeRefinement pi |
pi.getInput().getASourceUse() = use and
pi.getTest() = test and
test.getAChild*() = use
)
or
exists(SingleSuccessorGuard unipi |
unipi.getInput().getASourceUse() = use and
unipi.getTest() = test and
test.getAChild*() = use
)
}
private predicate pinode_test_part(ControlFlowNode outer, ControlFlowNode inner) {
exists(ControlFlowNode test, NameNode use |
pinode_test(test, use) and
test.getAChild*() = outer and
outer.getAChild+() = inner and
inner.getAChild*() = use
)
}
}
cached module Types {