+
+
+GORM errors are returned as a field of the return value instead of a separate return value.
+
+It is therefore very easy to miss that an error may occur and omit error handling routines.
+
+
+
+Ensure that GORM errors are checked.
+
+
+
+
+In the example below, the error from the database query is never checked:
+
+
+
+The corrected version checks and handles the error before returning.
+
+
+
+
+
+
+
+ GORM Error Handling.
+
+
+
+
diff --git a/ql/src/experimental/InconsistentCode/GORMErrorNotChecked.ql b/ql/src/experimental/InconsistentCode/GORMErrorNotChecked.ql
new file mode 100644
index 00000000000..15ab4450f6d
--- /dev/null
+++ b/ql/src/experimental/InconsistentCode/GORMErrorNotChecked.ql
@@ -0,0 +1,35 @@
+/**
+ * @name GORM error not checked
+ * @description A call that interacts with the database using the GORM library
+ * without checking whether there was an error.
+ * @kind problem
+ * @problem.severity warning
+ * @id go/examples/gorm-error-not-checked
+ * @precision high
+ */
+
+import go
+import semmle.go.frameworks.SQL
+
+from DataFlow::MethodCallNode call
+where
+ exists(string name | call.getTarget().hasQualifiedName(Gorm::packagePath(), "DB", name) |
+ name != "InstantSet" and
+ name != "LogMode"
+ ) and
+ // the value from the call does not:
+ not exists(DataFlow::Node succ | TaintTracking::localTaintStep*(call, succ) |
+ // get assigned to any variables
+ succ = any(Write w).getRhs()
+ or
+ // get returned
+ succ instanceof DataFlow::ResultNode
+ or
+ // have any methods chained on it
+ exists(DataFlow::MethodCallNode m | succ = m.getReceiver())
+ or
+ // have its `Error` field read
+ exists(DataFlow::FieldReadNode fr | fr.readsField(succ, _, _, "Error"))
+ )
+select call,
+ "This call appears to interact with the database without checking whether an error was encountered."
diff --git a/ql/src/experimental/InconsistentCode/GORMErrorNotCheckedGood.go b/ql/src/experimental/InconsistentCode/GORMErrorNotCheckedGood.go
new file mode 100644
index 00000000000..551e06fb66a
--- /dev/null
+++ b/ql/src/experimental/InconsistentCode/GORMErrorNotCheckedGood.go
@@ -0,0 +1,11 @@
+package main
+
+import "gorm.io/gorm"
+
+func getUserIdGood(db *gorm.DB, name string) int64 {
+ var user User
+ if err := db.Where("name = ?", name).First(&user).Error; err != nil {
+ // handle errors
+ }
+ return user.Id
+}
diff --git a/ql/test/experimental/InconsistentCode/GORMErrorNotChecked.expected b/ql/test/experimental/InconsistentCode/GORMErrorNotChecked.expected
new file mode 100644
index 00000000000..4d75b035fec
--- /dev/null
+++ b/ql/test/experimental/InconsistentCode/GORMErrorNotChecked.expected
@@ -0,0 +1 @@
+| GORMErrorNotChecked.go:7:2:7:40 | call to First | This call appears to interact with the database without checking whether an error was encountered. |
diff --git a/ql/test/experimental/InconsistentCode/GORMErrorNotChecked.go b/ql/test/experimental/InconsistentCode/GORMErrorNotChecked.go
new file mode 100644
index 00000000000..422e49b5f10
--- /dev/null
+++ b/ql/test/experimental/InconsistentCode/GORMErrorNotChecked.go
@@ -0,0 +1,9 @@
+package main
+
+import "gorm.io/gorm"
+
+func getUserId(db *gorm.DB, name string) int64 {
+ var user User
+ db.Where("name = ?", name).First(&user)
+ return user.Id
+}
diff --git a/ql/test/experimental/InconsistentCode/GORMErrorNotChecked.qhelp b/ql/test/experimental/InconsistentCode/GORMErrorNotChecked.qhelp
new file mode 100644
index 00000000000..1f64e06dc19
--- /dev/null
+++ b/ql/test/experimental/InconsistentCode/GORMErrorNotChecked.qhelp
@@ -0,0 +1,35 @@
+
+