-
Notifications
You must be signed in to change notification settings - Fork 226
Add external APIs query in extension #2314
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
3 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
7 changes: 7 additions & 0 deletions
7
extensions/ql-vscode/src/data-extensions-editor/queries/index.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| import { fetchExternalApisQuery as javaFetchExternalApisQuery } from "./java"; | ||
| import { Query } from "./query"; | ||
| import { QueryLanguage } from "../../common/query-language"; | ||
|
|
||
| export const fetchExternalApiQueries: Partial<Record<QueryLanguage, Query>> = { | ||
| [QueryLanguage.Java]: javaFetchExternalApisQuery, | ||
| }; |
183 changes: 183 additions & 0 deletions
183
extensions/ql-vscode/src/data-extensions-editor/queries/java.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,183 @@ | ||
| import { Query } from "./query"; | ||
|
|
||
| export const fetchExternalApisQuery: Query = { | ||
| mainQuery: `/** | ||
| * @name Usage of APIs coming from external libraries | ||
| * @description A list of 3rd party APIs used in the codebase. Excludes test and generated code. | ||
| * @tags telemetry | ||
| * @id java/telemetry/fetch-external-apis | ||
| */ | ||
|
|
||
| import java | ||
| import semmle.code.java.dataflow.internal.FlowSummaryImpl as FlowSummaryImpl | ||
| import ExternalApi | ||
|
|
||
| private Call aUsage(ExternalApi api) { | ||
| result.getCallee().getSourceDeclaration() = api and | ||
| not result.getFile() instanceof GeneratedFile | ||
| } | ||
|
|
||
| private boolean isSupported(ExternalApi api) { | ||
| api.isSupported() and result = true | ||
| or | ||
| api = any(FlowSummaryImpl::Public::NeutralCallable nsc).asCallable() and result = true | ||
| or | ||
| not api.isSupported() and | ||
| not api = any(FlowSummaryImpl::Public::NeutralCallable nsc).asCallable() and | ||
| result = false | ||
| } | ||
|
|
||
| from ExternalApi api, string apiName, boolean supported, Call usage | ||
| where | ||
| apiName = api.getApiName() and | ||
| supported = isSupported(api) and | ||
| usage = aUsage(api) | ||
| select apiName, supported, usage | ||
| `, | ||
| dependencies: { | ||
| "ExternalApi.qll": `/** Provides classes and predicates related to handling APIs from external libraries. */ | ||
|
|
||
| private import java | ||
| private import semmle.code.java.dataflow.DataFlow | ||
| private import semmle.code.java.dataflow.ExternalFlow | ||
| private import semmle.code.java.dataflow.FlowSources | ||
| private import semmle.code.java.dataflow.FlowSummary | ||
| private import semmle.code.java.dataflow.internal.DataFlowPrivate | ||
| private import semmle.code.java.dataflow.TaintTracking | ||
|
|
||
| pragma[nomagic] | ||
| private predicate isTestPackage(Package p) { | ||
| p.getName() | ||
| .matches([ | ||
| "org.junit%", "junit.%", "org.mockito%", "org.assertj%", | ||
| "com.github.tomakehurst.wiremock%", "org.hamcrest%", "org.springframework.test.%", | ||
| "org.springframework.mock.%", "org.springframework.boot.test.%", "reactor.test%", | ||
| "org.xmlunit%", "org.testcontainers.%", "org.opentest4j%", "org.mockserver%", | ||
| "org.powermock%", "org.skyscreamer.jsonassert%", "org.rnorth.visibleassertions", | ||
| "org.openqa.selenium%", "com.gargoylesoftware.htmlunit%", "org.jboss.arquillian.testng%", | ||
| "org.testng%" | ||
| ]) | ||
| } | ||
|
|
||
| /** | ||
| * A test library. | ||
| */ | ||
| private class TestLibrary extends RefType { | ||
| TestLibrary() { isTestPackage(this.getPackage()) } | ||
| } | ||
|
|
||
| private string containerAsJar(Container container) { | ||
| if container instanceof JarFile then result = container.getBaseName() else result = "rt.jar" | ||
| } | ||
|
|
||
| /** Holds if the given callable is not worth supporting. */ | ||
| private predicate isUninteresting(Callable c) { | ||
| c.getDeclaringType() instanceof TestLibrary or | ||
| c.(Constructor).isParameterless() | ||
| } | ||
|
|
||
| /** | ||
| * An external API from either the Standard Library or a 3rd party library. | ||
| */ | ||
| class ExternalApi extends Callable { | ||
| ExternalApi() { not this.fromSource() and not isUninteresting(this) } | ||
|
|
||
| /** | ||
| * Gets information about the external API in the form expected by the MaD modeling framework. | ||
| */ | ||
| string getApiName() { | ||
| result = | ||
| this.getDeclaringType().getPackage() + "." + this.getDeclaringType().getSourceDeclaration() + | ||
| "#" + this.getName() + paramsString(this) | ||
| } | ||
|
|
||
| /** | ||
| * Gets the jar file containing this API. Normalizes the Java Runtime to "rt.jar" despite the presence of modules. | ||
| */ | ||
| string jarContainer() { result = containerAsJar(this.getCompilationUnit().getParentContainer*()) } | ||
|
|
||
| /** Gets a node that is an input to a call to this API. */ | ||
| private DataFlow::Node getAnInput() { | ||
| exists(Call call | call.getCallee().getSourceDeclaration() = this | | ||
| result.asExpr().(Argument).getCall() = call or | ||
| result.(ArgumentNode).getCall().asCall() = call | ||
| ) | ||
| } | ||
|
|
||
| /** Gets a node that is an output from a call to this API. */ | ||
| private DataFlow::Node getAnOutput() { | ||
| exists(Call call | call.getCallee().getSourceDeclaration() = this | | ||
| result.asExpr() = call or | ||
| result.(DataFlow::PostUpdateNode).getPreUpdateNode().(ArgumentNode).getCall().asCall() = call | ||
| ) | ||
| } | ||
|
|
||
| /** Holds if this API has a supported summary. */ | ||
| pragma[nomagic] | ||
| predicate hasSummary() { | ||
| this = any(SummarizedCallable sc).asCallable() or | ||
| TaintTracking::localAdditionalTaintStep(this.getAnInput(), _) | ||
| } | ||
|
|
||
| pragma[nomagic] | ||
| predicate isSource() { | ||
| this.getAnOutput() instanceof RemoteFlowSource or sourceNode(this.getAnOutput(), _) | ||
| } | ||
|
|
||
| /** Holds if this API is a known sink. */ | ||
| pragma[nomagic] | ||
| predicate isSink() { sinkNode(this.getAnInput(), _) } | ||
|
|
||
| /** Holds if this API is supported by existing CodeQL libraries, that is, it is either a recognized source or sink or has a flow summary. */ | ||
| predicate isSupported() { this.hasSummary() or this.isSource() or this.isSink() } | ||
| } | ||
|
|
||
| /** DEPRECATED: Alias for ExternalApi */ | ||
| deprecated class ExternalAPI = ExternalApi; | ||
|
|
||
| /** | ||
| * Gets the limit for the number of results produced by a telemetry query. | ||
| */ | ||
| int resultLimit() { result = 1000 } | ||
|
|
||
| /** | ||
| * 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.getCallee().getSourceDeclaration() = api and | ||
| not c.getFile() instanceof GeneratedFile and | ||
| apiName = api.getApiName() and | ||
| getRelevantUsages(api) | ||
| ) | ||
| } | ||
|
|
||
| private int getOrder(string apiInfo) { | ||
| apiInfo = | ||
| rank[result](string info, int usages | | ||
| usages = getUsages(info) | ||
| | | ||
| info order by usages desc, info | ||
| ) | ||
| } | ||
|
|
||
| /** | ||
| * 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() | ||
| } | ||
| } | ||
| `, | ||
| }, | ||
| }; |
6 changes: 6 additions & 0 deletions
6
extensions/ql-vscode/src/data-extensions-editor/queries/query.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| export type Query = { | ||
| mainQuery: string; | ||
| dependencies?: { | ||
| [filename: string]: string; | ||
| }; | ||
| }; | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wonder if we should just inline
ExternalApi.qll, so that we just have a single query file that we run instead of having to store multiple files?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it would be easier to transfer these queries to the codeql repository if we leave them as separate files. So far, we've not had to make any modifications to the
ExternalApi.qllfile, so we would only need to add in the main query and not make any changes to theExternalApi.qllfile. If we do combine these files, we'd need to split them out again in the future.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I dont think it would be too hard to do that split up later, but I dont feel strongly. It also doesnt seem super complicated to have extra dependency files because we also need to have the synthetic pack. So happy to leave it like this.