Skip to content

Commit be5b7bb

Browse files
authored
Merge pull request github#5022 from yoff/python-split-lambdas
Python: Callable for lambdas
2 parents 50f2557 + 2120868 commit be5b7bb

File tree

4 files changed

+44
-1
lines changed

4 files changed

+44
-1
lines changed

python/ql/src/semmle/python/Function.qll

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,16 @@ class Function extends Function_, Scope, AstNode {
3939
exists(YieldFrom y | y.getScope() = this)
4040
}
4141

42+
/**
43+
* Holds if this function represents a lambda.
44+
*
45+
* The extractor reifies each lambda expression as a (local) function with the name
46+
* "lambda". As `lambda` is a keyword in Python, it's impossible to create a function with this
47+
* name otherwise, and so it's impossible to get a non-lambda function accidentally
48+
* classified as a lambda.
49+
*/
50+
predicate isLambda() { this.getName() = "lambda" }
51+
4252
/** Whether this function is declared in a class and is named `__init__` */
4353
predicate isInitMethod() { this.isMethod() and this.getName() = "__init__" }
4454

python/ql/src/semmle/python/dataflow/new/internal/DataFlowPrivate.qll

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -519,10 +519,12 @@ import ArgumentPassing
519519
*/
520520
newtype TDataFlowCallable =
521521
TCallableValue(CallableValue callable) {
522-
callable instanceof FunctionValue
522+
callable instanceof FunctionValue and
523+
not callable.(FunctionValue).isLambda()
523524
or
524525
callable instanceof ClassValue
525526
} or
527+
TLambda(Function lambda) { lambda.isLambda() } or
526528
TModule(Module m)
527529

528530
/** Represents a callable. */
@@ -565,6 +567,27 @@ class DataFlowCallableValue extends DataFlowCallable, TCallableValue {
565567
override CallableValue getCallableValue() { result = callable }
566568
}
567569

570+
/** A class representing a callable lambda. */
571+
class DataFlowLambda extends DataFlowCallable, TLambda {
572+
Function lambda;
573+
574+
DataFlowLambda() { this = TLambda(lambda) }
575+
576+
override string toString() { result = lambda.toString() }
577+
578+
override CallNode getACall() { result = getCallableValue().getACall() }
579+
580+
override Scope getScope() { result = lambda.getEvaluatingScope() }
581+
582+
override NameNode getParameter(int n) { result = getParameter(getCallableValue(), n) }
583+
584+
override string getName() { result = "Lambda callable" }
585+
586+
override FunctionValue getCallableValue() {
587+
result.getOrigin().getNode() = lambda.getDefinition()
588+
}
589+
}
590+
568591
/** A class representing the scope in which a `ModuleVariableNode` appears. */
569592
class DataFlowModuleScope extends DataFlowCallable, TModule {
570593
Module mod;

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -720,6 +720,9 @@ abstract class FunctionValue extends CallableValue {
720720

721721
/** Gets a class that this function may return */
722722
abstract ClassValue getAnInferredReturnType();
723+
724+
/** Holds if this function represents a lambda. */
725+
predicate isLambda() { this.getOrigin().getNode() instanceof Lambda }
723726
}
724727

725728
/** Class representing Python functions */

python/ql/test/experimental/dataflow/consistency/test.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -249,3 +249,10 @@ def synth_arg_kwOverflow():
249249

250250
def synth_arg_kwUnpacked():
251251
overflowCallee(**{"p": "42"})
252+
253+
def split_lambda(cond):
254+
if cond:
255+
pass
256+
foo = lambda x: False
257+
if cond:
258+
pass

0 commit comments

Comments
 (0)