Skip to content

Conversation

@Gaoyan1999
Copy link
Contributor

@Gaoyan1999 Gaoyan1999 commented Dec 3, 2025

Summary

This PR introduces a proof-of-concept implementation of SARIF report generation for the Checker Framework. It enables checkers to produce SARIF 2.1.0–compliant JSON report files containing diagnostic messages and their corresponding source locations.

Unit tests are not included at this stage. I’d like to first align with contributors on the overall design before adding tests

Motivation

Related issue: #5666

How to Use SARIF

javac -processor NullnessChecker -AsarifOutput=report.sarif MyFile.java

This generates a report.sarif file containing all diagnostics in SARIF 2.1.0 format.

The report.sarif file would be like that:

{
  "version" : "2.1.0",
  "runs" : [ {
    "tool" : {
      "driver" : {
        "name" : "Checker Framework",
        "version" : "3.52.1-SNAPSHOT"
      }
    },
    "results" : [ {
      "ruleId" : "assignment",
      "level" : "error",
      "message" : {
        "text" : "[assignment] incompatible types in assignment.\nfound   : Set<@KeyFor(\"sharedCounts1\") String>\nrequired: Set<@KeyFor({\"sharedBooks\", \"sharedCounts1\"}) String>"
      },
      "locations" : [ {
        "physicalLocation" : {
          "artifactLocation" : {
            "uri" : "file:///Users/gaoyan/workspace/gsoc/fork/checker-framework/checker/tests/nullness/KeyForMultiple.java"
          },
          "region" : {
            "startLine" : 25,
            "startColumn" : 73,
            "endLine" : 25,
            "endColumn" : 95
          }
        }
      } ]
    }, {
      "ruleId" : "assignment",
      "level" : "error",
      "message" : {
        "text" : "[assignment] incompatible types in assignment.\nfound   : Set<@KeyFor(\"sharedCounts2\") String>\nrequired: Set<@KeyFor({\"sharedBooks\", \"sharedCounts2\"}) String>"
      },
      "locations" : [ {
        "physicalLocation" : {
          "artifactLocation" : {
            "uri" : "file:///Users/gaoyan/workspace/gsoc/fork/checker-framework/checker/tests/nullness/KeyForMultiple.java"
          },
          "region" : {
            "startLine" : 41,
            "startColumn" : 73,
            "endLine" : 41,
            "endColumn" : 95
          }
        }
      } ]
    } ]
  } ]
}

Implementation Details

Message Flow

  1. The SARIF generator is initialized in the root checker’s initChecker() method to ensure a single instance per compilation.

  2. printOrStoreMessage() retrieves the root checker’s generator and forwards diagnostic messages to it.

  3. At the end of processing, typeProcessingOver() writes the collected results to the SARIF file.

Compound Checker Support

  • Only the root checker creates and owns the SARIF generator.

  • Subcheckers access the same instance via getRootChecker().

  • This ensures all diagnostics from compound checkers are included in a single unified SARIF report.

Future Work

This POC lays the foundation, and several enhancements can be implemented later:

  • Support for NOTE and WARNING message types
  • Rule metadata extraction: populate run.tool.driver.rules with richer rule descriptions from messages.properties (currently only ruleId is included)
  • Additional SARIF features (code flows, fixes, etc.)
  • Add a static webpage for rendering SARIF output
  • Update the user manual

Backward Compatibility

The feature is fully backward compatible:

SARIF generation is opt-in via the -AsarifOutput option

Existing console output remains unchanged

No breaking API changes

Failures in SARIF generation do not impact compilation

@Gaoyan1999 Gaoyan1999 changed the title Enhancement: enhancing report with adding Sarif Report(POC) Enhancement #5666: enhancing report with adding Sarif Report(POC) Dec 3, 2025
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Dec 3, 2025

📝 Walkthrough

Walkthrough

The PR adds SARIF report generation to the Checker Framework. Two Gradle dependencies were added: com.fasterxml.jackson.core:jackson-databind:2.15.0 and com.contrastsecurity:java-sarif:2.0. A new SarifReportGenerator class builds SARIF 2.1.0 logs from diagnostics. SourceChecker gains a sarifOutput option, fields to track SARIF state, initialization and teardown to create and write the report, and wiring to send diagnostics (including a messageKey) to the SARIF generator. CheckerMessage now includes a messageKey field.

Pre-merge checks and finishing touches

✅ Passed checks (1 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed Docstring coverage is 81.25% which is sufficient. The required threshold is 80.00%.
✨ Finishing touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 6

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 892d41c and 618c231.

📒 Files selected for processing (3)
  • framework/build.gradle (1 hunks)
  • framework/src/main/java/org/checkerframework/framework/report/SarifReportGenerator.java (1 hunks)
  • framework/src/main/java/org/checkerframework/framework/source/SourceChecker.java (12 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
framework/src/main/java/org/checkerframework/framework/source/SourceChecker.java (1)
framework/src/main/java/org/checkerframework/framework/report/SarifReportGenerator.java (1)
  • SarifReportGenerator (33-205)
🔇 Additional comments (8)
framework/src/main/java/org/checkerframework/framework/report/SarifReportGenerator.java (1)

122-153: LGTM!

The addResult method correctly filters diagnostic kinds, constructs SARIF Result objects with proper location information, and manages artifact registration. The implementation aligns with SARIF 2.1.0 specifications.

framework/src/main/java/org/checkerframework/framework/source/SourceChecker.java (6)

623-630: LGTM!

The SARIF-related fields are properly declared with appropriate nullability annotations and follow the existing field naming conventions in the class.


750-761: LGTM!

The getRootChecker() method correctly traverses the parent chain to find the root ancestor checker. The protected visibility appropriately allows subcheckers to access the root's SARIF generator.


1142-1150: LGTM!

The SARIF initialization correctly ensures only the root checker creates the SarifReportGenerator, validates the required output path argument, and throws a clear UserError when the path is missing.


1601-1621: LGTM!

The updated printOrStoreMessage method correctly propagates the messageKey and ensures all subcheckers route their diagnostics to the root checker's SarifReportGenerator. This properly implements the single-report-per-compilation requirement mentioned in the PR objectives.


3551-3576: LGTM!

The CheckerMessage class is correctly extended with the messageKey field. The decision to not include messageKey in equals()/hashCode() is appropriate since it's metadata for SARIF reporting rather than part of the message's identity.


1519-1519: LGTM!

The messageKey is correctly propagated through the report() method to enable SARIF rule identification.

framework/build.gradle (1)

56-57: Verify shadowJar configuration for these new dependencies and review jackson-databind version.

Per the shadowing requirement noted in the context, confirm that the shadowJar configuration in the root build.gradle properly handles jackson-databind and java-sarif. Additionally, jackson-databind 2.15.0 is affected by CVE-2023-35116 (a disputed DoS vulnerability affecting versions through 2.15.2); consider upgrading to a patched version if available. Note that java-sarif:2.0 is already the latest released version.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

♻️ Duplicate comments (2)
framework/src/main/java/org/checkerframework/framework/source/SourceChecker.java (1)

1078-1092: Ensure SARIF write failures cannot break compilation

The PR description says SARIF generation is best‑effort and should not affect compilation, but typeProcessingOver currently only catches IOException. Other plausible failures from writeReport (for example, InvalidPathException from Paths.get, or unexpected runtime exceptions from Jackson) would propagate and could abort a build.

Also, if an invariant is ever broken and sarifOutputPath is null, the assert won’t protect production runs (assertions are typically disabled).

I recommend tightening this block to both validate the path and guard against unexpected runtime failures:

-    if (parentChecker == null && sarifReportGenerator != null) {
-      assert sarifOutputPath != null;
-      try {
-        sarifReportGenerator.writeReport(sarifOutputPath);
-        message(Diagnostic.Kind.NOTE, "SARIF report written to: " + sarifOutputPath);
-      } catch (IOException e) {
-        message(Diagnostic.Kind.WARNING, "Failed to write SARIF report: " + e.getMessage());
-      }
-    }
+    if (parentChecker == null && sarifReportGenerator != null) {
+      if (sarifOutputPath == null || sarifOutputPath.isEmpty()) {
+        message(
+            Diagnostic.Kind.WARNING,
+            "SARIF output was enabled but no valid -AsarifOutput path was provided; skipping report.");
+      } else {
+        try {
+          sarifReportGenerator.writeReport(sarifOutputPath);
+          message(Diagnostic.Kind.NOTE, "SARIF report written to: " + sarifOutputPath);
+        } catch (IOException | RuntimeException e) {
+          message(
+              Diagnostic.Kind.WARNING,
+              "Failed to write SARIF report (compilation will continue): " + e.getMessage());
+        }
+      }
+    }

This keeps SARIF truly best‑effort while making the invariant about sarifOutputPath explicit.

framework/src/main/java/org/checkerframework/framework/report/SarifReportGenerator.java (1)

189-215: Make SARIF file writes more robust (temp file + atomic move)

Writing directly to outputPath risks leaving a partially written SARIF file if writeValue fails mid‑stream (disk full, process killed, etc.). You can improve robustness by writing to a temporary file in the same directory and then moving it into place.

For example:

+import java.nio.file.Files;
+import java.nio.file.StandardCopyOption;
@@
   public void writeReport(String outputPath) throws IOException {
@@
-    // Write SARIF log to JSON file
-    ObjectMapper mapper = new ObjectMapper();
-    Path path = Paths.get(outputPath);
-    mapper.writerWithDefaultPrettyPrinter().writeValue(path.toFile(), sarifLog);
+    ObjectMapper mapper = new ObjectMapper();
+    Path path = Paths.get(outputPath);
+    Path parent = path.getParent();
+    if (parent == null) {
+      parent = Paths.get(System.getProperty("java.io.tmpdir"));
+    }
+    Path tempFile = Files.createTempFile(parent, "sarif-", ".tmp");
+    try {
+      mapper.writerWithDefaultPrettyPrinter().writeValue(tempFile.toFile(), sarifLog);
+      Files.move(
+          tempFile,
+          path,
+          StandardCopyOption.REPLACE_EXISTING,
+          StandardCopyOption.ATOMIC_MOVE);
+    } catch (IOException e) {
+      Files.deleteIfExists(tempFile);
+      throw e;
+    }
   }

This ensures consumers see either the old complete file or the new one, but never a truncated file.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 618c231 and ff6ad1e.

📒 Files selected for processing (2)
  • framework/src/main/java/org/checkerframework/framework/report/SarifReportGenerator.java (1 hunks)
  • framework/src/main/java/org/checkerframework/framework/source/SourceChecker.java (12 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
framework/src/main/java/org/checkerframework/framework/source/SourceChecker.java (1)
framework/src/main/java/org/checkerframework/framework/report/SarifReportGenerator.java (1)
  • SarifReportGenerator (44-216)
🔇 Additional comments (1)
framework/src/main/java/org/checkerframework/framework/source/SourceChecker.java (1)

1452-1524: Validate messaging API changes and update documentation for messageKey

The new messageKey plumbing through report, printOrStoreMessage, and CheckerMessage looks reasonable for SARIF, but there are a few follow‑ups to avoid regressions:

  • printOrStoreMessage's signature changed (additional messageKey parameter). Any subclasses (notably BaseTypeChecker and external checkers that override printOrStoreMessage) must be updated to override the new signature; otherwise those overrides will silently stop intercepting diagnostics. Please confirm all such overrides were updated.
  • The Javadocs for both printOrStoreMessage overloads, and for CheckerMessage, don't document the new messageKey parameter. Updating them will make the contract clearer, especially now that the field is used for SARIF ruleId.
  • CheckerMessage.equals/hashCode deliberately ignore messageKey, so equality semantics remain "same kind/message/source/checker". If you intend two messages that only differ in messageKey to be treated as distinct in the store, messageKey should be folded into equals/hashCode; if not, a brief comment explaining why it's excluded would help future maintainers.

Functionally, forwarding to the root checker's sarifReportGenerator from printOrStoreMessage looks correct and ensures compound checkers share a single SARIF run.

Also applies to: 1602-1621, 2317-2323, 3532-3578

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

♻️ Duplicate comments (1)
framework/src/main/java/org/checkerframework/framework/report/SarifReportGenerator.java (1)

164-184: Consider atomic write to avoid partially written SARIF files

writeReport currently writes directly to the target path. If the JVM crashes or an IOException occurs mid‑write, callers may end up with a truncated SARIF file.

As previously suggested in an earlier review, you could write to a temporary file in the same directory and then atomically move it into place:

+    java.nio.file.Path path = Paths.get(outputPath);
+    java.nio.file.Path tempFile =
+        java.nio.file.Files.createTempFile(path.getParent(), "sarif", ".tmp");
@@
-    ObjectMapper mapper = new ObjectMapper();
-    Path path = Paths.get(outputPath);
-    mapper.writerWithDefaultPrettyPrinter().writeValue(path.toFile(), sarifLog);
+    ObjectMapper mapper = new ObjectMapper();
+    try {
+      mapper.writerWithDefaultPrettyPrinter().writeValue(tempFile.toFile(), sarifLog);
+      java.nio.file.Files.move(
+          tempFile,
+          path,
+          java.nio.file.StandardCopyOption.REPLACE_EXISTING,
+          java.nio.file.StandardCopyOption.ATOMIC_MOVE);
+    } catch (IOException e) {
+      java.nio.file.Files.deleteIfExists(tempFile);
+      throw e;
+    }

You can pull the additional java.nio.file types into imports if you prefer.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ff6ad1e and e26dc06.

📒 Files selected for processing (1)
  • framework/src/main/java/org/checkerframework/framework/report/SarifReportGenerator.java (1 hunks)
🔇 Additional comments (1)
framework/src/main/java/org/checkerframework/framework/report/SarifReportGenerator.java (1)

143-157: Version lookup fallback is reasonable

The git.properties lookup with try‑with‑resources and a quiet fallback to "Unknown" is straightforward and safe; this is fine for a reporting-only feature.

@mernst
Copy link
Member

mernst commented Dec 17, 2025

@Gaoyan1999 The title includes "proof of concept". Do you want feedback on this pull request, or not yet?

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e26dc06 and 1691171.

📒 Files selected for processing (1)
  • framework/build.gradle (1 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (7)
  • GitHub Check: typetools.checker-framework (typecheck_part2_jdk25)
  • GitHub Check: typetools.checker-framework (inference_part1_jdk25)
  • GitHub Check: typetools.checker-framework (typecheck_part1_jdk25)
  • GitHub Check: typetools.checker-framework (misc_jdk25)
  • GitHub Check: typetools.checker-framework (junit_jdk25)
  • GitHub Check: typetools.checker-framework (inference_part2_jdk25)
  • GitHub Check: typetools.checker-framework (nonjunit_jdk25)

@Gaoyan1999
Copy link
Contributor Author

@Gaoyan1999 The title includes "proof of concept". Do you want feedback on this pull request, or not yet?

@mernst Yes, I’d appreciate feedback.

This is just a simple PoC for now — no tests and only the happy path.
I’ve listed some potential follow-up work in the “Future work” section of the description.

At this stage, I’m mainly looking for feedback on the overall idea and whether it’s worth continuing and building this out further.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants