Skip to content

Commit e1b61d3

Browse files
authored
Merge pull request #1423 from markshannon/python-extend-api
Python: Extend the object API.
2 parents cc70817 + 97294e1 commit e1b61d3

File tree

21 files changed

+247
-103
lines changed

21 files changed

+247
-103
lines changed

python/ql/src/semmle/python/objects/Callables.qll

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -270,7 +270,10 @@ class BuiltinFunctionObjectInternal extends CallableObjectInternal, TBuiltinFunc
270270
}
271271

272272
override predicate neverReturns() {
273-
this = Module::named("sys").attr("exit")
273+
exists(ModuleObjectInternal sys |
274+
sys.getName() = "sys" and
275+
sys.attribute("exit", this, _)
276+
)
274277
}
275278

276279
override predicate functionAndOffset(CallableObjectInternal function, int offset) {

python/ql/src/semmle/python/objects/Classes.qll

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ private import semmle.python.types.Builtins
1111
/** Class representing classes */
1212
abstract class ClassObjectInternal extends ObjectInternal {
1313

14-
string getName() {
14+
override string getName() {
1515
result = this.getClassDeclaration().getName()
1616
}
1717

python/ql/src/semmle/python/objects/Constants.qll

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,8 @@ abstract class ConstantObjectInternal extends ObjectInternal {
6767
this = instance
6868
}
6969

70+
override string getName() { none() }
71+
7072
}
7173

7274
private abstract class BooleanObjectInternal extends ConstantObjectInternal {

python/ql/src/semmle/python/objects/Descriptors.qll

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ private import semmle.python.types.Builtins
1111
class PropertyInternal extends ObjectInternal, TProperty {
1212

1313
/** Gets the name of this property */
14-
string getName() {
14+
override string getName() {
1515
result = this.getGetter().getName()
1616
}
1717

@@ -172,6 +172,10 @@ class ClassMethodObjectInternal extends ObjectInternal, TClassMethod {
172172

173173
override int length() { none() }
174174

175+
override string getName() {
176+
result = this.getFunction().getName()
177+
}
178+
175179
}
176180

177181
class StaticMethodObjectInternal extends ObjectInternal, TStaticMethod {
@@ -239,4 +243,8 @@ class StaticMethodObjectInternal extends ObjectInternal, TStaticMethod {
239243

240244
override int length() { none() }
241245

246+
override string getName() {
247+
result = this.getFunction().getName()
248+
}
249+
242250
}

python/ql/src/semmle/python/objects/Instances.qll

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,8 @@ abstract class InstanceObject extends ObjectInternal {
4949
/** Holds if `init` in the context `callee` is the initializer of this instance */
5050
abstract predicate initializer(PythonFunctionObjectInternal init, Context callee);
5151

52+
override string getName() { none() }
53+
5254
}
5355

5456
private predicate self_variable_reaching_init_exit(EssaVariable self) {
@@ -362,6 +364,8 @@ class UnknownInstanceInternal extends TUnknownInstance, ObjectInternal {
362364
result = lengthFromClass(this.getClass())
363365
}
364366

367+
override string getName() { none() }
368+
365369
}
366370

367371
private int lengthFromClass(ClassObjectInternal cls) {
@@ -461,5 +465,7 @@ class SuperInstance extends TSuperInstance, ObjectInternal {
461465
none()
462466
}
463467

468+
override string getName() { none() }
469+
464470
}
465471

python/ql/src/semmle/python/objects/Modules.qll

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,6 @@ private import semmle.python.types.Builtins
1010
/** A class representing modules */
1111
abstract class ModuleObjectInternal extends ObjectInternal {
1212

13-
/** Gets the name of this module */
14-
abstract string getName();
15-
1613
/** Gets the source scope of this module, if it has one. */
1714
abstract Module getSourceModule();
1815

@@ -408,5 +405,8 @@ class AbsentModuleAttributeObjectInternal extends ObjectInternal, TAbsentModuleA
408405
any()
409406
}
410407

408+
/* We know what this is called, but not its innate name */
409+
override string getName() { none() }
410+
411411
}
412412

python/ql/src/semmle/python/objects/ObjectAPI.qll

Lines changed: 124 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,18 @@ private import TObject
1111
private import semmle.python.objects.ObjectInternal
1212
private import semmle.python.pointsto.PointsTo
1313
private import semmle.python.pointsto.PointsToContext
14+
private import semmle.python.pointsto.MRO
1415

1516
/* Use the term `ObjectSource` to refer to DB entity. Either a CFG node
1617
* for Python objects, or `@py_cobject` entity for built-in objects.
1718
*/
1819
class ObjectSource = Object;
1920

21+
/* Aliases for scopes */
22+
class FunctionScope = Function;
23+
class ClassScope = Class;
24+
class ModuleScope = Module;
25+
2026
/** Class representing values in the Python program
2127
* Each `Value` is a static approximation to a set of one or more real objects.
2228
*/
@@ -41,7 +47,7 @@ class Value extends TObject {
4147
* Strictly, the `Value` representing the class of the objects
4248
* represented by this Value.
4349
*/
44-
Value getClass() {
50+
ClassValue getClass() {
4551
result = this.(ObjectInternal).getClass()
4652
}
4753

@@ -87,6 +93,21 @@ class Value extends TObject {
8793
this.(ObjectInternal).isMissing()
8894
}
8995

96+
predicate hasLocationInfo(string filepath, int bl, int bc, int el, int ec) {
97+
this.(ObjectInternal).getOrigin().getLocation().hasLocationInfo(filepath, bl, bc, el, ec)
98+
or
99+
not exists(this.(ObjectInternal).getOrigin()) and
100+
filepath = "" and bl = 0 and bc = 0 and el = 0 and ec = 0
101+
}
102+
103+
/** Gets the name of this value, if it has one.
104+
* Note this is the innate name of the
105+
* object, not necessarily all the names by which it can be called.
106+
*/
107+
final string getName() {
108+
result = this.(ObjectInternal).getName()
109+
}
110+
90111
}
91112

92113
/** Class representing modules in the Python program
@@ -110,37 +131,63 @@ class ModuleValue extends Value {
110131
py_exports(this.getScope(), name)
111132
}
112133

113-
/** Gets the name of this module */
114-
string getName() {
115-
result = this.(ModuleObjectInternal).getName()
116-
}
117-
118134
/** Gets the scope for this module, provided that it is a Python module. */
119-
Module getScope() {
135+
ModuleScope getScope() {
120136
result = this.(ModuleObjectInternal).getSourceModule()
121137
}
122138

139+
/** Gets the container path for this module. Will be the file for a Python module,
140+
* the folder for a package and no result for a builtin module.
141+
*/
142+
Container getPath() {
143+
result = this.(PackageObjectInternal).getFolder()
144+
or
145+
result = this.(PythonModuleObjectInternal).getSourceModule().getFile()
146+
}
147+
123148
}
124149

125150
module Module {
126151

127-
/** Gets the `ModuleValue` named `name` */
152+
/** Gets the `ModuleValue` named `name`.
153+
*
154+
* Note that the name used to refer to a module is not
155+
* necessarily its name. For example,
156+
* there are modules refered to by the name `os.path`,
157+
* but that are not named `os.path`, for example the module `posixpath`.
158+
* Such that the follwing is true:
159+
* `Module::named("os.path").getName() = "posixpath"
160+
*/
128161
ModuleValue named(string name) {
129162
result.getName() = name
163+
or
164+
result = named(name, _)
165+
}
166+
167+
/* Prevent runaway recursion when a module has itself as an attribute. */
168+
private ModuleValue named(string name, int dots) {
169+
dots = 0 and not name.charAt(_) = "." and
170+
result.getName() = name
171+
or
172+
dots <= 3 and
173+
exists(string modname, string attrname |
174+
name = modname + "." + attrname |
175+
result = named(modname, dots-1).attr(attrname)
176+
)
130177
}
131178

132179
}
133180

134181
module Value {
135182

136183
/** Gets the `Value` named `name`.
137-
* If has at least one '.' in `name`, then the part of
184+
* If there is at least one '.' in `name`, then the part of
138185
* the name to the left of the rightmost '.' is interpreted as a module name
139186
* and the part after the rightmost '.' as an attribute of that module.
140-
* For example, `Value::named("os.path.join")` is the `Value` representing the function
141-
* `join` in the module `os.path`.
187+
* For example, `Value::named("os.path.join")` is the `Value` representing the
188+
* `join` function of the `os.path` module.
142189
* If there is no '.' in `name`, then the `Value` returned is the builtin
143-
* object of that name.
190+
* object of that name.
144191
* For example `Value::named("len")` is the `Value` representing the `len` built-in function.
145192
*/
146193
Value named(string name) {
@@ -150,6 +197,12 @@ module Value {
150197
)
151198
or
152199
result = ObjectInternal::builtin(name)
200+
or
201+
name = "None" and result = ObjectInternal::none_()
202+
or
203+
name = "True" and result = TTrue()
204+
or
205+
name = "False" and result = TFalse()
153206
}
154207

155208
}
@@ -171,9 +224,8 @@ class CallableValue extends Value {
171224
this.(CallableObjectInternal).neverReturns()
172225
}
173226

174-
175227
/** Gets the scope for this function, provided that it is a Python function. */
176-
Function getScope() {
228+
FunctionScope getScope() {
177229
result = this.(PythonFunctionObjectInternal).getScope()
178230
}
179231

@@ -252,5 +304,63 @@ class ClassValue extends Value {
252304
this.(ClassObjectInternal).lookup(name, result, _)
253305
}
254306

307+
predicate isCallable() {
308+
this.(ClassObjectInternal).lookup("__call__", _, _)
309+
}
310+
311+
/** Gets the qualified name for this class.
312+
* Should return the same name as the `__qualname__` attribute on classes in Python 3.
313+
*/
314+
string getQualifiedName() {
315+
result = this.(ClassObjectInternal).getBuiltin().getName()
316+
or
317+
result = this.(PythonClassObjectInternal).getScope().getQualifiedName()
318+
}
319+
320+
/** Gets the MRO for this class */
321+
MRO getMro() {
322+
result = Types::getMro(this)
323+
}
324+
325+
predicate failedInference(string reason) {
326+
Types::failedInference(this, reason)
327+
}
328+
329+
/** Gets the nth base class of this class */
330+
ClassValue getBaseType(int n) {
331+
result = Types::getBase(this, n)
332+
}
333+
334+
/** Holds if this class is a new style class.
335+
A new style class is one that implicitly or explicitly inherits from `object`. */
336+
predicate isNewStyle() {
337+
Types::isNewStyle(this)
338+
}
339+
340+
/** Holds if this class is an old style class.
341+
An old style class is one that does not inherit from `object`. */
342+
predicate isOldStyle() {
343+
Types::isOldStyle(this)
344+
}
345+
346+
/** Gets the scope associated with this class, if it is not a builtin class */
347+
ClassScope getScope() {
348+
result = this.(PythonClassObjectInternal).getScope()
349+
}
350+
351+
}
352+
353+
/** A method-resolution-order sequence of classes */
354+
class MRO extends TClassList {
355+
356+
string toString() {
357+
result = this.(ClassList).toString()
358+
}
359+
360+
/** Gets the `n`th class in this MRO */
361+
ClassValue getItem(int n) {
362+
result = this.(ClassList).getItem(n)
363+
}
364+
255365
}
256366

python/ql/src/semmle/python/objects/ObjectInternal.qll

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,12 @@ class ObjectInternal extends TObject {
161161
none()
162162
}
163163

164+
/** Gets the name of this of this object if it has a meaningful name.
165+
* Note that the name of an object is not necessarily the name by which it is called
166+
* For example the function named `posixpath.join` will be called `os.path.join`.
167+
*/
168+
abstract string getName();
169+
164170
}
165171

166172

@@ -240,6 +246,9 @@ class BuiltinOpaqueObjectInternal extends ObjectInternal, TBuiltinOpaqueObject {
240246

241247
override int length() { none() }
242248

249+
override string getName() {
250+
result = this.getBuiltin().getName()
251+
}
243252
}
244253

245254

@@ -315,6 +324,8 @@ class UnknownInternal extends ObjectInternal, TUnknown {
315324

316325
override int length() { result = -1 }
317326

327+
override string getName() { none() }
328+
318329
}
319330

320331
class UndefinedInternal extends ObjectInternal, TUndefined {
@@ -391,6 +402,8 @@ class UndefinedInternal extends ObjectInternal, TUndefined {
391402

392403
override int length() { none() }
393404

405+
override string getName() { none() }
406+
394407
}
395408

396409
module ObjectInternal {

python/ql/src/semmle/python/objects/Sequences.qll

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@ abstract class SequenceObjectInternal extends ObjectInternal {
3030

3131
pragma [noinline] override predicate binds(ObjectInternal instance, string name, ObjectInternal descriptor) { none() }
3232

33+
override string getName() { none() }
34+
3335
}
3436

3537
abstract class TupleObjectInternal extends SequenceObjectInternal {

python/ql/src/semmle/python/pointsto/MRO.qll

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ private import semmle.python.pointsto.PointsToContext
2525
private import semmle.python.types.Builtins
2626

2727

28-
cached private newtype TClassList = Empty()
28+
cached newtype TClassList = Empty()
2929
or
3030
Cons(ClassObjectInternal head, TClassList tail) {
3131
required_cons(head, tail)
@@ -80,12 +80,18 @@ class ClassList extends TClassList {
8080
or
8181
exists(ClassObjectInternal head |
8282
head = this.getHead() |
83-
this.getTail() = Empty() and result = head.getName()
83+
this.getTail() = Empty() and result = className(head)
8484
or
85-
this.getTail() != Empty() and result = head.getName() + ", " + this.getTail().contents()
85+
this.getTail() != Empty() and result = className(head) + ", " + this.getTail().contents()
8686
)
8787
}
8888

89+
private string className(ClassObjectInternal cls) {
90+
result = cls.getName()
91+
or
92+
cls = ObjectInternal::unknownClass() and result = "??"
93+
}
94+
8995
int length() {
9096
this = Empty() and result = 0
9197
or

0 commit comments

Comments
 (0)