diff --git a/ql/src/experimental/CWE-400/DatabaseCallInLoop.go b/ql/src/experimental/CWE-400/DatabaseCallInLoop.go new file mode 100644 index 000000000..138bbbcd9 --- /dev/null +++ b/ql/src/experimental/CWE-400/DatabaseCallInLoop.go @@ -0,0 +1,13 @@ +package main + +import "gorm.io/gorm" + +func getUsers(db *gorm.DB, names []string) []User { + res := make([]User, 0, len(names)) + for _, name := range names { + var user User + db.Where("name = ?", name).First(&user) + res = append(res, user) + } + return res +} diff --git a/ql/src/experimental/CWE-400/DatabaseCallInLoop.qhelp b/ql/src/experimental/CWE-400/DatabaseCallInLoop.qhelp new file mode 100644 index 000000000..d9c8a50fa --- /dev/null +++ b/ql/src/experimental/CWE-400/DatabaseCallInLoop.qhelp @@ -0,0 +1,29 @@ + + + + +

Database calls in loops are slower than running a single query and consume more resources. This +can lead to denial of service attacks if the loop bounds can be controlled by an attacker.

+
+ + +

Ensure that where possible, database queries are not run in a loop, instead running a single query to get all relevant data.

+ +
+ + +

In the example below, users in a database are queried one by one in a loop:

+ + + +

This is corrected by running a single query that selects all of the users at once:

+ + + +
+ + + +
diff --git a/ql/src/experimental/CWE-400/DatabaseCallInLoop.ql b/ql/src/experimental/CWE-400/DatabaseCallInLoop.ql new file mode 100644 index 000000000..2e4f25fe4 --- /dev/null +++ b/ql/src/experimental/CWE-400/DatabaseCallInLoop.ql @@ -0,0 +1,69 @@ +/** + * @name Database call in loop + * @description Detects database operations within loops. + * Doing operations in series can be slow and lead to N+1 situations. + * @kind path-problem + * @problem.severity warning + * @precision high + * @id go/examples/database-call-in-loop + */ + +import go + +class DatabaseAccess extends DataFlow::MethodCallNode { + DatabaseAccess() { + exists(string name | + this.getTarget().hasQualifiedName(Gorm::packagePath(), "DB", name) and + // all terminating Gorm methods + name = + [ + "Find", "Take", "Last", "Scan", "Row", "Rows", "ScanRows", "Pluck", "Count", "First", + "FirstOrInit", "FindOrCreate", "Update", "Updates", "UpdateColumn", "UpdateColumns", + "Save", "Create", "Delete", "Exec" + ] + ) + } +} + +class CallGraphNode extends Locatable { + CallGraphNode() { + this instanceof LoopStmt + or + this instanceof CallExpr + or + this instanceof FuncDef + } +} + +/** + * Holds if `pred` calls `succ`, i.e. is an edge in the call graph, + * This includes explicit edges from call -> callee, to produce better paths. + */ +predicate callGraphEdge(CallGraphNode pred, CallGraphNode succ) { + // Go from a loop to an enclosed expression. + pred.(LoopStmt).getBody().getAChild*() = succ.(CallExpr) + or + // Go from a call to the called function. + pred.(CallExpr) = succ.(FuncDef).getACall().asExpr() + or + // Go from a function to an enclosed loop. + pred.(FuncDef) = succ.(LoopStmt).getEnclosingFunction() + or + // Go from a function to an enclosed call. + pred.(FuncDef) = succ.(CallExpr).getEnclosingFunction() +} + +query predicate edges(CallGraphNode pred, CallGraphNode succ) { + callGraphEdge(pred, succ) and + // Limit the range of edges to only those that are relevant. + // This helps to speed up the query by reducing the size of the outputted path information. + exists(LoopStmt loop, DatabaseAccess dbAccess | + // is between a loop and a db access + callGraphEdge*(loop, pred) and + callGraphEdge*(succ, dbAccess.asExpr()) + ) +} + +from LoopStmt loop, DatabaseAccess dbAccess +where edges*(loop, dbAccess.asExpr()) +select dbAccess, loop, dbAccess, "$@ is called in $@", dbAccess, dbAccess.toString(), loop, "a loop" diff --git a/ql/src/experimental/CWE-400/DatabaseCallInLoopGood.go b/ql/src/experimental/CWE-400/DatabaseCallInLoopGood.go new file mode 100644 index 000000000..22928a6ab --- /dev/null +++ b/ql/src/experimental/CWE-400/DatabaseCallInLoopGood.go @@ -0,0 +1,9 @@ +package main + +import "gorm.io/gorm" + +func getUsersGood(db *gorm.DB, names []string) []User { + res := make([]User, 0, len(names)) + db.Where("name IN ?", names).Find(&res) + return res +} diff --git a/ql/src/experimental/InconsistentCode/DeferInLoop.go b/ql/src/experimental/InconsistentCode/DeferInLoop.go new file mode 100644 index 000000000..1b57d1855 --- /dev/null +++ b/ql/src/experimental/InconsistentCode/DeferInLoop.go @@ -0,0 +1,14 @@ +package main + +import "os" + +func openFiles(filenames []string) { + for _, filename := range filenames { + file, err := os.Open(filename) + defer file.Close() + if err != nil { + // handle error + } + // work on file + } +} diff --git a/ql/src/experimental/InconsistentCode/DeferInLoop.qhelp b/ql/src/experimental/InconsistentCode/DeferInLoop.qhelp new file mode 100644 index 000000000..ea31e6829 --- /dev/null +++ b/ql/src/experimental/InconsistentCode/DeferInLoop.qhelp @@ -0,0 +1,32 @@ + + + + +

A deferred statement in a loop will not execute until the end of the function. This can lead to unintentionally holding resources open, like file handles or database transactions.

+
+ + +

Either run the deferred function manually, or create a subroutine that contains the defer.

+ +
+ + +

In the example below, the files opened in the loop are not closed until the end of the function:

+ + + +

The corrected version puts the loop body into a function.

+ + + +
+ + +
  • + Defer statements. +
  • + +
    +
    diff --git a/ql/examples/snippets/deferinloop.ql b/ql/src/experimental/InconsistentCode/DeferInLoop.ql similarity index 100% rename from ql/examples/snippets/deferinloop.ql rename to ql/src/experimental/InconsistentCode/DeferInLoop.ql diff --git a/ql/src/experimental/InconsistentCode/DeferInLoopGood.go b/ql/src/experimental/InconsistentCode/DeferInLoopGood.go new file mode 100644 index 000000000..d28bc8e4c --- /dev/null +++ b/ql/src/experimental/InconsistentCode/DeferInLoopGood.go @@ -0,0 +1,18 @@ +package main + +import "os" + +func openFile(filename string) { + file, err := os.Open(filename) + defer file.Close() + if err != nil { + // handle error + } + // work on file +} + +func openFilesGood(filenames []string) { + for _, filename := range filenames { + openFile(filename) + } +} diff --git a/ql/src/experimental/InconsistentCode/GORMErrorNotChecked.go b/ql/src/experimental/InconsistentCode/GORMErrorNotChecked.go new file mode 100644 index 000000000..422e49b5f --- /dev/null +++ b/ql/src/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/src/experimental/InconsistentCode/GORMErrorNotChecked.qhelp b/ql/src/experimental/InconsistentCode/GORMErrorNotChecked.qhelp new file mode 100644 index 000000000..499e38eef --- /dev/null +++ b/ql/src/experimental/InconsistentCode/GORMErrorNotChecked.qhelp @@ -0,0 +1,34 @@ + + + + +

    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 000000000..15ab4450f --- /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 000000000..551e06fb6 --- /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/CWE-400/DatabaseCallInLoop.expected b/ql/test/experimental/CWE-400/DatabaseCallInLoop.expected new file mode 100644 index 000000000..bb197203f --- /dev/null +++ b/ql/test/experimental/CWE-400/DatabaseCallInLoop.expected @@ -0,0 +1,13 @@ +edges +| DatabaseCallInLoop.go:7:2:11:2 | range statement | DatabaseCallInLoop.go:9:3:9:41 | call to First | +| test.go:10:1:12:1 | function declaration | test.go:11:2:11:13 | call to Take | +| test.go:14:1:16:1 | function declaration | test.go:15:2:15:13 | call to runQuery | +| test.go:15:2:15:13 | call to runQuery | test.go:10:1:12:1 | function declaration | +| test.go:20:2:22:2 | for statement | test.go:21:3:21:14 | call to runQuery | +| test.go:21:3:21:14 | call to runQuery | test.go:10:1:12:1 | function declaration | +| test.go:24:2:26:2 | for statement | test.go:25:3:25:17 | call to runRunQuery | +| test.go:25:3:25:17 | call to runRunQuery | test.go:14:1:16:1 | function declaration | +#select +| DatabaseCallInLoop.go:9:3:9:41 | call to First | DatabaseCallInLoop.go:7:2:11:2 | range statement | DatabaseCallInLoop.go:9:3:9:41 | call to First | $@ is called in $@ | DatabaseCallInLoop.go:9:3:9:41 | call to First | call to First | DatabaseCallInLoop.go:7:2:11:2 | range statement | a loop | +| test.go:11:2:11:13 | call to Take | test.go:20:2:22:2 | for statement | test.go:11:2:11:13 | call to Take | $@ is called in $@ | test.go:11:2:11:13 | call to Take | call to Take | test.go:20:2:22:2 | for statement | a loop | +| test.go:11:2:11:13 | call to Take | test.go:24:2:26:2 | for statement | test.go:11:2:11:13 | call to Take | $@ is called in $@ | test.go:11:2:11:13 | call to Take | call to Take | test.go:24:2:26:2 | for statement | a loop | diff --git a/ql/test/experimental/CWE-400/DatabaseCallInLoop.go b/ql/test/experimental/CWE-400/DatabaseCallInLoop.go new file mode 100644 index 000000000..138bbbcd9 --- /dev/null +++ b/ql/test/experimental/CWE-400/DatabaseCallInLoop.go @@ -0,0 +1,13 @@ +package main + +import "gorm.io/gorm" + +func getUsers(db *gorm.DB, names []string) []User { + res := make([]User, 0, len(names)) + for _, name := range names { + var user User + db.Where("name = ?", name).First(&user) + res = append(res, user) + } + return res +} diff --git a/ql/test/experimental/CWE-400/DatabaseCallInLoop.qlref b/ql/test/experimental/CWE-400/DatabaseCallInLoop.qlref new file mode 100644 index 000000000..63f27c9b4 --- /dev/null +++ b/ql/test/experimental/CWE-400/DatabaseCallInLoop.qlref @@ -0,0 +1 @@ +experimental/CWE-400/DatabaseCallInLoop.ql diff --git a/ql/test/experimental/CWE-400/DatabaseCallInLoopGood.go b/ql/test/experimental/CWE-400/DatabaseCallInLoopGood.go new file mode 100644 index 000000000..22928a6ab --- /dev/null +++ b/ql/test/experimental/CWE-400/DatabaseCallInLoopGood.go @@ -0,0 +1,9 @@ +package main + +import "gorm.io/gorm" + +func getUsersGood(db *gorm.DB, names []string) []User { + res := make([]User, 0, len(names)) + db.Where("name IN ?", names).Find(&res) + return res +} diff --git a/ql/test/experimental/CWE-400/go.mod b/ql/test/experimental/CWE-400/go.mod new file mode 100644 index 000000000..8fa98bf28 --- /dev/null +++ b/ql/test/experimental/CWE-400/go.mod @@ -0,0 +1,5 @@ +module query-tests/databasecallinloop + +go 1.16 + +require gorm.io/gorm v1.21.12 diff --git a/ql/test/experimental/CWE-400/test.go b/ql/test/experimental/CWE-400/test.go new file mode 100644 index 000000000..725fb541b --- /dev/null +++ b/ql/test/experimental/CWE-400/test.go @@ -0,0 +1,27 @@ +package main + +import "gorm.io/gorm" + +type User struct { + Id int64 + Name string +} + +func runQuery(db *gorm.DB) { + db.Take(nil) +} + +func runRunQuery(db *gorm.DB) { + runQuery(db) +} + +func main() { + var db *gorm.DB + for i := 0; i < 10; i++ { + runQuery(db) + } + + for i := 10; i > 0; i-- { + runRunQuery(db) + } +} diff --git a/ql/test/experimental/CWE-400/vendor/gorm.io/gorm/License b/ql/test/experimental/CWE-400/vendor/gorm.io/gorm/License new file mode 100644 index 000000000..037e1653e --- /dev/null +++ b/ql/test/experimental/CWE-400/vendor/gorm.io/gorm/License @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2013-NOW Jinzhu + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/ql/test/experimental/CWE-400/vendor/gorm.io/gorm/stub.go b/ql/test/experimental/CWE-400/vendor/gorm.io/gorm/stub.go new file mode 100644 index 000000000..dd9f079c4 --- /dev/null +++ b/ql/test/experimental/CWE-400/vendor/gorm.io/gorm/stub.go @@ -0,0 +1,803 @@ +// Code generated by depstubber. DO NOT EDIT. +// This is a simple stub for gorm.io/gorm, strictly for use in testing. + +// See the LICENSE file for information about the licensing of the original library. +// Source: gorm.io/gorm (exports: DB; functions: ) + +// Package gorm is a stub of gorm.io/gorm, generated by depstubber. +package gorm + +import ( + context "context" + sql "database/sql" + reflect "reflect" + strings "strings" + sync "sync" + time "time" +) + +type Association struct { + DB *DB + Relationship interface{} + Error error +} + +func (_ *Association) Append(_ ...interface{}) error { + return nil +} + +func (_ *Association) Clear() error { + return nil +} + +func (_ *Association) Count() int64 { + return 0 +} + +func (_ *Association) Delete(_ ...interface{}) error { + return nil +} + +func (_ *Association) Find(_ interface{}, _ ...interface{}) error { + return nil +} + +func (_ *Association) Replace(_ ...interface{}) error { + return nil +} + +type ColumnType interface { + DatabaseTypeName() string + DecimalSize() (int64, int64, bool) + Length() (int64, bool) + Name() string + Nullable() (bool, bool) +} + +type Config struct { + SkipDefaultTransaction bool + NamingStrategy interface{} + FullSaveAssociations bool + Logger interface{} + NowFunc func() time.Time + DryRun bool + PrepareStmt bool + DisableAutomaticPing bool + DisableForeignKeyConstraintWhenMigrating bool + DisableNestedTransaction bool + AllowGlobalUpdate bool + QueryFields bool + CreateBatchSize int + ClauseBuilders map[string]interface{} + ConnPool ConnPool + Dialector Dialector + Plugins map[string]Plugin +} + +func (_ Config) BindVarTo(_ interface{}, _ *Statement, _ interface{}) {} + +func (_ Config) DataTypeOf(_ interface{}) string { + return "" +} + +func (_ Config) DefaultValueOf(_ interface{}) interface{} { + return nil +} + +func (_ Config) Explain(_ string, _ ...interface{}) string { + return "" +} + +func (_ Config) Initialize(_ *DB) error { + return nil +} + +func (_ Config) Migrator(_ *DB) Migrator { + return nil +} + +func (_ Config) Name() string { + return "" +} + +func (_ Config) QuoteTo(_ interface{}, _ string) {} + +func (_ *Config) AfterInitialize(_ *DB) error { + return nil +} + +func (_ *Config) Apply(_ *Config) error { + return nil +} + +type ConnPool interface { + ExecContext(_ context.Context, _ string, _ ...interface{}) (sql.Result, error) + PrepareContext(_ context.Context, _ string) (*sql.Stmt, error) + QueryContext(_ context.Context, _ string, _ ...interface{}) (*sql.Rows, error) + QueryRowContext(_ context.Context, _ string, _ ...interface{}) *sql.Row +} + +type DB struct { + Config *Config + Error error + RowsAffected int64 + Statement *Statement +} + +func (_ DB) AfterInitialize(_ *DB) error { + return nil +} + +func (_ DB) Apply(_ *Config) error { + return nil +} + +func (_ DB) BindVarTo(_ interface{}, _ *Statement, _ interface{}) {} + +func (_ DB) DataTypeOf(_ interface{}) string { + return "" +} + +func (_ DB) DefaultValueOf(_ interface{}) interface{} { + return nil +} + +func (_ DB) Explain(_ string, _ ...interface{}) string { + return "" +} + +func (_ DB) Initialize(_ *DB) error { + return nil +} + +func (_ DB) Name() string { + return "" +} + +func (_ DB) QuoteTo(_ interface{}, _ string) {} + +func (_ *DB) AddError(_ error) error { + return nil +} + +func (_ *DB) Assign(_ ...interface{}) *DB { + return nil +} + +func (_ *DB) Association(_ string) *Association { + return nil +} + +func (_ *DB) Attrs(_ ...interface{}) *DB { + return nil +} + +func (_ *DB) AutoMigrate(_ ...interface{}) error { + return nil +} + +func (_ *DB) Begin(_ ...*sql.TxOptions) *DB { + return nil +} + +func (_ *DB) Callback() interface{} { + return nil +} + +func (_ *DB) Clauses(_ ...interface{}) *DB { + return nil +} + +func (_ *DB) Commit() *DB { + return nil +} + +func (_ *DB) Count(_ *int64) *DB { + return nil +} + +func (_ *DB) Create(_ interface{}) *DB { + return nil +} + +func (_ *DB) CreateInBatches(_ interface{}, _ int) *DB { + return nil +} + +func (_ *DB) DB() (*sql.DB, error) { + return nil, nil +} + +func (_ *DB) Debug() *DB { + return nil +} + +func (_ *DB) Delete(_ interface{}, _ ...interface{}) *DB { + return nil +} + +func (_ *DB) Distinct(_ ...interface{}) *DB { + return nil +} + +func (_ *DB) Exec(_ string, _ ...interface{}) *DB { + return nil +} + +func (_ *DB) Find(_ interface{}, _ ...interface{}) *DB { + return nil +} + +func (_ *DB) FindInBatches(_ interface{}, _ int, _ func(*DB, int) error) *DB { + return nil +} + +func (_ *DB) First(_ interface{}, _ ...interface{}) *DB { + return nil +} + +func (_ *DB) FirstOrCreate(_ interface{}, _ ...interface{}) *DB { + return nil +} + +func (_ *DB) FirstOrInit(_ interface{}, _ ...interface{}) *DB { + return nil +} + +func (_ *DB) Get(_ string) (interface{}, bool) { + return nil, false +} + +func (_ *DB) Group(_ string) *DB { + return nil +} + +func (_ *DB) Having(_ interface{}, _ ...interface{}) *DB { + return nil +} + +func (_ *DB) InstanceGet(_ string) (interface{}, bool) { + return nil, false +} + +func (_ *DB) InstanceSet(_ string, _ interface{}) *DB { + return nil +} + +func (_ *DB) Joins(_ string, _ ...interface{}) *DB { + return nil +} + +func (_ *DB) Last(_ interface{}, _ ...interface{}) *DB { + return nil +} + +func (_ *DB) Limit(_ int) *DB { + return nil +} + +func (_ *DB) Migrator() Migrator { + return nil +} + +func (_ *DB) Model(_ interface{}) *DB { + return nil +} + +func (_ *DB) Not(_ interface{}, _ ...interface{}) *DB { + return nil +} + +func (_ *DB) Offset(_ int) *DB { + return nil +} + +func (_ *DB) Omit(_ ...string) *DB { + return nil +} + +func (_ *DB) Or(_ interface{}, _ ...interface{}) *DB { + return nil +} + +func (_ *DB) Order(_ interface{}) *DB { + return nil +} + +func (_ *DB) Pluck(_ string, _ interface{}) *DB { + return nil +} + +func (_ *DB) Preload(_ string, _ ...interface{}) *DB { + return nil +} + +func (_ *DB) Raw(_ string, _ ...interface{}) *DB { + return nil +} + +func (_ *DB) Rollback() *DB { + return nil +} + +func (_ *DB) RollbackTo(_ string) *DB { + return nil +} + +func (_ *DB) Row() *sql.Row { + return nil +} + +func (_ *DB) Rows() (*sql.Rows, error) { + return nil, nil +} + +func (_ *DB) Save(_ interface{}) *DB { + return nil +} + +func (_ *DB) SavePoint(_ string) *DB { + return nil +} + +func (_ *DB) Scan(_ interface{}) *DB { + return nil +} + +func (_ *DB) ScanRows(_ *sql.Rows, _ interface{}) error { + return nil +} + +func (_ *DB) Scopes(_ ...func(*DB) *DB) *DB { + return nil +} + +func (_ *DB) Select(_ interface{}, _ ...interface{}) *DB { + return nil +} + +func (_ *DB) Session(_ *Session) *DB { + return nil +} + +func (_ *DB) Set(_ string, _ interface{}) *DB { + return nil +} + +func (_ *DB) SetupJoinTable(_ interface{}, _ string, _ interface{}) error { + return nil +} + +func (_ *DB) Table(_ string, _ ...interface{}) *DB { + return nil +} + +func (_ *DB) Take(_ interface{}, _ ...interface{}) *DB { + return nil +} + +func (_ *DB) Transaction(_ func(*DB) error, _ ...*sql.TxOptions) error { + return nil +} + +func (_ *DB) Unscoped() *DB { + return nil +} + +func (_ *DB) Update(_ string, _ interface{}) *DB { + return nil +} + +func (_ *DB) UpdateColumn(_ string, _ interface{}) *DB { + return nil +} + +func (_ *DB) UpdateColumns(_ interface{}) *DB { + return nil +} + +func (_ *DB) Updates(_ interface{}) *DB { + return nil +} + +func (_ *DB) Use(_ Plugin) error { + return nil +} + +func (_ *DB) Where(_ interface{}, _ ...interface{}) *DB { + return nil +} + +func (_ *DB) WithContext(_ context.Context) *DB { + return nil +} + +type Dialector interface { + BindVarTo(_ interface{}, _ *Statement, _ interface{}) + DataTypeOf(_ interface{}) string + DefaultValueOf(_ interface{}) interface{} + Explain(_ string, _ ...interface{}) string + Initialize(_ *DB) error + Migrator(_ *DB) Migrator + Name() string + QuoteTo(_ interface{}, _ string) +} + +type Migrator interface { + AddColumn(_ interface{}, _ string) error + AlterColumn(_ interface{}, _ string) error + AutoMigrate(_ ...interface{}) error + ColumnTypes(_ interface{}) ([]ColumnType, error) + CreateConstraint(_ interface{}, _ string) error + CreateIndex(_ interface{}, _ string) error + CreateTable(_ ...interface{}) error + CreateView(_ string, _ ViewOption) error + CurrentDatabase() string + DropColumn(_ interface{}, _ string) error + DropConstraint(_ interface{}, _ string) error + DropIndex(_ interface{}, _ string) error + DropTable(_ ...interface{}) error + DropView(_ string) error + FullDataTypeOf(_ interface{}) interface{} + HasColumn(_ interface{}, _ string) bool + HasConstraint(_ interface{}, _ string) bool + HasIndex(_ interface{}, _ string) bool + HasTable(_ interface{}) bool + MigrateColumn(_ interface{}, _ interface{}, _ ColumnType) error + RenameColumn(_ interface{}, _ string, _ string) error + RenameIndex(_ interface{}, _ string, _ string) error + RenameTable(_ interface{}, _ interface{}) error +} + +type Plugin interface { + Initialize(_ *DB) error + Name() string +} + +type Session struct { + DryRun bool + PrepareStmt bool + NewDB bool + SkipHooks bool + SkipDefaultTransaction bool + DisableNestedTransaction bool + AllowGlobalUpdate bool + FullSaveAssociations bool + QueryFields bool + Context context.Context + Logger interface{} + NowFunc func() time.Time + CreateBatchSize int +} + +type Statement struct { + DB *DB + TableExpr interface{} + Table string + Model interface{} + Unscoped bool + Dest interface{} + ReflectValue reflect.Value + Clauses map[string]interface{} + BuildClauses []string + Distinct bool + Selects []string + Omits []string + Joins []interface{} + Preloads map[string][]interface{} + Settings sync.Map + ConnPool ConnPool + Schema interface{} + Context context.Context + RaiseErrorOnNotFound bool + SkipHooks bool + SQL strings.Builder + Vars []interface{} + CurDestIndex int +} + +func (_ Statement) AddError(_ error) error { + return nil +} + +func (_ Statement) AfterInitialize(_ *DB) error { + return nil +} + +func (_ Statement) Apply(_ *Config) error { + return nil +} + +func (_ Statement) Assign(_ ...interface{}) *DB { + return nil +} + +func (_ Statement) Association(_ string) *Association { + return nil +} + +func (_ Statement) Attrs(_ ...interface{}) *DB { + return nil +} + +func (_ Statement) AutoMigrate(_ ...interface{}) error { + return nil +} + +func (_ Statement) Begin(_ ...*sql.TxOptions) *DB { + return nil +} + +func (_ Statement) BindVarTo(_ interface{}, _ *Statement, _ interface{}) {} + +func (_ Statement) Callback() interface{} { + return nil +} + +func (_ Statement) Commit() *DB { + return nil +} + +func (_ Statement) Count(_ *int64) *DB { + return nil +} + +func (_ Statement) Create(_ interface{}) *DB { + return nil +} + +func (_ Statement) CreateInBatches(_ interface{}, _ int) *DB { + return nil +} + +func (_ Statement) DataTypeOf(_ interface{}) string { + return "" +} + +func (_ Statement) Debug() *DB { + return nil +} + +func (_ Statement) DefaultValueOf(_ interface{}) interface{} { + return nil +} + +func (_ Statement) Delete(_ interface{}, _ ...interface{}) *DB { + return nil +} + +func (_ Statement) Exec(_ string, _ ...interface{}) *DB { + return nil +} + +func (_ Statement) Explain(_ string, _ ...interface{}) string { + return "" +} + +func (_ Statement) Find(_ interface{}, _ ...interface{}) *DB { + return nil +} + +func (_ Statement) FindInBatches(_ interface{}, _ int, _ func(*DB, int) error) *DB { + return nil +} + +func (_ Statement) First(_ interface{}, _ ...interface{}) *DB { + return nil +} + +func (_ Statement) FirstOrCreate(_ interface{}, _ ...interface{}) *DB { + return nil +} + +func (_ Statement) FirstOrInit(_ interface{}, _ ...interface{}) *DB { + return nil +} + +func (_ Statement) Get(_ string) (interface{}, bool) { + return nil, false +} + +func (_ Statement) Group(_ string) *DB { + return nil +} + +func (_ Statement) Having(_ interface{}, _ ...interface{}) *DB { + return nil +} + +func (_ Statement) Initialize(_ *DB) error { + return nil +} + +func (_ Statement) InstanceGet(_ string) (interface{}, bool) { + return nil, false +} + +func (_ Statement) InstanceSet(_ string, _ interface{}) *DB { + return nil +} + +func (_ Statement) Last(_ interface{}, _ ...interface{}) *DB { + return nil +} + +func (_ Statement) Limit(_ int) *DB { + return nil +} + +func (_ Statement) Migrator() Migrator { + return nil +} + +func (_ Statement) Name() string { + return "" +} + +func (_ Statement) Not(_ interface{}, _ ...interface{}) *DB { + return nil +} + +func (_ Statement) Offset(_ int) *DB { + return nil +} + +func (_ Statement) Omit(_ ...string) *DB { + return nil +} + +func (_ Statement) Or(_ interface{}, _ ...interface{}) *DB { + return nil +} + +func (_ Statement) Order(_ interface{}) *DB { + return nil +} + +func (_ Statement) Pluck(_ string, _ interface{}) *DB { + return nil +} + +func (_ Statement) Preload(_ string, _ ...interface{}) *DB { + return nil +} + +func (_ Statement) Raw(_ string, _ ...interface{}) *DB { + return nil +} + +func (_ Statement) Rollback() *DB { + return nil +} + +func (_ Statement) RollbackTo(_ string) *DB { + return nil +} + +func (_ Statement) Row() *sql.Row { + return nil +} + +func (_ Statement) Rows() (*sql.Rows, error) { + return nil, nil +} + +func (_ Statement) Save(_ interface{}) *DB { + return nil +} + +func (_ Statement) SavePoint(_ string) *DB { + return nil +} + +func (_ Statement) Scan(_ interface{}) *DB { + return nil +} + +func (_ Statement) ScanRows(_ *sql.Rows, _ interface{}) error { + return nil +} + +func (_ Statement) Scopes(_ ...func(*DB) *DB) *DB { + return nil +} + +func (_ Statement) Select(_ interface{}, _ ...interface{}) *DB { + return nil +} + +func (_ Statement) Session(_ *Session) *DB { + return nil +} + +func (_ Statement) Set(_ string, _ interface{}) *DB { + return nil +} + +func (_ Statement) SetupJoinTable(_ interface{}, _ string, _ interface{}) error { + return nil +} + +func (_ Statement) Take(_ interface{}, _ ...interface{}) *DB { + return nil +} + +func (_ Statement) Transaction(_ func(*DB) error, _ ...*sql.TxOptions) error { + return nil +} + +func (_ Statement) Update(_ string, _ interface{}) *DB { + return nil +} + +func (_ Statement) UpdateColumn(_ string, _ interface{}) *DB { + return nil +} + +func (_ Statement) UpdateColumns(_ interface{}) *DB { + return nil +} + +func (_ Statement) Updates(_ interface{}) *DB { + return nil +} + +func (_ Statement) Use(_ Plugin) error { + return nil +} + +func (_ Statement) Where(_ interface{}, _ ...interface{}) *DB { + return nil +} + +func (_ Statement) WithContext(_ context.Context) *DB { + return nil +} + +func (_ *Statement) AddClause(_ interface{}) {} + +func (_ *Statement) AddClauseIfNotExists(_ interface{}) {} + +func (_ *Statement) AddVar(_ interface{}, _ ...interface{}) {} + +func (_ *Statement) Build(_ ...string) {} + +func (_ *Statement) BuildCondition(_ interface{}, _ ...interface{}) []interface{} { + return nil +} + +func (_ *Statement) Changed(_ ...string) bool { + return false +} + +func (_ *Statement) Parse(_ interface{}) error { + return nil +} + +func (_ *Statement) Quote(_ interface{}) string { + return "" +} + +func (_ *Statement) QuoteTo(_ interface{}, _ interface{}) {} + +func (_ *Statement) SelectAndOmitColumns(_ bool, _ bool) (map[string]bool, bool) { + return nil, false +} + +func (_ *Statement) SetColumn(_ string, _ interface{}, _ ...bool) {} + +func (_ *Statement) WriteByte(_ byte) error { + return nil +} + +func (_ *Statement) WriteQuoted(_ interface{}) {} + +func (_ *Statement) WriteString(_ string) (int, error) { + return 0, nil +} + +type ViewOption struct { + Replace bool + CheckOption string + Query *DB +} diff --git a/ql/test/experimental/CWE-400/vendor/modules.txt b/ql/test/experimental/CWE-400/vendor/modules.txt new file mode 100644 index 000000000..52a1f62b3 --- /dev/null +++ b/ql/test/experimental/CWE-400/vendor/modules.txt @@ -0,0 +1,3 @@ +# gorm.io/gorm v1.21.12 +## explicit +gorm.io/gorm diff --git a/ql/test/experimental/InconsistentCode/DeferInLoop.expected b/ql/test/experimental/InconsistentCode/DeferInLoop.expected new file mode 100644 index 000000000..8652787cc --- /dev/null +++ b/ql/test/experimental/InconsistentCode/DeferInLoop.expected @@ -0,0 +1,6 @@ +| DeferInLoop.go:8:3:8:20 | defer statement | This defer statement is in a $@. | DeferInLoop.go:6:2:13:2 | range statement | loop | +| test.go:6:3:6:14 | defer statement | This defer statement is in a $@. | test.go:5:2:7:2 | range statement | loop | +| test.go:11:4:11:15 | defer statement | This defer statement is in a $@. | test.go:9:2:13:2 | range statement | loop | +| test.go:16:3:16:14 | defer statement | This defer statement is in a $@. | test.go:15:2:17:2 | for statement | loop | +| test.go:20:3:20:14 | defer statement | This defer statement is in a $@. | test.go:19:2:21:2 | for statement | loop | +| test.go:24:3:24:14 | defer statement | This defer statement is in a $@. | test.go:23:2:25:2 | for statement | loop | diff --git a/ql/test/experimental/InconsistentCode/DeferInLoop.go b/ql/test/experimental/InconsistentCode/DeferInLoop.go new file mode 100644 index 000000000..1b57d1855 --- /dev/null +++ b/ql/test/experimental/InconsistentCode/DeferInLoop.go @@ -0,0 +1,14 @@ +package main + +import "os" + +func openFiles(filenames []string) { + for _, filename := range filenames { + file, err := os.Open(filename) + defer file.Close() + if err != nil { + // handle error + } + // work on file + } +} diff --git a/ql/test/experimental/InconsistentCode/DeferInLoop.qlref b/ql/test/experimental/InconsistentCode/DeferInLoop.qlref new file mode 100644 index 000000000..e50bcf4fd --- /dev/null +++ b/ql/test/experimental/InconsistentCode/DeferInLoop.qlref @@ -0,0 +1 @@ +experimental/InconsistentCode/DeferInLoop.ql diff --git a/ql/test/experimental/InconsistentCode/DeferInLoopGood.go b/ql/test/experimental/InconsistentCode/DeferInLoopGood.go new file mode 100644 index 000000000..d28bc8e4c --- /dev/null +++ b/ql/test/experimental/InconsistentCode/DeferInLoopGood.go @@ -0,0 +1,18 @@ +package main + +import "os" + +func openFile(filename string) { + file, err := os.Open(filename) + defer file.Close() + if err != nil { + // handle error + } + // work on file +} + +func openFilesGood(filenames []string) { + for _, filename := range filenames { + openFile(filename) + } +} diff --git a/ql/test/experimental/InconsistentCode/GORMErrorNotChecked.expected b/ql/test/experimental/InconsistentCode/GORMErrorNotChecked.expected new file mode 100644 index 000000000..4d75b035f --- /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 000000000..422e49b5f --- /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 000000000..1f64e06dc --- /dev/null +++ b/ql/test/experimental/InconsistentCode/GORMErrorNotChecked.qhelp @@ -0,0 +1,35 @@ + + + + +

    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 corrected version of user checks err before using ptr.

    + + + +
    + + +
  • + The Go Blog: + Error handling and Go. +
  • + +
    +
    diff --git a/ql/test/experimental/InconsistentCode/GORMErrorNotChecked.qlref b/ql/test/experimental/InconsistentCode/GORMErrorNotChecked.qlref new file mode 100644 index 000000000..b52256ad5 --- /dev/null +++ b/ql/test/experimental/InconsistentCode/GORMErrorNotChecked.qlref @@ -0,0 +1 @@ +experimental/InconsistentCode/GORMErrorNotChecked.ql diff --git a/ql/test/experimental/InconsistentCode/GORMErrorNotCheckedGood.go b/ql/test/experimental/InconsistentCode/GORMErrorNotCheckedGood.go new file mode 100644 index 000000000..551e06fb6 --- /dev/null +++ b/ql/test/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/go.mod b/ql/test/experimental/InconsistentCode/go.mod new file mode 100644 index 000000000..e3b89d1ab --- /dev/null +++ b/ql/test/experimental/InconsistentCode/go.mod @@ -0,0 +1,5 @@ +module query-tests/gormerrornotchecked + +go 1.16 + +require gorm.io/gorm v1.21.12 diff --git a/ql/test/experimental/InconsistentCode/gormerrornotchecked b/ql/test/experimental/InconsistentCode/gormerrornotchecked new file mode 100755 index 000000000..7f02a8e30 Binary files /dev/null and b/ql/test/experimental/InconsistentCode/gormerrornotchecked differ diff --git a/ql/test/experimental/InconsistentCode/test.go b/ql/test/experimental/InconsistentCode/test.go new file mode 100644 index 000000000..1dc64350b --- /dev/null +++ b/ql/test/experimental/InconsistentCode/test.go @@ -0,0 +1,26 @@ +package main + +func test() { + var xs []int + for _ = range xs { + defer test() // not ok + } + + for _ = range xs { + if true { + defer test() // not ok + } + } + + for i := 0; i < 10; i++ { + defer test() + } + + for true { + defer test() // not ok + } + + for false { + defer test() // fine but caught + } +} diff --git a/ql/test/experimental/InconsistentCode/util.go b/ql/test/experimental/InconsistentCode/util.go new file mode 100644 index 000000000..0074e1f23 --- /dev/null +++ b/ql/test/experimental/InconsistentCode/util.go @@ -0,0 +1,9 @@ +package main + +type User struct { + Id int64 + Name string +} + +func main() { +} diff --git a/ql/test/experimental/InconsistentCode/vendor/gorm.io/gorm/License b/ql/test/experimental/InconsistentCode/vendor/gorm.io/gorm/License new file mode 100644 index 000000000..037e1653e --- /dev/null +++ b/ql/test/experimental/InconsistentCode/vendor/gorm.io/gorm/License @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2013-NOW Jinzhu + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/ql/test/experimental/InconsistentCode/vendor/gorm.io/gorm/stub.go b/ql/test/experimental/InconsistentCode/vendor/gorm.io/gorm/stub.go new file mode 100644 index 000000000..dd9f079c4 --- /dev/null +++ b/ql/test/experimental/InconsistentCode/vendor/gorm.io/gorm/stub.go @@ -0,0 +1,803 @@ +// Code generated by depstubber. DO NOT EDIT. +// This is a simple stub for gorm.io/gorm, strictly for use in testing. + +// See the LICENSE file for information about the licensing of the original library. +// Source: gorm.io/gorm (exports: DB; functions: ) + +// Package gorm is a stub of gorm.io/gorm, generated by depstubber. +package gorm + +import ( + context "context" + sql "database/sql" + reflect "reflect" + strings "strings" + sync "sync" + time "time" +) + +type Association struct { + DB *DB + Relationship interface{} + Error error +} + +func (_ *Association) Append(_ ...interface{}) error { + return nil +} + +func (_ *Association) Clear() error { + return nil +} + +func (_ *Association) Count() int64 { + return 0 +} + +func (_ *Association) Delete(_ ...interface{}) error { + return nil +} + +func (_ *Association) Find(_ interface{}, _ ...interface{}) error { + return nil +} + +func (_ *Association) Replace(_ ...interface{}) error { + return nil +} + +type ColumnType interface { + DatabaseTypeName() string + DecimalSize() (int64, int64, bool) + Length() (int64, bool) + Name() string + Nullable() (bool, bool) +} + +type Config struct { + SkipDefaultTransaction bool + NamingStrategy interface{} + FullSaveAssociations bool + Logger interface{} + NowFunc func() time.Time + DryRun bool + PrepareStmt bool + DisableAutomaticPing bool + DisableForeignKeyConstraintWhenMigrating bool + DisableNestedTransaction bool + AllowGlobalUpdate bool + QueryFields bool + CreateBatchSize int + ClauseBuilders map[string]interface{} + ConnPool ConnPool + Dialector Dialector + Plugins map[string]Plugin +} + +func (_ Config) BindVarTo(_ interface{}, _ *Statement, _ interface{}) {} + +func (_ Config) DataTypeOf(_ interface{}) string { + return "" +} + +func (_ Config) DefaultValueOf(_ interface{}) interface{} { + return nil +} + +func (_ Config) Explain(_ string, _ ...interface{}) string { + return "" +} + +func (_ Config) Initialize(_ *DB) error { + return nil +} + +func (_ Config) Migrator(_ *DB) Migrator { + return nil +} + +func (_ Config) Name() string { + return "" +} + +func (_ Config) QuoteTo(_ interface{}, _ string) {} + +func (_ *Config) AfterInitialize(_ *DB) error { + return nil +} + +func (_ *Config) Apply(_ *Config) error { + return nil +} + +type ConnPool interface { + ExecContext(_ context.Context, _ string, _ ...interface{}) (sql.Result, error) + PrepareContext(_ context.Context, _ string) (*sql.Stmt, error) + QueryContext(_ context.Context, _ string, _ ...interface{}) (*sql.Rows, error) + QueryRowContext(_ context.Context, _ string, _ ...interface{}) *sql.Row +} + +type DB struct { + Config *Config + Error error + RowsAffected int64 + Statement *Statement +} + +func (_ DB) AfterInitialize(_ *DB) error { + return nil +} + +func (_ DB) Apply(_ *Config) error { + return nil +} + +func (_ DB) BindVarTo(_ interface{}, _ *Statement, _ interface{}) {} + +func (_ DB) DataTypeOf(_ interface{}) string { + return "" +} + +func (_ DB) DefaultValueOf(_ interface{}) interface{} { + return nil +} + +func (_ DB) Explain(_ string, _ ...interface{}) string { + return "" +} + +func (_ DB) Initialize(_ *DB) error { + return nil +} + +func (_ DB) Name() string { + return "" +} + +func (_ DB) QuoteTo(_ interface{}, _ string) {} + +func (_ *DB) AddError(_ error) error { + return nil +} + +func (_ *DB) Assign(_ ...interface{}) *DB { + return nil +} + +func (_ *DB) Association(_ string) *Association { + return nil +} + +func (_ *DB) Attrs(_ ...interface{}) *DB { + return nil +} + +func (_ *DB) AutoMigrate(_ ...interface{}) error { + return nil +} + +func (_ *DB) Begin(_ ...*sql.TxOptions) *DB { + return nil +} + +func (_ *DB) Callback() interface{} { + return nil +} + +func (_ *DB) Clauses(_ ...interface{}) *DB { + return nil +} + +func (_ *DB) Commit() *DB { + return nil +} + +func (_ *DB) Count(_ *int64) *DB { + return nil +} + +func (_ *DB) Create(_ interface{}) *DB { + return nil +} + +func (_ *DB) CreateInBatches(_ interface{}, _ int) *DB { + return nil +} + +func (_ *DB) DB() (*sql.DB, error) { + return nil, nil +} + +func (_ *DB) Debug() *DB { + return nil +} + +func (_ *DB) Delete(_ interface{}, _ ...interface{}) *DB { + return nil +} + +func (_ *DB) Distinct(_ ...interface{}) *DB { + return nil +} + +func (_ *DB) Exec(_ string, _ ...interface{}) *DB { + return nil +} + +func (_ *DB) Find(_ interface{}, _ ...interface{}) *DB { + return nil +} + +func (_ *DB) FindInBatches(_ interface{}, _ int, _ func(*DB, int) error) *DB { + return nil +} + +func (_ *DB) First(_ interface{}, _ ...interface{}) *DB { + return nil +} + +func (_ *DB) FirstOrCreate(_ interface{}, _ ...interface{}) *DB { + return nil +} + +func (_ *DB) FirstOrInit(_ interface{}, _ ...interface{}) *DB { + return nil +} + +func (_ *DB) Get(_ string) (interface{}, bool) { + return nil, false +} + +func (_ *DB) Group(_ string) *DB { + return nil +} + +func (_ *DB) Having(_ interface{}, _ ...interface{}) *DB { + return nil +} + +func (_ *DB) InstanceGet(_ string) (interface{}, bool) { + return nil, false +} + +func (_ *DB) InstanceSet(_ string, _ interface{}) *DB { + return nil +} + +func (_ *DB) Joins(_ string, _ ...interface{}) *DB { + return nil +} + +func (_ *DB) Last(_ interface{}, _ ...interface{}) *DB { + return nil +} + +func (_ *DB) Limit(_ int) *DB { + return nil +} + +func (_ *DB) Migrator() Migrator { + return nil +} + +func (_ *DB) Model(_ interface{}) *DB { + return nil +} + +func (_ *DB) Not(_ interface{}, _ ...interface{}) *DB { + return nil +} + +func (_ *DB) Offset(_ int) *DB { + return nil +} + +func (_ *DB) Omit(_ ...string) *DB { + return nil +} + +func (_ *DB) Or(_ interface{}, _ ...interface{}) *DB { + return nil +} + +func (_ *DB) Order(_ interface{}) *DB { + return nil +} + +func (_ *DB) Pluck(_ string, _ interface{}) *DB { + return nil +} + +func (_ *DB) Preload(_ string, _ ...interface{}) *DB { + return nil +} + +func (_ *DB) Raw(_ string, _ ...interface{}) *DB { + return nil +} + +func (_ *DB) Rollback() *DB { + return nil +} + +func (_ *DB) RollbackTo(_ string) *DB { + return nil +} + +func (_ *DB) Row() *sql.Row { + return nil +} + +func (_ *DB) Rows() (*sql.Rows, error) { + return nil, nil +} + +func (_ *DB) Save(_ interface{}) *DB { + return nil +} + +func (_ *DB) SavePoint(_ string) *DB { + return nil +} + +func (_ *DB) Scan(_ interface{}) *DB { + return nil +} + +func (_ *DB) ScanRows(_ *sql.Rows, _ interface{}) error { + return nil +} + +func (_ *DB) Scopes(_ ...func(*DB) *DB) *DB { + return nil +} + +func (_ *DB) Select(_ interface{}, _ ...interface{}) *DB { + return nil +} + +func (_ *DB) Session(_ *Session) *DB { + return nil +} + +func (_ *DB) Set(_ string, _ interface{}) *DB { + return nil +} + +func (_ *DB) SetupJoinTable(_ interface{}, _ string, _ interface{}) error { + return nil +} + +func (_ *DB) Table(_ string, _ ...interface{}) *DB { + return nil +} + +func (_ *DB) Take(_ interface{}, _ ...interface{}) *DB { + return nil +} + +func (_ *DB) Transaction(_ func(*DB) error, _ ...*sql.TxOptions) error { + return nil +} + +func (_ *DB) Unscoped() *DB { + return nil +} + +func (_ *DB) Update(_ string, _ interface{}) *DB { + return nil +} + +func (_ *DB) UpdateColumn(_ string, _ interface{}) *DB { + return nil +} + +func (_ *DB) UpdateColumns(_ interface{}) *DB { + return nil +} + +func (_ *DB) Updates(_ interface{}) *DB { + return nil +} + +func (_ *DB) Use(_ Plugin) error { + return nil +} + +func (_ *DB) Where(_ interface{}, _ ...interface{}) *DB { + return nil +} + +func (_ *DB) WithContext(_ context.Context) *DB { + return nil +} + +type Dialector interface { + BindVarTo(_ interface{}, _ *Statement, _ interface{}) + DataTypeOf(_ interface{}) string + DefaultValueOf(_ interface{}) interface{} + Explain(_ string, _ ...interface{}) string + Initialize(_ *DB) error + Migrator(_ *DB) Migrator + Name() string + QuoteTo(_ interface{}, _ string) +} + +type Migrator interface { + AddColumn(_ interface{}, _ string) error + AlterColumn(_ interface{}, _ string) error + AutoMigrate(_ ...interface{}) error + ColumnTypes(_ interface{}) ([]ColumnType, error) + CreateConstraint(_ interface{}, _ string) error + CreateIndex(_ interface{}, _ string) error + CreateTable(_ ...interface{}) error + CreateView(_ string, _ ViewOption) error + CurrentDatabase() string + DropColumn(_ interface{}, _ string) error + DropConstraint(_ interface{}, _ string) error + DropIndex(_ interface{}, _ string) error + DropTable(_ ...interface{}) error + DropView(_ string) error + FullDataTypeOf(_ interface{}) interface{} + HasColumn(_ interface{}, _ string) bool + HasConstraint(_ interface{}, _ string) bool + HasIndex(_ interface{}, _ string) bool + HasTable(_ interface{}) bool + MigrateColumn(_ interface{}, _ interface{}, _ ColumnType) error + RenameColumn(_ interface{}, _ string, _ string) error + RenameIndex(_ interface{}, _ string, _ string) error + RenameTable(_ interface{}, _ interface{}) error +} + +type Plugin interface { + Initialize(_ *DB) error + Name() string +} + +type Session struct { + DryRun bool + PrepareStmt bool + NewDB bool + SkipHooks bool + SkipDefaultTransaction bool + DisableNestedTransaction bool + AllowGlobalUpdate bool + FullSaveAssociations bool + QueryFields bool + Context context.Context + Logger interface{} + NowFunc func() time.Time + CreateBatchSize int +} + +type Statement struct { + DB *DB + TableExpr interface{} + Table string + Model interface{} + Unscoped bool + Dest interface{} + ReflectValue reflect.Value + Clauses map[string]interface{} + BuildClauses []string + Distinct bool + Selects []string + Omits []string + Joins []interface{} + Preloads map[string][]interface{} + Settings sync.Map + ConnPool ConnPool + Schema interface{} + Context context.Context + RaiseErrorOnNotFound bool + SkipHooks bool + SQL strings.Builder + Vars []interface{} + CurDestIndex int +} + +func (_ Statement) AddError(_ error) error { + return nil +} + +func (_ Statement) AfterInitialize(_ *DB) error { + return nil +} + +func (_ Statement) Apply(_ *Config) error { + return nil +} + +func (_ Statement) Assign(_ ...interface{}) *DB { + return nil +} + +func (_ Statement) Association(_ string) *Association { + return nil +} + +func (_ Statement) Attrs(_ ...interface{}) *DB { + return nil +} + +func (_ Statement) AutoMigrate(_ ...interface{}) error { + return nil +} + +func (_ Statement) Begin(_ ...*sql.TxOptions) *DB { + return nil +} + +func (_ Statement) BindVarTo(_ interface{}, _ *Statement, _ interface{}) {} + +func (_ Statement) Callback() interface{} { + return nil +} + +func (_ Statement) Commit() *DB { + return nil +} + +func (_ Statement) Count(_ *int64) *DB { + return nil +} + +func (_ Statement) Create(_ interface{}) *DB { + return nil +} + +func (_ Statement) CreateInBatches(_ interface{}, _ int) *DB { + return nil +} + +func (_ Statement) DataTypeOf(_ interface{}) string { + return "" +} + +func (_ Statement) Debug() *DB { + return nil +} + +func (_ Statement) DefaultValueOf(_ interface{}) interface{} { + return nil +} + +func (_ Statement) Delete(_ interface{}, _ ...interface{}) *DB { + return nil +} + +func (_ Statement) Exec(_ string, _ ...interface{}) *DB { + return nil +} + +func (_ Statement) Explain(_ string, _ ...interface{}) string { + return "" +} + +func (_ Statement) Find(_ interface{}, _ ...interface{}) *DB { + return nil +} + +func (_ Statement) FindInBatches(_ interface{}, _ int, _ func(*DB, int) error) *DB { + return nil +} + +func (_ Statement) First(_ interface{}, _ ...interface{}) *DB { + return nil +} + +func (_ Statement) FirstOrCreate(_ interface{}, _ ...interface{}) *DB { + return nil +} + +func (_ Statement) FirstOrInit(_ interface{}, _ ...interface{}) *DB { + return nil +} + +func (_ Statement) Get(_ string) (interface{}, bool) { + return nil, false +} + +func (_ Statement) Group(_ string) *DB { + return nil +} + +func (_ Statement) Having(_ interface{}, _ ...interface{}) *DB { + return nil +} + +func (_ Statement) Initialize(_ *DB) error { + return nil +} + +func (_ Statement) InstanceGet(_ string) (interface{}, bool) { + return nil, false +} + +func (_ Statement) InstanceSet(_ string, _ interface{}) *DB { + return nil +} + +func (_ Statement) Last(_ interface{}, _ ...interface{}) *DB { + return nil +} + +func (_ Statement) Limit(_ int) *DB { + return nil +} + +func (_ Statement) Migrator() Migrator { + return nil +} + +func (_ Statement) Name() string { + return "" +} + +func (_ Statement) Not(_ interface{}, _ ...interface{}) *DB { + return nil +} + +func (_ Statement) Offset(_ int) *DB { + return nil +} + +func (_ Statement) Omit(_ ...string) *DB { + return nil +} + +func (_ Statement) Or(_ interface{}, _ ...interface{}) *DB { + return nil +} + +func (_ Statement) Order(_ interface{}) *DB { + return nil +} + +func (_ Statement) Pluck(_ string, _ interface{}) *DB { + return nil +} + +func (_ Statement) Preload(_ string, _ ...interface{}) *DB { + return nil +} + +func (_ Statement) Raw(_ string, _ ...interface{}) *DB { + return nil +} + +func (_ Statement) Rollback() *DB { + return nil +} + +func (_ Statement) RollbackTo(_ string) *DB { + return nil +} + +func (_ Statement) Row() *sql.Row { + return nil +} + +func (_ Statement) Rows() (*sql.Rows, error) { + return nil, nil +} + +func (_ Statement) Save(_ interface{}) *DB { + return nil +} + +func (_ Statement) SavePoint(_ string) *DB { + return nil +} + +func (_ Statement) Scan(_ interface{}) *DB { + return nil +} + +func (_ Statement) ScanRows(_ *sql.Rows, _ interface{}) error { + return nil +} + +func (_ Statement) Scopes(_ ...func(*DB) *DB) *DB { + return nil +} + +func (_ Statement) Select(_ interface{}, _ ...interface{}) *DB { + return nil +} + +func (_ Statement) Session(_ *Session) *DB { + return nil +} + +func (_ Statement) Set(_ string, _ interface{}) *DB { + return nil +} + +func (_ Statement) SetupJoinTable(_ interface{}, _ string, _ interface{}) error { + return nil +} + +func (_ Statement) Take(_ interface{}, _ ...interface{}) *DB { + return nil +} + +func (_ Statement) Transaction(_ func(*DB) error, _ ...*sql.TxOptions) error { + return nil +} + +func (_ Statement) Update(_ string, _ interface{}) *DB { + return nil +} + +func (_ Statement) UpdateColumn(_ string, _ interface{}) *DB { + return nil +} + +func (_ Statement) UpdateColumns(_ interface{}) *DB { + return nil +} + +func (_ Statement) Updates(_ interface{}) *DB { + return nil +} + +func (_ Statement) Use(_ Plugin) error { + return nil +} + +func (_ Statement) Where(_ interface{}, _ ...interface{}) *DB { + return nil +} + +func (_ Statement) WithContext(_ context.Context) *DB { + return nil +} + +func (_ *Statement) AddClause(_ interface{}) {} + +func (_ *Statement) AddClauseIfNotExists(_ interface{}) {} + +func (_ *Statement) AddVar(_ interface{}, _ ...interface{}) {} + +func (_ *Statement) Build(_ ...string) {} + +func (_ *Statement) BuildCondition(_ interface{}, _ ...interface{}) []interface{} { + return nil +} + +func (_ *Statement) Changed(_ ...string) bool { + return false +} + +func (_ *Statement) Parse(_ interface{}) error { + return nil +} + +func (_ *Statement) Quote(_ interface{}) string { + return "" +} + +func (_ *Statement) QuoteTo(_ interface{}, _ interface{}) {} + +func (_ *Statement) SelectAndOmitColumns(_ bool, _ bool) (map[string]bool, bool) { + return nil, false +} + +func (_ *Statement) SetColumn(_ string, _ interface{}, _ ...bool) {} + +func (_ *Statement) WriteByte(_ byte) error { + return nil +} + +func (_ *Statement) WriteQuoted(_ interface{}) {} + +func (_ *Statement) WriteString(_ string) (int, error) { + return 0, nil +} + +type ViewOption struct { + Replace bool + CheckOption string + Query *DB +} diff --git a/ql/test/experimental/InconsistentCode/vendor/modules.txt b/ql/test/experimental/InconsistentCode/vendor/modules.txt new file mode 100644 index 000000000..52a1f62b3 --- /dev/null +++ b/ql/test/experimental/InconsistentCode/vendor/modules.txt @@ -0,0 +1,3 @@ +# gorm.io/gorm v1.21.12 +## explicit +gorm.io/gorm