| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
1 parent a4585d8 commit 73bc2d7
3 files changed
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -172,6 +172,8 @@ module TypeTrackingInput implements Shared::TypeTrackingInput<Location> { | |||
| 172 | 172 | /** Holds if there is a level step from `nodeFrom` to `nodeTo`, which does not depend on the call graph. */ | |
| 173 | 173 | predicate levelStepNoCall(Node nodeFrom, LocalSourceNode nodeTo) { | |
| 174 | 174 | TypeTrackerSummaryFlow::levelStepNoCall(nodeFrom, nodeTo) | |
| 175 | + or | ||
| 176 | + localFieldStep(nodeFrom, nodeTo) | ||
| 175 | 177 | } | |
| 176 | 178 | ||
| 177 | 179 | /** | |
@@ -317,6 +319,51 @@ module TypeTrackingInput implements Shared::TypeTrackingInput<Location> { | |||
| 317 | 319 | ) | |
| 318 | 320 | } | |
| 319 | 321 | ||
| 322 | + /** | ||
| 323 | + * Holds if `ref` accesses attribute `attr` of `self`, where `self` is the first | ||
| 324 | + * parameter of an instance method of `cls` (i.e. an access of the form `self.attr`). | ||
| 325 | + * | ||
| 326 | + * Static methods and class methods are excluded, since their first parameter is not a | ||
| 327 | + * `self` instance reference. | ||
| 328 | + */ | ||
| 329 | + private predicate selfAttrRef(Class cls, string attr, DataFlowPublic::AttrRef ref) { | ||
| 330 | + exists(Function method, Name selfUse | | ||
| 331 | + method = cls.getAMethod() and | ||
| 332 | + not DataFlowDispatch::isStaticmethod(method) and | ||
| 333 | + not DataFlowDispatch::isClassmethod(method) and | ||
| 334 | + selfUse.getVariable() = method.getArg(0).(Name).getVariable() and | ||
| 335 | + ref.getObject().asCfgNode().getNode() = selfUse and | ||
| 336 | + ref.mayHaveAttributeName(attr) | ||
| 337 | + ) | ||
| 338 | + } | ||
| 339 | + | ||
| 340 | + /** | ||
| 341 | + * Holds if `nodeFrom` is written to attribute `self.attr` in some instance method of a | ||
| 342 | + * class, and `nodeTo` reads attribute `self.attr` in some (possibly different) instance | ||
| 343 | + * method of the same class. | ||
| 344 | + * | ||
| 345 | + * This models flow through instance attributes (`self.foo`): a value stored into | ||
| 346 | + * `self.foo` in one method can be read from `self.foo` in another method. Type-tracking | ||
| 347 | + * handles the store and read steps via `AttrWrite`/`AttrRead`, but on its own it cannot | ||
| 348 | + * relate the `self` of the writing method to the `self` of the reading method. Following | ||
| 349 | + * the approach used for Ruby and JavaScript, we model this directly as a level step from | ||
| 350 | + * the written value to the read reference, for any pair of methods on the class (not | ||
| 351 | + * just from `__init__`). | ||
| 352 | + * | ||
| 353 | + * This is an over-approximation: it is instance-insensitive (it does not distinguish | ||
| 354 | + * between different instances of the same class) and order-insensitive (it does not | ||
| 355 | + * require the write to happen before the read), matching the precision of | ||
| 356 | + * instance-attribute handling for Ruby and JavaScript. | ||
| 357 | + */ | ||
| 358 | + private predicate localFieldStep(Node nodeFrom, LocalSourceNode nodeTo) { | ||
| 359 | + exists(Class cls, string attr, DataFlowPublic::AttrWrite write, DataFlowPublic::AttrRead read | | ||
| 360 | + selfAttrRef(cls, attr, write) and | ||
| 361 | + nodeFrom = write.getValue() and | ||
| 362 | + selfAttrRef(cls, attr, read) and | ||
| 363 | + nodeTo = read | ||
| 364 | + ) | ||
| 365 | + } | ||
| 366 | + | ||
| 320 | 367 | /** | |
| 321 | 368 | * Holds if data can flow from `node1` to `node2` in a way that discards call contexts. | |
| 322 | 369 | */ | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -151,10 +151,10 @@ def __init__(self): # $ tracked=foo | |||
| 151 | 151 | self.foo = tracked # $ tracked=foo tracked | |
| 152 | 152 | ||
| 153 | 153 | def print_foo(self): # $ MISSING: tracked=foo | |
| 154 | - print(self.foo) # $ MISSING: tracked=foo tracked | ||
| 154 | + print(self.foo) # $ tracked MISSING: tracked=foo | ||
| 155 | 155 | ||
| 156 | 156 | def possibly_uncalled_method(self): # $ MISSING: tracked=foo | |
| 157 | - print(self.foo) # $ MISSING: tracked=foo tracked | ||
| 157 | + print(self.foo) # $ tracked MISSING: tracked=foo | ||
| 158 | 158 | ||
| 159 | 159 | instance = MyClass2() | |
| 160 | 160 | print(instance.foo) # $ MISSING: tracked=foo tracked | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -11,11 +11,10 @@ | |||
| 11 | 11 | ||
| 12 | 12 | # Connection stored in a class attribute (`self._conn`) and used in another method. | |
| 13 | 13 | # | |
| 14 | - # This is currently NOT detected: the `Connection::instance()`/`execute()` predicates in | ||
| 15 | - # PEP249.qll are based on type tracking, which cannot follow a value that is stored into a | ||
| 16 | - # `self` attribute in one method and read from a `self` attribute in another method (see the | ||
| 17 | - # `MISSING` markers below). Regular (global) data flow handles this case correctly, so the | ||
| 18 | - # limitation is specific to the type-tracking-based modeling. | ||
| 14 | + # This is detected because type tracking includes a level step modelling flow through | ||
| 15 | + # instance attributes: a value written to `self._conn` in one method (here `__init__`) can | ||
| 16 | + # be read back from `self._conn` (directly or via a getter) in any other method on the same | ||
| 17 | + # class. This follows the same approach used for instance fields in Ruby and JavaScript. | ||
| 19 | 18 | class Database: | |
| 20 | 19 | def __init__(self): | |
| 21 | 20 | self._conn = dbapi.connect(address="hostname", port=300, user="username") | |
@@ -26,10 +25,10 @@ def get_connection(self): | |||
| 26 | 25 | def run_via_getter(self): | |
| 27 | 26 | conn = self.get_connection() | |
| 28 | 27 | cursor = conn.cursor() | |
| 29 | - cursor.execute("getter sql") # $ MISSING: getSql="getter sql" | ||
| 28 | + cursor.execute("getter sql") # $ getSql="getter sql" | ||
| 30 | 29 | ||
| 31 | 30 | def run_direct(self): | |
| 32 | - self._conn.execute("direct sql") # $ MISSING: getSql="direct sql" | ||
| 31 | + self._conn.execute("direct sql") # $ getSql="direct sql" | ||
| 33 | 32 | ||
| 34 | 33 | ||
| 35 | 34 | db = Database() | |
| Back | FazBrowse Home | New Git URL |
0 commit comments