Skip to content

Commit

Permalink
C#: Move external api declarations to the library pack.
Browse files Browse the repository at this point in the history
  • Loading branch information
michaelnebel committed Dec 17, 2024
1 parent 132dbd7 commit a91c1dc
Show file tree
Hide file tree
Showing 12 changed files with 193 additions and 185 deletions.
163 changes: 163 additions & 0 deletions csharp/ql/lib/semmle/code/csharp/telemetry/ExternalApi.qll
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
/** Provides classes and predicates related to handling APIs from external libraries. */

private import csharp
private import semmle.code.csharp.dispatch.Dispatch
private import semmle.code.csharp.dataflow.FlowSummary
private import semmle.code.csharp.dataflow.internal.DataFlowPrivate
private import semmle.code.csharp.dataflow.internal.DataFlowDispatch as DataFlowDispatch
private import semmle.code.csharp.dataflow.internal.ExternalFlow
private import semmle.code.csharp.dataflow.internal.FlowSummaryImpl as FlowSummaryImpl
private import semmle.code.csharp.dataflow.internal.TaintTrackingPrivate
private import semmle.code.csharp.security.dataflow.flowsources.ApiSources as ApiSources
private import semmle.code.csharp.security.dataflow.flowsinks.ApiSinks as ApiSinks
private import TestLibrary

/** Holds if the given callable is not worth supporting. */
private predicate isUninteresting(Callable c) {
c.getDeclaringType() instanceof TestLibrary
or
c.(Constructor).isParameterless()
or
// The data flow library uses read/store steps for properties, so we don't need to model them,
// if both a getter and a setter exist.
c.(Accessor).getDeclaration().(Property).isReadWrite()
}

/**
* An external API from either the C# Standard Library or a 3rd party library.
*/
class ExternalApi extends Callable {
ExternalApi() {
this.isUnboundDeclaration() and
this.fromLibrary() and
this.(Modifiable).isEffectivelyPublic() and
not isUninteresting(this)
}

/**
* Gets the unbound type, name and parameter types of this API.
*/
bindingset[this]
private string getSignature() {
result =
nestedName(this.getDeclaringType().getUnboundDeclaration()) + "." + this.getName() + "(" +
parameterQualifiedTypeNamesToString(this) + ")"
}

/**
* Gets the namespace of this API.
*/
bindingset[this]
string getNamespace() { this.getDeclaringType().hasFullyQualifiedName(result, _) }

/**
* Gets the namespace and signature of this API.
*/
bindingset[this]
string getApiName() { result = this.getNamespace() + "#" + this.getSignature() }

/** Gets a node that is an input to a call to this API. */
private ArgumentNode getAnInput() {
result
.getCall()
.(DataFlowDispatch::NonDelegateDataFlowCall)
.getATarget(_)
.getUnboundDeclaration() = this
}

/** Gets a node that is an output from a call to this API. */
private DataFlow::Node getAnOutput() {
exists(Call c, DataFlowDispatch::NonDelegateDataFlowCall dc |
dc.getDispatchCall().getCall() = c and
c.getTarget().getUnboundDeclaration() = this
|
result = DataFlowDispatch::getAnOutNode(dc, _)
)
}

/** Holds if this API has a supported summary. */
pragma[nomagic]
predicate hasSummary() {
this instanceof SummarizedCallable
or
defaultAdditionalTaintStep(this.getAnInput(), _, _)
}

/** Holds if this API is a known source. */
pragma[nomagic]
predicate isSource() { this.getAnOutput() instanceof ApiSources::SourceNode }

/** Holds if this API is a known sink. */
pragma[nomagic]
predicate isSink() { this.getAnInput() instanceof ApiSinks::SinkNode }

/** Holds if this API is a known neutral. */
pragma[nomagic]
predicate isNeutral() { this instanceof FlowSummaryImpl::Public::NeutralCallable }

/**
* Holds if this API is supported by existing CodeQL libraries, that is, it is either a
* recognized source, sink or neutral or it has a flow summary.
*/
predicate isSupported() {
this.hasSummary() or this.isSource() or this.isSink() or this.isNeutral()
}
}

/**
* Gets the nested name of the type `t`.
*
* If the type is not a nested type, the result is the same as \`getName()\`.
* Otherwise the name of the nested type is prefixed with a \`+\` and appended to
* the name of the enclosing type, which might be a nested type as well.
*/
private string nestedName(Type t) {
not exists(t.getDeclaringType().getUnboundDeclaration()) and
result = t.getName()
or
nestedName(t.getDeclaringType().getUnboundDeclaration()) + "+" + t.getName() = result
}

/**
* Gets the limit for the number of results produced by a telemetry query.
*/
int resultLimit() { result = 100 }

/**
* Holds if it is relevant to count usages of `api`.
*/
signature predicate relevantApi(ExternalApi api);

/**
* Given a predicate to count relevant API usages, this module provides a predicate
* for restricting the number or returned results based on a certain limit.
*/
module Results<relevantApi/1 getRelevantUsages> {
private int getUsages(string apiName) {
result =
strictcount(Call c, ExternalApi api |
c.getTarget().getUnboundDeclaration() = api and
apiName = api.getApiName() and
getRelevantUsages(api) and
c.fromSource()
)
}

private int getOrder(string apiName) {
apiName =
rank[result](string name, int usages |
usages = getUsages(name)
|
name order by usages desc, name
)
}

/**
* Holds if there exists an API with `apiName` that is being used `usages` times
* and if it is in the top results (guarded by resultLimit).
*/
predicate restrict(string apiName, int usages) {
usages = getUsages(apiName) and
getOrder(apiName) <= resultLimit()
}
}
16 changes: 16 additions & 0 deletions csharp/ql/lib/semmle/code/csharp/telemetry/TestLibrary.qll
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
private import csharp

pragma[nomagic]
private predicate isTestNamespace(Namespace ns) {
ns.getFullName()
.matches([
"NUnit.Framework%", "Xunit%", "Microsoft.VisualStudio.TestTools.UnitTesting%", "Moq%"
])
}

/**
* A test library.
*/
class TestLibrary extends RefType {
TestLibrary() { isTestNamespace(this.getNamespace()) }
}
165 changes: 3 additions & 162 deletions csharp/ql/src/Telemetry/ExternalApi.qll
Original file line number Diff line number Diff line change
@@ -1,163 +1,4 @@
/** Provides classes and predicates related to handling APIs from external libraries. */
// Use `semmle.code.csharp.telemetry.ExternalApi` instead.
deprecated module;

private import csharp
private import semmle.code.csharp.dispatch.Dispatch
private import semmle.code.csharp.dataflow.FlowSummary
private import semmle.code.csharp.dataflow.internal.DataFlowPrivate
private import semmle.code.csharp.dataflow.internal.DataFlowDispatch as DataFlowDispatch
private import semmle.code.csharp.dataflow.internal.ExternalFlow
private import semmle.code.csharp.dataflow.internal.FlowSummaryImpl as FlowSummaryImpl
private import semmle.code.csharp.dataflow.internal.TaintTrackingPrivate
private import semmle.code.csharp.security.dataflow.flowsources.ApiSources as ApiSources
private import semmle.code.csharp.security.dataflow.flowsinks.ApiSinks as ApiSinks
private import TestLibrary

/** Holds if the given callable is not worth supporting. */
private predicate isUninteresting(Callable c) {
c.getDeclaringType() instanceof TestLibrary
or
c.(Constructor).isParameterless()
or
// The data flow library uses read/store steps for properties, so we don't need to model them,
// if both a getter and a setter exist.
c.(Accessor).getDeclaration().(Property).isReadWrite()
}

/**
* An external API from either the C# Standard Library or a 3rd party library.
*/
class ExternalApi extends Callable {
ExternalApi() {
this.isUnboundDeclaration() and
this.fromLibrary() and
this.(Modifiable).isEffectivelyPublic() and
not isUninteresting(this)
}

/**
* Gets the unbound type, name and parameter types of this API.
*/
bindingset[this]
private string getSignature() {
result =
nestedName(this.getDeclaringType().getUnboundDeclaration()) + "." + this.getName() + "(" +
parameterQualifiedTypeNamesToString(this) + ")"
}

/**
* Gets the namespace of this API.
*/
bindingset[this]
string getNamespace() { this.getDeclaringType().hasFullyQualifiedName(result, _) }

/**
* Gets the namespace and signature of this API.
*/
bindingset[this]
string getApiName() { result = this.getNamespace() + "#" + this.getSignature() }

/** Gets a node that is an input to a call to this API. */
private ArgumentNode getAnInput() {
result
.getCall()
.(DataFlowDispatch::NonDelegateDataFlowCall)
.getATarget(_)
.getUnboundDeclaration() = this
}

/** Gets a node that is an output from a call to this API. */
private DataFlow::Node getAnOutput() {
exists(Call c, DataFlowDispatch::NonDelegateDataFlowCall dc |
dc.getDispatchCall().getCall() = c and
c.getTarget().getUnboundDeclaration() = this
|
result = DataFlowDispatch::getAnOutNode(dc, _)
)
}

/** Holds if this API has a supported summary. */
pragma[nomagic]
predicate hasSummary() {
this instanceof SummarizedCallable
or
defaultAdditionalTaintStep(this.getAnInput(), _, _)
}

/** Holds if this API is a known source. */
pragma[nomagic]
predicate isSource() { this.getAnOutput() instanceof ApiSources::SourceNode }

/** Holds if this API is a known sink. */
pragma[nomagic]
predicate isSink() { this.getAnInput() instanceof ApiSinks::SinkNode }

/** Holds if this API is a known neutral. */
pragma[nomagic]
predicate isNeutral() { this instanceof FlowSummaryImpl::Public::NeutralCallable }

/**
* Holds if this API is supported by existing CodeQL libraries, that is, it is either a
* recognized source, sink or neutral or it has a flow summary.
*/
predicate isSupported() {
this.hasSummary() or this.isSource() or this.isSink() or this.isNeutral()
}
}

/**
* Gets the nested name of the type `t`.
*
* If the type is not a nested type, the result is the same as \`getName()\`.
* Otherwise the name of the nested type is prefixed with a \`+\` and appended to
* the name of the enclosing type, which might be a nested type as well.
*/
private string nestedName(Type t) {
not exists(t.getDeclaringType().getUnboundDeclaration()) and
result = t.getName()
or
nestedName(t.getDeclaringType().getUnboundDeclaration()) + "+" + t.getName() = result
}

/**
* Gets the limit for the number of results produced by a telemetry query.
*/
int resultLimit() { result = 100 }

/**
* Holds if it is relevant to count usages of `api`.
*/
signature predicate relevantApi(ExternalApi api);

/**
* Given a predicate to count relevant API usages, this module provides a predicate
* for restricting the number or returned results based on a certain limit.
*/
module Results<relevantApi/1 getRelevantUsages> {
private int getUsages(string apiName) {
result =
strictcount(Call c, ExternalApi api |
c.getTarget().getUnboundDeclaration() = api and
apiName = api.getApiName() and
getRelevantUsages(api) and
c.fromSource()
)
}

private int getOrder(string apiName) {
apiName =
rank[result](string name, int usages |
usages = getUsages(name)
|
name order by usages desc, name
)
}

/**
* Holds if there exists an API with `apiName` that is being used `usages` times
* and if it is in the top results (guarded by resultLimit).
*/
predicate restrict(string apiName, int usages) {
usages = getUsages(apiName) and
getOrder(apiName) <= resultLimit()
}
}
import semmle.code.csharp.telemetry.ExternalApi
2 changes: 1 addition & 1 deletion csharp/ql/src/Telemetry/ExternalLibraryUsage.ql
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

private import csharp
private import semmle.code.csharp.dispatch.Dispatch
private import ExternalApi
private import semmle.code.csharp.telemetry.ExternalApi

private predicate getRelevantUsages(string namespace, int usages) {
usages =
Expand Down
2 changes: 1 addition & 1 deletion csharp/ql/src/Telemetry/SupportedExternalApis.ql
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

private import csharp
private import semmle.code.csharp.dispatch.Dispatch
private import ExternalApi
private import semmle.code.csharp.telemetry.ExternalApi

private predicate relevant(ExternalApi api) { api.isSupported() }

Expand Down
2 changes: 1 addition & 1 deletion csharp/ql/src/Telemetry/SupportedExternalSinks.ql
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

private import csharp
private import semmle.code.csharp.dispatch.Dispatch
private import ExternalApi
private import semmle.code.csharp.telemetry.ExternalApi

private predicate relevant(ExternalApi api) { api.isSink() }

Expand Down
2 changes: 1 addition & 1 deletion csharp/ql/src/Telemetry/SupportedExternalSources.ql
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

private import csharp
private import semmle.code.csharp.dispatch.Dispatch
private import ExternalApi
private import semmle.code.csharp.telemetry.ExternalApi

private predicate relevant(ExternalApi api) { api.isSource() }

Expand Down
2 changes: 1 addition & 1 deletion csharp/ql/src/Telemetry/SupportedExternalTaint.ql
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

private import csharp
private import semmle.code.csharp.dispatch.Dispatch
private import ExternalApi
private import semmle.code.csharp.telemetry.ExternalApi

private predicate relevant(ExternalApi api) { api.hasSummary() }

Expand Down
Loading

0 comments on commit a91c1dc

Please sign in to comment.