Skip to content

Commit 31e1bef

Browse files
committed
Add framework mode queries
1 parent 82cdf03 commit 31e1bef

File tree

5 files changed

+247
-7
lines changed

5 files changed

+247
-7
lines changed

extensions/ql-vscode/src/data-extensions-editor/external-api-usage-query.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ export async function runQuery({
6161

6262
const queryDir = (await dir({ unsafeCleanup: true })).path;
6363
const queryFile = join(queryDir, "FetchExternalApis.ql");
64-
await writeFile(queryFile, query.mainQuery, "utf8");
64+
await writeFile(queryFile, query.applicationModeQuery, "utf8");
6565

6666
if (query.dependencies) {
6767
for (const [filename, contents] of Object.entries(query.dependencies)) {

extensions/ql-vscode/src/data-extensions-editor/queries/csharp.ts

Lines changed: 134 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { Query } from "./query";
22

33
export const fetchExternalApisQuery: Query = {
4-
mainQuery: `/**
4+
applicationModeQuery: `/**
55
* @name Usage of APIs coming from external libraries
66
* @description A list of 3rd party APIs used in the codebase.
77
* @tags telemetry
@@ -27,6 +27,139 @@ where
2727
supported = isSupported(api) and
2828
usage = aUsage(api)
2929
select usage, apiName, supported.toString(), "supported", api.getFile().getBaseName(), "library"
30+
`,
31+
frameworkModeQuery: `/**
32+
* @name Usage of APIs coming from external libraries
33+
* @description A list of 3rd party APIs used in the codebase.
34+
* @tags telemetry
35+
* @kind problem
36+
* @id cs/telemetry/fetch-external-apis
37+
*/
38+
39+
private import csharp
40+
private import dotnet
41+
private import semmle.code.csharp.dispatch.Dispatch
42+
private import semmle.code.csharp.dataflow.ExternalFlow
43+
private import semmle.code.csharp.dataflow.FlowSummary
44+
private import semmle.code.csharp.dataflow.internal.DataFlowImplCommon as DataFlowImplCommon
45+
private import semmle.code.csharp.dataflow.internal.DataFlowPrivate
46+
private import semmle.code.csharp.dataflow.internal.DataFlowDispatch as DataFlowDispatch
47+
private import semmle.code.csharp.dataflow.internal.FlowSummaryImpl as FlowSummaryImpl
48+
private import semmle.code.csharp.dataflow.internal.TaintTrackingPrivate
49+
private import semmle.code.csharp.security.dataflow.flowsources.Remote
50+
51+
pragma[nomagic]
52+
private predicate isTestNamespace(Namespace ns) {
53+
ns.getFullName()
54+
.matches([
55+
"NUnit.Framework%", "Xunit%", "Microsoft.VisualStudio.TestTools.UnitTesting%", "Moq%"
56+
])
57+
}
58+
59+
/**
60+
* A test library.
61+
*/
62+
class TestLibrary extends RefType {
63+
TestLibrary() { isTestNamespace(this.getNamespace()) }
64+
}
65+
66+
/** Holds if the given callable is not worth supporting. */
67+
private predicate isUninteresting(DotNet::Callable c) {
68+
c.getDeclaringType() instanceof TestLibrary or
69+
c.(Constructor).isParameterless()
70+
}
71+
72+
class PublicMethod extends DotNet::Member {
73+
PublicMethod() { this.isPublic() and not isUninteresting(this) and exists(this.(DotNet::Member)) }
74+
75+
/**
76+
* Gets the unbound type, name and parameter types of this API.
77+
*/
78+
bindingset[this]
79+
private string getSignature() {
80+
result =
81+
this.getDeclaringType().getUnboundDeclaration() + "." + this.getName() + "(" +
82+
parameterQualifiedTypeNamesToString(this) + ")"
83+
}
84+
85+
/**
86+
* Gets the namespace of this API.
87+
*/
88+
bindingset[this]
89+
string getNamespace() { this.getDeclaringType().hasQualifiedName(result, _) }
90+
91+
/**
92+
* Gets the namespace and signature of this API.
93+
*/
94+
bindingset[this]
95+
string getApiName() { result = this.getNamespace() + "#" + this.getSignature() }
96+
97+
/** Gets a node that is an input to a call to this API. */
98+
private ArgumentNode getAnInput() {
99+
result
100+
.getCall()
101+
.(DataFlowDispatch::NonDelegateDataFlowCall)
102+
.getATarget(_)
103+
.getUnboundDeclaration() = this
104+
}
105+
106+
/** Gets a node that is an output from a call to this API. */
107+
private DataFlow::Node getAnOutput() {
108+
exists(
109+
Call c, DataFlowDispatch::NonDelegateDataFlowCall dc, DataFlowImplCommon::ReturnKindExt ret
110+
|
111+
dc.getDispatchCall().getCall() = c and
112+
c.getTarget().getUnboundDeclaration() = this
113+
|
114+
result = ret.getAnOutNode(dc)
115+
)
116+
}
117+
118+
/** Holds if this API has a supported summary. */
119+
pragma[nomagic]
120+
predicate hasSummary() {
121+
this instanceof SummarizedCallable
122+
or
123+
defaultAdditionalTaintStep(this.getAnInput(), _)
124+
}
125+
126+
/** Holds if this API is a known source. */
127+
pragma[nomagic]
128+
predicate isSource() {
129+
this.getAnOutput() instanceof RemoteFlowSource or sourceNode(this.getAnOutput(), _)
130+
}
131+
132+
/** Holds if this API is a known sink. */
133+
pragma[nomagic]
134+
predicate isSink() { sinkNode(this.getAnInput(), _) }
135+
136+
/** Holds if this API is a known neutral. */
137+
pragma[nomagic]
138+
predicate isNeutral() { this instanceof FlowSummaryImpl::Public::NeutralCallable }
139+
140+
/**
141+
* Holds if this API is supported by existing CodeQL libraries, that is, it is either a
142+
* recognized source, sink or neutral or it has a flow summary.
143+
*/
144+
predicate isSupported() {
145+
this.hasSummary() or this.isSource() or this.isSink() or this.isNeutral()
146+
}
147+
}
148+
149+
private boolean isSupported(PublicMethod publicMethod) {
150+
publicMethod.isSupported() and result = true
151+
or
152+
not publicMethod.isSupported() and
153+
result = false
154+
}
155+
156+
from PublicMethod publicMethod, string apiName, boolean supported
157+
where
158+
apiName = publicMethod.getApiName() and
159+
publicMethod.getDeclaringType().fromSource() and
160+
supported = isSupported(publicMethod)
161+
select publicMethod, apiName, supported.toString(), "supported",
162+
publicMethod.getFile().getBaseName(), "library"
30163
`,
31164
dependencies: {
32165
"ExternalApi.qll": `/** Provides classes and predicates related to handling APIs from external libraries. */

extensions/ql-vscode/src/data-extensions-editor/queries/java.ts

Lines changed: 95 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { Query } from "./query";
22

33
export const fetchExternalApisQuery: Query = {
4-
mainQuery: `/**
4+
applicationModeQuery: `/**
55
* @name Usage of APIs coming from external libraries
66
* @description A list of 3rd party APIs used in the codebase. Excludes test and generated code.
77
* @tags telemetry
@@ -29,6 +29,100 @@ where
2929
supported = isSupported(api) and
3030
usage = aUsage(api)
3131
select usage, apiName, supported.toString(), "supported", api.jarContainer(), "library"
32+
`,
33+
frameworkModeQuery: `/**
34+
* @name Public methods
35+
* @description A list of APIs callable by consumers. Excludes test and generated code.
36+
* @tags telemetry
37+
* @kind problem
38+
* @id java/telemetry/fetch-public-methods
39+
*/
40+
41+
import java
42+
private import semmle.code.java.dataflow.DataFlow
43+
private import semmle.code.java.dataflow.ExternalFlow
44+
private import semmle.code.java.dataflow.FlowSources
45+
private import semmle.code.java.dataflow.FlowSummary
46+
private import semmle.code.java.dataflow.internal.DataFlowPrivate
47+
private import semmle.code.java.dataflow.internal.FlowSummaryImpl as FlowSummaryImpl
48+
private import semmle.code.java.dataflow.internal.ModelExclusions
49+
50+
/** Holds if the given callable is not worth supporting. */
51+
private predicate isUninteresting(Callable c) {
52+
c.getDeclaringType() instanceof TestLibrary or
53+
c.(Constructor).isParameterless()
54+
}
55+
56+
class PublicMethod extends Callable {
57+
PublicMethod() { this.isPublic() and not isUninteresting(this) }
58+
59+
/**
60+
* Gets information about the external API in the form expected by the MaD modeling framework.
61+
*/
62+
string getApiName() {
63+
result =
64+
this.getDeclaringType().getPackage() + "." + this.getDeclaringType().getSourceDeclaration() +
65+
"#" + this.getName() + paramsString(this)
66+
}
67+
68+
/** Gets a node that is an input to a call to this API. */
69+
private DataFlow::Node getAnInput() {
70+
exists(Call call | call.getCallee().getSourceDeclaration() = this |
71+
result.asExpr().(Argument).getCall() = call or
72+
result.(ArgumentNode).getCall().asCall() = call
73+
)
74+
}
75+
76+
/** Gets a node that is an output from a call to this API. */
77+
private DataFlow::Node getAnOutput() {
78+
exists(Call call | call.getCallee().getSourceDeclaration() = this |
79+
result.asExpr() = call or
80+
result.(DataFlow::PostUpdateNode).getPreUpdateNode().(ArgumentNode).getCall().asCall() = call
81+
)
82+
}
83+
84+
/** Holds if this API has a supported summary. */
85+
pragma[nomagic]
86+
predicate hasSummary() {
87+
this = any(SummarizedCallable sc).asCallable() or
88+
TaintTracking::localAdditionalTaintStep(this.getAnInput(), _)
89+
}
90+
91+
pragma[nomagic]
92+
predicate isSource() {
93+
this.getAnOutput() instanceof RemoteFlowSource or sourceNode(this.getAnOutput(), _)
94+
}
95+
96+
/** Holds if this API is a known sink. */
97+
pragma[nomagic]
98+
predicate isSink() { sinkNode(this.getAnInput(), _) }
99+
100+
/** Holds if this API is a known neutral. */
101+
pragma[nomagic]
102+
predicate isNeutral() { this = any(FlowSummaryImpl::Public::NeutralCallable nsc).asCallable() }
103+
104+
/**
105+
* Holds if this API is supported by existing CodeQL libraries, that is, it is either a
106+
* recognized source, sink or neutral or it has a flow summary.
107+
*/
108+
predicate isSupported() {
109+
this.hasSummary() or this.isSource() or this.isSink() or this.isNeutral()
110+
}
111+
}
112+
113+
private boolean isSupported(PublicMethod publicMethod) {
114+
publicMethod.isSupported() and result = true
115+
or
116+
not publicMethod.isSupported() and result = false
117+
}
118+
119+
from PublicMethod publicMethod, string apiName, boolean supported
120+
where
121+
apiName = publicMethod.getApiName() and
122+
publicMethod.getCompilationUnit().isSourceFile() and
123+
supported = isSupported(publicMethod)
124+
select publicMethod, apiName, supported.toString(), "supported",
125+
publicMethod.getCompilationUnit().getParentContainer().getBaseName(), "library"
32126
`,
33127
dependencies: {
34128
"ExternalApi.qll": `/** Provides classes and predicates related to handling APIs from external libraries. */

extensions/ql-vscode/src/data-extensions-editor/queries/query.ts

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,29 @@
11
export type Query = {
22
/**
3-
* The main query.
3+
* The application query.
44
*
55
* It should select all usages of external APIs, and return the following result pattern:
66
* - usage: the usage of the external API. This is an entity.
77
* - apiName: the name of the external API. This is a string.
8-
* - supported: whether the external API is supported by the extension. This should be a string representation of a boolean to satify the result pattern for a problem query.
8+
* - supported: whether the external API is modeled. This should be a string representation of a boolean to satify the result pattern for a problem query.
99
* - "supported": a string literal. This is required to make the query a valid problem query.
1010
* - libraryName: the name of the library that contains the external API. This is a string and usually the basename of a file.
1111
* - "library": a string literal. This is required to make the query a valid problem query.
1212
*/
13-
mainQuery: string;
13+
applicationModeQuery: string;
14+
/**
15+
* The framework query.
16+
*
17+
* It should select all methods that are callable by applications, which is usually all public methods (and constructors).
18+
* The result pattern should be as follows:
19+
* - method: the method that is callable by applications. This is an entity.
20+
* - apiName: the name of the external API. This is a string.
21+
* - supported: whether this method is modeled. This should be a string representation of a boolean to satify the result pattern for a problem query.
22+
* - "supported": a string literal. This is required to make the query a valid problem query.
23+
* - libraryName: an arbitrary string. This is required to make it match the structure of the application query.
24+
* - "library": a string literal. This is required to make the query a valid problem query.
25+
*/
26+
frameworkModeQuery: string;
1427
dependencies?: {
1528
[filename: string]: string;
1629
};

extensions/ql-vscode/test/vscode-tests/no-workspace/data-extensions-editor/external-api-usage-query.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,7 @@ describe("runQuery", () => {
112112

113113
expect(
114114
await readFile(join(queryDirectory, "FetchExternalApis.ql"), "utf8"),
115-
).toEqual(query.mainQuery);
115+
).toEqual(query.applicationModeQuery);
116116

117117
for (const [filename, contents] of Object.entries(
118118
query.dependencies ?? {},

0 commit comments

Comments
 (0)