|
| 1 | +/** |
| 2 | + * @name Database call in loop |
| 3 | + * @description Detects database operations within loops. |
| 4 | + * Doing operations in series can be slow and lead to N+1 situations. |
| 5 | + * @kind path-problem |
| 6 | + * @problem.severity warning |
| 7 | + * @precision high |
| 8 | + * @id go/examples/database-call-in-loop |
| 9 | + */ |
| 10 | + |
| 11 | +import go |
| 12 | + |
| 13 | +class DatabaseAccess extends DataFlow::MethodCallNode { |
| 14 | + DatabaseAccess() { |
| 15 | + exists(string name | |
| 16 | + this.getTarget().hasQualifiedName(Gorm::packagePath(), "DB", name) and |
| 17 | + // all terminating Gorm methods |
| 18 | + name = |
| 19 | + [ |
| 20 | + "Find", "Take", "Last", "Scan", "Row", "Rows", "ScanRows", "Pluck", "Count", "First", |
| 21 | + "FirstOrInit", "FindOrCreate", "Update", "Updates", "UpdateColumn", "UpdateColumns", |
| 22 | + "Save", "Create", "Delete", "Exec" |
| 23 | + ] |
| 24 | + ) |
| 25 | + } |
| 26 | +} |
| 27 | + |
| 28 | +class CallGraphNode extends Locatable { |
| 29 | + CallGraphNode() { |
| 30 | + this instanceof LoopStmt |
| 31 | + or |
| 32 | + this instanceof CallExpr |
| 33 | + or |
| 34 | + this instanceof FuncDef |
| 35 | + } |
| 36 | +} |
| 37 | + |
| 38 | +/** |
| 39 | + * Holds if `pred` calls `succ`, i.e. is an edge in the call graph, |
| 40 | + * This includes explicit edges from call -> callee, to produce better paths. |
| 41 | + */ |
| 42 | +predicate callGraphEdge(CallGraphNode pred, CallGraphNode succ) { |
| 43 | + // Go from a loop to an enclosed expression. |
| 44 | + pred.(LoopStmt).getBody().getAChild*() = succ.(CallExpr) |
| 45 | + or |
| 46 | + // Go from a call to the called function. |
| 47 | + pred.(CallExpr) = succ.(FuncDef).getACall().asExpr() |
| 48 | + or |
| 49 | + // Go from a function to an enclosed loop. |
| 50 | + pred.(FuncDef) = succ.(LoopStmt).getEnclosingFunction() |
| 51 | + or |
| 52 | + // Go from a function to an enclosed call. |
| 53 | + pred.(FuncDef) = succ.(CallExpr).getEnclosingFunction() |
| 54 | +} |
| 55 | + |
| 56 | +query predicate edges(CallGraphNode pred, CallGraphNode succ) { |
| 57 | + callGraphEdge(pred, succ) and |
| 58 | + // Limit the range of edges to only those that are relevant. |
| 59 | + // This helps to speed up the query by reducing the size of the outputted path information. |
| 60 | + exists(LoopStmt loop, DatabaseAccess dbAccess | |
| 61 | + // is between a loop and a db access |
| 62 | + callGraphEdge*(loop, pred) and |
| 63 | + callGraphEdge*(succ, dbAccess.asExpr()) |
| 64 | + ) |
| 65 | +} |
| 66 | + |
| 67 | +from LoopStmt loop, DatabaseAccess dbAccess |
| 68 | +where edges*(loop, dbAccess.asExpr()) |
| 69 | +select dbAccess, loop, dbAccess, "$@ is called in $@", dbAccess, dbAccess.toString(), loop, "a loop" |
0 commit comments