Tolerate missing symbols when resolving fields
This mirrors existing logic for resolving simple names of types.
It's possible this could cause turbine to fail to resolve a compile-time
constant that was pruned from the reduced classpath, except the current
classpath reduction never prunes supertypes of any accessible types.
There is a single instance in the depot where a pre-compiled jar
references a supertype that doesn't exist, and triggers a crash here.
Longer-term, enabling strict deps enforcement of static members will
ensure that any classes we read constant fields from are direct deps.
If overly aggressive classpath pruning did cause a constant field to be
skipped, it would either happen in a context where a constant was
required (switch case, annotation value) and the error would be obvious,
or else a constant field might fail to be given a constant value.
MOE_MIGRATED_REVID=138703676
diff --git a/java/com/google/turbine/binder/Resolve.java b/java/com/google/turbine/binder/Resolve.java
index f54f1cd..7b9e490 100644
--- a/java/com/google/turbine/binder/Resolve.java
+++ b/java/com/google/turbine/binder/Resolve.java
@@ -106,6 +106,9 @@
public static FieldInfo resolveField(
Env<ClassSymbol, TypeBoundClass> env, ClassSymbol origin, ClassSymbol sym, String name) {
TypeBoundClass info = env.get(sym);
+ if (info == null) {
+ return null;
+ }
for (FieldInfo f : info.fields()) {
if (f.name().equals(name)) {
return f;
diff --git a/javatests/com/google/turbine/binder/BinderTest.java b/javatests/com/google/turbine/binder/BinderTest.java
index f389560..737b707 100644
--- a/javatests/com/google/turbine/binder/BinderTest.java
+++ b/javatests/com/google/turbine/binder/BinderTest.java
@@ -25,21 +25,32 @@
import com.google.turbine.binder.bound.SourceTypeBoundClass;
import com.google.turbine.binder.env.LazyEnv;
import com.google.turbine.binder.sym.ClassSymbol;
+import com.google.turbine.lower.IntegrationTestSupport;
import com.google.turbine.model.TurbineFlag;
import com.google.turbine.parse.Parser;
import com.google.turbine.tree.Tree;
+import java.io.OutputStream;
+import java.lang.annotation.ElementType;
+import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
+import java.util.Map;
+import java.util.jar.JarEntry;
+import java.util.jar.JarOutputStream;
+import org.junit.Rule;
import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
@RunWith(JUnit4.class)
public class BinderTest {
+ @Rule public final TemporaryFolder temporaryFolder = new TemporaryFolder();
+
private static final ImmutableList<Path> BOOTCLASSPATH =
ImmutableList.of(Paths.get(System.getProperty("java.home")).resolve("lib/rt.jar"));
@@ -236,6 +247,42 @@
.isEqualTo(new ClassSymbol("lib/Lib$Inner"));
}
+ @Test
+ public void incompleteClasspath() throws Exception {
+
+ Map<String, byte[]> lib =
+ IntegrationTestSupport.runJavac(
+ ImmutableMap.of(
+ "A.java", "class A {}",
+ "B.java", "class B extends A {}"),
+ ImmutableList.of(),
+ BOOTCLASSPATH);
+
+ // create a jar containing only B
+ Path libJar = temporaryFolder.newFile("lib.jar").toPath();
+ try (OutputStream os = Files.newOutputStream(libJar);
+ JarOutputStream jos = new JarOutputStream(os)) {
+ jos.putNextEntry(new JarEntry("B.class"));
+ jos.write(lib.get("B"));
+ }
+
+ List<Tree.CompUnit> units = new ArrayList<>();
+ units.add(
+ parseLines(
+ "import java.lang.annotation.Target;",
+ "import java.lang.annotation.ElementType;",
+ "public class C implements B {",
+ " @Target(ElementType.TYPE_USE)",
+ " @interface A {};",
+ "}"));
+
+ ImmutableMap<ClassSymbol, SourceTypeBoundClass> bound =
+ Binder.bind(units, ImmutableList.of(libJar), BOOTCLASSPATH).units();
+
+ SourceTypeBoundClass a = bound.get(new ClassSymbol("C$A"));
+ assertThat(a.annotationMetadata().target()).containsExactly(ElementType.TYPE_USE);
+ }
+
private Tree.CompUnit parseLines(String... lines) {
return Parser.parse(Joiner.on('\n').join(lines));
}