mirror of
https://github.com/github/codeql.git
synced 2026-04-30 11:15:13 +02:00
Python points-to: Fully document object classes.
This commit is contained in:
@@ -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() {
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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() + "'"
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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, _)
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
@@ -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, _)
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
35
python/ql/src/semmle/python/objects/ObjectModel.md
Normal file
35
python/ql/src/semmle/python/objects/ObjectModel.md
Normal 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.
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user