Merge pull request #19165 from joefarebrother/python-qual-loop-var-capture

Python: Modernize the Loop Variable Capture query
This commit is contained in:
Joe Farebrother
2025-04-10 13:07:05 +01:00
committed by GitHub
15 changed files with 216 additions and 145 deletions

View File

@@ -1,8 +0,0 @@
| test.py:5:9:5:20 | FunctionExpr | Capture of loop variable $@. | test.py:4:5:4:23 | For | x |
| test.py:10:6:10:14 | Lambda | Capture of loop variable $@. | test.py:10:5:10:36 | ListComp | i |
| test.py:42:6:42:14 | Lambda | Capture of loop variable $@. | test.py:42:5:42:56 | ListComp | i |
| test.py:43:6:43:14 | Lambda | Capture of loop variable $@. | test.py:43:5:43:56 | ListComp | j |
| test.py:45:6:45:14 | Lambda | Capture of loop variable $@. | test.py:45:5:45:36 | SetComp | i |
| test.py:49:8:49:16 | Lambda | Capture of loop variable $@. | test.py:49:5:49:38 | DictComp | i |
| test.py:57:6:57:14 | Lambda | Capture of loop variable $@. | test.py:57:6:57:35 | GeneratorExp | i |
| test.py:62:10:62:18 | Lambda | Capture of loop variable $@. | test.py:62:10:62:39 | GeneratorExp | i |

View File

@@ -1 +0,0 @@
Variables/LoopVariableCapture.ql

View File

@@ -0,0 +1,19 @@
import python
import Variables.LoopVariableCapture.LoopVariableCaptureQuery
import utils.test.InlineExpectationsTest
module MethodArgTest implements TestSig {
string getARelevantTag() { result = "capturedVar" }
predicate hasActualResult(Location location, string element, string tag, string value) {
exists(CallableExpr capturing, Variable var |
escapingCapture(capturing, _, var, _, _) and
element = capturing.toString() and
location = capturing.getLocation() and
tag = "capturedVar" and
value = var.getId()
)
}
}
import MakeTest<MethodArgTest>

View File

@@ -2,12 +2,12 @@
def bad1():
results = []
for x in range(10):
def inner():
def inner(): # $capturedVar=x
return x
results.append(inner)
results.append(inner)
return results
a = [lambda: i for i in range(1, 4)]
a = [lambda: i for i in range(1, 4)] # $capturedVar=i
for f in a:
print(f())
@@ -18,7 +18,14 @@ def good1():
for y in range(10):
def inner(y=y):
return y
results.append(inner)
results.append(inner)
return results
# OK: Using default argument.
def good2():
results = []
for y in range(10):
results.append(lambda y=y: y)
return results
#Factory function
@@ -39,14 +46,14 @@ def ok1():
result += inner()
return result
b = [lambda: i for i in range(1, 4) for j in range(1,5)]
c = [lambda: j for i in range(1, 4) for j in range(1,5)]
b = [lambda: i for i in range(1, 4) for j in range(1,5)] # $capturedVar=i
c = [lambda: j for i in range(1, 4) for j in range(1,5)] # $capturedVar=j
s = {lambda: i for i in range(1, 4)}
s = {lambda: i for i in range(1, 4)} # $capturedVar=i
for f in s:
print(f())
d = {i:lambda: i for i in range(1, 4)}
d = {i:lambda: i for i in range(1, 4)} # $capturedVar=i
for k, f in d.items():
print(k, f())
@@ -54,14 +61,15 @@ for k, f in d.items():
#When the captured variable is used.
#So technically this is a false positive, but it is extremely fragile
#code, so I (Mark) think it is fine to report it as a violation.
g = (lambda: i for i in range(1, 4))
g = (lambda: i for i in range(1, 4)) # $capturedVar=i
for f in g:
print(f())
#But not if evaluated eagerly
l = list(lambda: i for i in range(1, 4))
l = list(lambda: i for i in range(1, 4)) # $capturedVar=i
for f in l:
print(f())
# This result is MISSING since the lambda is not detected to escape the loop
def odasa4860(asset_ids):
return dict((asset_id, filter(lambda c : c.asset_id == asset_id, xxx)) for asset_id in asset_ids)
return dict((asset_id, filter(lambda c : c.asset_id == asset_id, xxx)) for asset_id in asset_ids) # $MISSING: capturedVar=asset_id