Skip to content

Add external APIs query in extension#2314

Merged
koesie10 merged 3 commits intomainfrom
koesie10/use-query-in-extension
Apr 14, 2023
Merged

Add external APIs query in extension#2314
koesie10 merged 3 commits intomainfrom
koesie10/use-query-in-extension

Conversation

@koesie10
Copy link
Copy Markdown
Member

This adds the external API query text to the extension directly to avoid users having to copy the query to their local codeql submodule or having to checkout a specific branch.

This is a temporary solution until the queries are stabilized. Once they are, we will upstream these to github/codeql and use them like other contextual queries.

Since this is just a temporary solution, this is not the prettiest code and is not intended to be a long-term solution. It does not do proper caching and will create a new temporary directory for every query run. The performance hit of this is acceptable and expected.

Checklist

  • CHANGELOG.md has been updated to incorporate all user visible changes made by this pull request.
  • Issues have been created for any UI or other user-facing changes made by this pull request.
  • [Maintainers only] If this pull request makes user-facing changes that require documentation changes, open a corresponding docs pull request in the github/codeql repo and add the ready-for-doc-review label there.

@koesie10 koesie10 requested a review from a team as a code owner April 13, 2023 11:24
This adds the external API query text to the extension directly to avoid
users having to copy the query to their local `codeql` submodule or
having to checkout a specific branch.

This is a temporary solution until the queries are stabilized. Once they
are, we will upstream these to `github/codeql` and use them like other
contextual queries.

Since this is just a temporary solution, this is not the prettiest code
and is not intended to be a long-term solution. It does not do proper
caching and will create a new temporary directory for every query run.
The performance hit of this is acceptable and expected.
@koesie10 koesie10 force-pushed the koesie10/use-query-in-extension branch from cf0a36c to 5200871 Compare April 13, 2023 11:26
@koesie10 koesie10 requested a review from starcke April 13, 2023 11:30
Copy link
Copy Markdown
Contributor

@starcke starcke left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Generally looks good to me, just a question about whether we can simplify.

@@ -0,0 +1,6 @@
export type Query = {
mainQuery: string;
dependencies?: {
Copy link
Copy Markdown
Contributor

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?

Copy link
Copy Markdown
Member Author

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.qll file, so we would only need to add in the main query and not make any changes to the ExternalApi.qll file. If we do combine these files, we'd need to split them out again in the future.

Copy link
Copy Markdown
Contributor

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.

import { fetchExternalApisQuery as javaFetchExternalApisQuery } from "./java";
import { Query } from "./query";

export const fetchExternalApiQueries: Record<string, Query> = {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just a question, probably fine. Are languages just string or do we have a type for them?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We do, but the DatabaseItem.language is a string, so we'd need to cast it to a QueryLanguage which might hide problems rather than actually giving us type safety. I've changed it to use a QueryLanguage though since that will prevent us from adding unsupported types.

@starcke
Copy link
Copy Markdown
Contributor

starcke commented Apr 13, 2023

If we want to go with just a single file the following should work:

/**
  * @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 xyz
  */
 
  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.internal.FlowSummaryImpl as FlowSummaryImpl
  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()) }
  }
  
  /** 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 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 a known neutral. */
    pragma[nomagic]
    predicate isNeutral() { this = any(FlowSummaryImpl::Public::NeutralCallable nsc).asCallable() }
  
    /**
     * 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()
    }
  }
  
 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
   not api.isSupported() 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

Copy link
Copy Markdown
Contributor

@starcke starcke left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

@koesie10 koesie10 enabled auto-merge April 14, 2023 07:30
@koesie10 koesie10 merged commit e95e4a3 into main Apr 14, 2023
@koesie10 koesie10 deleted the koesie10/use-query-in-extension branch April 14, 2023 08:27
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants