Java: Add VS Code model editor queries

This commit is contained in:
Koen Vlaswinkel
2023-09-13 13:04:26 +02:00
parent 3bf0d66d6c
commit 7db082f3fd
15 changed files with 248 additions and 1 deletions

View File

@@ -25,7 +25,7 @@ class TestLibrary extends RefType {
}
/** Holds if the given file is a test file. */
private predicate isInTestFile(File file) {
predicate isInTestFile(File file) {
file.getAbsolutePath().matches(["%/test/%", "%/guava-tests/%", "%/guava-testlib/%"]) and
not file.getAbsolutePath().matches(["%/ql/test/%", "%/ql/automodel/test/%"]) // allows our test cases to work
}

View File

@@ -0,0 +1,139 @@
/** Provides classes and predicates related to handling APIs for the VS Code extension. */
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
private import semmle.code.java.dataflow.internal.ModelExclusions
/** Holds if the given callable/method is not worth supporting. */
private predicate isUninteresting(Callable c) {
c.getDeclaringType() instanceof TestLibrary or
c.(Constructor).isParameterless() or
c.getDeclaringType() instanceof AnonymousClass
}
/**
* A callable method from either the Standard Library, a 3rd party library or from the source.
*/
class CallableMethod extends Callable {
CallableMethod() { 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().nestedName() + "#" +
this.getName() + paramsString(this)
}
private string getJarName() {
result = this.getCompilationUnit().getParentContainer*().(JarFile).getBaseName()
}
private string getJarVersion() {
result = this.getCompilationUnit().getParentContainer*().(JarFile).getSpecificationVersion()
}
/**
* Gets the jar file containing this API. Normalizes the Java Runtime to "rt.jar" despite the presence of modules.
*/
string jarContainer() {
result = this.getJarName()
or
not exists(this.getJarName()) and result = "rt.jar"
}
/**
* Gets the version of the JAR file containing this API. Empty if no version is found in the JAR.
*/
string jarVersion() {
result = this.getJarVersion()
or
not exists(this.getJarVersion()) and result = ""
}
/** 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() {
exists(
string namespace, string type, string name, string signature, string kind, string provenance
|
neutralModel(namespace, type, name, signature, kind, provenance) and
this = interpretElement(namespace, type, false, name, signature, "")
)
}
/**
* 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()
}
}
boolean isSupported(CallableMethod method) {
method.isSupported() and result = true
or
not method.isSupported() and result = false
}
string supportedType(CallableMethod method) {
method.isSink() and result = "sink"
or
method.isSource() and result = "source"
or
method.hasSummary() and result = "summary"
or
method.isNeutral() and result = "neutral"
or
not method.isSupported() and result = ""
}
string methodClassification(Call method) {
isInTestFile(method.getLocation().getFile()) and result = "test"
or
method.getFile() instanceof GeneratedFile and result = "generated"
or
not isInTestFile(method.getLocation().getFile()) and
not method.getFile() instanceof GeneratedFile and
result = "source"
}

View File

@@ -0,0 +1,29 @@
/**
* @name Fetch model editor methods (application mode)
* @description A list of 3rd party APIs used in the codebase. Excludes test and generated code.
* @kind problem
* @problem.severity recommendation
* @id java/utils/modeleditor/fetch-application-mode-methods
* @tags modeleditor fetch methods application-mode
*/
private import java
private import AutomodelVsCode
class ExternalApi extends CallableMethod {
ExternalApi() { not this.fromSource() }
}
private Call aUsage(ExternalApi api) { result.getCallee().getSourceDeclaration() = api }
from
ExternalApi externalApi, string apiName, boolean supported, Call usage, string type,
string classification
where
apiName = externalApi.getApiName() and
supported = isSupported(externalApi) and
usage = aUsage(externalApi) and
type = supportedType(externalApi) and
classification = methodClassification(usage)
select usage, apiName, supported.toString(), "supported", externalApi.jarContainer(),
externalApi.jarVersion(), type, "type", classification, "classification"

View File

@@ -0,0 +1,23 @@
/**
* @name Fetch model editor methods (framework mode)
* @description A list of APIs callable by consumers. Excludes test and generated code.
* @kind problem
* @problem.severity recommendation
* @id java/utils/modeleditor/fetch-framework-mode-methods
* @tags modeleditor fetch methods framework-mode
*/
private import java
private import semmle.code.java.dataflow.internal.ModelExclusions
private import AutomodelVsCode
class PublicMethodFromSource extends CallableMethod, ModelApi { }
from PublicMethodFromSource publicMethod, string apiName, boolean supported, string type
where
apiName = publicMethod.getApiName() and
supported = isSupported(publicMethod) and
type = supportedType(publicMethod)
select publicMethod, apiName, supported.toString(), "supported",
publicMethod.getCompilationUnit().getParentContainer().getBaseName(), "library", type, "type",
"unknown", "classification"

View File

@@ -0,0 +1,8 @@
| com/github/codeql/test/NonPublicClass.java:5:5:5:28 | println(...) | java.io.PrintStream#println(String) | true | supported | rt.jar | | sink | type | source | classification |
| com/github/codeql/test/PublicClass.java:7:5:7:27 | println(...) | java.io.PrintStream#println(String) | true | supported | rt.jar | | sink | type | source | classification |
| com/github/codeql/test/PublicClass.java:11:5:11:27 | println(...) | java.io.PrintStream#println(String) | true | supported | rt.jar | | sink | type | source | classification |
| com/github/codeql/test/PublicClass.java:15:5:15:45 | println(...) | java.io.PrintStream#println(Object) | true | supported | rt.jar | | sink | type | source | classification |
| com/github/codeql/test/PublicClass.java:15:24:15:44 | get(...) | java.nio.file.Paths#get(String,String[]) | true | supported | rt.jar | | sink | type | source | classification |
| com/github/codeql/test/PublicClass.java:15:24:15:44 | get(...) | java.nio.file.Paths#get(String,String[]) | true | supported | rt.jar | | summary | type | source | classification |
| com/github/codeql/test/PublicClass.java:19:5:19:27 | println(...) | java.io.PrintStream#println(String) | true | supported | rt.jar | | sink | type | source | classification |
| com/github/codeql/test/PublicInterface.java:7:7:7:29 | println(...) | java.io.PrintStream#println(String) | true | supported | rt.jar | | sink | type | source | classification |

View File

@@ -0,0 +1 @@
utils/modeleditor/FetchApplicationModeMethods.ql

View File

@@ -0,0 +1,5 @@
| com/github/codeql/test/PublicClass.java:6:15:6:19 | stuff | com.github.codeql.test.PublicClass#stuff(String) | false | supported | test | library | | type | unknown | classification |
| com/github/codeql/test/PublicClass.java:10:22:10:32 | staticStuff | com.github.codeql.test.PublicClass#staticStuff(String) | false | supported | test | library | | type | unknown | classification |
| com/github/codeql/test/PublicClass.java:14:18:14:31 | nonPublicStuff | com.github.codeql.test.PublicClass#nonPublicStuff(String) | false | supported | test | library | | type | unknown | classification |
| com/github/codeql/test/PublicInterface.java:4:17:4:21 | stuff | com.github.codeql.test.PublicInterface#stuff(String) | false | supported | test | library | | type | unknown | classification |
| com/github/codeql/test/PublicInterface.java:6:24:6:34 | staticStuff | com.github.codeql.test.PublicInterface#staticStuff(String) | false | supported | test | library | | type | unknown | classification |

View File

@@ -0,0 +1 @@
utils/modeleditor/FetchFrameworkModeMethods.ql

View File

@@ -0,0 +1,7 @@
package com.github.codeql.test;
class NonPublicClass {
public void noCandidates(String here) {
System.out.println(here);
}
}

View File

@@ -0,0 +1,21 @@
package com.github.codeql.test;
import java.nio.file.Paths;
public class PublicClass {
public void stuff(String arg) {
System.out.println(arg);
}
public static void staticStuff(String arg) {
System.out.println(arg);
}
protected void nonPublicStuff(String arg) {
System.out.println(Paths.get("foo", arg));
}
void packagePrivateStuff(String arg) {
System.out.println(arg);
}
}

View File

@@ -0,0 +1,9 @@
package com.github.codeql.test;
public interface PublicInterface {
public void stuff(String arg);
public static void staticStuff(String arg) {
System.out.println(arg);
}
}