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) {