Skip to content

Commit fa1d4e6

Browse files
committed
Python: Extract poor mans function resolution (from django)
Since I also want to use this for aiohttp.web modeling
1 parent 85d9483 commit fa1d4e6

File tree

2 files changed

+93
-48
lines changed

2 files changed

+93
-48
lines changed

python/ql/src/semmle/python/frameworks/Django.qll

Lines changed: 3 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ private import semmle.python.Concepts
1111
private import semmle.python.ApiGraphs
1212
private import semmle.python.frameworks.PEP249
1313
private import semmle.python.regex
14+
private import semmle.python.frameworks.internal.PoorMansFunctionResolution
1415

1516
/**
1617
* Provides models for the `django` PyPI package.
@@ -1386,13 +1387,6 @@ private module PrivateDjango {
13861387
// ---------------------------------------------------------------------------
13871388
// Helpers
13881389
// ---------------------------------------------------------------------------
1389-
/**
1390-
* Gets the last decorator call for the function `func`, if `func` has decorators.
1391-
*/
1392-
private Expr lastDecoratorCall(Function func) {
1393-
result = func.getDefinition().(FunctionExpr).getADecoratorCall() and
1394-
not exists(Call other_decorator | other_decorator.getArg(0) = result)
1395-
}
13961390

13971391
/** Adds the `getASelfRef` member predicate when modeling a class. */
13981392
abstract private class SelfRefMixin extends Class {
@@ -1487,45 +1481,6 @@ private module PrivateDjango {
14871481
// ---------------------------------------------------------------------------
14881482
// routing modeling
14891483
// ---------------------------------------------------------------------------
1490-
/**
1491-
* Gets a reference to the Function `func`.
1492-
*
1493-
* The idea is that this function should be used as a route handler when setting up a
1494-
* route, but currently it just tracks all functions, since we can't do type-tracking
1495-
* backwards yet (TODO).
1496-
*/
1497-
private DataFlow::LocalSourceNode djangoRouteHandlerFunctionTracker(
1498-
DataFlow::TypeTracker t, Function func
1499-
) {
1500-
t.start() and
1501-
(
1502-
not exists(func.getADecorator()) and
1503-
result.asExpr() = func.getDefinition()
1504-
or
1505-
// If the function has decorators, we still want to model the function as being
1506-
// the request handler for a route setup. In such situations, we must track the
1507-
// last decorator call instead of the function itself.
1508-
//
1509-
// Note that this means that we blindly ignore what the decorator actually does to
1510-
// the function, which seems like an OK tradeoff.
1511-
result.asExpr() = lastDecoratorCall(func)
1512-
)
1513-
or
1514-
exists(DataFlow::TypeTracker t2 |
1515-
result = djangoRouteHandlerFunctionTracker(t2, func).track(t2, t)
1516-
)
1517-
}
1518-
1519-
/**
1520-
* Gets a reference to the Function `func`.
1521-
*
1522-
* The idea is that this function should be used as a route handler when setting up a
1523-
* route, but currently it just tracks all functions, since we can't do type-tracking
1524-
* backwards yet (TODO).
1525-
*/
1526-
private DataFlow::Node djangoRouteHandlerFunctionTracker(Function func) {
1527-
djangoRouteHandlerFunctionTracker(DataFlow::TypeTracker::end(), func).flowsTo(result)
1528-
}
15291484

15301485
/**
15311486
* In order to recognize a class as being a django view class, based on the `as_view`
@@ -1613,7 +1568,7 @@ private module PrivateDjango {
16131568
*/
16141569
private class DjangoRouteHandler extends Function {
16151570
DjangoRouteHandler() {
1616-
exists(DjangoRouteSetup route | route.getViewArg() = djangoRouteHandlerFunctionTracker(this))
1571+
exists(DjangoRouteSetup route | route.getViewArg() = poorMansFunctionTracker(this))
16171572
or
16181573
any(DjangoViewClass vc).getARequestHandler() = this
16191574
}
@@ -1663,7 +1618,7 @@ private module PrivateDjango {
16631618
abstract DataFlow::Node getViewArg();
16641619

16651620
final override DjangoRouteHandler getARequestHandler() {
1666-
djangoRouteHandlerFunctionTracker(result) = getViewArg()
1621+
poorMansFunctionTracker(result) = getViewArg()
16671622
or
16681623
exists(DjangoViewClass vc |
16691624
getViewArg() = vc.asViewResult() and
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
/**
2+
* INTERNAL: Do not use.
3+
*
4+
* Notice: The predicates provided in this module is a poor mans solution for function
5+
* resolution, and does not handle anything but the most simple cases.
6+
*
7+
* For example, in the code below, we're not able to tell anything about
8+
* `inst.my_method` (which is a bound-method)
9+
* ```py
10+
* class MyClass:
11+
* def my_method(self):
12+
* pass
13+
*
14+
* inst = MyClass()
15+
* print(inst.my_method)
16+
* ```
17+
*/
18+
19+
private import python
20+
private import semmle.python.dataflow.new.DataFlow
21+
22+
/**
23+
* Gets the last decorator call for the function `func`, if `func` has decorators.
24+
*/
25+
private Expr lastDecoratorCall(Function func) {
26+
result = func.getDefinition().(FunctionExpr).getADecoratorCall() and
27+
not exists(Call other_decorator | other_decorator.getArg(0) = result)
28+
}
29+
30+
/**
31+
* Gets a reference to the Function `func`.
32+
*
33+
* Notice: This is a poor mans solution for function resolution, and does not handle
34+
* anything but the most simple cases.
35+
*
36+
* For example, in the code below, we're not able to tell anything about
37+
* `inst.my_method` (which is a bound-method)
38+
* ```py
39+
* class MyClass:
40+
* def my_method(self):
41+
* pass
42+
*
43+
* inst = MyClass()
44+
* print(inst.my_method)
45+
* ```
46+
*/
47+
private DataFlow::LocalSourceNode poorMansFunctionTracker(
48+
DataFlow::TypeTracker t, Function func
49+
) {
50+
t.start() and
51+
(
52+
not exists(func.getADecorator()) and
53+
result.asExpr() = func.getDefinition()
54+
or
55+
// If the function has decorators, we still want to model the function as being
56+
// the request handler for a route setup. In such situations, we must track the
57+
// last decorator call instead of the function itself.
58+
//
59+
// Note that this means that we blindly ignore what the decorator actually does to
60+
// the function, which seems like an OK tradeoff.
61+
result.asExpr() = lastDecoratorCall(func)
62+
)
63+
or
64+
exists(DataFlow::TypeTracker t2 |
65+
result = poorMansFunctionTracker(t2, func).track(t2, t)
66+
)
67+
}
68+
69+
/**
70+
* INTERNAL: Do not use.
71+
*
72+
* Gets a reference to the Function `func`.
73+
*
74+
* Notice: This is a poor mans solution for function resolution, and does not handle
75+
* anything but the most simple cases.
76+
*
77+
* For example, in the code below, we're not able to tell anything about
78+
* `inst.my_method` (which is a bound-method)
79+
* ```py
80+
* class MyClass:
81+
* def my_method(self):
82+
* pass
83+
*
84+
* inst = MyClass()
85+
* print(inst.my_method)
86+
* ```
87+
*/
88+
DataFlow::Node poorMansFunctionTracker(Function func) {
89+
poorMansFunctionTracker(DataFlow::TypeTracker::end(), func).flowsTo(result)
90+
}

0 commit comments

Comments
 (0)