Skip to content

Commit 1367d38

Browse files
authored
Merge pull request #2329 from github/starcke/ext-csharp-query
Add support for running C# query.
2 parents 9b647ff + 1e42c11 commit 1367d38

File tree

4 files changed

+282
-90
lines changed

4 files changed

+282
-90
lines changed
Lines changed: 198 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,198 @@
1+
import { Query } from "./query";
2+
3+
export const fetchExternalApisQuery: Query = {
4+
mainQuery: `/**
5+
* @name Usage of APIs coming from external libraries
6+
* @description A list of 3rd party APIs used in the codebase.
7+
* @tags telemetry
8+
* @id cs/telemetry/fetch-external-apis
9+
*/
10+
11+
import csharp
12+
import semmle.code.csharp.dataflow.internal.FlowSummaryImpl as FlowSummaryImpl
13+
import ExternalApi
14+
15+
private Call aUsage(ExternalApi api) {
16+
result.getTarget().getUnboundDeclaration() = api
17+
}
18+
19+
private boolean isSupported(ExternalApi api) {
20+
api.isSupported() and result = true
21+
or
22+
not api.isSupported() and
23+
result = false
24+
}
25+
26+
from ExternalApi api, string apiName, boolean supported, Call usage
27+
where
28+
apiName = api.getApiName() and
29+
supported = isSupported(api) and
30+
usage = aUsage(api)
31+
select apiName, supported, usage
32+
`,
33+
dependencies: {
34+
"ExternalApi.qll": `/** Provides classes and predicates related to handling APIs from external libraries. */
35+
36+
private import csharp
37+
private import dotnet
38+
private import semmle.code.csharp.dispatch.Dispatch
39+
private import semmle.code.csharp.dataflow.ExternalFlow
40+
private import semmle.code.csharp.dataflow.FlowSummary
41+
private import semmle.code.csharp.dataflow.internal.DataFlowImplCommon as DataFlowImplCommon
42+
private import semmle.code.csharp.dataflow.internal.DataFlowPrivate
43+
private import semmle.code.csharp.dataflow.internal.DataFlowDispatch as DataFlowDispatch
44+
private import semmle.code.csharp.dataflow.internal.FlowSummaryImpl as FlowSummaryImpl
45+
private import semmle.code.csharp.dataflow.internal.TaintTrackingPrivate
46+
private import semmle.code.csharp.security.dataflow.flowsources.Remote
47+
48+
pragma[nomagic]
49+
private predicate isTestNamespace(Namespace ns) {
50+
ns.getFullName()
51+
.matches([
52+
"NUnit.Framework%", "Xunit%", "Microsoft.VisualStudio.TestTools.UnitTesting%", "Moq%"
53+
])
54+
}
55+
56+
/**
57+
* A test library.
58+
*/
59+
class TestLibrary extends RefType {
60+
TestLibrary() { isTestNamespace(this.getNamespace()) }
61+
}
62+
63+
/** Holds if the given callable is not worth supporting. */
64+
private predicate isUninteresting(DotNet::Callable c) {
65+
c.getDeclaringType() instanceof TestLibrary or
66+
c.(Constructor).isParameterless()
67+
}
68+
69+
/**
70+
* An external API from either the C# Standard Library or a 3rd party library.
71+
*/
72+
class ExternalApi extends DotNet::Callable {
73+
ExternalApi() {
74+
this.isUnboundDeclaration() and
75+
this.fromLibrary() and
76+
this.(Modifiable).isEffectivelyPublic() and
77+
not isUninteresting(this)
78+
}
79+
80+
/**
81+
* Gets the unbound type, name and parameter types of this API.
82+
*/
83+
bindingset[this]
84+
private string getSignature() {
85+
result =
86+
this.getDeclaringType().getUnboundDeclaration() + "." + this.getName() + "(" +
87+
parameterQualifiedTypeNamesToString(this) + ")"
88+
}
89+
90+
/**
91+
* Gets the namespace of this API.
92+
*/
93+
bindingset[this]
94+
string getNamespace() { this.getDeclaringType().hasQualifiedName(result, _) }
95+
96+
/**
97+
* Gets the namespace and signature of this API.
98+
*/
99+
bindingset[this]
100+
string getApiName() { result = this.getNamespace() + "#" + this.getSignature() }
101+
102+
/** Gets a node that is an input to a call to this API. */
103+
private ArgumentNode getAnInput() {
104+
result
105+
.getCall()
106+
.(DataFlowDispatch::NonDelegateDataFlowCall)
107+
.getATarget(_)
108+
.getUnboundDeclaration() = this
109+
}
110+
111+
/** Gets a node that is an output from a call to this API. */
112+
private DataFlow::Node getAnOutput() {
113+
exists(
114+
Call c, DataFlowDispatch::NonDelegateDataFlowCall dc, DataFlowImplCommon::ReturnKindExt ret
115+
|
116+
dc.getDispatchCall().getCall() = c and
117+
c.getTarget().getUnboundDeclaration() = this
118+
|
119+
result = ret.getAnOutNode(dc)
120+
)
121+
}
122+
123+
/** Holds if this API has a supported summary. */
124+
pragma[nomagic]
125+
predicate hasSummary() {
126+
this instanceof SummarizedCallable
127+
or
128+
defaultAdditionalTaintStep(this.getAnInput(), _)
129+
}
130+
131+
/** Holds if this API is a known source. */
132+
pragma[nomagic]
133+
predicate isSource() {
134+
this.getAnOutput() instanceof RemoteFlowSource or sourceNode(this.getAnOutput(), _)
135+
}
136+
137+
/** Holds if this API is a known sink. */
138+
pragma[nomagic]
139+
predicate isSink() { sinkNode(this.getAnInput(), _) }
140+
141+
/** Holds if this API is a known neutral. */
142+
pragma[nomagic]
143+
predicate isNeutral() { this instanceof FlowSummaryImpl::Public::NeutralCallable }
144+
145+
/**
146+
* Holds if this API is supported by existing CodeQL libraries, that is, it is either a
147+
* recognized source, sink or neutral or it has a flow summary.
148+
*/
149+
predicate isSupported() {
150+
this.hasSummary() or this.isSource() or this.isSink() or this.isNeutral()
151+
}
152+
}
153+
154+
/**
155+
* Gets the limit for the number of results produced by a telemetry query.
156+
*/
157+
int resultLimit() { result = 1000 }
158+
159+
/**
160+
* Holds if it is relevant to count usages of "api".
161+
*/
162+
signature predicate relevantApi(ExternalApi api);
163+
164+
/**
165+
* Given a predicate to count relevant API usages, this module provides a predicate
166+
* for restricting the number or returned results based on a certain limit.
167+
*/
168+
module Results<relevantApi/1 getRelevantUsages> {
169+
private int getUsages(string apiName) {
170+
result =
171+
strictcount(Call c, ExternalApi api |
172+
c.getTarget().getUnboundDeclaration() = api and
173+
apiName = api.getApiName() and
174+
getRelevantUsages(api)
175+
)
176+
}
177+
178+
private int getOrder(string apiName) {
179+
apiName =
180+
rank[result](string name, int usages |
181+
usages = getUsages(name)
182+
|
183+
name order by usages desc, name
184+
)
185+
}
186+
187+
/**
188+
* Holds if there exists an API with "apiName" that is being used "usages" times
189+
* and if it is in the top results (guarded by resultLimit).
190+
*/
191+
predicate restrict(string apiName, int usages) {
192+
usages = getUsages(apiName) and
193+
getOrder(apiName) <= resultLimit()
194+
}
195+
}
196+
`,
197+
},
198+
};
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
1+
import { fetchExternalApisQuery as csharpFetchExternalApisQuery } from "./csharp";
12
import { fetchExternalApisQuery as javaFetchExternalApisQuery } from "./java";
23
import { Query } from "./query";
34
import { QueryLanguage } from "../../common/query-language";
45

56
export const fetchExternalApiQueries: Partial<Record<QueryLanguage, Query>> = {
7+
[QueryLanguage.CSharp]: csharpFetchExternalApisQuery,
68
[QueryLanguage.Java]: javaFetchExternalApisQuery,
79
};

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

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,7 @@ private Call aUsage(ExternalApi api) {
2020
private boolean isSupported(ExternalApi api) {
2121
api.isSupported() and result = true
2222
or
23-
api = any(FlowSummaryImpl::Public::NeutralCallable nsc).asCallable() and result = true
24-
or
25-
not api.isSupported() and
26-
not api = any(FlowSummaryImpl::Public::NeutralCallable nsc).asCallable() and
27-
result = false
23+
not api.isSupported() and result = false
2824
}
2925
3026
from ExternalApi api, string apiName, boolean supported, Call usage

0 commit comments

Comments
 (0)