Internal change

PiperOrigin-RevId: 372613466
diff --git a/src/main/java/com/google/devtools/build/lib/rules/java/JavaCompilationHelper.java b/src/main/java/com/google/devtools/build/lib/rules/java/JavaCompilationHelper.java
index 12d4e85..c975fbc 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/java/JavaCompilationHelper.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/java/JavaCompilationHelper.java
@@ -41,6 +41,7 @@
 import com.google.devtools.build.lib.packages.TargetUtils;
 import com.google.devtools.build.lib.rules.java.JavaConfiguration.JavaClasspathMode;
 import com.google.devtools.build.lib.rules.java.JavaPluginInfo.JavaPluginData;
+import com.google.devtools.build.lib.rules.java.JavaToolchainProvider.JspecifyInfo;
 import com.google.devtools.build.lib.vfs.FileSystemUtils;
 import com.google.devtools.build.lib.vfs.PathFragment;
 import java.util.ArrayList;
@@ -200,6 +201,21 @@
     }
 
     JavaTargetAttributes attributes = getAttributes();
+
+    JspecifyInfo jspecifyInfo = javaToolchain.jspecifyInfo();
+    boolean jspecify =
+        getJavaConfiguration().experimentalEnableJspecify()
+            && jspecifyInfo != null
+            && jspecifyInfo.matches(ruleContext.getLabel());
+    if (jspecify) {
+      // JSpecify requires these on the compile-time classpath; see b/187113128
+      // Add them as non-direct deps (for the purposes of Strict Java Deps) to still require an
+      // explicit dep if they're directly used by the compiled source.
+      attributes =
+          attributes.appendAdditionalTransitiveClassPathEntries(
+              jspecifyInfo.jspecifyImplicitDeps());
+    }
+
     ImmutableList<Artifact> sourceJars = attributes.getSourceJars();
     JavaPluginData plugins = attributes.plugins().plugins();
     List<Artifact> resourceJars = new ArrayList<>();
@@ -250,6 +266,19 @@
       createResourceJarAction(originalOutput, ImmutableList.copyOf(resourceJars));
     }
 
+    ImmutableList<String> javacopts = customJavacOpts;
+    if (jspecify) {
+      plugins =
+          JavaPluginInfo.JavaPluginData.merge(
+              ImmutableList.of(plugins, jspecifyInfo.jspecifyProcessor()));
+      javacopts =
+          ImmutableList.<String>builder()
+              .addAll(javacopts)
+              // Add JSpecify options last to discourage overridding them, at least for now.
+              .addAll(jspecifyInfo.jspecifyJavacopts())
+              .build();
+    }
+
     JavaCompileActionBuilder builder = new JavaCompileActionBuilder(ruleContext, javaToolchain);
 
     JavaClasspathMode classpathMode = getJavaConfiguration().getReduceJavaClasspath();
@@ -298,7 +327,7 @@
     ImmutableSet<Artifact> sourceFiles = attributes.getSourceFiles();
     builder.setSourceFiles(sourceFiles);
     builder.setSourceJars(sourceJars);
-    builder.setJavacOpts(customJavacOpts);
+    builder.setJavacOpts(javacopts);
     builder.setJavacExecutionInfo(getExecutionInfo());
     builder.setCompressJar(true);
     builder.setBuiltinProcessorNames(javaToolchain.getHeaderCompilerBuiltinProcessors());
diff --git a/src/main/java/com/google/devtools/build/lib/rules/java/JavaConfiguration.java b/src/main/java/com/google/devtools/build/lib/rules/java/JavaConfiguration.java
index 618f33d..a8cce20 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/java/JavaConfiguration.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/java/JavaConfiguration.java
@@ -105,6 +105,7 @@
   private final boolean disallowResourceJars;
   private final boolean disallowLegacyJavaToolchainFlags;
   private final boolean experimentalTurbineAnnotationProcessing;
+  private final boolean experimentalEnableJspecify;
 
   // TODO(dmarting): remove once we have a proper solution for #2539
   private final boolean useLegacyBazelJavaTest;
@@ -163,6 +164,7 @@
     this.disallowLegacyJavaToolchainFlags = javaOptions.disallowLegacyJavaToolchainFlags;
     this.experimentalTurbineAnnotationProcessing =
         javaOptions.experimentalTurbineAnnotationProcessing;
+    this.experimentalEnableJspecify = javaOptions.experimentalEnableJspecify;
 
     if (javaOptions.disallowLegacyJavaToolchainFlags) {
       checkLegacyToolchainFlagIsUnset(
@@ -387,4 +389,8 @@
   public boolean experimentalTurbineAnnotationProcessing() {
     return experimentalTurbineAnnotationProcessing;
   }
+
+  public boolean experimentalEnableJspecify() {
+    return experimentalEnableJspecify;
+  }
 }
diff --git a/src/main/java/com/google/devtools/build/lib/rules/java/JavaOptions.java b/src/main/java/com/google/devtools/build/lib/rules/java/JavaOptions.java
index ce38280..c891d02 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/java/JavaOptions.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/java/JavaOptions.java
@@ -651,6 +651,14 @@
       help = "Deprecated no-op.")
   public boolean experimentalPublishJavaCcLinkParamsInfo;
 
+  @Option(
+      name = "experimental_enable_jspecify",
+      defaultValue = "true",
+      documentationCategory = OptionDocumentationCategory.UNDOCUMENTED,
+      effectTags = {OptionEffectTag.UNKNOWN},
+      help = "Enable experimental jspecify integration.")
+  public boolean experimentalEnableJspecify;
+
   Label defaultJavaBase() {
     return Label.parseAbsoluteUnchecked(DEFAULT_JAVABASE);
   }
diff --git a/src/main/java/com/google/devtools/build/lib/rules/java/JavaTargetAttributes.java b/src/main/java/com/google/devtools/build/lib/rules/java/JavaTargetAttributes.java
index c5d3ea9..b95bc07 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/java/JavaTargetAttributes.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/java/JavaTargetAttributes.java
@@ -473,6 +473,34 @@
         strictJavaDeps);
   }
 
+  JavaTargetAttributes appendAdditionalTransitiveClassPathEntries(
+      NestedSet<Artifact> additionalClassPathEntries) {
+    NestedSet<Artifact> compileTimeClassPath =
+        NestedSetBuilder.fromNestedSet(this.compileTimeClassPath)
+            .addTransitive(additionalClassPathEntries)
+            .build();
+    return new JavaTargetAttributes(
+        sourceFiles,
+        runtimeClassPath,
+        compileTimeClassPath,
+        bootClassPath,
+        sourcePath,
+        nativeLibraries,
+        plugins,
+        resources,
+        resourceJars,
+        messages,
+        sourceJars,
+        classPathResources,
+        additionalOutputs,
+        directJars,
+        compileTimeDependencyArtifacts,
+        targetLabel,
+        injectingRuleKind,
+        excludedArtifacts,
+        strictJavaDeps);
+  }
+
   public NestedSet<Artifact> getDirectJars() {
     return directJars;
   }
diff --git a/src/main/java/com/google/devtools/build/lib/rules/java/JavaToolchain.java b/src/main/java/com/google/devtools/build/lib/rules/java/JavaToolchain.java
index 63bc552..cedc95f 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/java/JavaToolchain.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/java/JavaToolchain.java
@@ -15,6 +15,8 @@
 
 import static com.google.common.base.Strings.isNullOrEmpty;
 import static com.google.common.collect.Iterables.getOnlyElement;
+import static com.google.devtools.build.lib.collect.nestedset.Order.STABLE_ORDER;
+import static com.google.devtools.build.lib.packages.Type.STRING;
 
 import com.google.common.base.Joiner;
 import com.google.common.collect.ImmutableList;
@@ -25,6 +27,7 @@
 import com.google.devtools.build.lib.actions.MutableActionGraph.ActionConflictException;
 import com.google.devtools.build.lib.analysis.ConfiguredTarget;
 import com.google.devtools.build.lib.analysis.FilesToRunProvider;
+import com.google.devtools.build.lib.analysis.PackageSpecificationProvider;
 import com.google.devtools.build.lib.analysis.PrerequisiteArtifacts;
 import com.google.devtools.build.lib.analysis.RuleConfiguredTargetBuilder;
 import com.google.devtools.build.lib.analysis.RuleConfiguredTargetFactory;
@@ -36,6 +39,8 @@
 import com.google.devtools.build.lib.collect.nestedset.NestedSetBuilder;
 import com.google.devtools.build.lib.collect.nestedset.Order;
 import com.google.devtools.build.lib.packages.Type;
+import com.google.devtools.build.lib.rules.java.JavaPluginInfo.JavaPluginData;
+import com.google.devtools.build.lib.rules.java.JavaToolchainProvider.JspecifyInfo;
 import java.util.List;
 import java.util.Map;
 
@@ -94,6 +99,33 @@
         JavaToolchainTool.fromFilesToRunProvider(
             ruleContext.getExecutablePrerequisite("header_compiler_direct"));
 
+    JspecifyInfo jspecifyInfo;
+    String jspecifyProcessorClass =
+        ruleContext.attributes().get("jspecify_processor_class", STRING);
+    if (jspecifyProcessorClass.isEmpty()) {
+      jspecifyInfo = null;
+    } else {
+      JavaPluginData jspecifyProcessor =
+          JavaPluginData.create(
+              NestedSetBuilder.create(STABLE_ORDER, jspecifyProcessorClass),
+              NestedSetBuilder.create(
+                  STABLE_ORDER, ruleContext.getPrerequisiteArtifact("jspecify_processor")),
+              PrerequisiteArtifacts.nestedSet(ruleContext, "jspecify_stubs"));
+      NestedSet<Artifact> jspecifyImplicitDeps =
+          NestedSetBuilder.create(
+              STABLE_ORDER, ruleContext.getPrerequisiteArtifact("jspecify_implicit_deps"));
+      ImmutableList<String> jspecifyJavacopts =
+          ImmutableList.copyOf(
+              ruleContext.attributes().get("jspecify_javacopts", Type.STRING_LIST));
+      ImmutableList<PackageSpecificationProvider> jspecifyPackages =
+          ImmutableList.copyOf(
+              ruleContext.getPrerequisites(
+                  "jspecify_packages", PackageSpecificationProvider.class));
+      jspecifyInfo =
+          JspecifyInfo.create(
+              jspecifyProcessor, jspecifyImplicitDeps, jspecifyJavacopts, jspecifyPackages);
+    }
+
     AndroidLintTool androidLint = AndroidLintTool.fromRuleContext(ruleContext);
 
     ImmutableList<JavaPackageConfigurationProvider> packageConfiguration =
@@ -119,6 +151,7 @@
             headerCompiler,
             headerCompilerDirect,
             androidLint,
+            jspecifyInfo,
             headerCompilerBuiltinProcessors,
             reducedClasspathIncompatibleProcessors,
             forciblyDisableHeaderCompilation,
diff --git a/src/main/java/com/google/devtools/build/lib/rules/java/JavaToolchainProvider.java b/src/main/java/com/google/devtools/build/lib/rules/java/JavaToolchainProvider.java
index 06c4cd6..1e48be8 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/java/JavaToolchainProvider.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/java/JavaToolchainProvider.java
@@ -15,11 +15,13 @@
 
 import static com.google.common.base.StandardSystemProperty.JAVA_SPECIFICATION_VERSION;
 
+import com.google.auto.value.AutoValue;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableListMultimap;
 import com.google.common.collect.ImmutableSet;
 import com.google.devtools.build.lib.actions.Artifact;
 import com.google.devtools.build.lib.analysis.FilesToRunProvider;
+import com.google.devtools.build.lib.analysis.PackageSpecificationProvider;
 import com.google.devtools.build.lib.analysis.ProviderCollection;
 import com.google.devtools.build.lib.analysis.RuleContext;
 import com.google.devtools.build.lib.analysis.RuleErrorConsumer;
@@ -31,7 +33,9 @@
 import com.google.devtools.build.lib.concurrent.ThreadSafety.Immutable;
 import com.google.devtools.build.lib.packages.BuiltinProvider;
 import com.google.devtools.build.lib.packages.NativeInfo;
+import com.google.devtools.build.lib.packages.PackageSpecification.PackageGroupContents;
 import com.google.devtools.build.lib.packages.Provider;
+import com.google.devtools.build.lib.rules.java.JavaPluginInfo.JavaPluginData;
 import com.google.devtools.build.lib.skyframe.serialization.autocodec.AutoCodec;
 import com.google.devtools.build.lib.skyframe.serialization.autocodec.AutoCodec.VisibleForSerialization;
 import com.google.devtools.build.lib.starlarkbuildapi.java.JavaToolchainStarlarkApiProviderApi;
@@ -96,6 +100,7 @@
       @Nullable JavaToolchainTool headerCompiler,
       @Nullable JavaToolchainTool headerCompilerDirect,
       @Nullable AndroidLintTool androidLint,
+      JspecifyInfo jspecifyInfo,
       ImmutableSet<String> headerCompilerBuiltinProcessors,
       ImmutableSet<String> reducedClasspathIncompatibleProcessors,
       boolean forciblyDisableHeaderCompilation,
@@ -120,6 +125,7 @@
         headerCompiler,
         headerCompilerDirect,
         androidLint,
+        jspecifyInfo,
         headerCompilerBuiltinProcessors,
         reducedClasspathIncompatibleProcessors,
         forciblyDisableHeaderCompilation,
@@ -149,6 +155,7 @@
   @Nullable private final JavaToolchainTool headerCompiler;
   @Nullable private final JavaToolchainTool headerCompilerDirect;
   @Nullable private final AndroidLintTool androidLint;
+  @Nullable private final JspecifyInfo jspecifyInfo;
   private final ImmutableSet<String> headerCompilerBuiltinProcessors;
   private final ImmutableSet<String> reducedClasspathIncompatibleProcessors;
   private final boolean forciblyDisableHeaderCompilation;
@@ -179,6 +186,7 @@
       @Nullable JavaToolchainTool headerCompiler,
       @Nullable JavaToolchainTool headerCompilerDirect,
       @Nullable AndroidLintTool androidLint,
+      @Nullable JspecifyInfo jspecifyInfo,
       ImmutableSet<String> headerCompilerBuiltinProcessors,
       ImmutableSet<String> reducedClasspathIncompatibleProcessors,
       boolean forciblyDisableHeaderCompilation,
@@ -207,6 +215,7 @@
     this.headerCompiler = headerCompiler;
     this.headerCompilerDirect = headerCompilerDirect;
     this.androidLint = androidLint;
+    this.jspecifyInfo = jspecifyInfo;
     this.headerCompilerBuiltinProcessors = headerCompilerBuiltinProcessors;
     this.reducedClasspathIncompatibleProcessors = reducedClasspathIncompatibleProcessors;
     this.forciblyDisableHeaderCompilation = forciblyDisableHeaderCompilation;
@@ -269,6 +278,11 @@
     return androidLint;
   }
 
+  @Nullable
+  public JspecifyInfo jspecifyInfo() {
+    return jspecifyInfo;
+  }
+
   /** Returns class names of annotation processors that are built in to the header compiler. */
   public ImmutableSet<String> getHeaderCompilerBuiltinProcessors() {
     return headerCompilerBuiltinProcessors;
@@ -441,4 +455,36 @@
   public Provider getProvider() {
     return PROVIDER;
   }
+
+  @AutoValue
+  abstract static class JspecifyInfo {
+
+    abstract JavaPluginData jspecifyProcessor();
+
+    abstract NestedSet<Artifact> jspecifyImplicitDeps();
+
+    abstract ImmutableList<String> jspecifyJavacopts();
+
+    abstract ImmutableList<PackageSpecificationProvider> jspecifyPackages();
+
+    boolean matches(Label label) {
+      for (PackageSpecificationProvider provider : jspecifyPackages()) {
+        for (PackageGroupContents specifications : provider.getPackageSpecifications().toList()) {
+          if (specifications.containsPackage(label.getPackageIdentifier())) {
+            return true;
+          }
+        }
+      }
+      return false;
+    }
+
+    static JspecifyInfo create(
+        JavaPluginData jspecifyProcessor,
+        NestedSet<Artifact> jspecifyImplicitDeps,
+        ImmutableList<String> jspecifyJavacopts,
+        ImmutableList<PackageSpecificationProvider> jspecifyPackages) {
+      return new AutoValue_JavaToolchainProvider_JspecifyInfo(
+          jspecifyProcessor, jspecifyImplicitDeps, jspecifyJavacopts, jspecifyPackages);
+    }
+  }
 }
diff --git a/src/main/java/com/google/devtools/build/lib/rules/java/JavaToolchainRule.java b/src/main/java/com/google/devtools/build/lib/rules/java/JavaToolchainRule.java
index a471252..8ce70a3 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/java/JavaToolchainRule.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/java/JavaToolchainRule.java
@@ -25,6 +25,7 @@
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
 import com.google.devtools.build.lib.analysis.BaseRuleClasses;
+import com.google.devtools.build.lib.analysis.PackageSpecificationProvider;
 import com.google.devtools.build.lib.analysis.RuleDefinition;
 import com.google.devtools.build.lib.analysis.RuleDefinitionEnvironment;
 import com.google.devtools.build.lib.analysis.config.ExecutionTransitionFactory;
@@ -327,6 +328,34 @@
                 .cfg(ExecutionTransitionFactory.create())
                 .mandatoryBuiltinProviders(
                     ImmutableList.of(JavaPackageConfigurationProvider.class)))
+        .add(attr("jspecify_processor_class", STRING).value("").undocumented("experimental"))
+        .add(
+            attr("jspecify_processor", LABEL)
+                .cfg(ExecutionTransitionFactory.create())
+                .allowedFileTypes(FileTypeSet.ANY_FILE)
+                .exec()
+                .undocumented("experimental"))
+        .add(
+            attr("jspecify_implicit_deps", LABEL)
+                .cfg(ExecutionTransitionFactory.create())
+                .allowedFileTypes(FileTypeSet.ANY_FILE)
+                .exec()
+                .undocumented("experimental"))
+        .add(
+            attr("jspecify_javacopts", STRING_LIST)
+                .value(ImmutableList.<String>of())
+                .undocumented("experimental"))
+        .add(
+            attr("jspecify_stubs", LABEL_LIST)
+                .cfg(ExecutionTransitionFactory.create())
+                .allowedFileTypes(FileTypeSet.ANY_FILE)
+                .undocumented("experimental"))
+        .add(
+            attr("jspecify_packages", LABEL_LIST)
+                .cfg(ExecutionTransitionFactory.create())
+                .allowedFileTypes()
+                .mandatoryBuiltinProviders(ImmutableList.of(PackageSpecificationProvider.class))
+                .undocumented("experimental"))
         .build();
   }