diff --git a/python/ql/src/semmle/python/Function.qll b/python/ql/src/semmle/python/Function.qll index 4ec4576bcd8..2c09188ccb1 100644 --- a/python/ql/src/semmle/python/Function.qll +++ b/python/ql/src/semmle/python/Function.qll @@ -39,6 +39,16 @@ class Function extends Function_, Scope, AstNode { exists(YieldFrom y | y.getScope() = this) } + /** + * Holds if this function represents a lambda. + * + * The extractor reifies each lambda expression as a (local) function with the name + * "lambda". As `lambda` is a keyword in Python, it's impossible to create a function with this + * name otherwise, and so it's impossible to get a non-lambda function accidentally + * classified as a lambda. + */ + predicate isLambda() { this.getName() = "lambda" } + /** Whether this function is declared in a class and is named `__init__` */ predicate isInitMethod() { this.isMethod() and this.getName() = "__init__" } diff --git a/python/ql/src/semmle/python/dataflow/new/internal/DataFlowPrivate.qll b/python/ql/src/semmle/python/dataflow/new/internal/DataFlowPrivate.qll index 1f8a7a37739..c9675421ba1 100644 --- a/python/ql/src/semmle/python/dataflow/new/internal/DataFlowPrivate.qll +++ b/python/ql/src/semmle/python/dataflow/new/internal/DataFlowPrivate.qll @@ -519,10 +519,12 @@ import ArgumentPassing */ newtype TDataFlowCallable = TCallableValue(CallableValue callable) { - callable instanceof FunctionValue + callable instanceof FunctionValue and + not callable.(FunctionValue).isLambda() or callable instanceof ClassValue } or + TLambda(Function lambda) { lambda.isLambda() } or TModule(Module m) /** Represents a callable. */ @@ -565,6 +567,27 @@ class DataFlowCallableValue extends DataFlowCallable, TCallableValue { override CallableValue getCallableValue() { result = callable } } +/** A class representing a callable lambda. */ +class DataFlowLambda extends DataFlowCallable, TLambda { + Function lambda; + + DataFlowLambda() { this = TLambda(lambda) } + + override string toString() { result = lambda.toString() } + + override CallNode getACall() { result = getCallableValue().getACall() } + + override Scope getScope() { result = lambda.getEvaluatingScope() } + + override NameNode getParameter(int n) { result = getParameter(getCallableValue(), n) } + + override string getName() { result = "Lambda callable" } + + override FunctionValue getCallableValue() { + result.getOrigin().getNode() = lambda.getDefinition() + } +} + /** A class representing the scope in which a `ModuleVariableNode` appears. */ class DataFlowModuleScope extends DataFlowCallable, TModule { Module mod; diff --git a/python/ql/src/semmle/python/objects/ObjectAPI.qll b/python/ql/src/semmle/python/objects/ObjectAPI.qll index faee953abbf..683bcc5ce36 100644 --- a/python/ql/src/semmle/python/objects/ObjectAPI.qll +++ b/python/ql/src/semmle/python/objects/ObjectAPI.qll @@ -720,6 +720,9 @@ abstract class FunctionValue extends CallableValue { /** Gets a class that this function may return */ abstract ClassValue getAnInferredReturnType(); + + /** Holds if this function represents a lambda. */ + predicate isLambda() { this.getOrigin().getNode() instanceof Lambda } } /** Class representing Python functions */ diff --git a/python/ql/test/experimental/dataflow/consistency/test.py b/python/ql/test/experimental/dataflow/consistency/test.py index e22ef663b6f..2402737bf02 100644 --- a/python/ql/test/experimental/dataflow/consistency/test.py +++ b/python/ql/test/experimental/dataflow/consistency/test.py @@ -249,3 +249,10 @@ def synth_arg_kwOverflow(): def synth_arg_kwUnpacked(): overflowCallee(**{"p": "42"}) + +def split_lambda(cond): + if cond: + pass + foo = lambda x: False + if cond: + pass