|
1 | 1 | /** |
2 | | - * @name Call graph quality |
3 | | - * @description Measures the number of resolved and unresolved calls, for diagnostic purposes. |
4 | | - * @id js/meta/call-graph-quality |
| 2 | + * Provides predicates for measuring the quality of the call graph, that is, |
| 3 | + * the number of calls that could be resolved to a callee. |
5 | 4 | */ |
6 | 5 |
|
7 | | -import javascript::DataFlow |
8 | | -import semmle.javascript.dataflow.internal.FlowSteps as FlowSteps |
9 | | -import semmle.javascript.dependencies.Dependencies |
10 | | -import semmle.javascript.dependencies.FrameworkLibraries |
11 | | -import semmle.javascript.frameworks.Testing |
| 6 | +import javascript |
| 7 | + |
| 8 | +private import semmle.javascript.dataflow.internal.FlowSteps as FlowSteps |
| 9 | +private import semmle.javascript.dependencies.Dependencies |
| 10 | +private import semmle.javascript.dependencies.FrameworkLibraries |
| 11 | +private import semmle.javascript.frameworks.Testing |
| 12 | +private import DataFlow |
| 13 | + |
| 14 | +/** |
| 15 | + * Gets the root folder of the snapshot. |
| 16 | + * |
| 17 | + * This is selected as the location for project-wide metrics. |
| 18 | + */ |
| 19 | +Folder projectRoot() { result.getRelativePath() = "" } |
12 | 20 |
|
13 | 21 | /** A file we ignore because it is a test file or compiled/generated/bundled code. */ |
14 | 22 | class IgnoredFile extends File { |
@@ -140,59 +148,60 @@ SourceNode resolvableCallback() { |
140 | 148 | } |
141 | 149 |
|
142 | 150 | /** |
143 | | - * Gets a call site that can be resolved to an function in the same project. |
| 151 | + * Acall site that can be resolved to a function in the same project. |
144 | 152 | */ |
145 | | -RelevantInvoke resolvedCall() { |
146 | | - FlowSteps::calls(result, _) |
147 | | - or |
148 | | - result = resolvableCallback().getAnInvocation() |
| 153 | +class ResolvedCall extends RelevantInvoke { |
| 154 | + ResolvedCall() { |
| 155 | + FlowSteps::calls(this, _) |
| 156 | + or |
| 157 | + this = resolvableCallback().getAnInvocation() |
| 158 | + } |
149 | 159 | } |
150 | 160 |
|
151 | 161 | /** |
152 | | - * Gets a call site that is believed to call an external function. |
| 162 | + * A call site that is believed to call an external function. |
153 | 163 | */ |
154 | | -RelevantInvoke externalCall() { |
155 | | - not result = resolvedCall() and // avoid double counting |
156 | | - ( |
157 | | - // Call to modelled external library |
158 | | - result = externalNode() |
159 | | - or |
160 | | - // 'require' call or similar |
161 | | - result = moduleImport(_) |
162 | | - or |
163 | | - // Resolved to externs file |
164 | | - exists(result.(InvokeNode).getACallee(1)) |
165 | | - or |
166 | | - // Modelled as taint step but isn't from an NPM module. E.g. `substring` or `push`. |
167 | | - exists(TaintTracking::AdditionalTaintStep step | |
168 | | - step.step(_, result) |
| 164 | +class ExternalCall extends RelevantInvoke { |
| 165 | + ExternalCall() { |
| 166 | + not this instanceof ResolvedCall and // avoid double counting |
| 167 | + ( |
| 168 | + // Call to modelled external library |
| 169 | + this = externalNode() |
169 | 170 | or |
170 | | - step.step(result.getAnArgument(), _) |
| 171 | + // 'require' call or similar |
| 172 | + this = moduleImport(_) |
171 | 173 | or |
172 | | - step.step(_, result.getCallback(_).getParameter(_)) |
173 | | - ) |
174 | | - or |
175 | | - // Commonly used methods that are usually external and not found by the above |
176 | | - exists(string name | name = result.(MethodCallNode).getMethodName() | |
177 | | - name = "indexOf" or |
178 | | - name = "lastIndexOf" or |
179 | | - name = "then" |
| 174 | + // Resolved to externs file |
| 175 | + exists(this.(InvokeNode).getACallee(1)) |
| 176 | + or |
| 177 | + // Modelled as taint step but isn't from an NPM module. E.g. `substring` or `push`. |
| 178 | + exists(TaintTracking::AdditionalTaintStep step | |
| 179 | + step.step(_, this) |
| 180 | + or |
| 181 | + step.step(this.getAnArgument(), _) |
| 182 | + ) |
| 183 | + or |
| 184 | + // Modelled as remote flow but not found by the above for whatever reason |
| 185 | + this instanceof RemoteFlowSource |
180 | 186 | ) |
181 | | - ) |
| 187 | + } |
182 | 188 | } |
183 | 189 |
|
184 | 190 | /** |
185 | 191 | * Gets a call site that could not be resolved. |
186 | 192 | */ |
187 | | -RelevantInvoke unresolvedCall() { |
188 | | - not result = resolvedCall() and |
189 | | - not result = externalCall() |
| 193 | +class UnresolvedCall extends RelevantInvoke { |
| 194 | + UnresolvedCall() { |
| 195 | + not this instanceof ResolvedCall and |
| 196 | + not this instanceof ExternalCall |
| 197 | + } |
190 | 198 | } |
191 | 199 |
|
192 | | -// Name all columns in the 'from' clause to get named columns in metadata |
193 | | -from int resolved, int calls, float ratio |
194 | | -where |
195 | | - calls = count(resolvedCall()) + count(unresolvedCall()) and |
196 | | - resolved = count(resolvedCall()) and |
197 | | - ratio = resolved / (float)calls |
198 | | -select resolved, calls, ratio |
| 200 | +/** |
| 201 | + * A call that is believed to call a function within the same project. |
| 202 | + */ |
| 203 | +class NonExternalCall extends RelevantInvoke { |
| 204 | + NonExternalCall() { |
| 205 | + not this instanceof ExternalCall |
| 206 | + } |
| 207 | +} |
0 commit comments