Skip to content

Commit fbe7615

Browse files
authored
Merge pull request #1512 from markshannon/python-better-handling-decorators
Python: Add opaque 'decorated object' object.
2 parents 8251553 + 8570b41 commit fbe7615

File tree

10 files changed

+128
-32
lines changed

10 files changed

+128
-32
lines changed

python/ql/src/semmle/python/Flow.qll

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -480,15 +480,23 @@ class CallNode extends ControlFlowNode {
480480
override Call getNode() { result = super.getNode() }
481481

482482
predicate isDecoratorCall() {
483-
exists(FunctionExpr func |
484-
this.getNode() = func.getADecoratorCall()
485-
)
483+
this.isClassDecoratorCall()
486484
or
485+
this.isFunctionDecoratorCall()
486+
}
487+
488+
predicate isClassDecoratorCall() {
487489
exists(ClassExpr cls |
488490
this.getNode() = cls.getADecoratorCall()
489491
)
490492
}
491493

494+
predicate isFunctionDecoratorCall() {
495+
exists(FunctionExpr func |
496+
this.getNode() = func.getADecoratorCall()
497+
)
498+
}
499+
492500
/** Gets the tuple (*) argument of this call, provided there is exactly one. */
493501
ControlFlowNode getStarArg() {
494502
result.getNode() = this.getNode().getStarArg() and

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

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -465,5 +465,4 @@ class BoundMethodObjectInternal extends CallableObjectInternal, TBoundMethod {
465465
this.getFunction().contextSensitiveCallee()
466466
}
467467

468-
}
469-
468+
}

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

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -523,6 +523,93 @@ module ObjectInternal {
523523

524524
}
525525

526+
class DecoratedFunction extends ObjectInternal, TDecoratedFunction {
527+
528+
529+
CallNode getDecoratorCall() {
530+
this = TDecoratedFunction(result)
531+
}
532+
533+
override Builtin getBuiltin() {
534+
none()
535+
}
536+
537+
private ObjectInternal decoratedObject() {
538+
PointsTo::pointsTo(this.getDecoratorCall().getArg(0), _, result, _)
539+
}
540+
541+
override string getName() {
542+
result = this.decoratedObject().getName()
543+
}
544+
545+
override string toString() {
546+
result = "Decorated " + this.decoratedObject().toString()
547+
}
548+
549+
override boolean booleanValue() { result = true }
550+
551+
override ClassDecl getClassDeclaration() {
552+
none()
553+
}
554+
555+
override boolean isClass() { result = false }
556+
557+
override ObjectInternal getClass() { result = TUnknownClass() }
558+
559+
override predicate introducedAt(ControlFlowNode node, PointsToContext context) {
560+
none()
561+
}
562+
563+
override predicate notTestableForEquality() { none() }
564+
565+
override predicate callResult(PointsToContext callee, ObjectInternal obj, CfgOrigin origin) {
566+
none()
567+
}
568+
569+
override predicate callResult(ObjectInternal obj, CfgOrigin origin) {
570+
obj = ObjectInternal::unknown() and origin = CfgOrigin::unknown()
571+
}
572+
573+
override ControlFlowNode getOrigin() {
574+
result = this.getDecoratorCall()
575+
}
576+
577+
override int intValue() {
578+
none()
579+
}
580+
581+
override string strValue() {
582+
none()
583+
}
584+
585+
override predicate calleeAndOffset(Function scope, int paramOffset) {
586+
none()
587+
}
588+
589+
override predicate attribute(string name, ObjectInternal value, CfgOrigin origin) {
590+
none()
591+
}
592+
593+
override predicate attributesUnknown() { none() }
594+
595+
override predicate subscriptUnknown() { none() }
596+
597+
override boolean isDescriptor() { result = false }
598+
599+
pragma [noinline] override predicate descriptorGetClass(ObjectInternal cls, ObjectInternal value, CfgOrigin origin) { none() }
600+
601+
pragma [noinline] override predicate descriptorGetInstance(ObjectInternal instance, ObjectInternal value, CfgOrigin origin) { none() }
602+
603+
pragma [noinline] override predicate binds(ObjectInternal instance, string name, ObjectInternal descriptor) { none() }
604+
605+
override int length() { none() }
606+
607+
override ObjectInternal getIterNext() { none() }
608+
609+
override predicate contextSensitiveCallee() { none() }
610+
611+
}
612+
526613
/** Helper for boolean predicates returning both `true` and `false` */
527614
boolean maybe() {
528615
result = true or result = false

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -222,6 +222,11 @@ cached newtype TObject =
222222
not common_module_name(modname + "." + attrname)
223223
)
224224
}
225+
or
226+
/* Opaque object representing the result of calling a decorator on a function that we don't understand */
227+
TDecoratedFunction(CallNode call) {
228+
call.isFunctionDecoratorCall()
229+
}
225230

226231
private predicate is_power_2(int n) {
227232
n = 1 or

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

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -833,18 +833,18 @@ module InterProceduralPointsTo {
833833
(value != ObjectInternal::unknown() or not f.isDecoratorCall()) and
834834
call_points_to_from_callee(f, context, value, origin)
835835
or
836-
call_result_is_first_argument(f, context) and
836+
f.isFunctionDecoratorCall() and
837+
call_points_to_from_callee(f, context, ObjectInternal::unknown(), _) and
838+
value = TDecoratedFunction(f) and origin = f
839+
or
840+
f.isClassDecoratorCall() and
841+
call_points_to_from_callee(f, context, ObjectInternal::unknown(), _) and
837842
PointsToInternal::pointsTo(f.getArg(0), context, value, origin)
838843
or
839-
Expressions::typeCallPointsTo(f, context, value, origin, _, _)
840-
}
841-
842-
/** Helper for call_points_to to improve join-order */
843-
private predicate call_result_is_first_argument(CallNode f, PointsToContext context) {
844-
Types::six_add_metaclass(f, context, _, _)
844+
Types::six_add_metaclass(f, context, _, _) and
845+
PointsToInternal::pointsTo(f.getArg(0), context, value, origin)
845846
or
846-
/* A decorator and we don't understand it. Use the original, undecorated value */
847-
f.isDecoratorCall() and call_points_to_from_callee(f, context, ObjectInternal::unknown(), _)
847+
Expressions::typeCallPointsTo(f, context, value, origin, _, _)
848848
}
849849

850850
/** Helper for call_points_to to improve join-order */

python/ql/test/library-tests/PointsTo/decorators/Test.expected

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,18 +13,18 @@
1313
| 16 | ControlFlowNode for func | Function func3 | test.py:31 |
1414
| 16 | ControlFlowNode for functools | Module functools | test.py:1 |
1515
| 17 | ControlFlowNode for args | args | test.py:17 |
16-
| 17 | ControlFlowNode for wrapper | Function wrapper | test.py:17 |
16+
| 17 | ControlFlowNode for wrapper | Attribute()() | test.py:16 |
1717
| 18 | ControlFlowNode for args | args | test.py:17 |
18-
| 20 | ControlFlowNode for wrapper | Function wrapper | test.py:17 |
18+
| 20 | ControlFlowNode for wrapper | Attribute()() | test.py:16 |
1919
| 22 | ControlFlowNode for annotate | Function annotate | test.py:3 |
2020
| 23 | ControlFlowNode for func1 | Function func1 | test.py:23 |
2121
| 26 | ControlFlowNode for wraps1 | Function wraps1 | test.py:9 |
2222
| 27 | ControlFlowNode for func2 | Function wrapper | test.py:10 |
2323
| 30 | ControlFlowNode for wraps2 | Function wraps2 | test.py:15 |
24-
| 31 | ControlFlowNode for func3 | Function wrapper | test.py:17 |
24+
| 31 | ControlFlowNode for func3 | Attribute()() | test.py:16 |
2525
| 41 | ControlFlowNode for func1 | Function func1 | test.py:23 |
2626
| 42 | ControlFlowNode for func2 | Function wrapper | test.py:10 |
27-
| 43 | ControlFlowNode for func3 | Function wrapper | test.py:17 |
27+
| 43 | ControlFlowNode for func3 | Attribute()() | test.py:16 |
2828
| 48 | ControlFlowNode for None | NoneType None | test.py:48 |
2929
| 48 | ControlFlowNode for register | Function register | test.py:48 |
3030
| 49 | ControlFlowNode for decorator | Function decorator | test.py:49 |
@@ -45,8 +45,8 @@
4545
| 60 | ControlFlowNode for foo | Function foo | test.py:60 |
4646
| 63 | ControlFlowNode for foo | Function foo | test.py:60 |
4747
| 65 | ControlFlowNode for register | Function register | test.py:48 |
48-
| 66 | ControlFlowNode for bar | Function bar | test.py:66 |
49-
| 69 | ControlFlowNode for bar | Function bar | test.py:66 |
48+
| 66 | ControlFlowNode for bar | register() | test.py:65 |
49+
| 69 | ControlFlowNode for bar | register() | test.py:65 |
5050
| 71 | ControlFlowNode for register | Function register | test.py:48 |
5151
| 72 | ControlFlowNode for baz | Function baz | test.py:72 |
5252
| 75 | ControlFlowNode for baz | Function baz | test.py:72 |

python/ql/test/library-tests/PointsTo/decorators/Values.expected

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,13 @@
1212
| test.py:16:22:16:25 | ControlFlowNode for func | runtime | Unknown value |
1313
| test.py:16:22:16:25 | ControlFlowNode for func | test.py:30 from import | Function func3 |
1414
| test.py:18:21:18:24 | ControlFlowNode for args | runtime | instance of tuple |
15-
| test.py:20:12:20:18 | ControlFlowNode for wrapper | test.py:30 from import | Function wraps2.wrapper |
15+
| test.py:20:12:20:18 | ControlFlowNode for wrapper | test.py:30 from import | Decorated Function wraps2.wrapper |
1616
| test.py:22:2:22:9 | ControlFlowNode for annotate | import | Function annotate |
1717
| test.py:26:2:26:7 | ControlFlowNode for wraps1 | import | Function wraps1 |
1818
| test.py:30:2:30:7 | ControlFlowNode for wraps2 | import | Function wraps2 |
1919
| test.py:41:1:41:5 | ControlFlowNode for func1 | import | Function func1 |
2020
| test.py:42:1:42:5 | ControlFlowNode for func2 | import | Function wraps1.wrapper |
21-
| test.py:43:1:43:5 | ControlFlowNode for func3 | import | Function wraps2.wrapper |
21+
| test.py:43:1:43:5 | ControlFlowNode for func3 | import | Decorated Function wraps2.wrapper |
2222
| test.py:48:19:48:22 | ControlFlowNode for None | import | None |
2323
| test.py:50:16:50:23 | ControlFlowNode for callable | runtime | Builtin-function callable |
2424
| test.py:50:16:50:23 | ControlFlowNode for callable | test.py:55 from runtime | Builtin-function callable |
@@ -53,6 +53,6 @@
5353
| test.py:59:2:59:9 | ControlFlowNode for register | import | Function register |
5454
| test.py:63:1:63:3 | ControlFlowNode for foo | import | Function foo |
5555
| test.py:65:2:65:9 | ControlFlowNode for register | import | Function register |
56-
| test.py:69:1:69:3 | ControlFlowNode for bar | import | Function bar |
56+
| test.py:69:1:69:3 | ControlFlowNode for bar | import | Decorated Function bar |
5757
| test.py:71:2:71:9 | ControlFlowNode for register | import | Function register |
5858
| test.py:75:1:75:3 | ControlFlowNode for baz | import | Function baz |

python/ql/test/library-tests/PointsTo/general/GlobalPointsTo.expected

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,9 +32,9 @@
3232
| Class X | 36 | ControlFlowNode for classmethod() | classmethod() |
3333
| Class X | 37 | ControlFlowNode for FunctionExpr | Function method1 |
3434
| Class X | 37 | ControlFlowNode for method1 | classmethod() |
35-
| Class X | 40 | ControlFlowNode for deco() | Function method2 |
35+
| Class X | 40 | ControlFlowNode for deco() | deco() |
3636
| Class X | 41 | ControlFlowNode for FunctionExpr | Function method2 |
37-
| Class X | 41 | ControlFlowNode for method2 | Function method2 |
37+
| Class X | 41 | ControlFlowNode for method2 | deco() |
3838
| Module pointsto_test | 17 | ControlFlowNode for Attribute | list object |
3939
| Module pointsto_test | 17 | ControlFlowNode for Compare | bool False |
4040
| Module pointsto_test | 17 | ControlFlowNode for Compare | bool True |
@@ -85,7 +85,7 @@
8585
| Module pointsto_test | 66 | ControlFlowNode for tuple | builtin-class tuple |
8686
| Module pointsto_test | 69 | ControlFlowNode for Attribute | Attribute |
8787
| Module pointsto_test | 69 | ControlFlowNode for X | class X |
88-
| Module pointsto_test | 70 | ControlFlowNode for Attribute | Function method2 |
88+
| Module pointsto_test | 70 | ControlFlowNode for Attribute | deco() |
8989
| Module pointsto_test | 70 | ControlFlowNode for X | class X |
9090
| Module pointsto_test | 72 | ControlFlowNode for ImportExpr | Module abc |
9191
| Module pointsto_test | 72 | ControlFlowNode for ImportMember | Function abstractmethod |

python/ql/test/library-tests/PointsTo/general/LocalPointsTo.expected

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -49,9 +49,9 @@
4949
| 36 | ControlFlowNode for classmethod() | classmethod() |
5050
| 37 | ControlFlowNode for FunctionExpr | Function method1 |
5151
| 37 | ControlFlowNode for method1 | classmethod() |
52-
| 40 | ControlFlowNode for deco() | Function method2 |
52+
| 40 | ControlFlowNode for deco() | deco() |
5353
| 41 | ControlFlowNode for FunctionExpr | Function method2 |
54-
| 41 | ControlFlowNode for method2 | Function method2 |
54+
| 41 | ControlFlowNode for method2 | deco() |
5555
| 44 | ControlFlowNode for FunctionExpr | Function deco |
5656
| 44 | ControlFlowNode for deco | Function deco |
5757
| 47 | ControlFlowNode for v1 | class C |
@@ -93,7 +93,7 @@
9393
| 66 | ControlFlowNode for tuple | builtin-class tuple |
9494
| 69 | ControlFlowNode for Attribute | Attribute |
9595
| 69 | ControlFlowNode for X | class X |
96-
| 70 | ControlFlowNode for Attribute | Function method2 |
96+
| 70 | ControlFlowNode for Attribute | deco() |
9797
| 70 | ControlFlowNode for X | class X |
9898
| 72 | ControlFlowNode for ImportExpr | Module abc |
9999
| 72 | ControlFlowNode for ImportMember | Function abstractmethod |

python/ql/test/library-tests/PointsTo/general/LocalPointsToType.expected

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -51,9 +51,7 @@
5151
| 36 | ControlFlowNode for classmethod() | classmethod() | builtin-class classmethod |
5252
| 37 | ControlFlowNode for FunctionExpr | Function method1 | builtin-class function |
5353
| 37 | ControlFlowNode for method1 | classmethod() | builtin-class classmethod |
54-
| 40 | ControlFlowNode for deco() | Function method2 | builtin-class function |
5554
| 41 | ControlFlowNode for FunctionExpr | Function method2 | builtin-class function |
56-
| 41 | ControlFlowNode for method2 | Function method2 | builtin-class function |
5755
| 44 | ControlFlowNode for FunctionExpr | Function deco | builtin-class function |
5856
| 44 | ControlFlowNode for deco | Function deco | builtin-class function |
5957
| 47 | ControlFlowNode for v1 | class C | builtin-class type |
@@ -96,7 +94,6 @@
9694
| 66 | ControlFlowNode for tuple | builtin-class tuple | builtin-class type |
9795
| 69 | ControlFlowNode for Attribute | Attribute | builtin-class method |
9896
| 69 | ControlFlowNode for X | class X | builtin-class type |
99-
| 70 | ControlFlowNode for Attribute | Function method2 | builtin-class function |
10097
| 70 | ControlFlowNode for X | class X | builtin-class type |
10198
| 72 | ControlFlowNode for ImportExpr | Module abc | builtin-class module |
10299
| 72 | ControlFlowNode for ImportMember | Function abstractmethod | builtin-class function |

0 commit comments

Comments
 (0)