From f4a24b035375edb153503a69a1165330c4d2b64c Mon Sep 17 00:00:00 2001
From: Max Schaefer
Date: Tue, 26 Nov 2019 16:19:17 +0000
Subject: [PATCH 1/3] Improve type information for tuple elements.
We would previously rely on the type information of the target variable into which the element is stored, but that could be a more general type.
For example, in the assignment
```go
x, y := f()
```
the type of `x` might be an interface while the type of `f()[0]` is a concrete type implementing that interface.
---
ql/src/semmle/go/controlflow/IR.qll | 70 +++++++++++++++++++----------
1 file changed, 46 insertions(+), 24 deletions(-)
diff --git a/ql/src/semmle/go/controlflow/IR.qll b/ql/src/semmle/go/controlflow/IR.qll
index a8d498fb016..7de15bb3b1d 100644
--- a/ql/src/semmle/go/controlflow/IR.qll
+++ b/ql/src/semmle/go/controlflow/IR.qll
@@ -552,42 +552,64 @@ module IR {
ExtractTupleElementInstruction() { this = MkExtractNode(s, i) }
- /** Holds if this extracts the `idx`th value of the result of `base`. */
- predicate extractsElement(Instruction base, int idx) {
+ Instruction getBase() {
exists(Expr baseExpr |
baseExpr = s.(Assignment).getRhs() or
baseExpr = s.(ValueSpec).getInit()
|
- base = evalExprInstruction(baseExpr) and
- idx = i
+ result = evalExprInstruction(baseExpr)
)
or
- base = MkNextNode(s) and
- idx = i
+ result = MkNextNode(s)
or
- base = evalExprInstruction(s.(ReturnStmt).getExpr()) and
- idx = i
+ result = evalExprInstruction(s.(ReturnStmt).getExpr())
or
- base = evalExprInstruction(s.(CallExpr).getArgument(0)) and
- idx = i
+ result = evalExprInstruction(s.(CallExpr).getArgument(0).stripParens())
+ }
+
+ /** Holds if this extracts the `idx`th value of the result of `base`. */
+ predicate extractsElement(Instruction base, int idx) {
+ base = getBase() and idx = i
}
override Type getResultType() {
- result = s.(Assignment).getLhs(i).getType()
+ exists(CallExpr c | getBase() = evalExprInstruction(c) |
+ result = c.getTarget().getResultType(i)
+ )
or
- result = s.(ValueSpec).getNameExpr(i).getType()
- or
- i = 0 and
- result = s.(RangeStmt).getKey().getType()
- or
- i = 1 and
- result = s.(RangeStmt).getValue().getType()
- or
- result = s.(ReturnStmt).getEnclosingFunction().getType().getResultType(i)
- or
- exists(CallExpr inner |
- inner = s.(CallExpr).getArgument(0).stripParens() and
- result = inner.getTarget().getResultType(i)
+ exists(Type rangeType | rangeType = s.(RangeStmt).getDomain().getType().getUnderlyingType() |
+ exists(Type baseType |
+ baseType = rangeType.(ArrayType).getElementType() or
+ baseType = rangeType.(PointerType).getBaseType().getUnderlyingType().(ArrayType).getElementType() or
+ baseType = rangeType.(SliceType).getElementType() |
+ i = 0 and
+ result instanceof IntType
+ or
+ i = 1 and
+ result = baseType
+ )
+ or
+ rangeType instanceof StringType and
+ (
+ i = 0 and
+ result instanceof IntType
+ or
+ result = Builtin::rune().getType()
+ )
+ or
+ exists(MapType map | map = rangeType |
+ i = 0 and
+ result = map.getKeyType()
+ or
+ i = 1 and
+ result = map.getValueType()
+ )
+ or
+ i = 0 and
+ result = rangeType.(RecvChanType).getElementType()
+ or
+ i = 0 and
+ result = rangeType.(SendRecvChanType).getElementType()
)
}
From e5a12e97381c8170bb0b377c5f934dab4243f198 Mon Sep 17 00:00:00 2001
From: Max Schaefer
Date: Tue, 26 Nov 2019 16:22:32 +0000
Subject: [PATCH 2/3] Add new query ImpossibleInterfaceNilCheck.
---
change-notes/1.24/analysis-go.md | 1 +
.../ImpossibleInterfaceNilCheck.go | 16 ++++++
.../ImpossibleInterfaceNilCheck.qhelp | 48 ++++++++++++++++++
.../ImpossibleInterfaceNilCheck.ql | 49 +++++++++++++++++++
.../ImpossibleInterfaceNilCheckGood.go | 12 +++++
.../ImpossibleInterfaceNilCheck.expected | 2 +
.../ImpossibleInterfaceNilCheck.go | 14 ++++++
.../ImpossibleInterfaceNilCheck.qlref | 1 +
.../ImpossibleInterfaceNilCheckGood.go | 12 +++++
.../ImpossibleInterfaceNilCheck/lib.go | 11 +++++
.../ImpossibleInterfaceNilCheck/tst.go | 27 ++++++++++
11 files changed, 193 insertions(+)
create mode 100644 ql/src/RedundantCode/ImpossibleInterfaceNilCheck.go
create mode 100644 ql/src/RedundantCode/ImpossibleInterfaceNilCheck.qhelp
create mode 100644 ql/src/RedundantCode/ImpossibleInterfaceNilCheck.ql
create mode 100644 ql/src/RedundantCode/ImpossibleInterfaceNilCheckGood.go
create mode 100644 ql/test/query-tests/RedundantCode/ImpossibleInterfaceNilCheck/ImpossibleInterfaceNilCheck.expected
create mode 100644 ql/test/query-tests/RedundantCode/ImpossibleInterfaceNilCheck/ImpossibleInterfaceNilCheck.go
create mode 100644 ql/test/query-tests/RedundantCode/ImpossibleInterfaceNilCheck/ImpossibleInterfaceNilCheck.qlref
create mode 100644 ql/test/query-tests/RedundantCode/ImpossibleInterfaceNilCheck/ImpossibleInterfaceNilCheckGood.go
create mode 100644 ql/test/query-tests/RedundantCode/ImpossibleInterfaceNilCheck/lib.go
create mode 100644 ql/test/query-tests/RedundantCode/ImpossibleInterfaceNilCheck/tst.go
diff --git a/change-notes/1.24/analysis-go.md b/change-notes/1.24/analysis-go.md
index 1e9b8c40bd0..5a8c9570cdb 100644
--- a/change-notes/1.24/analysis-go.md
+++ b/change-notes/1.24/analysis-go.md
@@ -5,6 +5,7 @@
| **Query** | **Tags** | **Purpose** |
|---------------------------------------------------------------------------|----------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------|
| Constant length comparison (`go/constant-length-comparison`) | correctness | Highlights code that checks the length of an array or slice against a constant before indexing it using a variable, suggesting a logic error. Results are shown on LGTM by default. |
+| Impossible interface nil check (`go/impossible-interface-nil-check`) | correctness | Highlights code that compares an interface value that cannot be `nil` to `nil`, suggesting a logic error. Results are shown on LGTM by default. |
## Changes to existing queries
diff --git a/ql/src/RedundantCode/ImpossibleInterfaceNilCheck.go b/ql/src/RedundantCode/ImpossibleInterfaceNilCheck.go
new file mode 100644
index 00000000000..04ed671a6fc
--- /dev/null
+++ b/ql/src/RedundantCode/ImpossibleInterfaceNilCheck.go
@@ -0,0 +1,16 @@
+package main
+
+import "fmt"
+
+func fetch(url string) (string, *RequestError)
+
+func niceFetch(url string) {
+ var s string
+ var e error
+ s, e = fetch(url)
+ if e != nil {
+ fmt.Printf("Unable to fetch URL: %v\n", e)
+ } else {
+ fmt.Printf("URL contents: %s\n", s)
+ }
+}
diff --git a/ql/src/RedundantCode/ImpossibleInterfaceNilCheck.qhelp b/ql/src/RedundantCode/ImpossibleInterfaceNilCheck.qhelp
new file mode 100644
index 00000000000..7a08f4ed14a
--- /dev/null
+++ b/ql/src/RedundantCode/ImpossibleInterfaceNilCheck.qhelp
@@ -0,0 +1,48 @@
+
+
+
+
+
+Interface values in Go are type tagged, that is, they are essentially pairs of the form
+(value, type), where value is a non-interface value with the given
+type. Such a pair is never nil, even if value is
+nil.
+
+
+In particular, if a non-interface value v is assigned to a variable x
+whose type is an interface, then x will never be nil, regardless of
+v. Comparing x to nil is pointless, and may indicate
+a misunderstanding of Go interface values or some other underlying bug.
+
+
+
+
+
+Carefully inspect the comparison to ensure it is not a symptom of a bug.
+
+
+
+
+
+The following example shows a declaration of a function fetch which fetches the
+contents of a URL, returning either the contents or an error value, which is a pointer to a custom
+error type RequestError (not shown). The function niceFetch wraps this
+function, printing out either the URL contents or an error message.
+
+
+
+However, because e is declared to be of type error, which is an
+interface, the nil check will never succeed, since e can never be
+nil.
+
+
+In this case, the problem can be solved by using a short variable declaration using operator
+:=, which will automatically infer the more precise non-interface type
+*ResourceError for e, making the nil check behave as
+expected.
+
+
+
+
diff --git a/ql/src/RedundantCode/ImpossibleInterfaceNilCheck.ql b/ql/src/RedundantCode/ImpossibleInterfaceNilCheck.ql
new file mode 100644
index 00000000000..0f6153c34cf
--- /dev/null
+++ b/ql/src/RedundantCode/ImpossibleInterfaceNilCheck.ql
@@ -0,0 +1,49 @@
+/**
+ * @name Impossible interface nil check
+ * @description Comparing a non-nil interface value to nil may indicate a logic error.
+ * @kind problem
+ * @problem.severity warning
+ * @id go/impossible-interface-nil-check
+ * @tags correctness
+ * @precision high
+ */
+
+import go
+
+/**
+ * Holds if `eq` compares interface value `nd` to `nil`.
+ */
+predicate interfaceNilCheck(DataFlow::EqualityTestNode eq, DataFlow::Node nd) {
+ nd = eq.getAnOperand() and
+ nd.getType().getUnderlyingType() instanceof InterfaceType and
+ eq.getAnOperand().getType() instanceof NilLiteralType
+}
+
+/**
+ * Holds if the result of `nd` may flow to an interface `nil` check.
+ */
+predicate flowsToInterfaceNilCheck(DataFlow::Node nd) {
+ interfaceNilCheck(_, nd) or
+ flowsToInterfaceNilCheck(nd.getASuccessor())
+}
+
+/**
+ * Holds if `nd` is a non-nil interface value.
+ */
+predicate nonNilWrapper(DataFlow::Node nd) {
+ flowsToInterfaceNilCheck(nd) and
+ forex(DataFlow::Node pred | pred = nd.getAPredecessor() |
+ exists(Type predtp | predtp = pred.getType().getUnderlyingType() |
+ not predtp instanceof InterfaceType and
+ not predtp instanceof NilLiteralType
+ )
+ or
+ nonNilWrapper(pred)
+ )
+}
+
+from DataFlow::EqualityTestNode eq, DataFlow::Node nd
+where
+ interfaceNilCheck(eq, nd) and
+ nonNilWrapper(nd)
+select nd, "This value can never be nil, since it is a wrapped interface value."
diff --git a/ql/src/RedundantCode/ImpossibleInterfaceNilCheckGood.go b/ql/src/RedundantCode/ImpossibleInterfaceNilCheckGood.go
new file mode 100644
index 00000000000..f0b74568e6b
--- /dev/null
+++ b/ql/src/RedundantCode/ImpossibleInterfaceNilCheckGood.go
@@ -0,0 +1,12 @@
+package main
+
+import "fmt"
+
+func niceFetchGood(url string) {
+ s, e := fetch(url)
+ if e != nil {
+ fmt.Printf("Unable to fetch URL: %v\n", e)
+ } else {
+ fmt.Printf("URL contents: %s\n", s)
+ }
+}
diff --git a/ql/test/query-tests/RedundantCode/ImpossibleInterfaceNilCheck/ImpossibleInterfaceNilCheck.expected b/ql/test/query-tests/RedundantCode/ImpossibleInterfaceNilCheck/ImpossibleInterfaceNilCheck.expected
new file mode 100644
index 00000000000..e1294f702e2
--- /dev/null
+++ b/ql/test/query-tests/RedundantCode/ImpossibleInterfaceNilCheck/ImpossibleInterfaceNilCheck.expected
@@ -0,0 +1,2 @@
+| ImpossibleInterfaceNilCheck.go:9:5:9:5 | e | This value can never be nil, since it is a wrapped interface value. |
+| tst.go:10:14:10:14 | y | This value can never be nil, since it is a wrapped interface value. |
diff --git a/ql/test/query-tests/RedundantCode/ImpossibleInterfaceNilCheck/ImpossibleInterfaceNilCheck.go b/ql/test/query-tests/RedundantCode/ImpossibleInterfaceNilCheck/ImpossibleInterfaceNilCheck.go
new file mode 100644
index 00000000000..00b015d3814
--- /dev/null
+++ b/ql/test/query-tests/RedundantCode/ImpossibleInterfaceNilCheck/ImpossibleInterfaceNilCheck.go
@@ -0,0 +1,14 @@
+package main
+
+import "fmt"
+
+func niceFetch(url string) {
+ var s string
+ var e error
+ s, e = fetch(url)
+ if e != nil {
+ fmt.Printf("Unable to fetch URL: %v\n", e)
+ } else {
+ fmt.Printf("URL contents: %s\n", s)
+ }
+}
diff --git a/ql/test/query-tests/RedundantCode/ImpossibleInterfaceNilCheck/ImpossibleInterfaceNilCheck.qlref b/ql/test/query-tests/RedundantCode/ImpossibleInterfaceNilCheck/ImpossibleInterfaceNilCheck.qlref
new file mode 100644
index 00000000000..d858724be57
--- /dev/null
+++ b/ql/test/query-tests/RedundantCode/ImpossibleInterfaceNilCheck/ImpossibleInterfaceNilCheck.qlref
@@ -0,0 +1 @@
+RedundantCode/ImpossibleInterfaceNilCheck.ql
diff --git a/ql/test/query-tests/RedundantCode/ImpossibleInterfaceNilCheck/ImpossibleInterfaceNilCheckGood.go b/ql/test/query-tests/RedundantCode/ImpossibleInterfaceNilCheck/ImpossibleInterfaceNilCheckGood.go
new file mode 100644
index 00000000000..f0b74568e6b
--- /dev/null
+++ b/ql/test/query-tests/RedundantCode/ImpossibleInterfaceNilCheck/ImpossibleInterfaceNilCheckGood.go
@@ -0,0 +1,12 @@
+package main
+
+import "fmt"
+
+func niceFetchGood(url string) {
+ s, e := fetch(url)
+ if e != nil {
+ fmt.Printf("Unable to fetch URL: %v\n", e)
+ } else {
+ fmt.Printf("URL contents: %s\n", s)
+ }
+}
diff --git a/ql/test/query-tests/RedundantCode/ImpossibleInterfaceNilCheck/lib.go b/ql/test/query-tests/RedundantCode/ImpossibleInterfaceNilCheck/lib.go
new file mode 100644
index 00000000000..e38679df10b
--- /dev/null
+++ b/ql/test/query-tests/RedundantCode/ImpossibleInterfaceNilCheck/lib.go
@@ -0,0 +1,11 @@
+package main
+
+type RequestError string
+
+func (e RequestError) Error() string {
+ return string(e)
+}
+
+func fetch(url string) (string, *RequestError) {
+ return "stuff", nil
+}
diff --git a/ql/test/query-tests/RedundantCode/ImpossibleInterfaceNilCheck/tst.go b/ql/test/query-tests/RedundantCode/ImpossibleInterfaceNilCheck/tst.go
new file mode 100644
index 00000000000..81584045c13
--- /dev/null
+++ b/ql/test/query-tests/RedundantCode/ImpossibleInterfaceNilCheck/tst.go
@@ -0,0 +1,27 @@
+package main
+
+import "fmt"
+
+func test1() {
+ var x *int = nil
+ var y interface{} = x
+ fmt.Println(x == nil)
+ fmt.Println(x == y)
+ fmt.Println(y == nil) // NOT OK
+}
+
+func test2() {
+ var x *int = nil
+ test3(x)
+}
+
+func test3(y interface{}) {
+ // we don't want to flag this one, even though we might be able to
+ // inter-procedurally establish that y cannot be nil
+ fmt.Println(y == nil) // OK
+}
+
+func test4() {
+ var y interface{}
+ fmt.Println(y == nil) // OK
+}
From ba54cde86e623e4b3c8741206c2db4095ce09d92 Mon Sep 17 00:00:00 2001
From: Max Schaefer
Date: Wed, 27 Nov 2019 10:47:42 +0000
Subject: [PATCH 3/3] Add two references.
---
ql/src/RedundantCode/ImpossibleInterfaceNilCheck.qhelp | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/ql/src/RedundantCode/ImpossibleInterfaceNilCheck.qhelp b/ql/src/RedundantCode/ImpossibleInterfaceNilCheck.qhelp
index 7a08f4ed14a..b4305e99298 100644
--- a/ql/src/RedundantCode/ImpossibleInterfaceNilCheck.qhelp
+++ b/ql/src/RedundantCode/ImpossibleInterfaceNilCheck.qhelp
@@ -45,4 +45,9 @@ expected.
+
+
+Jordan Orelli: How to use interfaces in Go.
+Go Frequently Asked Questions: Why is my nil error value not equal to nil?.
+