Skip to content
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

Java: Add VS Code model editor queries #14199

Merged
merged 15 commits into from
Sep 28, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down
25 changes: 25 additions & 0 deletions java/ql/src/utils/modeleditor/ApplicationModeEndpoints.ql
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/**
* @name Fetch endpoints for use in the model editor (application mode)
* @description A list of 3rd party endpoints (methods) used in the codebase. Excludes test and generated code.
* @kind table
* @id java/utils/modeleditor/application-mode-endpoints
* @tags modeleditor endpoints application-mode
*/

private import java
private import ApplicationModeEndpointsQuery
private import ModelEditor

private Call aUsage(ExternalEndpoint endpoint) {
result.getCallee().getSourceDeclaration() = endpoint
}

from ExternalEndpoint endpoint, boolean supported, Call usage, string type, string classification
where
supported = isSupported(endpoint) and
usage = aUsage(endpoint) and
type = supportedType(endpoint) and
classification = usageClassification(usage)
select usage, endpoint.getPackageName(), endpoint.getTypeName(), endpoint.getName(),
endpoint.getParameterTypes(), supported, endpoint.jarContainer(), endpoint.jarVersion(), type,
classification
40 changes: 40 additions & 0 deletions java/ql/src/utils/modeleditor/ApplicationModeEndpointsQuery.qll
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
private import java
private import semmle.code.java.dataflow.ExternalFlow
private import semmle.code.java.dataflow.FlowSources
private import semmle.code.java.dataflow.internal.DataFlowPrivate
private import ModelEditor

/**
* A class of effectively public callables in library code.
*/
class ExternalEndpoint extends Endpoint {
ExternalEndpoint() { not this.fromSource() }

/** 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
)
}

override predicate hasSummary() {
Endpoint.super.hasSummary()
or
TaintTracking::localAdditionalTaintStep(this.getAnInput(), _)
}

override predicate isSource() {
this.getAnOutput() instanceof RemoteFlowSource or sourceNode(this.getAnOutput(), _)
}

override predicate isSink() { sinkNode(this.getAnInput(), _) }
}
19 changes: 19 additions & 0 deletions java/ql/src/utils/modeleditor/FrameworkModeEndpoints.ql
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
/**
* @name Fetch endpoints for use in the model editor (framework mode)
* @description A list of endpoints accessible (methods) for consumers of the library. Excludes test and generated code.
* @kind table
* @id java/utils/modeleditor/framework-mode-endpoints
* @tags modeleditor endpoints framework-mode
*/

private import java
private import FrameworkModeEndpointsQuery
private import ModelEditor

from PublicEndpointFromSource endpoint, boolean supported, string type
where
supported = isSupported(endpoint) and
type = supportedType(endpoint)
select endpoint, endpoint.getPackageName(), endpoint.getTypeName(), endpoint.getName(),
endpoint.getParameterTypes(), supported,
endpoint.getCompilationUnit().getParentContainer().getBaseName(), type
14 changes: 14 additions & 0 deletions java/ql/src/utils/modeleditor/FrameworkModeEndpointsQuery.qll
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
private import java
private import semmle.code.java.dataflow.internal.DataFlowPrivate
private import semmle.code.java.dataflow.internal.FlowSummaryImplSpecific
private import semmle.code.java.dataflow.internal.ModelExclusions
private import ModelEditor

/**
* A class of effectively public callables from source code.
*/
class PublicEndpointFromSource extends Endpoint, ModelApi {
override predicate isSource() { sourceElement(this, _, _, _) }

override predicate isSink() { sinkElement(this, _, _, _) }
}
119 changes: 119 additions & 0 deletions java/ql/src/utils/modeleditor/ModelEditor.qll
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
/** Provides classes and predicates related to handling APIs for the VS Code extension. */

private import java
private import semmle.code.java.dataflow.ExternalFlow
private import semmle.code.java.dataflow.FlowSummary
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 Endpoint extends Callable {
Endpoint() { not isUninteresting(this) }

/**
* Gets the package name of this endpoint.
*/
string getPackageName() { result = this.getDeclaringType().getPackage().getName() }

/**
* Gets the type name of this endpoint.
*/
string getTypeName() { result = this.getDeclaringType().nestedName() }

/**
* Gets the parameter types of this endpoint.
*/
string getParameterTypes() { result = 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 = ""
}

/** Holds if this API has a supported summary. */
pragma[nomagic]
predicate hasSummary() { this = any(SummarizedCallable sc).asCallable() }

/** Holds if this API is a known source. */
pragma[nomagic]
abstract predicate isSource();

/** Holds if this API is a known sink. */
pragma[nomagic]
abstract predicate isSink();

/** Holds if this API is a known neutral. */
pragma[nomagic]
predicate isNeutral() {
exists(string namespace, string type, string name, string signature |
neutralModel(namespace, type, name, signature, _, _) 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(Endpoint endpoint) {
endpoint.isSupported() and result = true
or
not endpoint.isSupported() and result = false
}

string supportedType(Endpoint endpoint) {
endpoint.isSink() and result = "sink"
or
endpoint.isSource() and result = "source"
or
endpoint.hasSummary() and result = "summary"
or
endpoint.isNeutral() and result = "neutral"
or
not endpoint.isSupported() and result = ""
}

string usageClassification(Call usage) {
isInTestFile(usage.getLocation().getFile()) and result = "test"
or
usage.getFile() instanceof GeneratedFile and result = "generated"
or
not isInTestFile(usage.getLocation().getFile()) and
not usage.getFile() instanceof GeneratedFile and
result = "source"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
| com/github/codeql/test/NonPublicClass.java:5:5:5:28 | println(...) | java.io | PrintStream | println | (String) | true | rt.jar | | sink | source |
| com/github/codeql/test/PublicClass.java:8:5:8:27 | println(...) | java.io | PrintStream | println | (String) | true | rt.jar | | sink | source |
| com/github/codeql/test/PublicClass.java:12:5:12:27 | println(...) | java.io | PrintStream | println | (String) | true | rt.jar | | sink | source |
| com/github/codeql/test/PublicClass.java:16:5:16:45 | println(...) | java.io | PrintStream | println | (Object) | true | rt.jar | | sink | source |
| com/github/codeql/test/PublicClass.java:16:24:16:44 | get(...) | java.nio.file | Paths | get | (String,String[]) | true | rt.jar | | sink | source |
| com/github/codeql/test/PublicClass.java:16:24:16:44 | get(...) | java.nio.file | Paths | get | (String,String[]) | true | rt.jar | | summary | source |
| com/github/codeql/test/PublicClass.java:20:5:20:68 | println(...) | java.io | PrintStream | println | (Object) | true | rt.jar | | sink | source |
| com/github/codeql/test/PublicClass.java:20:24:20:47 | getDefault(...) | java.nio.file | FileSystems | getDefault | () | false | rt.jar | | | source |
| com/github/codeql/test/PublicClass.java:20:24:20:67 | getPath(...) | java.nio.file | FileSystem | getPath | (String,String[]) | true | rt.jar | | sink | source |
| com/github/codeql/test/PublicClass.java:20:24:20:67 | getPath(...) | java.nio.file | FileSystem | getPath | (String,String[]) | true | rt.jar | | summary | source |
| com/github/codeql/test/PublicClass.java:24:5:24:27 | println(...) | java.io | PrintStream | println | (String) | true | rt.jar | | sink | source |
| com/github/codeql/test/PublicGenericClass.java:7:5:7:27 | println(...) | java.io | PrintStream | println | (Object) | true | rt.jar | | sink | source |
| com/github/codeql/test/PublicGenericClass.java:11:5:11:27 | println(...) | java.io | PrintStream | println | (Object) | true | rt.jar | | sink | source |
| com/github/codeql/test/PublicGenericInterface.java:8:7:8:29 | println(...) | java.io | PrintStream | println | (String) | true | rt.jar | | sink | source |
| com/github/codeql/test/PublicInterface.java:7:7:7:29 | println(...) | java.io | PrintStream | println | (String) | true | rt.jar | | sink | source |
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
utils/modeleditor/ApplicationModeEndpoints.ql
14 changes: 14 additions & 0 deletions java/ql/test/utils/modeleditor/FrameworkModeEndpoints.expected
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
| com/github/codeql/test/PublicClass.java:7:15:7:19 | stuff | com.github.codeql.test | PublicClass | stuff | (String) | false | test | |
| com/github/codeql/test/PublicClass.java:11:22:11:32 | staticStuff | com.github.codeql.test | PublicClass | staticStuff | (String) | false | test | |
| com/github/codeql/test/PublicClass.java:15:18:15:31 | protectedStuff | com.github.codeql.test | PublicClass | protectedStuff | (String) | false | test | |
| com/github/codeql/test/PublicClass.java:27:17:27:28 | summaryStuff | com.github.codeql.test | PublicClass | summaryStuff | (String) | true | test | summary |
| com/github/codeql/test/PublicClass.java:31:17:31:27 | sourceStuff | com.github.codeql.test | PublicClass | sourceStuff | () | true | test | source |
| com/github/codeql/test/PublicClass.java:35:15:35:23 | sinkStuff | com.github.codeql.test | PublicClass | sinkStuff | (String) | true | test | sink |
| com/github/codeql/test/PublicClass.java:39:15:39:26 | neutralStuff | com.github.codeql.test | PublicClass | neutralStuff | (String) | true | test | neutral |
| com/github/codeql/test/PublicGenericClass.java:6:15:6:19 | stuff | com.github.codeql.test | PublicGenericClass | stuff | (Object) | false | test | |
| com/github/codeql/test/PublicGenericClass.java:10:20:10:25 | stuff2 | com.github.codeql.test | PublicGenericClass | stuff2 | (Object) | false | test | |
| com/github/codeql/test/PublicGenericInterface.java:4:17:4:21 | stuff | com.github.codeql.test | PublicGenericInterface | stuff | (Object) | false | test | |
| com/github/codeql/test/PublicGenericInterface.java:5:22:5:27 | stuff2 | com.github.codeql.test | PublicGenericInterface | stuff2 | (Object) | false | test | |
| com/github/codeql/test/PublicGenericInterface.java:7:24:7:34 | staticStuff | com.github.codeql.test | PublicGenericInterface | staticStuff | (String) | false | test | |
| com/github/codeql/test/PublicInterface.java:4:17:4:21 | stuff | com.github.codeql.test | PublicInterface | stuff | (String) | false | test | |
| com/github/codeql/test/PublicInterface.java:6:24:6:34 | staticStuff | com.github.codeql.test | PublicInterface | staticStuff | (String) | false | test | |
24 changes: 24 additions & 0 deletions java/ql/test/utils/modeleditor/FrameworkModeEndpoints.ext.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
extensions:
- addsTo:
pack: codeql/java-all
extensible: sourceModel
data:
- ["com.github.codeql.test","PublicClass",true,"sourceStuff","()","","ReturnValue","remote","manual"]

- addsTo:
pack: codeql/java-all
extensible: sinkModel
data:
- ["com.github.codeql.test","PublicClass",true,"sinkStuff","(String)","","Argument[0]","sql-injection","manual"]

- addsTo:
pack: codeql/java-all
extensible: summaryModel
data:
- ["com.github.codeql.test","PublicClass",true,"summaryStuff","(String)","","Argument[0]","ReturnValue","taint","manual"]

- addsTo:
pack: codeql/java-all
extensible: neutralModel
data:
- ["com.github.codeql.test","PublicClass","neutralStuff","(String)","summary","manual"]
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
utils/modeleditor/FrameworkModeEndpoints.ql
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.github.codeql.test;

class NonPublicClass {
public void noCandidates(String here) {
System.out.println(here);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package com.github.codeql.test;

import java.nio.file.FileSystems;
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 protectedStuff(String arg) {
System.out.println(Paths.get("foo", arg));
}

private void privateStuff(String arg) {
System.out.println(FileSystems.getDefault().getPath("foo", arg));
}

void packagePrivateStuff(String arg) {
System.out.println(arg);
}

public String summaryStuff(String arg) {
return arg;
}

public String sourceStuff() {
return "stuff";
}

public void sinkStuff(String arg) {
// do nothing
}

public void neutralStuff(String arg) {
// do nothing
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package com.github.codeql.test;

import java.nio.file.Paths;

public class PublicGenericClass<T, T2> implements PublicGenericInterface<T> {
public void stuff(T arg) {
System.out.println(arg);
}

public <T3> void stuff2(T3 arg) {
System.out.println(arg);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.github.codeql.test;

public interface PublicGenericInterface<T> {
public void stuff(T arg);
public <T2> void stuff2(T2 arg);

public static void staticStuff(String arg) {
System.out.println(arg);
}
}
Original file line number Diff line number Diff line change
@@ -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);
}
}
1 change: 1 addition & 0 deletions misc/suite-helpers/code-scanning-selectors.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,4 +30,5 @@
- /Diagnostics/Internal/.*/
- exclude:
tags contain:
- modeleditor
- modelgenerator
1 change: 1 addition & 0 deletions misc/suite-helpers/security-and-quality-selectors.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,4 +31,5 @@
- /Diagnostics/Internal/.*/
- exclude:
tags contain:
- modeleditor
- modelgenerator
1 change: 1 addition & 0 deletions misc/suite-helpers/security-experimental-selectors.yml
Original file line number Diff line number Diff line change
Expand Up @@ -42,4 +42,5 @@
- /Diagnostics/Internal/.*/
- exclude:
tags contain:
- modeleditor
- model-generator
Loading