|
1 | 1 | /** |
2 | | - * @id ruby/could-be-async |
| 2 | + * @name Could be async |
| 3 | + * @description Use `ActiveRecord::Relation#load_async` to load records asynchronously. |
3 | 4 | * @kind problem |
4 | 5 | * @problem.severity info |
5 | | - * @problem.description Use `ActiveRecord::Relation#load_async` to load records asynchronously. |
| 6 | + * @precision high |
| 7 | + * @id rb/could-be-async |
| 8 | + * @tags performance |
6 | 9 | */ |
7 | 10 |
|
8 | 11 | import ruby |
9 | | -// private import codeql.ruby.CFG |
10 | 12 | import codeql.ruby.Concepts |
11 | 13 | import codeql.ruby.frameworks.ActiveRecord |
12 | 14 |
|
13 | | -// collection.each { |item| expensiveCall(item) } |
14 | | -predicate expensiveCall(DataFlow::CallNode c) { c instanceof SqlExecution } |
15 | | - |
16 | 15 | string loopMethodName() { |
17 | | - // TODO: check these |
18 | 16 | result in [ |
19 | 17 | "each", "reverse_each", "map", "map!", "foreach", "flat_map", "in_batches", "one?", "all?", |
20 | 18 | "collect", "collect!", "select", "select!", "reject", "reject!" |
21 | | - // , "collect", "select", "reject", "find_all", "find", |
22 | | - // "detect", "any?", "all?", "one?", "none?", "one", "none", "min", "max", "minmax", "min_by", |
23 | | - // "max_by", "minmax_by", "sort", "sort_by", "sort_by!", "sort" |
24 | 19 | ] |
25 | 20 | } |
26 | 21 |
|
@@ -51,38 +46,22 @@ predicate happensInInnermostLoop(LoopingCall loop, DataFlow::CallNode e) { |
51 | 46 | not happensInOuterLoop(loop, e) |
52 | 47 | } |
53 | 48 |
|
54 | | -// Active Record |
55 | | -private class LoadElementsCall extends ActiveRecordInstanceMethodCall { |
56 | | - LoadElementsCall() { |
57 | | - this.getMethodName() in ["latest", "find_by", "where", "count", "find"] and |
58 | | - not any(PluckCall p).chaines() = this |
59 | | - } |
60 | | -} |
61 | | - |
62 | 49 | private class PluckCall extends ActiveRecordInstanceMethodCall { |
63 | 50 | PluckCall() { this.getMethodName() in ["pluck"] } |
64 | 51 |
|
65 | | - ActiveRecordInstanceMethodCall chaines() { result = getChain(this.getInstance()) } |
| 52 | + ActiveRecordInstance chaines() { result = getChain(this) } |
66 | 53 | } |
67 | 54 |
|
68 | | -private ActiveRecordInstanceMethodCall getChain(ActiveRecordInstanceMethodCall c) { |
69 | | - result = c |
| 55 | +private ActiveRecordInstance getChain(ActiveRecordInstanceMethodCall c) { |
| 56 | + result = c.getInstance() |
70 | 57 | or |
71 | 58 | result = getChain(c.getInstance()) |
72 | 59 | } |
73 | 60 |
|
74 | | -ActiveRecordInstanceMethodCall longChain(ActiveRecordInstanceMethodCall c) { |
75 | | - 2 < strictcount(ActiveRecordInstanceMethodCall ch | ch = getChain(c)) and |
76 | | - result = getChain(c) and |
77 | | - result != c |
78 | | -} |
79 | | - |
80 | | -ActiveRecordInstanceMethodCall pluckChain(PluckCall c) { result = longChain(c) } |
81 | | - |
82 | | -// from LoopingCall loopCall |
83 | | -// select loopCall, loopCall.getLoopBlock() |
84 | 61 | from LoopingCall loop, DataFlow::CallNode call, string message |
85 | 62 | where |
| 63 | + not call.getLocation().getFile().getAbsolutePath().matches("%test%") and |
| 64 | + not call = any(PluckCall p).chaines() and |
86 | 65 | happensInInnermostLoop(loop, call) and |
87 | 66 | ( |
88 | 67 | call instanceof ActiveRecordModelFinderCall and |
|
0 commit comments