diff --git a/framework/build.gradle b/framework/build.gradle
index 52a44bf02dc..294030b4e96 100644
--- a/framework/build.gradle
+++ b/framework/build.gradle
@@ -67,6 +67,8 @@ dependencies {
implementation "org.plumelib:plume-util:${plumeUtilVersion}"
implementation "org.plumelib:reflection-util:${reflectionUtilVersion}"
implementation 'io.github.classgraph:classgraph:4.8.161'
+ implementation 'de.jcup.sarif.java:sarif-2.1.0:1.1.0' // support for sarif files
+
testImplementation "junit:junit:${junitVersion}"
testImplementation project(':framework-test')
diff --git a/framework/src/main/java/org/checkerframework/framework/sarif/SarifFacade.java b/framework/src/main/java/org/checkerframework/framework/sarif/SarifFacade.java
new file mode 100644
index 00000000000..93bd50c78e9
--- /dev/null
+++ b/framework/src/main/java/org/checkerframework/framework/sarif/SarifFacade.java
@@ -0,0 +1,132 @@
+package org.checkerframework.framework.sarif;
+
+import com.sun.source.tree.CompilationUnitTree;
+import com.sun.source.tree.Tree;
+import com.sun.tools.javac.model.JavacElements;
+import com.sun.tools.javac.processing.JavacProcessingEnvironment;
+import com.sun.tools.javac.tree.JCTree;
+import com.sun.tools.javac.util.JCDiagnostic;
+import com.sun.tools.javac.util.Pair;
+
+import org.checkerframework.checker.nullness.qual.Nullable;
+
+import java.nio.file.Path;
+import java.util.Collections;
+
+import javax.lang.model.element.Element;
+import javax.tools.Diagnostic;
+import javax.tools.JavaFileObject;
+
+import de.jcup.sarif_2_1_0.model.*;
+
+/**
+ * Provide support for handling with SARIF outputs. If you want to output a SARIF you need to call
+ * {@link #initialize(Path)}, which also adds a shutdown hook to the JVM.
+ *
+ *
Use {@link #addResult(Diagnostic.Kind, String, Element)} and others to report an issue.
+ *
+ * @author Alexander Weigl
+ * @version 1 (20.07.23)
+ */
+public class SarifFacade {
+ @Nullable private static SarifSchema210 schema;
+ @Nullable private static Run run1;
+
+ public static SarifSchema210 getReport() {
+ return schema;
+ }
+
+ public static void addResult(Result e) {
+ if (run1 != null) {
+ run1.getResults().add(e);
+ }
+ }
+
+ /**
+ * @param sink
+ */
+ public static void initialize(Path sink) {
+ schema = new SarifSchema210();
+ run1 = new Run();
+ Tool toolCheckerFramework = new Tool();
+ ToolComponent driver = new ToolComponent();
+ driver.setName("checker-framework");
+ String driverGuid = "1234-guid-test-tool-driver-id";
+ driver.setGuid(driverGuid);
+ driver.setFullName("Only-Test");
+ toolCheckerFramework.setDriver(driver);
+ run1.setTool(toolCheckerFramework);
+ schema.getRuns().add(run1);
+
+ Runtime.getRuntime().addShutdownHook(new Thread(() -> SarifFacade.save(sink)));
+ }
+
+ public static void save(Path sink) {}
+
+ public static void addResult(Diagnostic.Kind kind, String messageText, Element preciseSource) {
+ JCDiagnostic.DiagnosticPosition pos = null;
+ JavacProcessingEnvironment processingEnv = null; // TODO How to receive this
+ JavacElements elemUtils = processingEnv.getElementUtils();
+ Pair treeTop = elemUtils.getTreeAndTopLevel(e, a, v);
+ if (treeTop != null) {
+ JavaFileObject newSource = treeTop.snd.sourcefile;
+ if (newSource != null) {
+ // save the old version and reinstate it later
+ pos = treeTop.fst.pos();
+ }
+ }
+ }
+
+ public static void addResult(
+ Diagnostic.Kind kind,
+ String messageText,
+ String uri,
+ int lineStart,
+ int columnStart,
+ int lineEnd,
+ int columnEnd) {
+ Result e = new Result();
+ e.setLevel(toKind(kind));
+ Location loc = new Location();
+ PhysicalLocation pl = new PhysicalLocation();
+ ArtifactLocation al = new ArtifactLocation();
+ al.setUri(uri);
+ pl.setArtifactLocation(al);
+ Region region = new Region();
+ region.setStartLine(lineStart);
+ region.setStartColumn(columnStart);
+ region.setEndColumn(columnEnd);
+ region.setEndLine(lineEnd);
+ pl.setRegion(region);
+ loc.setPhysicalLocation(pl);
+ e.setLocations(Collections.singletonList(loc));
+ Message message = new Message();
+ message.setText(messageText);
+ e.setMessage(message);
+ addResult(e);
+ }
+
+ private static Result.Level toKind(Diagnostic.Kind kind) {
+ switch (kind) {
+ case ERROR:
+ return Result.Level.ERROR;
+ case WARNING:
+ case MANDATORY_WARNING:
+ return Result.Level.WARNING;
+ case NOTE:
+ return Result.Level.NOTE;
+ case OTHER:
+ return Result.Level.NONE;
+ }
+ throw new IllegalArgumentException("unreachable");
+ }
+
+ public static void addResult(
+ Diagnostic.Kind kind,
+ String messageText,
+ Tree preciseSource,
+ CompilationUnitTree currentRoot) {
+ JCDiagnostic.DiagnosticPosition pos = ((JCTree) preciseSource).pos();
+ // addResult(kind, messageText, );
+ }
+}
diff --git a/framework/src/main/java/org/checkerframework/framework/source/SourceChecker.java b/framework/src/main/java/org/checkerframework/framework/source/SourceChecker.java
index 24813e9b5e6..9e70090797f 100644
--- a/framework/src/main/java/org/checkerframework/framework/source/SourceChecker.java
+++ b/framework/src/main/java/org/checkerframework/framework/source/SourceChecker.java
@@ -28,6 +28,7 @@
import org.checkerframework.checker.signature.qual.FullyQualifiedName;
import org.checkerframework.common.basetype.BaseTypeChecker;
import org.checkerframework.framework.qual.AnnotatedFor;
+import org.checkerframework.framework.sarif.SarifFacade;
import org.checkerframework.framework.type.AnnotatedTypeFactory;
import org.checkerframework.framework.util.CheckerMain;
import org.checkerframework.framework.util.OptionConfiguration;
@@ -57,6 +58,7 @@
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
import java.net.URI;
+import java.nio.file.Path;
import java.time.Instant;
import java.util.ArrayDeque;
import java.util.ArrayList;
@@ -345,6 +347,10 @@
// org.checkerframework.framework.source.SourceChecker.message(Kind, Object, String, Object...)
"detailedmsgtext",
+ // SARIF output: Specify a path where the sarif file should be written (JSON)
+ // Useful for further processing!
+ "sarif",
+
/// Stub and JDK libraries
// Ignore the standard jdk.astub file; primarily for testing or debugging.
@@ -585,6 +591,11 @@ public abstract class SourceChecker extends AbstractTypeProcessor implements Opt
/** Default constructor. */
protected SourceChecker() {}
+ /**
+ * If not null, initialize the {@link SarifFacade} and dump a sarif output to the given path.
+ */
+ @Nullable private Path sarifOutput;
+
/** True if the -Afilenames command-line argument was passed. */
private boolean printFilenames;
@@ -986,12 +997,20 @@ public void initChecker() {
this.activeLints = createActiveLints(getOptions());
}
+ final String sarif = getOption("sarif");
+ if (sarif != null) {
+ sarifOutput = Paths.get(sarif);
+ }
printFilenames = hasOption("filenames");
warns = hasOption("warns");
showSuppressWarningsStrings = hasOption("showSuppressWarningsStrings");
requirePrefixInWarningSuppressions = hasOption("requirePrefixInWarningSuppressions");
showPrefixInWarningMessages = hasOption("showPrefixInWarningMessages");
warnUnneededSuppressions = hasOption("warnUnneededSuppressions");
+
+ if (sarifOutput != null) {
+ SarifFacade.initialize(sarifOutput);
+ }
}
/** Output the warning about source level at most once. */
@@ -1236,8 +1255,10 @@ private void report(
}
if (preciseSource instanceof Element) {
+ SarifFacade.addResult(kind, messageText, (Element) preciseSource);
messager.printMessage(kind, messageText, (Element) preciseSource);
} else if (preciseSource instanceof Tree) {
+ SarifFacade.addResult(kind, messageText, (Tree) preciseSource, currentRoot);
printOrStoreMessage(kind, messageText, (Tree) preciseSource, currentRoot);
} else {
throw new BugInCF("invalid position source, class=" + preciseSource.getClass());
@@ -1679,7 +1700,7 @@ public final boolean getLintOption(String name, boolean def) {
* @param name the name of the lint option to set
* @param val the option value
* @see SourceChecker#getLintOption(String)
- * @see SourceChecker#getLintOption(String,boolean)
+ * @see SourceChecker#getLintOption(String, boolean)
*/
protected final void setLintOption(String name, boolean val) {
if (!this.getSupportedLintOptions().contains(name)) {
@@ -1864,7 +1885,7 @@ public final boolean hasOption(String name) {
/**
* {@inheritDoc}
*
- * @see SourceChecker#getLintOption(String,boolean)
+ * @see SourceChecker#getLintOption(String, boolean)
*/
@Override
public final String getOption(String name) {
@@ -1874,7 +1895,7 @@ public final String getOption(String name) {
/**
* {@inheritDoc}
*
- * @see SourceChecker#getLintOption(String,boolean)
+ * @see SourceChecker#getLintOption(String, boolean)
*/
@Override
public final boolean getBooleanOption(String name) {
@@ -1884,7 +1905,7 @@ public final boolean getBooleanOption(String name) {
/**
* {@inheritDoc}
*
- * @see SourceChecker#getLintOption(String,boolean)
+ * @see SourceChecker#getLintOption(String, boolean)
*/
@Override
public final boolean getBooleanOption(String name, boolean defaultValue) {
@@ -1914,7 +1935,7 @@ public Map getOptions() {
/**
* {@inheritDoc}
*
- * @see SourceChecker#getLintOption(String,boolean)
+ * @see SourceChecker#getLintOption(String, boolean)
*/
@Override
public final String getOption(String name, String defaultValue) {