Skip to content

Commit

Permalink
Support record components when reading records from the classpath.
Browse files Browse the repository at this point in the history
There was a bit of guesswork involved in BytecodeBoundClass.

PiperOrigin-RevId: 705523945
  • Loading branch information
java-team-github-bot authored and Javac Team committed Dec 12, 2024
1 parent f04e3d2 commit 6cb8e7e
Show file tree
Hide file tree
Showing 4 changed files with 155 additions and 5 deletions.
33 changes: 32 additions & 1 deletion java/com/google/turbine/binder/bytecode/BytecodeBoundClass.java
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
import com.google.turbine.binder.sym.FieldSymbol;
import com.google.turbine.binder.sym.MethodSymbol;
import com.google.turbine.binder.sym.ParamSymbol;
import com.google.turbine.binder.sym.RecordComponentSymbol;
import com.google.turbine.binder.sym.TyVarSymbol;
import com.google.turbine.bytecode.ClassFile;
import com.google.turbine.bytecode.ClassFile.AnnotationInfo;
Expand All @@ -42,6 +43,7 @@
import com.google.turbine.bytecode.ClassFile.AnnotationInfo.ElementValue.EnumConstValue;
import com.google.turbine.bytecode.ClassFile.AnnotationInfo.ElementValue.Kind;
import com.google.turbine.bytecode.ClassFile.MethodInfo.ParameterInfo;
import com.google.turbine.bytecode.ClassFile.RecordInfo;
import com.google.turbine.bytecode.ClassFile.TypeAnnotationInfo;
import com.google.turbine.bytecode.ClassFile.TypeAnnotationInfo.TargetType;
import com.google.turbine.bytecode.ClassReader;
Expand Down Expand Up @@ -612,9 +614,38 @@ public ImmutableList<MethodInfo> methods() {
return methods.get();
}

private final Supplier<ImmutableList<RecordComponentInfo>> components =
Suppliers.memoize(
new Supplier<ImmutableList<RecordComponentInfo>>() {
@Override
public ImmutableList<RecordComponentInfo> get() {
var record = classFile.get().record();
if (record == null) {
return ImmutableList.of();
}
ImmutableList.Builder<RecordComponentInfo> result = ImmutableList.builder();
for (RecordInfo.RecordComponentInfo component : record.recordComponents()) {
Type type =
BytecodeBinder.bindTy(
new SigParser(firstNonNull(component.signature(), component.descriptor()))
.parseType(),
makeScope(env, sym, ImmutableMap.of()),
typeAnnotationsForTarget(component.typeAnnotations(), TargetType.FIELD));
result.add(
new RecordComponentInfo(
new RecordComponentSymbol(sym, component.name()),
type,
BytecodeBinder.bindAnnotations(
component.annotations(), makeScope(env, sym, ImmutableMap.of())),
/* access= */ 0));
}
return result.build();
}
});

@Override
public ImmutableList<RecordComponentInfo> components() {
return ImmutableList.of();
return components.get();
}

private final Supplier<@Nullable AnnotationMetadata> annotationMetadata =
Expand Down
50 changes: 49 additions & 1 deletion java/com/google/turbine/bytecode/ClassReader.java
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
import com.google.turbine.bytecode.ClassFile.ModuleInfo.ProvideInfo;
import com.google.turbine.bytecode.ClassFile.ModuleInfo.RequireInfo;
import com.google.turbine.bytecode.ClassFile.ModuleInfo.UseInfo;
import com.google.turbine.bytecode.ClassFile.RecordInfo;
import com.google.turbine.bytecode.ClassFile.TypeAnnotationInfo;
import com.google.turbine.model.Const;
import com.google.turbine.model.TurbineFlag;
Expand Down Expand Up @@ -112,6 +113,7 @@ private ClassFile read() {
ImmutableList.Builder<ClassFile.TypeAnnotationInfo> typeAnnotations = ImmutableList.builder();
ClassFile.ModuleInfo module = null;
String transitiveJar = null;
RecordInfo record = null;
int attributesCount = reader.u2();
for (int j = 0; j < attributesCount; j++) {
int attributeNameIndex = reader.u2();
Expand Down Expand Up @@ -141,6 +143,9 @@ private ClassFile read() {
case "TurbineTransitiveJar":
transitiveJar = readTurbineTransitiveJar(constantPool);
break;
case "Record":
record = readRecord(constantPool);
break;
default:
reader.skip(reader.u4());
break;
Expand All @@ -163,7 +168,7 @@ private ClassFile read() {
module,
/* nestHost= */ null,
/* nestMembers= */ ImmutableList.of(),
/* record= */ null,
record,
transitiveJar);
}

Expand Down Expand Up @@ -688,4 +693,47 @@ private String readTurbineTransitiveJar(ConstantPoolReader constantPool) {
int unusedLength = reader.u4();
return constantPool.utf8(reader.u2());
}

private RecordInfo readRecord(ConstantPoolReader constantPool) {
int unusedLength = reader.u4();
int componentsCount = reader.u2();

ImmutableList.Builder<RecordInfo.RecordComponentInfo> components = ImmutableList.builder();
for (int i = 0; i < componentsCount; i++) {
String name = constantPool.utf8(reader.u2());
String descriptor = constantPool.utf8(reader.u2());

int attributesCount = reader.u2();
ImmutableList.Builder<ClassFile.AnnotationInfo> annotations = ImmutableList.builder();
ImmutableList.Builder<ClassFile.TypeAnnotationInfo> typeAnnotations = ImmutableList.builder();
String signature = null;
for (int j = 0; j < attributesCount; j++) {
String attributeName = constantPool.utf8(reader.u2());
switch (attributeName) {
case "RuntimeInvisibleAnnotations":
readAnnotations(annotations, constantPool, RuntimeVisibility.INVISIBLE);
break;
case "RuntimeVisibleAnnotations":
readAnnotations(annotations, constantPool, RuntimeVisibility.VISIBLE);
break;
case "RuntimeInvisibleTypeAnnotations":
readTypeAnnotations(typeAnnotations, constantPool, RuntimeVisibility.INVISIBLE);
break;
case "RuntimeVisibleTypeAnnotations":
readTypeAnnotations(typeAnnotations, constantPool, RuntimeVisibility.VISIBLE);
break;
case "Signature":
signature = readSignature(constantPool);
break;
default:
reader.skip(reader.u4());
break;
}
}
components.add(
new RecordInfo.RecordComponentInfo(
name, descriptor, signature, annotations.build(), typeAnnotations.build()));
}
return new RecordInfo(components.build());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -144,10 +144,7 @@ public static Map<String, byte[]> removeUnsupportedAttributes(Map<String, byte[]
c.nestHostClass = null;
// TODO(b/307939333): class reading for sealed classes
c.permittedSubclasses = null;
// TODO(b/307939164): class reading for record components
c.recordComponents = null;
// this is a synthetic access flag that ASM sets if recordComponents is present
c.access &= ~Opcodes.ACC_RECORD;
}
return toByteCode(classes);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@
import static javax.lang.model.util.ElementFilter.typesIn;
import static org.junit.Assert.assertThrows;

import com.google.auto.common.MoreElements;
import com.google.auto.common.MoreTypes;
import com.google.common.base.Joiner;
import com.google.common.base.Splitter;
import com.google.common.collect.ImmutableList;
Expand Down Expand Up @@ -72,6 +74,7 @@
import javax.lang.model.type.DeclaredType;
import javax.lang.model.type.ErrorType;
import javax.lang.model.type.ExecutableType;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.util.ElementFilter;
import javax.tools.Diagnostic;
import javax.tools.FileObject;
Expand Down Expand Up @@ -606,6 +609,77 @@ public void recordProcessing() throws IOException {
"METHOD y()");
}

@SupportedAnnotationTypes("*")
public static class RecordFromADistanceProcessor extends AbstractProcessor {
@Override
public SourceVersion getSupportedSourceVersion() {
return SourceVersion.latestSupported();
}

@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
processingEnv
.getMessager()
.printMessage(
Diagnostic.Kind.ERROR,
roundEnv
.getElementsAnnotatedWith(processingEnv.getElementUtils().getTypeElement("foo.R"))
.stream()
.flatMap(e -> processingEnv.getElementUtils().getAllAnnotationMirrors(e).stream())
.flatMap(a -> a.getElementValues().values().stream())
.flatMap(
x ->
MoreElements.asType(
MoreTypes.asDeclared((TypeMirror) x.getValue()).asElement())
.getRecordComponents()
.stream())
.map(x -> x.getSimpleName())
.collect(toImmutableList())
.toString());
return false;
}
}

@Test
public void bytecodeRecord_componentsAvailable() throws IOException {
Map<String, byte[]> library =
IntegrationTestSupport.runTurbine(
ImmutableMap.of(
"MyRecord.java", "package foo; public record MyRecord(int x, int y) {}"),
ImmutableList.of());
Path libJar = temporaryFolder.newFile("lib.jar").toPath();
try (OutputStream os = Files.newOutputStream(libJar);
JarOutputStream jos = new JarOutputStream(os)) {
jos.putNextEntry(new JarEntry("foo/MyRecord.class"));
jos.write(requireNonNull(library.get("foo/MyRecord")));
}

ImmutableList<Tree.CompUnit> units =
parseUnit(
"=== Y.java ===", //
"package foo;",
"@interface R { Class<? extends Record> value(); }",
"@R(MyRecord.class)",
"class Y {}");

TurbineLog log = new TurbineLog();
BindingResult unused =
Binder.bind(
log,
units,
ClassPathBinder.bindClasspath(ImmutableList.of(libJar)),
ProcessorInfo.create(
ImmutableList.of(new RecordFromADistanceProcessor()),
getClass().getClassLoader(),
ImmutableMap.of(),
SourceVersion.latestSupported()),
TestClassPaths.TURBINE_BOOTCLASSPATH,
Optional.empty());
ImmutableList<String> messages =
log.diagnostics().stream().map(TurbineDiagnostic::message).collect(toImmutableList());
assertThat(messages).contains("[x, y]");
}

@Test
public void missingElementValue() {
ImmutableList<Tree.CompUnit> units =
Expand Down

0 comments on commit 6cb8e7e

Please sign in to comment.