Python points-to: Fully document object classes.

This commit is contained in:
Mark Shannon
2019-05-16 16:18:54 +01:00
parent d6d72dcef4
commit 2f940d013b
11 changed files with 309 additions and 118 deletions

View File

@@ -11,26 +11,33 @@ private import semmle.python.types.Builtins
abstract class CallableObjectInternal extends ObjectInternal {
override int intValue() {
none()
}
/** Gets the name of this callable */
abstract string getName();
override string strValue() {
none()
}
/** Gets the scope of this callable if it has one */
abstract Function getScope();
/** Gets a call to this callable from the given context */
abstract CallNode getACall(PointsToContext ctx);
/** Gets a call to this callable */
CallNode getACall() { result = this.getACall(_) }
override boolean isClass() { result = false }
/** The boolean value of this object, if it has one */
override boolean booleanValue() {
result = true
}
override boolean booleanValue() { result = true }
override ClassDecl getClassDeclaration() {
none()
}
override ClassDecl getClassDeclaration() { none() }
abstract string getName();
pragma [noinline] override predicate binds(ObjectInternal instance, string name, ObjectInternal descriptor) { none() }
abstract NameNode getParameter(int n);
abstract NameNode getParameterByName(string name);
abstract predicate neverReturns();
override int length() { none() }
pragma [noinline] override predicate attribute(string name, ObjectInternal value, CfgOrigin origin) {
none()
@@ -38,27 +45,15 @@ abstract class CallableObjectInternal extends ObjectInternal {
pragma [noinline] override predicate attributesUnknown() { none() }
abstract Function getScope();
pragma [noinline] override predicate binds(ObjectInternal instance, string name, ObjectInternal descriptor) { none() }
abstract CallNode getACall(PointsToContext ctx);
CallNode getACall() { result = this.getACall(_) }
abstract NameNode getParameter(int n);
abstract NameNode getParameterByName(string name);
override int length() { none() }
abstract predicate neverReturns();
override predicate subscriptUnknown() { none() }
override int intValue() { none() }
override string strValue() { none() }
}
/** Class representing Python functions */
class PythonFunctionObjectInternal extends CallableObjectInternal, TPythonFunctionObject {
override Function getScope() {
@@ -176,6 +171,7 @@ private BasicBlock blockReturningNone(Function func) {
}
/** Class representing built-in functions such as `len` or `print`. */
class BuiltinFunctionObjectInternal extends CallableObjectInternal, TBuiltinFunctionObject {
override Builtin getBuiltin() {
@@ -283,7 +279,8 @@ class BuiltinFunctionObjectInternal extends CallableObjectInternal, TBuiltinFunc
}
/** Class representing methods of built-in classes (otherwise known as method-descriptors) such as `list.append`.
*/
class BuiltinMethodObjectInternal extends CallableObjectInternal, TBuiltinMethodObject {
override Builtin getBuiltin() {
@@ -372,6 +369,11 @@ class BuiltinMethodObjectInternal extends CallableObjectInternal, TBuiltinMethod
}
/** Class representing bound-methods.
* Note that built-in methods, such as `[].append` are also represented as bound-methods.
* Although built-in methods and bound-methods are distinct classes in CPython, their behaviour
* is the same and we treat them identically.
*/
class BoundMethodObjectInternal extends CallableObjectInternal, TBoundMethod {
override Builtin getBuiltin() {

View File

@@ -8,23 +8,9 @@ private import semmle.python.pointsto.PointsToContext
private import semmle.python.pointsto.MRO
private import semmle.python.types.Builtins
/** Class representing classes */
abstract class ClassObjectInternal extends ObjectInternal {
override boolean booleanValue() {
result = true
}
override boolean isClass() { result = true }
override int intValue() {
none()
}
override string strValue() {
none()
}
string getName() {
result = this.getClassDeclaration().getName()
}
@@ -33,6 +19,33 @@ abstract class ClassObjectInternal extends ObjectInternal {
result = Types::getMro(this).isSpecial()
}
/** Looks up the attribute `name` on this class.
* Note that this may be different from `this.attr(name)`.
* For example given the class:
* ```class C:
* @classmethod
* def f(cls): pass
* ```
* `this.lookup("f")` is equivent to `C.__dict__['f']`, which is the class-method
* whereas
* `this.attr("f") is equivalent to `C.f`, which is a bound-method.
*/
abstract predicate lookup(string name, ObjectInternal value, CfgOrigin origin);
/** Holds if this is a subclass of the `Iterable` abstract base class. */
boolean isIterableSubclass() {
this = ObjectInternal::builtin("list") and result = true
or
this = ObjectInternal::builtin("set") and result = true
or
this = ObjectInternal::builtin("dict") and result = true
or
this != ObjectInternal::builtin("list") and
this != ObjectInternal::builtin("set") and
this != ObjectInternal::builtin("dict") and
result = false
}
override boolean isDescriptor() { result = false }
pragma [noinline] override predicate descriptorGetClass(ObjectInternal cls, ObjectInternal value, CfgOrigin origin) { none() }
@@ -46,8 +59,6 @@ abstract class ClassObjectInternal extends ObjectInternal {
descriptor.isDescriptor() = true
}
abstract predicate lookup(string name, ObjectInternal value, CfgOrigin origin);
/** Approximation to descriptor protocol, skipping meta-descriptor protocol */
pragma [noinline] override predicate attribute(string name, ObjectInternal value, CfgOrigin origin) {
exists(ObjectInternal descriptor, CfgOrigin desc_origin |
@@ -62,24 +73,25 @@ abstract class ClassObjectInternal extends ObjectInternal {
override int length() { none() }
boolean isIterableSubclass() {
this = ObjectInternal::builtin("list") and result = true
or
this = ObjectInternal::builtin("set") and result = true
or
this = ObjectInternal::builtin("dict") and result = true
or
this != ObjectInternal::builtin("list") and
this != ObjectInternal::builtin("set") and
this != ObjectInternal::builtin("dict") and
result = false
override boolean booleanValue() { result = true }
override boolean isClass() { result = true }
override int intValue() {
none()
}
override string strValue() {
none()
}
override predicate subscriptUnknown() { none() }
}
/** Class representing Python source classes */
class PythonClassObjectInternal extends ClassObjectInternal, TPythonClassObject {
/** Gets the scope for this Python class */
Class getScope() {
exists(ClassExpr expr |
this = TPythonClassObject(expr.getAFlowNode()) and
@@ -141,6 +153,7 @@ class PythonClassObjectInternal extends ClassObjectInternal, TPythonClassObject
}
/** Class representing built-in classes, except `type` */
class BuiltinClassObjectInternal extends ClassObjectInternal, TBuiltinClassObject {
override Builtin getBuiltin() {
@@ -194,7 +207,7 @@ class BuiltinClassObjectInternal extends ClassObjectInternal, TBuiltinClassObjec
}
/** A class representing an unknown class */
class UnknownClassInternal extends ClassObjectInternal, TUnknownClass {
override string toString() {
@@ -243,6 +256,7 @@ class UnknownClassInternal extends ClassObjectInternal, TUnknownClass {
}
/** A class representing the built-in class `type`. */
class TypeInternal extends ClassObjectInternal, TType {
override string toString() {
@@ -292,6 +306,7 @@ class TypeInternal extends ClassObjectInternal, TType {
}
/** A class representing a dynamically created class `type(name, *args, **kwargs)`. */
class DynamicallyCreatedClass extends ClassObjectInternal, TDynamicClass {
override string toString() {

View File

@@ -8,7 +8,10 @@ private import semmle.python.pointsto.PointsToContext
private import semmle.python.types.Builtins
/** Class representing constants.
* Includes `None`, `True` and `False` as
* well as strings and integers.
*/
abstract class ConstantObjectInternal extends ObjectInternal {
override ClassDecl getClassDeclaration() {
@@ -66,7 +69,7 @@ abstract class ConstantObjectInternal extends ObjectInternal {
}
abstract class BooleanObjectInternal extends ConstantObjectInternal {
private abstract class BooleanObjectInternal extends ConstantObjectInternal {
BooleanObjectInternal() {
this = TTrue() or this = TFalse()
@@ -79,9 +82,13 @@ abstract class BooleanObjectInternal extends ConstantObjectInternal {
override int length() { none() }
override string strValue() {
none()
}
}
class TrueObjectInternal extends BooleanObjectInternal, TTrue {
private class TrueObjectInternal extends BooleanObjectInternal, TTrue {
override string toString() {
result = "bool True"
@@ -99,17 +106,13 @@ class TrueObjectInternal extends BooleanObjectInternal, TTrue {
result = 1
}
override string strValue() {
none()
}
override Builtin getBuiltin() {
result = Builtin::special("True")
}
}
class FalseObjectInternal extends BooleanObjectInternal, TFalse {
private class FalseObjectInternal extends BooleanObjectInternal, TFalse {
override string toString() {
result = "bool False"
@@ -127,17 +130,13 @@ class FalseObjectInternal extends BooleanObjectInternal, TFalse {
result = 0
}
override string strValue() {
none()
}
override Builtin getBuiltin() {
result = Builtin::special("False")
}
}
class NoneObjectInternal extends ConstantObjectInternal, TNone {
private class NoneObjectInternal extends ConstantObjectInternal, TNone {
override string toString() {
result = "None"
@@ -172,7 +171,7 @@ class NoneObjectInternal extends ConstantObjectInternal, TNone {
}
class IntObjectInternal extends ConstantObjectInternal, TInt {
private class IntObjectInternal extends ConstantObjectInternal, TInt {
override string toString() {
result = "int " + this.intValue().toString()
@@ -209,7 +208,7 @@ class IntObjectInternal extends ConstantObjectInternal, TInt {
}
class FloatObjectInternal extends ConstantObjectInternal, TFloat {
private class FloatObjectInternal extends ConstantObjectInternal, TFloat {
override string toString() {
if this.floatValue() = this.floatValue().floor() then (
@@ -255,7 +254,7 @@ class FloatObjectInternal extends ConstantObjectInternal, TFloat {
}
class UnicodeObjectInternal extends ConstantObjectInternal, TUnicode {
private class UnicodeObjectInternal extends ConstantObjectInternal, TUnicode {
override string toString() {
result = "'" + this.strValue() + "'"
@@ -296,7 +295,7 @@ class UnicodeObjectInternal extends ConstantObjectInternal, TUnicode {
}
class BytesObjectInternal extends ConstantObjectInternal, TBytes {
private class BytesObjectInternal extends ConstantObjectInternal, TBytes {
override string toString() {
result = "'" + this.strValue() + "'"

View File

@@ -7,8 +7,30 @@ private import semmle.python.pointsto.PointsToContext
private import semmle.python.pointsto.MRO
private import semmle.python.types.Builtins
/** Class representing property objects in Python */
class PropertyInternal extends ObjectInternal, TProperty {
/** Gets the name of this property */
string getName() {
result = this.getGetter().getName()
}
/** Gets the getter function of this property */
CallableObjectInternal getGetter() {
this = TProperty(_, _, result)
}
/** Gets the setter function of this property */
CallableObjectInternal getSetter() {
exists(CallNode call, AttrNode setter |
call.getFunction() = setter and
PointsToInternal::pointsTo(setter.getObject("setter"), this.getContext(), this, _) and
PointsToInternal::pointsTo(call.getArg(0), this.getContext(), result, _)
)
}
private Context getContext() { this = TProperty(_,result, _) }
override string toString() {
result = "property" + this.getName()
}
@@ -25,18 +47,10 @@ class PropertyInternal extends ObjectInternal, TProperty {
override ObjectInternal getClass() { result = ObjectInternal::property() }
CallableObjectInternal getGetter() {
this = TProperty(_, _, result)
}
override boolean isComparable() { result = true }
override Builtin getBuiltin() { none() }
string getName() {
result = this.getGetter().getName()
}
override ControlFlowNode getOrigin() { this = TProperty(result, _, _) }
override predicate callResult(PointsToContext callee, ObjectInternal obj, CfgOrigin origin) { none() }
@@ -58,17 +72,6 @@ class PropertyInternal extends ObjectInternal, TProperty {
override boolean isDescriptor() { result = true }
override int length() { none() }
private Context getContext() { this = TProperty(_,result, _) }
CallableObjectInternal getSetter() {
exists(CallNode call, AttrNode setter |
call.getFunction() = setter and
PointsToInternal::pointsTo(setter.getObject("setter"), this.getContext(), this, _) and
PointsToInternal::pointsTo(call.getArg(0), this.getContext(), result, _)
)
}
pragma [noinline] override predicate binds(ObjectInternal cls, string name, ObjectInternal descriptor) { none() }
pragma [noinline] override predicate descriptorGetClass(ObjectInternal cls, ObjectInternal value, CfgOrigin origin) {
@@ -90,6 +93,7 @@ class PropertyInternal extends ObjectInternal, TProperty {
}
/** A class representing classmethods in Python */
class ClassMethodObjectInternal extends ObjectInternal, TClassMethod {
override string toString() {
@@ -105,6 +109,7 @@ class ClassMethodObjectInternal extends ObjectInternal, TClassMethod {
)
}
/** Gets the function wrapped by this classmethod object */
CallableObjectInternal getFunction() {
this = TClassMethod(_, result)
}

View File

@@ -8,6 +8,7 @@ private import semmle.python.pointsto.MRO
private import semmle.python.pointsto.PointsToContext
private import semmle.python.types.Builtins
/** A class representing instances */
abstract class InstanceObject extends ObjectInternal {
pragma [nomagic]
@@ -36,6 +37,7 @@ abstract class InstanceObject extends ObjectInternal {
)
}
/** Holds if `init` in the context `callee` is the initializer of this instance */
abstract predicate initializer(PythonFunctionObjectInternal init, Context callee);
}
@@ -46,6 +48,9 @@ private predicate self_variable_reaching_init_exit(EssaVariable self) {
self.getScope().getName() = "__init__"
}
/** A class representing instances instantiated at a specific point in the program (statically)
* For example the code `C()` would be a specific instance of `C`.
*/
class SpecificInstanceInternal extends TSpecificInstance, InstanceObject {
override string toString() {
@@ -147,7 +152,8 @@ class SpecificInstanceInternal extends TSpecificInstance, InstanceObject {
}
/** A class representing context-free instances represented by `self` in the source code
*/
class SelfInstanceInternal extends TSelfInstance, InstanceObject {
override string toString() {
@@ -248,7 +254,7 @@ class SelfInstanceInternal extends TSelfInstance, InstanceObject {
}
/** Represents a value that has a known class, but no other information */
/** A class representing a value that has a known class, but no other information */
class UnknownInstanceInternal extends TUnknownInstance, ObjectInternal {
override string toString() {
@@ -360,6 +366,7 @@ private predicate cls_descriptor(ClassObjectInternal cls, string name, ObjectInt
descriptor.isDescriptor() = true
}
/** A class representing an instance of the `super` class */
class SuperInstance extends TSuperInstance, ObjectInternal {
override string toString() {
@@ -375,10 +382,12 @@ class SuperInstance extends TSuperInstance, ObjectInternal {
)
}
/** Gets the class declared as the starting point for MRO lookup. */
ClassObjectInternal getStartClass() {
this = TSuperInstance(_, result)
}
/** Gets 'self' object */
ObjectInternal getSelf() {
this = TSuperInstance(result, _)
}

View File

@@ -7,10 +7,13 @@ private import semmle.python.pointsto.MRO
private import semmle.python.pointsto.PointsToContext
private import semmle.python.types.Builtins
/** A class representing modules */
abstract class ModuleObjectInternal extends ObjectInternal {
/** Gets the name of this module */
abstract string getName();
/** Gets the source scope of this module, if it has one. */
abstract Module getSourceModule();
override predicate callResult(ObjectInternal obj, CfgOrigin origin) {
@@ -47,12 +50,14 @@ abstract class ModuleObjectInternal extends ObjectInternal {
override predicate subscriptUnknown() { any() }
/** Holds if this module is a `__init__.py` module. */
predicate isInitModule() {
any(PackageObjectInternal package).getInitModule() = this
}
}
/** A class representing built-in modules */
class BuiltinModuleObjectInternal extends ModuleObjectInternal, TBuiltinModuleObject {
override Builtin getBuiltin() {
@@ -104,6 +109,7 @@ class BuiltinModuleObjectInternal extends ModuleObjectInternal, TBuiltinModuleOb
}
/** A class representing packages */
class PackageObjectInternal extends ModuleObjectInternal, TPackageObject {
override Builtin getBuiltin() {
@@ -114,6 +120,7 @@ class PackageObjectInternal extends ModuleObjectInternal, TPackageObject {
result = "Package " + this.getName()
}
/** Gets the folder for this package */
Folder getFolder() {
this = TPackageObject(result)
}
@@ -134,6 +141,7 @@ class PackageObjectInternal extends ModuleObjectInternal, TPackageObject {
result.getFile() = this.getFolder().getFile("__init__.py")
}
/** Gets the init module of this package */
PythonModuleObjectInternal getInitModule() {
result = TPythonModule(this.getSourceModule())
}
@@ -145,6 +153,7 @@ class PackageObjectInternal extends ModuleObjectInternal, TPackageObject {
)
}
/** Gets the submodule `name` of this package */
ModuleObjectInternal submodule(string name) {
result.getName() = this.getName() + "." + name
}
@@ -196,6 +205,7 @@ class PackageObjectInternal extends ModuleObjectInternal, TPackageObject {
}
/** A class representing Python modules */
class PythonModuleObjectInternal extends ModuleObjectInternal, TPythonModule {
override Builtin getBuiltin() {
@@ -222,10 +232,6 @@ class PythonModuleObjectInternal extends ModuleObjectInternal, TPythonModule {
this = TPythonModule(result)
}
PythonModuleObjectInternal getInitModule() {
result = TPythonModule(this.getSourceModule())
}
override int intValue() {
none()
}
@@ -250,6 +256,7 @@ class PythonModuleObjectInternal extends ModuleObjectInternal, TPythonModule {
}
/** A class representing a module that is missing from the DB, but inferred to exists from imports. */
class AbsentModuleObjectInternal extends ModuleObjectInternal, TAbsentModule {
override Builtin getBuiltin() {
@@ -279,10 +286,6 @@ class AbsentModuleObjectInternal extends ModuleObjectInternal, TAbsentModule {
none()
}
PythonModuleObjectInternal getInitModule() {
none()
}
override int intValue() {
none()
}
@@ -311,6 +314,7 @@ class AbsentModuleObjectInternal extends ModuleObjectInternal, TAbsentModule {
}
/** A class representing an attribute of a missing module. */
class AbsentModuleAttributeObjectInternal extends ObjectInternal, TAbsentModuleAttribute {
override Builtin getBuiltin() {
@@ -337,10 +341,6 @@ class AbsentModuleAttributeObjectInternal extends ObjectInternal, TAbsentModuleA
none()
}
PythonModuleObjectInternal getInitModule() {
none()
}
override int intValue() {
none()
}

View File

@@ -1,11 +1,25 @@
/**
* Public API for "objects"
* A `Value` is a static approximation to a set of runtime objects.
*/
import python
private import TObject
private import semmle.python.objects.ObjectInternal
private import semmle.python.pointsto.PointsTo
private import semmle.python.pointsto.PointsToContext
/* Use the term `ObjectSource` to refer to DB entity. Either a CFG node
* for Python objects, or `@py_cobject` entity for built-in objects.
*/
class ObjectSource = Object;
/** Class representing values in the Python program
* Each `Value` is a static approximation to a set of one or more real objects.
*/
class Value extends TObject {
Value() {
@@ -18,14 +32,20 @@ class Value extends TObject {
result = this.(ObjectInternal).toString()
}
/** Gets a `ControlFlowNode` that refers to this object. */
ControlFlowNode getAReference() {
PointsToInternal::pointsTo(result, _, this, _)
}
/** Gets the class of this object.
* Strictly, the `Value` representing the class of the objects
* represented by this Value.
*/
Value getClass() {
result = this.(ObjectInternal).getClass()
}
/** Gets a call to this object */
CallNode getACall() {
PointsToInternal::pointsTo(result.getFunction(), _, this, _)
or
@@ -35,6 +55,7 @@ class Value extends TObject {
)
}
/** Gets a call to this object with the given `caller` context. */
CallNode getACall(PointsToContext caller) {
PointsToInternal::pointsTo(result.getFunction(), caller, this, _)
or
@@ -44,15 +65,21 @@ class Value extends TObject {
)
}
/** Gets a `Value` that represents the attribute `name` of this object. */
Value attr(string name) {
this.(ObjectInternal).attribute(name, result, _)
}
/* For backwards compatibility with old API */
/** DEPRECATED: For backwards compatibility with old API
* Use `Value` instead of `ObjectSource`.
*/
deprecated ObjectSource getSource() {
result = this.(ObjectInternal).getSource()
}
/** Holds if this value is builtin. Applies to built-in functions and methods,
* but also integers and strings.
*/
predicate isBuiltin() {
this.(ObjectInternal).isBuiltin()
}
@@ -67,12 +94,20 @@ class Value extends TObject {
}
/** Class representing modules in the Python program
* Each `ModuleValue` represents a module object in the Python program.
*/
class ModuleValue extends Value {
ModuleValue() {
this instanceof ModuleObjectInternal
}
/** Holds if this module "exports" name.
* That is, does it define `name` in `__all__` or is
* `__all__` not defined and `name` a global variable that does not start with "_"
* This is the set of names imported by `from ... import *`.
*/
predicate exports(string name) {
not this.(ModuleObjectInternal).attribute("__all__", _, _) and exists(this.attr(name))
and not name.charAt(0) = "_"
@@ -80,10 +115,12 @@ class ModuleValue extends Value {
py_exports(this.getScope(), name)
}
/** Gets the name of this module */
string getName() {
result = this.(ModuleObjectInternal).getName()
}
/** Gets the scope for this module, provided that it is a Python module. */
Module getScope() {
result = this.(ModuleObjectInternal).getSourceModule()
}
@@ -92,6 +129,7 @@ class ModuleValue extends Value {
module Module {
/** Gets the `ModuleValue` named `name` */
ModuleValue named(string name) {
result.getName() = name
}
@@ -100,6 +138,16 @@ module Module {
module Value {
/** Gets the `Value` named `name`.
* If has at least one '.' in `name`, then the part of
* the name to the left of the rightmost '.' is interpreted as a module name
* and the part after the rightmost '.' as an attribute of that module.
* For example, `Value::named("os.path.join")` is the `Value` representing the function
* `join` in the module `os.path`.
* If there is no '.' in `name`, then the `Value` returned is the builtin
* object of that name.
* For example `Value::named("len")` is the `Value` representing the `len` built-in function.
*/
Value named(string name) {
exists(string modname, string attrname |
name = modname + "." + attrname |
@@ -111,28 +159,40 @@ module Value {
}
/** Class representing callables in the Python program
* Callables include Python functions, built-in functions and bound-methods,
* but not classes.
*/
class CallableValue extends Value {
CallableValue() {
this instanceof CallableObjectInternal
}
/** Holds if this callable never returns once called.
* For example, `sys.exit`
*/
predicate neverReturns() {
this.(CallableObjectInternal).neverReturns()
}
/** Gets the scope for this function, provided that it is a Python function. */
Function getScope() {
result = this.(PythonFunctionObjectInternal).getScope()
}
/** Gets the `n`th parameter node of this callable. */
NameNode getParameter(int n) {
result = this.(CallableObjectInternal).getParameter(n)
}
/** Gets the `name`d parameter node of this callable. */
NameNode getParameterByName(string name) {
result = this.(CallableObjectInternal).getParameterByName(name)
}
/** Gets the argument corresponding to the `n'th parameter node of this callable. */
ControlFlowNode getArgumentForCall(CallNode call, int n) {
exists(ObjectInternal called, int offset |
PointsToInternal::pointsTo(call.getFunction(), _, called, _) and
@@ -147,6 +207,8 @@ class CallableValue extends Value {
)
}
/** Gets the argument corresponding to the `name`d parameter node of this callable. */
ControlFlowNode getNamedArgumentForCall(CallNode call, string name) {
exists(CallableObjectInternal called, int offset |
PointsToInternal::pointsTo(call.getFunction(), _, called, _) and
@@ -167,6 +229,8 @@ class CallableValue extends Value {
}
/** Class representing classes in the Python program, both Python and built-in.
*/
class ClassValue extends Value {
ClassValue() {
@@ -178,6 +242,17 @@ class ClassValue extends Value {
result = Types::getMro(this).getAnItem()
}
/** Looks up the attribute `name` on this class.
* Note that this may be different from `this.attr(name)`.
* For example given the class:
* ```class C:
* @classmethod
* def f(cls): pass
* ```
* `this.lookup("f")` is equivent to `C.__dict__['f']`, which is the class-method
* whereas
* `this.attr("f") is equivalent to `C.f`, which is a bound-method.
*/
Value lookup(string name) {
this.(ClassObjectInternal).lookup(name, result, _)
}

View File

@@ -1,4 +1,7 @@
import python
/**
* Internal object API.
* For use by points-to and testing only.
*/
private import semmle.python.objects.TObject
private import semmle.python.pointsto.PointsTo
@@ -13,6 +16,7 @@ import semmle.python.objects.Constants
import semmle.python.objects.Sequences
import semmle.python.objects.Descriptors
class ObjectInternal extends TObject {
abstract string toString();
@@ -21,14 +25,23 @@ class ObjectInternal extends TObject {
* true and false if the "object" represents a set of possible objects. */
abstract boolean booleanValue();
/** Holds if this object is introduced into the code base at `node` given the `context`
* This means that `node`, in `context`, points-to this object, but the object has not flowed
* there from anywhere else.
* Examples:
* * The object `None` is "introduced" by the keyword "None".
* * A bound method would be "introduced" when relevant attribute on an instance
* is accessed. In `x = X(); x.m` `x.m` introduces the bound method.
*/
abstract predicate introduced(ControlFlowNode node, PointsToContext context);
/** Gets the class declaration for this object, if it is a declared class. */
/** Gets the class declaration for this object, if it is a class with a declaration. */
abstract ClassDecl getClassDeclaration();
/** True if this "object" is a class. */
abstract boolean isClass();
/** Holds if this object is a class. That is, it's class inherits from `type` */
abstract ObjectInternal getClass();
/** True if this "object" can be meaningfully analysed for
@@ -44,7 +57,8 @@ class ObjectInternal extends TObject {
abstract Builtin getBuiltin();
/** Gets a control flow node that represents the source origin of this
* objects.
* objects. Source code objects should attempt to return
* exactly one result for this method.
*/
abstract ControlFlowNode getOrigin();
@@ -68,16 +82,27 @@ class ObjectInternal extends TObject {
*/
abstract string strValue();
/** Holds if the function `scope` is called when this object is called and `paramOffset`
* is the difference from the parameter position and the argument position.
* For a normal function `paramOffset` is 0. For classes and bound-methods it is 1.
*/
abstract predicate calleeAndOffset(Function scope, int paramOffset);
final predicate isBuiltin() {
exists(this.getBuiltin())
}
/** Holds if the result of getting the attribute `name` is `value` and that `value` comes
* from `origin`. Note this is *not* the same as class lookup. For example
* for an object `x` the attribute `name` (`x.name`) may refer to a bound-method, an attribute of the
* instance, or an attribute of the class.
*/
abstract predicate attribute(string name, ObjectInternal value, CfgOrigin origin);
/** Holds if the attributes of this object are wholy or partly unknowable */
abstract predicate attributesUnknown();
/** Holds if the result of subscripting this object are wholy or partly unknowable */
abstract predicate subscriptUnknown();
/** For backwards compatibility shim -- Not all objects have a "source".
@@ -90,11 +115,20 @@ class ObjectInternal extends TObject {
result = this.getBuiltin()
}
/** Holds if this object is a descriptor.
* Holds, for example, for functions and properties and not for integers.
*/
abstract boolean isDescriptor();
/** Holds if the result of attribute access on the class holding this descriptor is `value`, originating at `origin`
* For example, although `T.__dict__['name'] = classmethod(f)`, `T.name` is a bound-method, binding `f` and `T`
*/
pragma[nomagic]
abstract predicate descriptorGetClass(ObjectInternal cls, ObjectInternal value, CfgOrigin origin);
/** Holds if the result of attribute access on an instance of a class holding this descriptor is `value`, originating at `origin`
* For example, with `T.__dict__['name'] = classmethod(f)`, `T().name` is a bound-method, binding `f` and `T`
*/
pragma[nomagic]
abstract predicate descriptorGetInstance(ObjectInternal instance, ObjectInternal value, CfgOrigin origin);
@@ -109,6 +143,10 @@ class ObjectInternal extends TObject {
*/
abstract int length();
/** Holds if the object `function` is called when this object is called and `paramOffset`
* is the difference from the parameter position and the argument position.
* For a normal function `paramOffset` is 0. For classes and bound-methods it is 1.
*/
predicate functionAndOffset(CallableObjectInternal function, int offset) { none() }
/** Holds if this 'object' represents an entity that is inferred to exist

View File

@@ -0,0 +1,35 @@
# Object model for Python analysis
## General idea
Each 'object' in the analysis represents a static approximation to a set of objects in the actual program.
For objects like classes and functions there is a (mostly) one-to-one correspondence.
For instances, bound-methods and other short lived objects, one entity in the analysis represents a set of similar objects.
## APIs
Objects have two APIs; an internal and a user-facing API.
### Internal API
The internal API, exposed through `ObjectInternal` class, provides the predicates necessary for points-to to infer the behaviour of the object. This covers a number of operations:
* Truth tests
* Type tests and type(x) calls
* Attribute access
* Calls
* Subscripting
* Descriptors
* Comparing objects
* Treating the object as an integer or a string
Part of internal API exists to allow other objects to implement the points-to facing part of the API.
For example, behaviour of a (Python) class will determine the behaviour of instances of that class.
### User-facing API
The user-facing API, exposed through `Value` class, provides a higher level API designed for writing queries rather
than implementing points-to. It provides easy access to objects by name and the ability to find calls to, attributes of, and references to objects.

View File

@@ -13,6 +13,7 @@ private import semmle.python.types.Builtins
abstract class SequenceObjectInternal extends ObjectInternal {
/** Gets the `n`th item of this sequence, if one exists. */
abstract ObjectInternal getItem(int n);
/** The boolean value of this object, this may be both
@@ -41,7 +42,7 @@ abstract class TupleObjectInternal extends SequenceObjectInternal {
result = "(" + this.contents(0) + ")"
}
string contents(int n) {
private string contents(int n) {
n = this.length() - 1 and result = this.getItem(n).toString()
or
result = this.getItem(n).toString() + ", " + this.contents(n+1)

View File

@@ -304,8 +304,7 @@ private predicate self_parameter(ParameterDefinition def, PointsToContext contex
)
}
/** INTERNAL -- Use `not cls.isAbstract()` instead. */
cached predicate concrete_class(PythonClassObjectInternal cls) {
private cached predicate concrete_class(PythonClassObjectInternal cls) {
cls.getClass() != abcMetaClassObject()
or
exists(Class c |
@@ -364,12 +363,19 @@ predicate missing_imported_module(ControlFlowNode imp, Context ctx, string name)
)
}
/* Helper for missing modules to determine if name `x.y` is a module `x.y` or
* an attribute `y` of module `x`. This list should be added to as required.
*/
predicate common_module_name(string name) {
name = "zope.interface"
or
name = "six.moves"
}
/** A declaration of a class, either a built-in class or a source definition
* This acts as a helper for ClassObjectInternal allowing some lookup without
* recursion.
*/
library class ClassDecl extends @py_object {
ClassDecl() {
@@ -382,16 +388,19 @@ library class ClassDecl extends @py_object {
result = "ClassDecl"
}
/** Gets the class scope for Python class declarations */
Class getClass() {
result = this.(ControlFlowNode).getNode().(ClassExpr).getInnerScope()
}
/** Holds if this class declares the attribute `name` */
predicate declaresAttribute(string name) {
exists(this.(Builtin).getMember(name))
or
exists(SsaVariable var | name = var.getId() and var.getAUse() = this.getClass().getANormalExit())
}
/** Gets the name of this class */
string getName() {
result = this.(Builtin).getName()
or
@@ -423,6 +432,7 @@ library class ClassDecl extends @py_object {
this = Builtin::builtin("float")
}
/** Holds if for class `C`, `C()` returns an instance of `C` */
predicate callReturnsInstance() {
exists(Class pycls |
pycls = this.getClass() |
@@ -440,6 +450,7 @@ library class ClassDecl extends @py_object {
this instanceof Builtin
}
/** Holds if this class is the abstract base class */
predicate isAbstractBaseClass(string name) {
exists(Module m |
m.getName() = "_abcoll"
@@ -450,5 +461,6 @@ library class ClassDecl extends @py_object {
this.getName() = name
)
}
}