diff --git a/check_api/src/main/java/com/google/errorprone/scanner/ErrorProneInjector.java b/check_api/src/main/java/com/google/errorprone/scanner/ErrorProneInjector.java
new file mode 100644
index 00000000000..16215048dc0
--- /dev/null
+++ b/check_api/src/main/java/com/google/errorprone/scanner/ErrorProneInjector.java
@@ -0,0 +1,115 @@
+/*
+ * Copyright 2023 The Error Prone Authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.errorprone.scanner;
+
+import static com.google.common.collect.Lists.reverse;
+import static java.util.Arrays.stream;
+import static java.util.stream.Collectors.joining;
+
+import com.google.common.collect.ClassToInstanceMap;
+import com.google.common.collect.MutableClassToInstanceMap;
+import com.google.errorprone.ErrorProneFlags;
+import com.google.errorprone.annotations.CanIgnoreReturnValue;
+import com.google.inject.ProvisionException;
+import java.lang.reflect.Constructor;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Optional;
+import java.util.function.Predicate;
+
+/**
+ * An injector for ErrorProne.
+ *
+ *
This implements a very simplified subset of the functionality that Guice does. Specifically,
+ * it allows injecting only non-generic classes, and treats everything as a singleton within a given
+ * compilation.
+ */
+final class ErrorProneInjector {
+ private final ClassToInstanceMap instances = MutableClassToInstanceMap.create();
+
+ public static ErrorProneInjector create() {
+ return new ErrorProneInjector();
+ }
+
+ @CanIgnoreReturnValue
+ public ErrorProneInjector addBinding(Class clazz, T instance) {
+ instances.putInstance(clazz, instance);
+ return this;
+ }
+
+ public synchronized T getInstance(Class clazz) {
+ return getInstance(clazz, new ArrayList<>());
+ }
+
+ private synchronized T getInstance(Class clazz, List> path) {
+ var instance = instances.getInstance(clazz);
+ if (instance != null) {
+ return instance;
+ }
+ path.add(clazz);
+ Constructor constructor =
+ findConstructor(clazz)
+ .orElseThrow(
+ () ->
+ new ProvisionException(
+ "Failed to find an injectable constructor for "
+ + clazz.getCanonicalName()
+ + " requested by "
+ + printPath(path)));
+
+ constructor.setAccessible(true);
+
+ Object[] args =
+ stream(constructor.getParameterTypes()).map(c -> getInstance(c, path)).toArray();
+ T newInstance;
+ try {
+ newInstance = constructor.newInstance(args);
+ } catch (ReflectiveOperationException e) {
+ throw new ProvisionException("Failed to initialize " + clazz.getCanonicalName(), e);
+ }
+ instances.putInstance(clazz, newInstance);
+ return newInstance;
+ }
+
+ public static Optional> findConstructor(Class clazz) {
+ return findConstructorMatching(
+ clazz,
+ c ->
+ stream(c.getAnnotations())
+ .anyMatch(a -> a.annotationType().getSimpleName().equals("Inject")))
+ .or(
+ () ->
+ findConstructorMatching(
+ clazz,
+ c ->
+ stream(c.getParameters())
+ .allMatch(p -> p.getType().equals(ErrorProneFlags.class))));
+ }
+
+ @SuppressWarnings("unchecked")
+ private static Optional> findConstructorMatching(
+ Class clazz, Predicate> predicate) {
+ return stream(clazz.getDeclaredConstructors())
+ .filter(predicate)
+ .map(c -> (Constructor) c)
+ .findFirst();
+ }
+
+ private static String printPath(List> path) {
+ return reverse(path).stream().map(Class::getSimpleName).collect(joining(" <- "));
+ }
+}
diff --git a/check_api/src/main/java/com/google/errorprone/scanner/ScannerSupplierImpl.java b/check_api/src/main/java/com/google/errorprone/scanner/ScannerSupplierImpl.java
index 276f478a9fc..3bc1237c2d3 100644
--- a/check_api/src/main/java/com/google/errorprone/scanner/ScannerSupplierImpl.java
+++ b/check_api/src/main/java/com/google/errorprone/scanner/ScannerSupplierImpl.java
@@ -27,13 +27,7 @@
import com.google.errorprone.BugPattern.SeverityLevel;
import com.google.errorprone.ErrorProneFlags;
import com.google.errorprone.bugpatterns.BugChecker;
-import com.google.inject.Guice;
-import com.google.inject.Injector;
-import com.google.inject.ProvisionException;
import java.io.Serializable;
-import java.lang.reflect.Constructor;
-import java.util.Arrays;
-import java.util.Optional;
/**
* An implementation of a {@link ScannerSupplier}, abstracted as a set of all known {@link
@@ -46,7 +40,7 @@ class ScannerSupplierImpl extends ScannerSupplier implements Serializable {
private final ImmutableSet disabled;
private final ErrorProneFlags flags;
// Lazily initialized to make serialization easy.
- private transient Injector injector;
+ private transient ErrorProneInjector injector;
ScannerSupplierImpl(
ImmutableBiMap checks,
@@ -67,50 +61,9 @@ class ScannerSupplierImpl extends ScannerSupplier implements Serializable {
private BugChecker instantiateChecker(BugCheckerInfo checker) {
if (injector == null) {
- injector =
- Guice.createInjector(binder -> binder.bind(ErrorProneFlags.class).toInstance(flags));
- }
- try {
- return injector.getInstance(checker.checkerClass());
- } catch (ProvisionException | com.google.inject.ConfigurationException e) {
- // Fall back to the old path for external checks.
- // TODO(b/263227221): Consider stripping this internally after careful testing.
- return instantiateCheckerOldPath(checker);
- }
- }
-
- private BugChecker instantiateCheckerOldPath(BugCheckerInfo checker) {
- // Invoke BugChecker(ErrorProneFlags) constructor, if it exists.
- @SuppressWarnings("unchecked")
- /* getConstructors() actually returns Constructor[], though the return type is
- * Constructor>[]. See getConstructors() javadoc for more info. */
- Optional> flagsConstructor =
- Arrays.stream((Constructor[]) checker.checkerClass().getConstructors())
- .filter(
- c -> Arrays.equals(c.getParameterTypes(), new Class>[] {ErrorProneFlags.class}))
- .findFirst();
- if (flagsConstructor.isPresent()) {
- try {
- return flagsConstructor.get().newInstance(getFlags());
- } catch (ReflectiveOperationException e) {
- throw new LinkageError("Could not instantiate BugChecker.", e);
- }
- }
-
- // If no flags constructor, invoke default constructor.
- Class extends BugChecker> checkerClass = checker.checkerClass();
- try {
- return checkerClass.getConstructor().newInstance();
- } catch (NoSuchMethodException | IllegalAccessException e) {
- throw new LinkageError(
- String.format(
- "Could not instantiate BugChecker %s: Are both the class and the zero-arg"
- + " constructor public?",
- checkerClass),
- e);
- } catch (ReflectiveOperationException e) {
- throw new LinkageError("Could not instantiate BugChecker.", e);
+ injector = ErrorProneInjector.create().addBinding(ErrorProneFlags.class, flags);
}
+ return injector.getInstance(checker.checkerClass());
}
@Override
diff --git a/check_api/src/test/java/com/google/errorprone/scanner/ErrorProneInjectorTest.java b/check_api/src/test/java/com/google/errorprone/scanner/ErrorProneInjectorTest.java
new file mode 100644
index 00000000000..023b9ea30ac
--- /dev/null
+++ b/check_api/src/test/java/com/google/errorprone/scanner/ErrorProneInjectorTest.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright 2023 The Error Prone Authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.errorprone.scanner;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assert.assertThrows;
+
+import com.google.inject.ProvisionException;
+import javax.inject.Inject;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public final class ErrorProneInjectorTest {
+ @Test
+ public void retrievesPredefinedInstance() {
+ var injector = ErrorProneInjector.create().addBinding(Integer.class, 2);
+
+ assertThat(injector.getInstance(Integer.class))
+ .isSameInstanceAs(injector.getInstance(Integer.class));
+ }
+
+ @Test
+ public void noConstructor_injectable() {
+ var injector = ErrorProneInjector.create();
+
+ var unused = injector.getInstance(NoConstructor.class);
+ }
+
+ @Test
+ public void injectConstructor_injectable() {
+ var injector = ErrorProneInjector.create();
+
+ var unused = injector.getInstance(InjectConstructor.class);
+ }
+
+ @Test
+ public void bothConstructors_injectable() {
+ var injector = ErrorProneInjector.create().addBinding(Integer.class, 2);
+
+ var obj = injector.getInstance(InjectConstructorAndZeroArgConstructor.class);
+
+ assertThat(obj.x).isEqualTo(2);
+ }
+
+ @Test
+ public void pathInError() {
+ var injector = ErrorProneInjector.create();
+
+ var e =
+ assertThrows(
+ ProvisionException.class,
+ () -> injector.getInstance(InjectConstructorAndZeroArgConstructor.class));
+
+ assertThat(e).hasMessageThat().contains("Integer <- InjectConstructorAndZeroArgConstructor");
+ }
+
+ public static final class NoConstructor {}
+
+ public static final class InjectConstructor {
+ @Inject
+ InjectConstructor() {}
+ }
+
+ public static final class InjectConstructorAndZeroArgConstructor {
+ final int x;
+
+ @Inject
+ InjectConstructorAndZeroArgConstructor(Integer x) {
+ this.x = x;
+ }
+
+ InjectConstructorAndZeroArgConstructor() {
+ this.x = 0;
+ }
+ }
+}