| /* |
| * Copyright 2016 Google Inc. All Rights Reserved. |
| * |
| * 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.turbine.binder.bytecode; |
| |
| import static com.google.common.base.MoreObjects.firstNonNull; |
| import static com.google.common.base.Verify.verify; |
| import static java.util.Objects.requireNonNull; |
| |
| import com.google.common.base.Supplier; |
| import com.google.common.base.Suppliers; |
| import com.google.common.collect.ImmutableList; |
| import com.google.common.collect.ImmutableMap; |
| import com.google.common.collect.ImmutableSet; |
| import com.google.turbine.binder.bound.AnnotationMetadata; |
| import com.google.turbine.binder.bound.TypeBoundClass; |
| import com.google.turbine.binder.env.Env; |
| import com.google.turbine.binder.sym.ClassSymbol; |
| 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.TyVarSymbol; |
| import com.google.turbine.bytecode.ClassFile; |
| import com.google.turbine.bytecode.ClassFile.AnnotationInfo; |
| import com.google.turbine.bytecode.ClassFile.AnnotationInfo.ElementValue; |
| import com.google.turbine.bytecode.ClassFile.AnnotationInfo.ElementValue.ArrayValue; |
| import com.google.turbine.bytecode.ClassFile.AnnotationInfo.ElementValue.ConstTurbineClassValue; |
| 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.ClassReader; |
| import com.google.turbine.bytecode.sig.Sig; |
| import com.google.turbine.bytecode.sig.Sig.ClassSig; |
| import com.google.turbine.bytecode.sig.Sig.ClassTySig; |
| import com.google.turbine.bytecode.sig.Sig.TySig; |
| import com.google.turbine.bytecode.sig.SigParser; |
| import com.google.turbine.model.Const; |
| import com.google.turbine.model.TurbineElementType; |
| import com.google.turbine.model.TurbineFlag; |
| import com.google.turbine.model.TurbineTyKind; |
| import com.google.turbine.type.AnnoInfo; |
| import com.google.turbine.type.Type; |
| import com.google.turbine.type.Type.ClassTy; |
| import com.google.turbine.type.Type.IntersectionTy; |
| import java.lang.annotation.RetentionPolicy; |
| import java.util.Map; |
| import java.util.function.Function; |
| import org.jspecify.nullness.Nullable; |
| |
| /** |
| * A bound class backed by a class file. |
| * |
| * <p>Implements all of the phase-specific bound class interfaces, and lazily fills in data from the |
| * classfile needed to implement them. This is safe because the types in bytecode are already fully |
| * resolved and canonicalized so there are no cycles. The laziness also minimizes the amount of work |
| * done on the classpath. |
| */ |
| public class BytecodeBoundClass implements TypeBoundClass { |
| |
| private final ClassSymbol sym; |
| private final Env<ClassSymbol, BytecodeBoundClass> env; |
| private final Supplier<ClassFile> classFile; |
| private final @Nullable String jarFile; |
| |
| public BytecodeBoundClass( |
| ClassSymbol sym, |
| Supplier<byte[]> bytes, |
| Env<ClassSymbol, BytecodeBoundClass> env, |
| @Nullable String jarFile) { |
| this.sym = sym; |
| this.env = env; |
| this.jarFile = jarFile; |
| this.classFile = |
| Suppliers.memoize( |
| new Supplier<ClassFile>() { |
| @Override |
| public ClassFile get() { |
| ClassFile cf = ClassReader.read(jarFile + "!" + sym.binaryName(), bytes.get()); |
| verify( |
| cf.name().equals(sym.binaryName()), |
| "expected class data for %s, saw %s instead", |
| sym.binaryName(), |
| cf.name()); |
| return cf; |
| } |
| }); |
| } |
| |
| private final Supplier<TurbineTyKind> kind = |
| Suppliers.memoize( |
| new Supplier<TurbineTyKind>() { |
| @Override |
| public TurbineTyKind get() { |
| int access = access(); |
| if ((access & TurbineFlag.ACC_ANNOTATION) == TurbineFlag.ACC_ANNOTATION) { |
| return TurbineTyKind.ANNOTATION; |
| } |
| if ((access & TurbineFlag.ACC_INTERFACE) == TurbineFlag.ACC_INTERFACE) { |
| return TurbineTyKind.INTERFACE; |
| } |
| if ((access & TurbineFlag.ACC_ENUM) == TurbineFlag.ACC_ENUM) { |
| return TurbineTyKind.ENUM; |
| } |
| return TurbineTyKind.CLASS; |
| } |
| }); |
| |
| @Override |
| public TurbineTyKind kind() { |
| return kind.get(); |
| } |
| |
| private final Supplier<@Nullable ClassSymbol> owner = |
| Suppliers.memoize( |
| new Supplier<@Nullable ClassSymbol>() { |
| @Override |
| public @Nullable ClassSymbol get() { |
| for (ClassFile.InnerClass inner : classFile.get().innerClasses()) { |
| if (sym.binaryName().equals(inner.innerClass())) { |
| return new ClassSymbol(inner.outerClass()); |
| } |
| } |
| return null; |
| } |
| }); |
| |
| @Override |
| public @Nullable ClassSymbol owner() { |
| return owner.get(); |
| } |
| |
| private final Supplier<ImmutableMap<String, ClassSymbol>> children = |
| Suppliers.memoize( |
| new Supplier<ImmutableMap<String, ClassSymbol>>() { |
| @Override |
| public ImmutableMap<String, ClassSymbol> get() { |
| ImmutableMap.Builder<String, ClassSymbol> result = ImmutableMap.builder(); |
| for (ClassFile.InnerClass inner : classFile.get().innerClasses()) { |
| if (inner.innerName() == null) { |
| // anonymous class |
| continue; |
| } |
| if (sym.binaryName().equals(inner.outerClass())) { |
| result.put(inner.innerName(), new ClassSymbol(inner.innerClass())); |
| } |
| } |
| return result.build(); |
| } |
| }); |
| |
| @Override |
| public ImmutableMap<String, ClassSymbol> children() { |
| return children.get(); |
| } |
| |
| private final Supplier<Integer> access = |
| Suppliers.memoize( |
| new Supplier<Integer>() { |
| @Override |
| public Integer get() { |
| int access = classFile.get().access(); |
| for (ClassFile.InnerClass inner : classFile.get().innerClasses()) { |
| if (sym.binaryName().equals(inner.innerClass())) { |
| access = inner.access(); |
| } |
| } |
| return access; |
| } |
| }); |
| |
| @Override |
| public int access() { |
| return access.get(); |
| } |
| |
| private final Supplier<@Nullable ClassSig> sig = |
| Suppliers.memoize( |
| new Supplier<@Nullable ClassSig>() { |
| @Override |
| public @Nullable ClassSig get() { |
| String signature = classFile.get().signature(); |
| if (signature == null) { |
| return null; |
| } |
| return new SigParser(signature).parseClassSig(); |
| } |
| }); |
| |
| private final Supplier<ImmutableMap<String, TyVarSymbol>> tyParams = |
| Suppliers.memoize( |
| new Supplier<ImmutableMap<String, TyVarSymbol>>() { |
| @Override |
| public ImmutableMap<String, TyVarSymbol> get() { |
| ClassSig csig = sig.get(); |
| if (csig == null || csig.tyParams().isEmpty()) { |
| return ImmutableMap.of(); |
| } |
| ImmutableMap.Builder<String, TyVarSymbol> result = ImmutableMap.builder(); |
| for (Sig.TyParamSig p : csig.tyParams()) { |
| result.put(p.name(), new TyVarSymbol(sym, p.name())); |
| } |
| return result.build(); |
| } |
| }); |
| |
| @Override |
| public ImmutableMap<String, TyVarSymbol> typeParameters() { |
| return tyParams.get(); |
| } |
| |
| private final Supplier<@Nullable ClassSymbol> superclass = |
| Suppliers.memoize( |
| new Supplier<@Nullable ClassSymbol>() { |
| @Override |
| public @Nullable ClassSymbol get() { |
| String superclass = classFile.get().superName(); |
| if (superclass == null) { |
| return null; |
| } |
| return new ClassSymbol(superclass); |
| } |
| }); |
| |
| @Override |
| public @Nullable ClassSymbol superclass() { |
| return superclass.get(); |
| } |
| |
| private final Supplier<ImmutableList<ClassSymbol>> interfaces = |
| Suppliers.memoize( |
| new Supplier<ImmutableList<ClassSymbol>>() { |
| @Override |
| public ImmutableList<ClassSymbol> get() { |
| ImmutableList.Builder<ClassSymbol> result = ImmutableList.builder(); |
| for (String i : classFile.get().interfaces()) { |
| result.add(new ClassSymbol(i)); |
| } |
| return result.build(); |
| } |
| }); |
| |
| @Override |
| public ImmutableList<ClassSymbol> interfaces() { |
| return interfaces.get(); |
| } |
| |
| private final Supplier<@Nullable ClassTy> superClassType = |
| Suppliers.memoize( |
| new Supplier<@Nullable ClassTy>() { |
| @Override |
| public @Nullable ClassTy get() { |
| if (superclass() == null) { |
| return null; |
| } |
| if (sig.get() == null || sig.get().superClass() == null) { |
| return ClassTy.asNonParametricClassTy(superclass()); |
| } |
| return BytecodeBinder.bindClassTy( |
| sig.get().superClass(), makeScope(env, sym, ImmutableMap.of())); |
| } |
| }); |
| |
| @Override |
| public @Nullable ClassTy superClassType() { |
| return superClassType.get(); |
| } |
| |
| private final Supplier<ImmutableList<Type>> interfaceTypes = |
| Suppliers.memoize( |
| new Supplier<ImmutableList<Type>>() { |
| @Override |
| public ImmutableList<Type> get() { |
| if (interfaces().isEmpty()) { |
| return ImmutableList.of(); |
| } |
| ImmutableList.Builder<Type> result = ImmutableList.builder(); |
| if (sig.get() == null || sig.get().interfaces() == null) { |
| for (ClassSymbol sym : interfaces()) { |
| result.add(ClassTy.asNonParametricClassTy(sym)); |
| } |
| } else { |
| Function<String, TyVarSymbol> scope = makeScope(env, sym, ImmutableMap.of()); |
| for (ClassTySig classTySig : sig.get().interfaces()) { |
| result.add(BytecodeBinder.bindClassTy(classTySig, scope)); |
| } |
| } |
| return result.build(); |
| } |
| }); |
| |
| @Override |
| public ImmutableList<Type> interfaceTypes() { |
| return interfaceTypes.get(); |
| } |
| |
| private final Supplier<ImmutableMap<TyVarSymbol, TyVarInfo>> typeParameterTypes = |
| Suppliers.memoize( |
| new Supplier<ImmutableMap<TyVarSymbol, TyVarInfo>>() { |
| @Override |
| public ImmutableMap<TyVarSymbol, TyVarInfo> get() { |
| if (sig.get() == null) { |
| return ImmutableMap.of(); |
| } |
| ImmutableMap.Builder<TyVarSymbol, TyVarInfo> tparams = ImmutableMap.builder(); |
| Function<String, TyVarSymbol> scope = makeScope(env, sym, typeParameters()); |
| for (Sig.TyParamSig p : sig.get().tyParams()) { |
| // typeParameters() is constructed to guarantee the requireNonNull call is safe. |
| tparams.put(requireNonNull(typeParameters().get(p.name())), bindTyParam(p, scope)); |
| } |
| return tparams.build(); |
| } |
| }); |
| |
| private static TyVarInfo bindTyParam(Sig.TyParamSig sig, Function<String, TyVarSymbol> scope) { |
| ImmutableList.Builder<Type> bounds = ImmutableList.builder(); |
| if (sig.classBound() != null) { |
| bounds.add(BytecodeBinder.bindTy(sig.classBound(), scope)); |
| } |
| for (Sig.TySig t : sig.interfaceBounds()) { |
| bounds.add(BytecodeBinder.bindTy(t, scope)); |
| } |
| return new TyVarInfo( |
| IntersectionTy.create(bounds.build()), /* lowerBound= */ null, ImmutableList.of()); |
| } |
| |
| @Override |
| public ImmutableMap<TyVarSymbol, TyVarInfo> typeParameterTypes() { |
| return typeParameterTypes.get(); |
| } |
| |
| private final Supplier<ImmutableList<FieldInfo>> fields = |
| Suppliers.memoize( |
| new Supplier<ImmutableList<FieldInfo>>() { |
| @Override |
| public ImmutableList<FieldInfo> get() { |
| ImmutableList.Builder<FieldInfo> fields = ImmutableList.builder(); |
| for (ClassFile.FieldInfo cfi : classFile.get().fields()) { |
| FieldSymbol fieldSym = new FieldSymbol(sym, cfi.name()); |
| Type type = |
| BytecodeBinder.bindTy( |
| new SigParser(firstNonNull(cfi.signature(), cfi.descriptor())).parseType(), |
| makeScope(env, sym, ImmutableMap.of())); |
| int access = cfi.access(); |
| Const.Value value = cfi.value(); |
| if (value != null) { |
| value = BytecodeBinder.bindConstValue(type, value); |
| } |
| ImmutableList<AnnoInfo> annotations = |
| BytecodeBinder.bindAnnotations(cfi.annotations()); |
| fields.add( |
| new FieldInfo(fieldSym, type, access, annotations, /* decl= */ null, value)); |
| } |
| return fields.build(); |
| } |
| }); |
| |
| @Override |
| public ImmutableList<FieldInfo> fields() { |
| return fields.get(); |
| } |
| |
| private final Supplier<ImmutableList<MethodInfo>> methods = |
| Suppliers.memoize( |
| new Supplier<ImmutableList<MethodInfo>>() { |
| @Override |
| public ImmutableList<MethodInfo> get() { |
| ImmutableList.Builder<MethodInfo> methods = ImmutableList.builder(); |
| int idx = 0; |
| ClassFile cf = classFile.get(); |
| for (ClassFile.MethodInfo m : cf.methods()) { |
| if (m.name().equals("<clinit>")) { |
| // Don't bother reading class initializers, which we don't need |
| continue; |
| } |
| methods.add(bindMethod(cf, idx++, m)); |
| } |
| return methods.build(); |
| } |
| }); |
| |
| private MethodInfo bindMethod(ClassFile classFile, int methodIdx, ClassFile.MethodInfo m) { |
| MethodSymbol methodSymbol = new MethodSymbol(methodIdx, sym, m.name()); |
| Sig.MethodSig sig = new SigParser(firstNonNull(m.signature(), m.descriptor())).parseMethodSig(); |
| |
| ImmutableMap<String, TyVarSymbol> tyParams; |
| { |
| ImmutableMap.Builder<String, TyVarSymbol> result = ImmutableMap.builder(); |
| for (Sig.TyParamSig p : sig.tyParams()) { |
| result.put(p.name(), new TyVarSymbol(methodSymbol, p.name())); |
| } |
| tyParams = result.build(); |
| } |
| |
| ImmutableMap<TyVarSymbol, TyVarInfo> tyParamTypes; |
| { |
| ImmutableMap.Builder<TyVarSymbol, TyVarInfo> tparams = ImmutableMap.builder(); |
| Function<String, TyVarSymbol> scope = makeScope(env, sym, tyParams); |
| for (Sig.TyParamSig p : sig.tyParams()) { |
| // tyParams is constructed to guarantee the requireNonNull call is safe. |
| tparams.put(requireNonNull(tyParams.get(p.name())), bindTyParam(p, scope)); |
| } |
| tyParamTypes = tparams.build(); |
| } |
| |
| Function<String, TyVarSymbol> scope = makeScope(env, sym, tyParams); |
| |
| Type ret = BytecodeBinder.bindTy(sig.returnType(), scope); |
| |
| ImmutableList.Builder<ParamInfo> formals = ImmutableList.builder(); |
| int idx = 0; |
| for (Sig.TySig tySig : sig.params()) { |
| String name; |
| int access = 0; |
| if (idx < m.parameters().size()) { |
| ParameterInfo paramInfo = m.parameters().get(idx); |
| name = paramInfo.name(); |
| // ignore parameter modifiers for bug-parity with javac: |
| // https://bugs.openjdk.java.net/browse/JDK-8226216 |
| // access = paramInfo.access(); |
| } else { |
| name = "arg" + idx; |
| } |
| ImmutableList<AnnoInfo> annotations = |
| (idx < m.parameterAnnotations().size()) |
| ? BytecodeBinder.bindAnnotations(m.parameterAnnotations().get(idx)) |
| : ImmutableList.of(); |
| formals.add( |
| new ParamInfo( |
| new ParamSymbol(methodSymbol, name), |
| BytecodeBinder.bindTy(tySig, scope), |
| annotations, |
| access)); |
| idx++; |
| } |
| |
| ImmutableList.Builder<Type> exceptions = ImmutableList.builder(); |
| if (!sig.exceptions().isEmpty()) { |
| for (TySig e : sig.exceptions()) { |
| exceptions.add(BytecodeBinder.bindTy(e, scope)); |
| } |
| } else { |
| for (String e : m.exceptions()) { |
| exceptions.add(ClassTy.asNonParametricClassTy(new ClassSymbol(e))); |
| } |
| } |
| |
| Const defaultValue = |
| m.defaultValue() != null ? BytecodeBinder.bindValue(m.defaultValue()) : null; |
| |
| ImmutableList<AnnoInfo> annotations = BytecodeBinder.bindAnnotations(m.annotations()); |
| |
| int access = m.access(); |
| if (((classFile.access() & TurbineFlag.ACC_INTERFACE) == TurbineFlag.ACC_INTERFACE) |
| && (access & (TurbineFlag.ACC_ABSTRACT | TurbineFlag.ACC_STATIC)) == 0) { |
| access |= TurbineFlag.ACC_DEFAULT; |
| } |
| |
| return new MethodInfo( |
| methodSymbol, |
| tyParamTypes, |
| ret, |
| formals.build(), |
| exceptions.build(), |
| access, |
| defaultValue, |
| /* decl= */ null, |
| annotations, |
| /* receiver= */ null); |
| } |
| |
| @Override |
| public ImmutableList<MethodInfo> methods() { |
| return methods.get(); |
| } |
| |
| private final Supplier<@Nullable AnnotationMetadata> annotationMetadata = |
| Suppliers.memoize( |
| new Supplier<@Nullable AnnotationMetadata>() { |
| @Override |
| public @Nullable AnnotationMetadata get() { |
| if ((access() & TurbineFlag.ACC_ANNOTATION) != TurbineFlag.ACC_ANNOTATION) { |
| return null; |
| } |
| RetentionPolicy retention = null; |
| ImmutableSet<TurbineElementType> target = null; |
| ClassSymbol repeatable = null; |
| for (ClassFile.AnnotationInfo annotation : classFile.get().annotations()) { |
| switch (annotation.typeName()) { |
| case "Ljava/lang/annotation/Retention;": |
| retention = bindRetention(annotation); |
| break; |
| case "Ljava/lang/annotation/Target;": |
| target = bindTarget(annotation); |
| break; |
| case "Ljava/lang/annotation/Repeatable;": |
| repeatable = bindRepeatable(annotation); |
| break; |
| default: |
| break; |
| } |
| } |
| return new AnnotationMetadata(retention, target, repeatable); |
| } |
| }); |
| |
| private static @Nullable RetentionPolicy bindRetention(AnnotationInfo annotation) { |
| ElementValue val = annotation.elementValuePairs().get("value"); |
| if (val == null) { |
| return null; |
| } |
| if (val.kind() != Kind.ENUM) { |
| return null; |
| } |
| EnumConstValue enumVal = (EnumConstValue) val; |
| if (!enumVal.typeName().equals("Ljava/lang/annotation/RetentionPolicy;")) { |
| return null; |
| } |
| return RetentionPolicy.valueOf(enumVal.constName()); |
| } |
| |
| private static ImmutableSet<TurbineElementType> bindTarget(AnnotationInfo annotation) { |
| ImmutableSet.Builder<TurbineElementType> result = ImmutableSet.builder(); |
| ElementValue val = annotation.elementValuePairs().get("value"); |
| requireNonNull(val); |
| switch (val.kind()) { |
| case ARRAY: |
| for (ElementValue element : ((ArrayValue) val).elements()) { |
| if (element.kind() == Kind.ENUM) { |
| bindTargetElement(result, (EnumConstValue) element); |
| } |
| } |
| break; |
| case ENUM: |
| bindTargetElement(result, (EnumConstValue) val); |
| break; |
| default: |
| break; |
| } |
| return result.build(); |
| } |
| |
| private static void bindTargetElement( |
| ImmutableSet.Builder<TurbineElementType> target, EnumConstValue enumVal) { |
| if (enumVal.typeName().equals("Ljava/lang/annotation/ElementType;")) { |
| target.add(TurbineElementType.valueOf(enumVal.constName())); |
| } |
| } |
| |
| private static @Nullable ClassSymbol bindRepeatable(AnnotationInfo annotation) { |
| ElementValue val = annotation.elementValuePairs().get("value"); |
| if (val == null) { |
| return null; |
| } |
| switch (val.kind()) { |
| case CLASS: |
| String className = ((ConstTurbineClassValue) val).className(); |
| return new ClassSymbol(className.substring(1, className.length() - 1)); |
| default: |
| break; |
| } |
| return null; |
| } |
| |
| @Override |
| public @Nullable AnnotationMetadata annotationMetadata() { |
| return annotationMetadata.get(); |
| } |
| |
| private final Supplier<ImmutableList<AnnoInfo>> annotations = |
| Suppliers.memoize( |
| new Supplier<ImmutableList<AnnoInfo>>() { |
| @Override |
| public ImmutableList<AnnoInfo> get() { |
| return BytecodeBinder.bindAnnotations(classFile.get().annotations()); |
| } |
| }); |
| |
| @Override |
| public ImmutableList<AnnoInfo> annotations() { |
| return annotations.get(); |
| } |
| |
| /** |
| * Create a scope for resolving type variable symbols declared in the class, and any enclosing |
| * instances. |
| */ |
| private static Function<String, TyVarSymbol> makeScope( |
| final Env<ClassSymbol, BytecodeBoundClass> env, |
| final ClassSymbol sym, |
| final Map<String, TyVarSymbol> typeVariables) { |
| return new Function<String, TyVarSymbol>() { |
| @Override |
| public TyVarSymbol apply(String input) { |
| TyVarSymbol result = typeVariables.get(input); |
| if (result != null) { |
| return result; |
| } |
| ClassSymbol curr = sym; |
| while (curr != null) { |
| BytecodeBoundClass info = env.get(curr); |
| if (info == null) { |
| throw new AssertionError(curr); |
| } |
| result = info.typeParameters().get(input); |
| if (result != null) { |
| return result; |
| } |
| curr = info.owner(); |
| } |
| throw new AssertionError(input); |
| } |
| }; |
| } |
| |
| /** The jar file the symbol was loaded from. */ |
| public @Nullable String jarFile() { |
| String transitiveJar = classFile.get().transitiveJar(); |
| if (transitiveJar != null) { |
| return transitiveJar; |
| } |
| return jarFile; |
| } |
| |
| /** The class file the symbol was loaded from. */ |
| public ClassFile classFile() { |
| return classFile.get(); |
| } |
| } |