Skip to content

Instrument @NotNull assertions into the compiled JVM bytecode #SCL-22085 - #152

Merged
Vasil Vasilev (vasilmkd) merged 1 commit into
masterfrom
notnull
Jul 31, 2026
Merged

Instrument @NotNull assertions into the compiled JVM bytecode #SCL-22085#152
Vasil Vasilev (vasilmkd) merged 1 commit into
masterfrom
notnull

Conversation

@vasilmkd

Copy link
Copy Markdown
Contributor

Supersedes #150.

Summary

This PR adds @NotNull/@Nullable bytecode instrumentation, expanding the annotation
instrumentation feature introduced with the threading annotations (#SCL-18660). It provides
feature parity with IntelliJ's internal JPS build and the IntelliJ Platform Gradle Plugin's
instrumentCode task.

Two new settings control the feature:

  • instrumentNotNullAnnotations: SettingKey[Boolean] (default: false) — generates null
    checks for annotated method parameters (throwing IllegalArgumentException) and return
    values (throwing IllegalStateException), with the same bytecode and error messages as
    IntelliJ's own build.
  • notNullAnnotations: SettingKey[Seq[String]] (default:
    Seq("org.jetbrains.annotations.NotNull")) — the annotation classes to instrument.

Implementation

  • The instrumenter core (NotNullVerifyingInstrumenter, AuxiliaryMethodGenerator,
    FailSafeClassReader, FailSafeMethodVisitor) is copied verbatim from IntelliJ IDEA
    Community Edition (Apache 2.0, commit 7a35a7d7fe64). The only changes are the package
    name, the ASM package (org.jetbrains.org.objectweb.asmorg.objectweb.asm), and
    Opcodes.API_VERSIONOpcodes.ASM9, keeping the sources easy to diff against upstream.
    No Ant machinery is needed — the Gradle plugin's "Ant task" is a wrapper around this
    same class, which we call directly.
  • A small Scala wrapper (NotNullInstrumenter) handles what JPS's builder infrastructure
    does there: COMPUTE_FRAMES writer flags with common-superclass resolution against the
    project classpath (classDirectory + dependencyClasspath), plus the module-info and
    class-file-version gates.
  • Wired into the existing manipulateBytecode flow next to the threading instrumentation;
    both instrumenters share the changed-classes detection and zinc re-stamping.
  • Kotlin classes (kotlinc emits its own assertions), bridge methods, and provably non-null
    returns are skipped, as upstream does.

Tests

New NotNullInstrumenterTest (9 tests, modeled on IDEA's NotNullVerifyingInstrumenterTest):
fixtures are compiled with javax.tools at test runtime, instrumented, then loaded and
invoked in a throwaway classloader — covering parameter/return checks, parameter names from
debug info, custom messages, custom annotations with custom exception classes, enum and
inner-class constructor parameter offsets, the non-null peephole, and the Kotlin skip.

Housekeeping

Also included as separate commits:

  • sbt 1.12.14 (project + CI runner), sbt-ci-release 1.12.0
  • Scala 2.12.21
  • Library updates: scalatest 3.2.20, circe 0.14.16, httpclient5 5.6.2,
    marketplace-zip-signer 0.1.43, asm 9.10.1, commons-io 2.22.0, annotations 26.1.0
  • actions/checkout@v7

Add a NotNull/Nullable bytecode instrumentation feature matching what
IntelliJ's JPS build and the IntelliJ Platform Gradle Plugin do: null
checks are generated for @NotNull-annotated method parameters (throwing
IllegalArgumentException) and return values (IllegalStateException).

The instrumenter itself (NotNullVerifyingInstrumenter,
AuxiliaryMethodGenerator, FailSafeClassReader, FailSafeMethodVisitor) is
copied verbatim from IntelliJ IDEA Community Edition (Apache 2.0, commit
7a35a7d7fe64), with only the package, the ASM package, and
Opcodes.API_VERSION -> Opcodes.ASM9 changed.

It is wired into the existing manipulateBytecode flow next to the
threading-annotations instrumentation and controlled by two new
settings: instrumentNotNullAnnotations (default false) and
notNullAnnotations (default org.jetbrains.annotations.NotNull).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@vasilmkd
Vasil Vasilev (vasilmkd) merged commit caf1b37 into master Jul 31, 2026
1 check passed
@vasilmkd
Vasil Vasilev (vasilmkd) deleted the notnull branch July 31, 2026 08:54
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.

1 participant