Fix handling of repackaged transitive classes in jdeps
Turbine supports running with only direct dependencies on the classpath (see
https://github.com/google/turbine/commit/d1509927c68b994ecb9eb95a8ae8478da9f04ed4).
In this mode it repackages transitive supertypes of classes referenced in the
compilation, and saves them in the output jar under `META-INF/TRANSITIVE`.
When turbine records classes that were used in the compilation for `jdeps`
output, it was reporting the jar that it loaded a repackaged transitive class
from, instead of the jar where that repackaged transitive class was originally
found.
This change adds a `TurbineTransitiveJar` class file attribute to the
repackaged classes that records the path of the jar file they were originally
seen in, and updates the `jdeps` logic to report that original path instead of
the jar where the repackaged class was observed.
PiperOrigin-RevId: 380075237
diff --git a/java/com/google/turbine/binder/bytecode/BytecodeBoundClass.java b/java/com/google/turbine/binder/bytecode/BytecodeBoundClass.java
index 44b64bb..01da961 100644
--- a/java/com/google/turbine/binder/bytecode/BytecodeBoundClass.java
+++ b/java/com/google/turbine/binder/bytecode/BytecodeBoundClass.java
@@ -629,6 +629,10 @@
/** The jar file the symbol was loaded from. */
public @Nullable String jarFile() {
+ String transitiveJar = classFile.get().transitiveJar();
+ if (transitiveJar != null) {
+ return transitiveJar;
+ }
return jarFile;
}
diff --git a/java/com/google/turbine/bytecode/Attribute.java b/java/com/google/turbine/bytecode/Attribute.java
index 29efb60..7b415a7 100644
--- a/java/com/google/turbine/bytecode/Attribute.java
+++ b/java/com/google/turbine/bytecode/Attribute.java
@@ -41,7 +41,8 @@
RUNTIME_VISIBLE_TYPE_ANNOTATIONS("RuntimeVisibleTypeAnnotations"),
RUNTIME_INVISIBLE_TYPE_ANNOTATIONS("RuntimeInvisibleTypeAnnotations"),
METHOD_PARAMETERS("MethodParameters"),
- MODULE("Module");
+ MODULE("Module"),
+ TURBINE_TRANSITIVE_JAR("TurbineTransitiveJar");
private final String signature;
@@ -309,4 +310,19 @@
return module;
}
}
+
+ /** A custom attribute for recording the original jar of repackaged transitive classes. */
+ class TurbineTransitiveJar implements Attribute {
+
+ final String transitiveJar;
+
+ public TurbineTransitiveJar(String transitiveJar) {
+ this.transitiveJar = transitiveJar;
+ }
+
+ @Override
+ public Kind kind() {
+ return Kind.TURBINE_TRANSITIVE_JAR;
+ }
+ }
}
diff --git a/java/com/google/turbine/bytecode/AttributeWriter.java b/java/com/google/turbine/bytecode/AttributeWriter.java
index c5ffd16..84ca55f 100644
--- a/java/com/google/turbine/bytecode/AttributeWriter.java
+++ b/java/com/google/turbine/bytecode/AttributeWriter.java
@@ -24,6 +24,7 @@
import com.google.turbine.bytecode.Attribute.InnerClasses;
import com.google.turbine.bytecode.Attribute.MethodParameters;
import com.google.turbine.bytecode.Attribute.Signature;
+import com.google.turbine.bytecode.Attribute.TurbineTransitiveJar;
import com.google.turbine.bytecode.Attribute.TypeAnnotations;
import com.google.turbine.bytecode.ClassFile.AnnotationInfo;
import com.google.turbine.bytecode.ClassFile.MethodInfo.ParameterInfo;
@@ -87,6 +88,9 @@
case MODULE:
writeModule((Attribute.Module) attribute);
break;
+ case TURBINE_TRANSITIVE_JAR:
+ writeTurbineTransitiveJar((Attribute.TurbineTransitiveJar) attribute);
+ break;
}
}
@@ -266,4 +270,10 @@
output.writeInt(data.length);
output.write(data);
}
+
+ private void writeTurbineTransitiveJar(TurbineTransitiveJar attribute) {
+ output.writeShort(pool.utf8(attribute.kind().signature()));
+ output.writeInt(2);
+ output.writeShort(pool.utf8(attribute.transitiveJar));
+ }
}
diff --git a/java/com/google/turbine/bytecode/ClassFile.java b/java/com/google/turbine/bytecode/ClassFile.java
index 8ee2aac..e979edc 100644
--- a/java/com/google/turbine/bytecode/ClassFile.java
+++ b/java/com/google/turbine/bytecode/ClassFile.java
@@ -42,6 +42,7 @@
private final List<InnerClass> innerClasses;
private final ImmutableList<TypeAnnotationInfo> typeAnnotations;
@Nullable private final ModuleInfo module;
+ @Nullable private final String transitiveJar;
public ClassFile(
int access,
@@ -54,7 +55,8 @@
List<AnnotationInfo> annotations,
List<InnerClass> innerClasses,
ImmutableList<TypeAnnotationInfo> typeAnnotations,
- @Nullable ModuleInfo module) {
+ @Nullable ModuleInfo module,
+ @Nullable String transitiveJar) {
this.access = access;
this.name = name;
this.signature = signature;
@@ -66,6 +68,7 @@
this.innerClasses = innerClasses;
this.typeAnnotations = typeAnnotations;
this.module = module;
+ this.transitiveJar = transitiveJar;
}
/** Class access and property flags. */
@@ -124,6 +127,12 @@
return module;
}
+ /** The original jar of a repackaged transitive class. */
+ @Nullable
+ public String transitiveJar() {
+ return transitiveJar;
+ }
+
/** The contents of a JVMS §4.5 field_info structure. */
public static class FieldInfo {
diff --git a/java/com/google/turbine/bytecode/ClassReader.java b/java/com/google/turbine/bytecode/ClassReader.java
index 9c79b42..ac8b1e1 100644
--- a/java/com/google/turbine/bytecode/ClassReader.java
+++ b/java/com/google/turbine/bytecode/ClassReader.java
@@ -106,6 +106,7 @@
List<ClassFile.InnerClass> innerclasses = ImmutableList.of();
ImmutableList.Builder<ClassFile.AnnotationInfo> annotations = ImmutableList.builder();
ClassFile.ModuleInfo module = null;
+ String transitiveJar = null;
int attributesCount = reader.u2();
for (int j = 0; j < attributesCount; j++) {
int attributeNameIndex = reader.u2();
@@ -124,6 +125,9 @@
case "Module":
module = readModule(constantPool);
break;
+ case "TurbineTransitiveJar":
+ transitiveJar = readTurbineTransitiveJar(constantPool);
+ break;
default:
reader.skip(reader.u4());
break;
@@ -141,7 +145,8 @@
annotations.build(),
innerclasses,
ImmutableList.of(),
- module);
+ module,
+ transitiveJar);
}
/** Reads a JVMS 4.7.9 Signature attribute. */
@@ -509,4 +514,9 @@
}
return fields;
}
+
+ private String readTurbineTransitiveJar(ConstantPoolReader constantPool) {
+ reader.u4(); // length
+ return constantPool.utf8(reader.u2());
+ }
}
diff --git a/java/com/google/turbine/bytecode/LowerAttributes.java b/java/com/google/turbine/bytecode/LowerAttributes.java
index 42fcf5c..5ae42af 100644
--- a/java/com/google/turbine/bytecode/LowerAttributes.java
+++ b/java/com/google/turbine/bytecode/LowerAttributes.java
@@ -45,6 +45,9 @@
if (classfile.module() != null) {
attributes.add(new Attribute.Module(classfile.module()));
}
+ if (classfile.transitiveJar() != null) {
+ attributes.add(new Attribute.TurbineTransitiveJar(classfile.transitiveJar()));
+ }
return attributes;
}
diff --git a/java/com/google/turbine/deps/Transitive.java b/java/com/google/turbine/deps/Transitive.java
index a1ddc83..75d23f6 100644
--- a/java/com/google/turbine/deps/Transitive.java
+++ b/java/com/google/turbine/deps/Transitive.java
@@ -33,6 +33,7 @@
import com.google.turbine.model.TurbineFlag;
import java.util.LinkedHashSet;
import java.util.Set;
+import org.checkerframework.checker.nullness.qual.Nullable;
/**
* Collects the minimal compile-time API for symbols in the supertype closure of compiled classes.
@@ -54,7 +55,8 @@
// don't export symbols loaded from the bootclasspath
continue;
}
- transitive.put(sym.binaryName(), ClassWriter.writeClass(trimClass(info.classFile())));
+ transitive.put(
+ sym.binaryName(), ClassWriter.writeClass(trimClass(info.classFile(), info.jarFile())));
}
return transitive.build();
}
@@ -62,7 +64,7 @@
/**
* Removes information from repackaged classes that will not be needed by upstream compilations.
*/
- public static ClassFile trimClass(ClassFile cf) {
+ public static ClassFile trimClass(ClassFile cf, @Nullable String jarFile) {
// drop non-constant fields
ImmutableList.Builder<FieldInfo> fields = ImmutableList.builder();
for (FieldInfo f : cf.fields()) {
@@ -80,6 +82,12 @@
innerClasses.add(i);
}
}
+ // Include the original jar file name when repackaging transitive deps. If the same transitive
+ // dep is repackaged more than once, keep the original name.
+ String transitiveJar = cf.transitiveJar();
+ if (transitiveJar == null) {
+ transitiveJar = jarFile;
+ }
return new ClassFile(
cf.access(),
cf.name(),
@@ -96,7 +104,8 @@
cf.annotations(),
innerClasses.build(),
cf.typeAnnotations(),
- /* module= */ null);
+ /* module= */ null,
+ /* transitiveJar = */ transitiveJar);
}
private static Set<ClassSymbol> superClosure(BindingResult bound) {
diff --git a/java/com/google/turbine/lower/Lower.java b/java/com/google/turbine/lower/Lower.java
index 0f7bb90..971bbe4 100644
--- a/java/com/google/turbine/lower/Lower.java
+++ b/java/com/google/turbine/lower/Lower.java
@@ -185,7 +185,8 @@
annotations,
innerClasses.build(),
/* typeAnnotations= */ ImmutableList.of(),
- moduleInfo);
+ moduleInfo,
+ /* transitiveJar= */ null);
symbols.addAll(sig.classes);
return ClassWriter.writeClass(classfile);
}
@@ -279,7 +280,8 @@
annotations,
inners,
typeAnnotations,
- /* module= */ null);
+ /* module= */ null,
+ /* transitiveJar= */ null);
symbols.addAll(sig.classes);
diff --git a/javatests/com/google/turbine/bytecode/ClassReaderTest.java b/javatests/com/google/turbine/bytecode/ClassReaderTest.java
index 02b5b56..9a9fdb1 100644
--- a/javatests/com/google/turbine/bytecode/ClassReaderTest.java
+++ b/javatests/com/google/turbine/bytecode/ClassReaderTest.java
@@ -35,6 +35,8 @@
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
import org.objectweb.asm.AnnotationVisitor;
+import org.objectweb.asm.Attribute;
+import org.objectweb.asm.ByteVector;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.FieldVisitor;
import org.objectweb.asm.ModuleVisitor;
@@ -336,4 +338,28 @@
assertThat(p2.descriptor()).isEqualTo("p2");
assertThat(p2.implDescriptors()).containsExactly("p2i1", "p2i2", "p2i3");
}
+
+ @Test
+ public void transitiveJar() {
+ ClassWriter cw = new ClassWriter(0);
+ cw.visit(
+ 52,
+ Opcodes.ACC_PUBLIC | Opcodes.ACC_FINAL | Opcodes.ACC_SUPER,
+ "Hello",
+ null,
+ "java/lang/Object",
+ null);
+ cw.visitAttribute(
+ new Attribute("TurbineTransitiveJar") {
+ @Override
+ protected ByteVector write(
+ ClassWriter classWriter, byte[] code, int codeLength, int maxStack, int maxLocals) {
+ ByteVector result = new ByteVector();
+ result.putShort(classWriter.newUTF8("path/to/transitive.jar"));
+ return result;
+ }
+ });
+ ClassFile cf = ClassReader.read(null, cw.toByteArray());
+ assertThat(cf.transitiveJar()).isEqualTo("path/to/transitive.jar");
+ }
}
diff --git a/javatests/com/google/turbine/deps/TransitiveTest.java b/javatests/com/google/turbine/deps/TransitiveTest.java
index d35f87d..f08e899 100644
--- a/javatests/com/google/turbine/deps/TransitiveTest.java
+++ b/javatests/com/google/turbine/deps/TransitiveTest.java
@@ -17,19 +17,26 @@
package com.google.turbine.deps;
import static com.google.common.collect.ImmutableList.toImmutableList;
+import static com.google.common.collect.ImmutableMap.toImmutableMap;
import static com.google.common.collect.Iterables.getOnlyElement;
import static com.google.common.truth.Truth.assertThat;
import static com.google.turbine.testing.TestClassPaths.optionsWithBootclasspath;
import static java.nio.charset.StandardCharsets.UTF_8;
import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Iterables;
import com.google.common.io.ByteStreams;
+import com.google.protobuf.ExtensionRegistry;
import com.google.turbine.bytecode.ClassFile;
import com.google.turbine.bytecode.ClassFile.InnerClass;
import com.google.turbine.bytecode.ClassReader;
import com.google.turbine.main.Main;
+import com.google.turbine.proto.DepsProto;
+import com.google.turbine.proto.DepsProto.Dependency.Kind;
+import java.io.BufferedInputStream;
import java.io.IOException;
+import java.io.InputStream;
import java.io.OutputStream;
import java.nio.file.Files;
import java.nio.file.Path;
@@ -74,7 +81,7 @@
}
}
- private Map<String, byte[]> readJar(Path libb) throws IOException {
+ private static Map<String, byte[]> readJar(Path libb) throws IOException {
Map<String, byte[]> jarEntries = new LinkedHashMap<>();
try (JarFile jf = new JarFile(libb.toFile())) {
Enumeration<JarEntry> entries = jf.entries();
@@ -137,11 +144,19 @@
.methods())
.hasSize(1);
+ // When a.A is repackaged as a transitive class in libb, its 'transitive jar' attribute
+ // should record the path to the original liba jar.
+ assertThat(a.transitiveJar()).isEqualTo(liba.toString());
+ // The transitive jar attribute is only set for transitive classes, not e.g. b.B in libb:
+ ClassFile b = ClassReader.read(null, readJar(libb).get("b/B.class"));
+ assertThat(b.transitiveJar()).isNull();
+
// A class that references members of the transitive supertype A by simple name
// compiles cleanly against the repackaged version of A.
// Explicitly use turbine; javac-turbine doesn't support direct-classpath compilations.
Path libc = temporaryFolder.newFolder().toPath().resolve("out.jar");
+ Path libcDeps = temporaryFolder.newFolder().toPath().resolve("out.jdeps");
ImmutableList<String> sources =
new SourceBuilder()
.addSourceLines(
@@ -161,6 +176,7 @@
.setClassPath(
ImmutableList.of(libb).stream().map(Path::toString).collect(toImmutableList()))
.setOutput(libc.toString())
+ .setOutputDeps(libcDeps.toString())
.build());
assertThat(readJar(libc).keySet())
@@ -170,6 +186,20 @@
"META-INF/TRANSITIVE/a/A.class",
"META-INF/TRANSITIVE/a/A$Anno.class",
"META-INF/TRANSITIVE/a/A$Inner.class");
+
+ // liba is recorded as an explicit dep, even thought it's only present as a transitive class
+ // repackaged in lib
+ assertThat(readDeps(libcDeps))
+ .containsExactly(liba.toString(), Kind.EXPLICIT, libb.toString(), Kind.EXPLICIT);
+ }
+
+ private static ImmutableMap<String, Kind> readDeps(Path libcDeps) throws IOException {
+ DepsProto.Dependencies.Builder deps = DepsProto.Dependencies.newBuilder();
+ try (InputStream is = new BufferedInputStream(Files.newInputStream(libcDeps))) {
+ deps.mergeFrom(is, ExtensionRegistry.getEmptyRegistry());
+ }
+ return deps.getDependencyList().stream()
+ .collect(toImmutableMap(d -> d.getPath(), d -> d.getKind()));
}
@Test