From 9400442bea84f90e44e359443f32ddea1ad29fac Mon Sep 17 00:00:00 2001 From: Max Schaefer Date: Tue, 21 Jan 2020 16:33:13 +0000 Subject: [PATCH 01/12] Add call graph test. This test uses annotations to encode the expected output directly into the source, hence the `.expected` files are trivial. --- .../go/dataflow/CallGraph/getACall.expected | 2 ++ .../semmle/go/dataflow/CallGraph/getACall.ql | 11 ++++++ .../go/dataflow/CallGraph/getACallee.expected | 2 ++ .../go/dataflow/CallGraph/getACallee.ql | 25 +++++++++++++ .../semmle/go/dataflow/CallGraph/main.go | 35 +++++++++++++++++++ 5 files changed, 75 insertions(+) create mode 100644 ql/test/library-tests/semmle/go/dataflow/CallGraph/getACall.expected create mode 100644 ql/test/library-tests/semmle/go/dataflow/CallGraph/getACall.ql create mode 100644 ql/test/library-tests/semmle/go/dataflow/CallGraph/getACallee.expected create mode 100644 ql/test/library-tests/semmle/go/dataflow/CallGraph/getACallee.ql create mode 100644 ql/test/library-tests/semmle/go/dataflow/CallGraph/main.go diff --git a/ql/test/library-tests/semmle/go/dataflow/CallGraph/getACall.expected b/ql/test/library-tests/semmle/go/dataflow/CallGraph/getACall.expected new file mode 100644 index 00000000000..553fdf373eb --- /dev/null +++ b/ql/test/library-tests/semmle/go/dataflow/CallGraph/getACall.expected @@ -0,0 +1,2 @@ +missingCall +spuriousCall diff --git a/ql/test/library-tests/semmle/go/dataflow/CallGraph/getACall.ql b/ql/test/library-tests/semmle/go/dataflow/CallGraph/getACall.ql new file mode 100644 index 00000000000..82e50120d5e --- /dev/null +++ b/ql/test/library-tests/semmle/go/dataflow/CallGraph/getACall.ql @@ -0,0 +1,11 @@ +import go + +query predicate missingCall(DeclaredFunction f, DataFlow::CallNode call) { + call.getACallee() = f.getFuncDecl() and + not call = f.getACall() +} + +query predicate spuriousCall(DeclaredFunction f, DataFlow::CallNode call) { + call = f.getACall() and + exists(FuncDecl fd | fd = f.getFuncDecl() | not call.getACallee() = fd) +} diff --git a/ql/test/library-tests/semmle/go/dataflow/CallGraph/getACallee.expected b/ql/test/library-tests/semmle/go/dataflow/CallGraph/getACallee.expected new file mode 100644 index 00000000000..381d576a20b --- /dev/null +++ b/ql/test/library-tests/semmle/go/dataflow/CallGraph/getACallee.expected @@ -0,0 +1,2 @@ +missingCallee +spuriousCallee diff --git a/ql/test/library-tests/semmle/go/dataflow/CallGraph/getACallee.ql b/ql/test/library-tests/semmle/go/dataflow/CallGraph/getACallee.ql new file mode 100644 index 00000000000..99e85587d55 --- /dev/null +++ b/ql/test/library-tests/semmle/go/dataflow/CallGraph/getACallee.ql @@ -0,0 +1,25 @@ +import go + +/** + * Gets a string `val` such that there is a comment on the same line as `l` + * that contains the substring `key: val`. + */ +string metadata(Locatable l, string key) { + exists(string f, int line, Comment c, string kv | + l.hasLocationInfo(f, line, _, _, _) and + c.hasLocationInfo(f, line, _, _, _) and + kv = c.getText().regexpFind("\\b(\\w+: \\S+)", _, _) and + key = kv.regexpCapture("(\\w+): (\\S+)", 1) and + result = kv.regexpCapture("(\\w+): (\\S+)", 2) + ) +} + +query predicate missingCallee(DataFlow::CallNode call, FuncDef callee) { + metadata(call.asExpr(), "callee") = metadata(callee, "name") and + not call.getACallee() = callee +} + +query predicate spuriousCallee(DataFlow::CallNode call, FuncDef callee) { + call.getACallee() = callee and + not metadata(call.asExpr(), "callee") = metadata(callee, "name") +} diff --git a/ql/test/library-tests/semmle/go/dataflow/CallGraph/main.go b/ql/test/library-tests/semmle/go/dataflow/CallGraph/main.go new file mode 100644 index 00000000000..b9bc5e7ada3 --- /dev/null +++ b/ql/test/library-tests/semmle/go/dataflow/CallGraph/main.go @@ -0,0 +1,35 @@ +package main + +type I interface { + m() // name: I.m +} + +type s1 struct{} + +type s2 struct{} + +type s3 struct{} + +type mybool bool + +func (s1) m() {} // name: s1.m + +func (*s2) m() {} // name: s2.m + +func (s3) m(int) {} // name: s3.m + +func (mybool) m() {} // name: mybool.m + +func test(x *s1, y s2, z s3, b mybool, i I) { + x.m() // callee: s1.m + y.m() // callee: s2.m + z.m(0) // callee: s3.m + b.m() // callee: mybool.m + i.m() // callee: s1.m callee: s2.m callee: mybool.m + s1.m(*x) // callee: s1.m + s3.m(z, 0) // callee: s3.m + mybool.m(b) // callee: mybool.m + (func() {})() // name: func(){} callee: func(){} + id := func(x int) int { return x } // name: id + id(1) // callee: id +} From 93a84684a5eefe0b9df54a69960742cf26dc8e66 Mon Sep 17 00:00:00 2001 From: Max Schaefer Date: Wed, 5 Feb 2020 13:42:06 +0000 Subject: [PATCH 02/12] Remove predicate `CallExpr.calls`. This sort of reasoning should be done at the data-flow level. --- ql/src/semmle/go/Expr.qll | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/ql/src/semmle/go/Expr.qll b/ql/src/semmle/go/Expr.qll index 2fa9559809c..b86c73d3697 100644 --- a/ql/src/semmle/go/Expr.qll +++ b/ql/src/semmle/go/Expr.qll @@ -442,14 +442,6 @@ class CallExpr extends CallOrConversionExpr { /** Gets the expression representing the function being called. */ Expr getCalleeExpr() { result = getChildExpr(0) } - /** Holds if this call is of the form `base.method(...)`. */ - predicate calls(Expr base, string method) { - exists(SelectorExpr callee | callee = getCalleeExpr().stripParens() | - callee.getBase() = base and - method = callee.getSelector().getName() - ) - } - /** Gets the `i`th argument expression of this call (0-based). */ Expr getArgument(int i) { i >= 0 and @@ -462,10 +454,13 @@ class CallExpr extends CallOrConversionExpr { /** Gets the number of argument expressions of this call. */ int getNumArgument() { result = count(getAnArgument()) } - /** Gets the name of the invoked function or method. */ + /** Gets the name of the invoked function or method if it can be determined syntactically. */ string getCalleeName() { - result = getCalleeExpr().stripParens().(Ident).getName() or - calls(_, result) + exists(Expr callee | callee = getCalleeExpr().stripParens() | + result = callee.(Ident).getName() + or + result = callee.(SelectorExpr).getSelector().getName() + ) } /** Gets the declared target of this call. */ From 253a394ae0a6947050c7dfa383c58b8e242f90ae Mon Sep 17 00:00:00 2001 From: Max Schaefer Date: Wed, 5 Feb 2020 13:44:35 +0000 Subject: [PATCH 03/12] Make CallNode.getCalleeName() more robust to missing type information. --- ql/src/semmle/go/dataflow/internal/DataFlowUtil.qll | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ql/src/semmle/go/dataflow/internal/DataFlowUtil.qll b/ql/src/semmle/go/dataflow/internal/DataFlowUtil.qll index b51f85d440e..f12c05ecc43 100644 --- a/ql/src/semmle/go/dataflow/internal/DataFlowUtil.qll +++ b/ql/src/semmle/go/dataflow/internal/DataFlowUtil.qll @@ -268,7 +268,7 @@ class CallNode extends ExprNode { FuncDef getACallee() { result = expr.getACallee() } /** Gets the name of the function or method being called, if it can be determined. */ - string getCalleeName() { result = expr.getTarget().getName() } + string getCalleeName() { result = expr.getTarget().getName() or result = expr.getCalleeName() } /** Gets the data flow node specifying the function to be called. */ Node getCalleeNode() { result = exprNode(expr.getCalleeExpr()) } From cf0e38b22cb386e8061a9bda4f5efb2ff63f1866 Mon Sep 17 00:00:00 2001 From: Max Schaefer Date: Wed, 5 Feb 2020 13:49:54 +0000 Subject: [PATCH 04/12] Move virtual dispatch resolution from `CallExpr` to `CallNode` and generalise it very slightly. --- ql/src/semmle/go/Expr.qll | 14 ++--------- .../go/dataflow/internal/DataFlowUtil.qll | 25 ++++++++++++++++--- 2 files changed, 24 insertions(+), 15 deletions(-) diff --git a/ql/src/semmle/go/Expr.qll b/ql/src/semmle/go/Expr.qll index b86c73d3697..5b080e639c8 100644 --- a/ql/src/semmle/go/Expr.qll +++ b/ql/src/semmle/go/Expr.qll @@ -476,18 +476,8 @@ class CallExpr extends CallOrConversionExpr { * interface type. */ FuncDef getACallee() { - result = getTarget().(DeclaredFunction).getFuncDecl() - or - exists(SelectorExpr sel, InterfaceType declaredRecv, Type actualRecv | - sel = getCalleeExpr().stripParens() and - declaredRecv = sel.getBase().getType().getUnderlyingType() and - actualRecv.implements(declaredRecv) - | - result = actualRecv - .(PointerType) - .getBaseType() - .(NamedType) - .getMethodDecl(sel.getSelector().getName()) + exists(DataFlow::CallNode call | call.asExpr() = this | + result = call.getACallee() ) } diff --git a/ql/src/semmle/go/dataflow/internal/DataFlowUtil.qll b/ql/src/semmle/go/dataflow/internal/DataFlowUtil.qll index f12c05ecc43..ce8a67563ef 100644 --- a/ql/src/semmle/go/dataflow/internal/DataFlowUtil.qll +++ b/ql/src/semmle/go/dataflow/internal/DataFlowUtil.qll @@ -264,8 +264,27 @@ class CallNode extends ExprNode { /** Gets the declared target of this call */ Function getTarget() { result = expr.getTarget() } - /** Get the definition of a possible target of this call. See `CallExpr.getACallee`. */ - FuncDef getACallee() { result = expr.getACallee() } + /** + * Gets the definition of a possible target of this call. + * + * For non-virtual calls, there is at most one possible call target (but there may be none if the + * target has no declaration). + * + * For virtual calls, we look up possible targets in all types that implement the receiver + * interface type. + */ + FuncDef getACallee() { + result = getTarget().(DeclaredFunction).getFuncDecl() + or + exists(DataFlow::Node calleeSource | calleeSource.getASuccessor*() = getCalleeNode() | + exists(Method m, InterfaceType declaredRecv, Type actualRecv | + calleeSource = m.getARead() and + declaredRecv = m.getReceiverType().(NamedType).getBaseType() and + actualRecv.implements(declaredRecv) and + result = actualRecv.getMethod(m.getName()).(DeclaredFunction).getFuncDecl() + ) + ) + } /** Gets the name of the function or method being called, if it can be determined. */ string getCalleeName() { result = expr.getTarget().getName() or result = expr.getCalleeName() } @@ -328,7 +347,7 @@ class MethodCallNode extends CallNode { override Method getTarget() { result = expr.getTarget() } - override MethodDecl getACallee() { result = expr.getACallee() } + override MethodDecl getACallee() { result = super.getACallee() } } /** A representation of a receiver initialization. */ From 84002f585e5d342e24e94aaabf2845c10fa4a84e Mon Sep 17 00:00:00 2001 From: Max Schaefer Date: Wed, 5 Feb 2020 13:52:18 +0000 Subject: [PATCH 05/12] Remove `CallExpr.getACallee()`. --- ql/src/semmle/go/Expr.qll | 15 --------------- .../go/dataflow/internal/DataFlowDispatch.qll | 4 +++- 2 files changed, 3 insertions(+), 16 deletions(-) diff --git a/ql/src/semmle/go/Expr.qll b/ql/src/semmle/go/Expr.qll index 5b080e639c8..3ea5102d7b6 100644 --- a/ql/src/semmle/go/Expr.qll +++ b/ql/src/semmle/go/Expr.qll @@ -466,21 +466,6 @@ class CallExpr extends CallOrConversionExpr { /** Gets the declared target of this call. */ Function getTarget() { this = result.getACallExpr() } - /** - * Gets the definition of a possible target of this call. - * - * For non-virtual calls, there is at most one possible call target (but there may be none if the - * target has no declaration). - * - * For virtual calls, we look up possible targets in all types that implement the receiver - * interface type. - */ - FuncDef getACallee() { - exists(DataFlow::CallNode call | call.asExpr() = this | - result = call.getACallee() - ) - } - override predicate mayHaveOwnSideEffects() { getTarget().mayHaveSideEffects() or not exists(getTarget()) diff --git a/ql/src/semmle/go/dataflow/internal/DataFlowDispatch.qll b/ql/src/semmle/go/dataflow/internal/DataFlowDispatch.qll index c85e26316ca..71ccabec179 100644 --- a/ql/src/semmle/go/dataflow/internal/DataFlowDispatch.qll +++ b/ql/src/semmle/go/dataflow/internal/DataFlowDispatch.qll @@ -7,7 +7,9 @@ DataFlowCallable viableImpl(DataFlowCall ma) { result = viableCallable(ma) } * Gets a function that might be called by `call`. */ DataFlowCallable viableCallable(CallExpr ma) { - result = ma.getACallee() + exists(DataFlow::CallNode c | c.asExpr() = ma | + result = c.getACallee() + ) } /** From 39b7272241cd461546c61474655a45bf99c8cfb7 Mon Sep 17 00:00:00 2001 From: Max Schaefer Date: Wed, 5 Feb 2020 13:56:47 +0000 Subject: [PATCH 06/12] Teach `Function.getACall` to take virtual dispatch into account. --- ql/src/semmle/go/Scopes.qll | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/ql/src/semmle/go/Scopes.qll b/ql/src/semmle/go/Scopes.qll index dc6e8ef52b9..0fc1060e18f 100644 --- a/ql/src/semmle/go/Scopes.qll +++ b/ql/src/semmle/go/Scopes.qll @@ -280,7 +280,11 @@ class Function extends ValueEntity, @functionobject { CallExpr getACallExpr() { result.getCalleeExpr() = getAReference() } /** Gets a call to this function. */ - DataFlow::CallNode getACall() { result.getExpr() = getACallExpr() } + DataFlow::CallNode getACall() { + this = result.getTarget() + or + this.(DeclaredFunction).getFuncDecl() = result.getACallee() + } /** Holds if this function has no observable side effects. */ predicate mayHaveSideEffects() { none() } From 46a8f8c8edeb21618ea9ea909e34988ef829a23d Mon Sep 17 00:00:00 2001 From: Max Schaefer Date: Wed, 5 Feb 2020 13:57:03 +0000 Subject: [PATCH 07/12] Remove `Function.getACallExpr`. --- ql/src/RedundantCode/NegativeLengthCheck.ql | 4 ++-- ql/src/semmle/go/Expr.qll | 2 +- ql/src/semmle/go/Scopes.qll | 3 --- 3 files changed, 3 insertions(+), 6 deletions(-) diff --git a/ql/src/RedundantCode/NegativeLengthCheck.ql b/ql/src/RedundantCode/NegativeLengthCheck.ql index c199d09fbc8..491d286f0b9 100644 --- a/ql/src/RedundantCode/NegativeLengthCheck.ql +++ b/ql/src/RedundantCode/NegativeLengthCheck.ql @@ -16,7 +16,7 @@ where (len = Builtin::len() or len = Builtin::cap()) and ( exists(RelationalComparisonExpr rel | rel = cmp | - rel.getLesserOperand() = len.getACallExpr() and + rel.getLesserOperand() = len.getACall().asExpr() and rel.getGreaterOperand().getIntValue() = ub and ( ub < 0 @@ -27,7 +27,7 @@ where ) or exists(EqualityTestExpr eq | eq = cmp | - eq.getAnOperand() = len.getACallExpr() and + eq.getAnOperand() = len.getACall().asExpr() and eq.getAnOperand().getIntValue() = ub and ub < 0 and r = "equal" diff --git a/ql/src/semmle/go/Expr.qll b/ql/src/semmle/go/Expr.qll index 3ea5102d7b6..857a7e487ef 100644 --- a/ql/src/semmle/go/Expr.qll +++ b/ql/src/semmle/go/Expr.qll @@ -464,7 +464,7 @@ class CallExpr extends CallOrConversionExpr { } /** Gets the declared target of this call. */ - Function getTarget() { this = result.getACallExpr() } + Function getTarget() { getCalleeExpr() = result.getAReference() } override predicate mayHaveOwnSideEffects() { getTarget().mayHaveSideEffects() or diff --git a/ql/src/semmle/go/Scopes.qll b/ql/src/semmle/go/Scopes.qll index 0fc1060e18f..f1407576764 100644 --- a/ql/src/semmle/go/Scopes.qll +++ b/ql/src/semmle/go/Scopes.qll @@ -276,9 +276,6 @@ class Field extends Variable { /** A built-in or declared function. */ class Function extends ValueEntity, @functionobject { - /** Gets an expression representing a call to this function. */ - CallExpr getACallExpr() { result.getCalleeExpr() = getAReference() } - /** Gets a call to this function. */ DataFlow::CallNode getACall() { this = result.getTarget() From f6305f019d498a56a4287bff9891ede61896842f Mon Sep 17 00:00:00 2001 From: Max Schaefer Date: Wed, 5 Feb 2020 14:01:16 +0000 Subject: [PATCH 08/12] Minor refactoring. --- ql/src/semmle/go/dataflow/internal/DataFlowUtil.qll | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/ql/src/semmle/go/dataflow/internal/DataFlowUtil.qll b/ql/src/semmle/go/dataflow/internal/DataFlowUtil.qll index ce8a67563ef..8f1bdc9a04f 100644 --- a/ql/src/semmle/go/dataflow/internal/DataFlowUtil.qll +++ b/ql/src/semmle/go/dataflow/internal/DataFlowUtil.qll @@ -264,6 +264,10 @@ class CallNode extends ExprNode { /** Gets the declared target of this call */ Function getTarget() { result = expr.getTarget() } + private DataFlow::Node getACalleeSource() { + result.getASuccessor*() = getCalleeNode() + } + /** * Gets the definition of a possible target of this call. * @@ -276,7 +280,7 @@ class CallNode extends ExprNode { FuncDef getACallee() { result = getTarget().(DeclaredFunction).getFuncDecl() or - exists(DataFlow::Node calleeSource | calleeSource.getASuccessor*() = getCalleeNode() | + exists(DataFlow::Node calleeSource | calleeSource = getACalleeSource() | exists(Method m, InterfaceType declaredRecv, Type actualRecv | calleeSource = m.getARead() and declaredRecv = m.getReceiverType().(NamedType).getBaseType() and @@ -334,10 +338,7 @@ class CallNode extends ExprNode { /** Gets the data flow node corresponding to the receiver of this call, if any. */ Node getReceiver() { - exists(MethodReadNode mrn | - mrn.getASuccessor*() = this.getCalleeNode() and - result = mrn.getReceiver() - ) + result = getACalleeSource().(MethodReadNode).getReceiver() } } From 8b0d271717b0966d40c52c7c3c71ea83c809097c Mon Sep 17 00:00:00 2001 From: Max Schaefer Date: Wed, 5 Feb 2020 14:01:50 +0000 Subject: [PATCH 09/12] Locally resolve calls to function expressions. --- ql/src/semmle/go/dataflow/internal/DataFlowUtil.qll | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ql/src/semmle/go/dataflow/internal/DataFlowUtil.qll b/ql/src/semmle/go/dataflow/internal/DataFlowUtil.qll index 8f1bdc9a04f..d442bb6520b 100644 --- a/ql/src/semmle/go/dataflow/internal/DataFlowUtil.qll +++ b/ql/src/semmle/go/dataflow/internal/DataFlowUtil.qll @@ -281,6 +281,8 @@ class CallNode extends ExprNode { result = getTarget().(DeclaredFunction).getFuncDecl() or exists(DataFlow::Node calleeSource | calleeSource = getACalleeSource() | + result = calleeSource.asExpr() + or exists(Method m, InterfaceType declaredRecv, Type actualRecv | calleeSource = m.getARead() and declaredRecv = m.getReceiverType().(NamedType).getBaseType() and From 69edfe08dfd0b613ed237dedd979ff39f8407da8 Mon Sep 17 00:00:00 2001 From: Max Schaefer Date: Thu, 6 Feb 2020 12:28:49 +0000 Subject: [PATCH 10/12] Make regular expression for format strings more precise. --- ql/src/semmle/go/StringOps.qll | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/ql/src/semmle/go/StringOps.qll b/ql/src/semmle/go/StringOps.qll index ec5ebdb218c..1064e41fc31 100644 --- a/ql/src/semmle/go/StringOps.qll +++ b/ql/src/semmle/go/StringOps.qll @@ -214,13 +214,17 @@ module StringOps { * width and precision specifiers, but not including `*` specifiers or explicit argument * indices. */ + pragma[noinline] private string getFormatComponentRegex() { - exists(string literal, string opt_flag, string opt_width, string operator, string verb | + exists(string literal, string opt_flag, string width, string prec, string opt_width_and_prec, string operator, string verb | literal = "([^%]|%%)+" and opt_flag = "[-+ #0]?" and - opt_width = "((\\d*|\\*)(\\.(\\d*|\\*))?)?" and + width = "\\d+|\\*" and + prec = "\\.(\\d+|\\*)" and + // either a width followed by an optional prec, or just a prec, or nothing + opt_width_and_prec = "((" + width + ")(" + prec + ")?|(" + prec + "))?" and operator = "[bcdeEfFgGoOpqstTxXUv]" and - verb = "(%" + opt_flag + opt_width + operator + ")" + verb = "(%" + opt_flag + opt_width_and_prec + operator + ")" | result = "(" + literal + "|" + verb + ")" ) From 72de4728a2ddab72c937da0af859f4ca31f5cae3 Mon Sep 17 00:00:00 2001 From: Max Schaefer Date: Fri, 7 Feb 2020 11:09:33 +0000 Subject: [PATCH 11/12] Suppress unhelpful magic. --- ql/src/semmle/go/Scopes.qll | 1 + 1 file changed, 1 insertion(+) diff --git a/ql/src/semmle/go/Scopes.qll b/ql/src/semmle/go/Scopes.qll index f1407576764..9e64ec2bea4 100644 --- a/ql/src/semmle/go/Scopes.qll +++ b/ql/src/semmle/go/Scopes.qll @@ -277,6 +277,7 @@ class Field extends Variable { /** A built-in or declared function. */ class Function extends ValueEntity, @functionobject { /** Gets a call to this function. */ + pragma[nomagic] DataFlow::CallNode getACall() { this = result.getTarget() or From 6aa0d631dd51ce50f3e89099d956a5d5c6fb1683 Mon Sep 17 00:00:00 2001 From: Max Schaefer Date: Mon, 10 Feb 2020 20:59:13 +0000 Subject: [PATCH 12/12] Address review comments. --- ql/src/semmle/go/StringOps.qll | 3 +-- ql/src/semmle/go/dataflow/internal/DataFlowDispatch.qll | 4 +--- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/ql/src/semmle/go/StringOps.qll b/ql/src/semmle/go/StringOps.qll index 1064e41fc31..58b7d6d982b 100644 --- a/ql/src/semmle/go/StringOps.qll +++ b/ql/src/semmle/go/StringOps.qll @@ -221,8 +221,7 @@ module StringOps { opt_flag = "[-+ #0]?" and width = "\\d+|\\*" and prec = "\\.(\\d+|\\*)" and - // either a width followed by an optional prec, or just a prec, or nothing - opt_width_and_prec = "((" + width + ")(" + prec + ")?|(" + prec + "))?" and + opt_width_and_prec = "(" + width + ")?(" + prec + ")?" and operator = "[bcdeEfFgGoOpqstTxXUv]" and verb = "(%" + opt_flag + opt_width_and_prec + operator + ")" | diff --git a/ql/src/semmle/go/dataflow/internal/DataFlowDispatch.qll b/ql/src/semmle/go/dataflow/internal/DataFlowDispatch.qll index 71ccabec179..a44a905490b 100644 --- a/ql/src/semmle/go/dataflow/internal/DataFlowDispatch.qll +++ b/ql/src/semmle/go/dataflow/internal/DataFlowDispatch.qll @@ -7,9 +7,7 @@ DataFlowCallable viableImpl(DataFlowCall ma) { result = viableCallable(ma) } * Gets a function that might be called by `call`. */ DataFlowCallable viableCallable(CallExpr ma) { - exists(DataFlow::CallNode c | c.asExpr() = ma | - result = c.getACallee() - ) + result = DataFlow::exprNode(ma).(DataFlow::CallNode).getACallee() } /**