/* * 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.lower; import static com.google.common.collect.Iterables.getOnlyElement; import static com.google.turbine.binder.DisambiguateTypeAnnotations.groupRepeated; import com.google.common.base.Function; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import com.google.turbine.binder.bound.EnumConstantValue; import com.google.turbine.binder.bound.ModuleInfo.ExportInfo; import com.google.turbine.binder.bound.ModuleInfo.OpenInfo; import com.google.turbine.binder.bound.ModuleInfo.ProvideInfo; import com.google.turbine.binder.bound.ModuleInfo.RequireInfo; import com.google.turbine.binder.bound.ModuleInfo.UseInfo; import com.google.turbine.binder.bound.SourceModuleInfo; import com.google.turbine.binder.bound.SourceTypeBoundClass; import com.google.turbine.binder.bound.TurbineAnnotationValue; import com.google.turbine.binder.bound.TurbineClassValue; import com.google.turbine.binder.bound.TypeBoundClass; import com.google.turbine.binder.bound.TypeBoundClass.FieldInfo; import com.google.turbine.binder.bound.TypeBoundClass.MethodInfo; import com.google.turbine.binder.bound.TypeBoundClass.ParamInfo; import com.google.turbine.binder.bound.TypeBoundClass.TyVarInfo; import com.google.turbine.binder.bytecode.BytecodeBoundClass; import com.google.turbine.binder.env.CompoundEnv; import com.google.turbine.binder.env.Env; import com.google.turbine.binder.env.SimpleEnv; import com.google.turbine.binder.sym.ClassSymbol; import com.google.turbine.binder.sym.Symbol; 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.MethodInfo.ParameterInfo; import com.google.turbine.bytecode.ClassFile.TypeAnnotationInfo; import com.google.turbine.bytecode.ClassFile.TypeAnnotationInfo.Target; import com.google.turbine.bytecode.ClassFile.TypeAnnotationInfo.TargetType; import com.google.turbine.bytecode.ClassFile.TypeAnnotationInfo.ThrowsTarget; import com.google.turbine.bytecode.ClassFile.TypeAnnotationInfo.TypePath; import com.google.turbine.bytecode.ClassWriter; import com.google.turbine.bytecode.sig.Sig; import com.google.turbine.bytecode.sig.Sig.MethodSig; import com.google.turbine.bytecode.sig.Sig.TySig; import com.google.turbine.bytecode.sig.SigWriter; import com.google.turbine.diag.SourceFile; import com.google.turbine.diag.TurbineError; import com.google.turbine.diag.TurbineError.ErrorKind; import com.google.turbine.model.Const; import com.google.turbine.model.TurbineFlag; import com.google.turbine.model.TurbineTyKind; import com.google.turbine.model.TurbineVisibility; import com.google.turbine.type.AnnoInfo; import com.google.turbine.type.Type; import com.google.turbine.type.Type.ArrayTy; import com.google.turbine.type.Type.ClassTy; import com.google.turbine.type.Type.ClassTy.SimpleClassTy; import com.google.turbine.type.Type.TyKind; import com.google.turbine.type.Type.TyVar; import com.google.turbine.type.Type.WildTy; import com.google.turbine.types.Erasure; import java.lang.annotation.RetentionPolicy; import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Deque; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Set; import org.checkerframework.checker.nullness.qual.Nullable; /** Lowering from bound classes to bytecode. */ public class Lower { /** The lowered compilation output. */ public static class Lowered { private final ImmutableMap bytes; private final ImmutableSet symbols; public Lowered(ImmutableMap bytes, ImmutableSet symbols) { this.bytes = bytes; this.symbols = symbols; } /** Returns the bytecode for classes in the compilation. */ public ImmutableMap bytes() { return bytes; } /** Returns the set of all referenced symbols in the compilation. */ public ImmutableSet symbols() { return symbols; } } /** Lowers all given classes to bytecode. */ public static Lowered lowerAll( ImmutableMap units, ImmutableList modules, Env classpath) { CompoundEnv env = CompoundEnv.of(classpath).append(new SimpleEnv<>(units)); ImmutableMap.Builder result = ImmutableMap.builder(); Set symbols = new LinkedHashSet<>(); for (ClassSymbol sym : units.keySet()) { result.put(sym.binaryName(), lower(units.get(sym), env, sym, symbols)); } if (modules.size() == 1) { // single module mode: the module-info.class file is at the root result.put("module-info", lower(getOnlyElement(modules), env, symbols)); } else { // multi-module mode: the output module-info.class are in a directory corresponding to their // package for (SourceModuleInfo module : modules) { result.put(module.name().replace('.', '/') + "/module-info", lower(module, env, symbols)); } } return new Lowered(result.build(), ImmutableSet.copyOf(symbols)); } /** Lowers a class to bytecode. */ public static byte[] lower( SourceTypeBoundClass info, Env env, ClassSymbol sym, Set symbols) { return new Lower(env).lower(info, sym, symbols); } private static byte[] lower( SourceModuleInfo module, CompoundEnv env, Set symbols) { return new Lower(env).lower(module, symbols); } private final LowerSignature sig = new LowerSignature(); private final Env env; public Lower(Env env) { this.env = env; } private byte[] lower(SourceModuleInfo module, Set symbols) { String name = "module-info"; ImmutableList annotations = lowerAnnotations(module.annos()); ClassFile.ModuleInfo moduleInfo = lowerModule(module); ImmutableList.Builder innerClasses = ImmutableList.builder(); { Set all = new LinkedHashSet<>(); for (ClassSymbol sym : sig.classes) { addEnclosing(module.source(), env, all, sym); } for (ClassSymbol innerSym : all) { innerClasses.add(innerClass(env, innerSym)); } } ClassFile classfile = new ClassFile( /* access= */ TurbineFlag.ACC_MODULE, name, /* signature= */ null, /* superClass= */ null, /* interfaces= */ ImmutableList.of(), /* methods= */ ImmutableList.of(), /* fields= */ ImmutableList.of(), annotations, innerClasses.build(), /* typeAnnotations= */ ImmutableList.of(), moduleInfo); symbols.addAll(sig.classes); return ClassWriter.writeClass(classfile); } private ClassFile.ModuleInfo lowerModule(SourceModuleInfo module) { ImmutableList.Builder requires = ImmutableList.builder(); for (RequireInfo require : module.requires()) { requires.add( new ClassFile.ModuleInfo.RequireInfo( require.moduleName(), require.flags(), require.version())); } ImmutableList.Builder exports = ImmutableList.builder(); for (ExportInfo export : module.exports()) { int exportAccess = 0; // not synthetic or mandated exports.add( new ClassFile.ModuleInfo.ExportInfo( export.packageName(), exportAccess, export.modules())); } ImmutableList.Builder opens = ImmutableList.builder(); for (OpenInfo open : module.opens()) { int openAccess = 0; // not synthetic or mandated opens.add(new ClassFile.ModuleInfo.OpenInfo(open.packageName(), openAccess, open.modules())); } ImmutableList.Builder uses = ImmutableList.builder(); for (UseInfo use : module.uses()) { uses.add(new ClassFile.ModuleInfo.UseInfo(sig.descriptor(use.sym()))); } ImmutableList.Builder provides = ImmutableList.builder(); for (ProvideInfo provide : module.provides()) { ImmutableList.Builder impls = ImmutableList.builder(); for (ClassSymbol impl : provide.impls()) { impls.add(sig.descriptor(impl)); } provides.add( new ClassFile.ModuleInfo.ProvideInfo(sig.descriptor(provide.sym()), impls.build())); } return new ClassFile.ModuleInfo( module.name(), module.flags(), module.version(), requires.build(), exports.build(), opens.build(), uses.build(), provides.build()); } private byte[] lower(SourceTypeBoundClass info, ClassSymbol sym, Set symbols) { int access = classAccess(info); String name = sig.descriptor(sym); String signature = sig.classSignature(info, env); String superName = info.superclass() != null ? sig.descriptor(info.superclass()) : null; List interfaces = new ArrayList<>(); for (ClassSymbol i : info.interfaces()) { interfaces.add(sig.descriptor(i)); } List methods = new ArrayList<>(); for (MethodInfo m : info.methods()) { if (TurbineVisibility.fromAccess(m.access()) == TurbineVisibility.PRIVATE) { // TODO(cushon): drop private members earlier? continue; } methods.add(lowerMethod(m, sym)); } ImmutableList.Builder fields = ImmutableList.builder(); for (FieldInfo f : info.fields()) { if ((f.access() & TurbineFlag.ACC_PRIVATE) == TurbineFlag.ACC_PRIVATE) { // TODO(cushon): drop private members earlier? continue; } fields.add(lowerField(f)); } ImmutableList annotations = lowerAnnotations(info.annotations()); ImmutableList typeAnnotations = classTypeAnnotations(info); ImmutableList inners = collectInnerClasses(info.source(), sym, info); ClassFile classfile = new ClassFile( access, name, signature, superName, interfaces, methods, fields.build(), annotations, inners, typeAnnotations, /* module= */ null); symbols.addAll(sig.classes); return ClassWriter.writeClass(classfile); } private ClassFile.MethodInfo lowerMethod(final MethodInfo m, final ClassSymbol sym) { int access = m.access(); Function tenv = new TyVarEnv(m.tyParams()); String name = m.name(); String desc = methodDescriptor(m, tenv); String signature = sig.methodSignature(env, m, sym); ImmutableList.Builder exceptions = ImmutableList.builder(); if (!m.exceptions().isEmpty()) { for (Type e : m.exceptions()) { exceptions.add(sig.descriptor(((ClassTy) Erasure.erase(e, tenv)).sym())); } } ElementValue defaultValue = m.defaultValue() != null ? annotationValue(m.defaultValue()) : null; ImmutableList annotations = lowerAnnotations(m.annotations()); ImmutableList> paramAnnotations = parameterAnnotations(m); ImmutableList typeAnnotations = methodTypeAnnotations(m); ImmutableList parameters = methodParameters(m); return new ClassFile.MethodInfo( access, name, desc, signature, exceptions.build(), defaultValue, annotations, paramAnnotations, typeAnnotations, parameters); } private ImmutableList methodParameters(MethodInfo m) { ImmutableList.Builder result = ImmutableList.builder(); for (ParamInfo p : m.parameters()) { result.add(new ParameterInfo(p.name(), p.access() & PARAMETER_ACCESS_MASK)); } return result.build(); } private static final int PARAMETER_ACCESS_MASK = TurbineFlag.ACC_MANDATED | TurbineFlag.ACC_FINAL | TurbineFlag.ACC_SYNTHETIC; private ImmutableList> parameterAnnotations(MethodInfo m) { ImmutableList.Builder> annotations = ImmutableList.builder(); for (ParamInfo parameter : m.parameters()) { if (parameter.synthetic()) { continue; } if (parameter.annotations().isEmpty()) { annotations.add(ImmutableList.of()); continue; } ImmutableList.Builder parameterAnnotations = ImmutableList.builder(); for (AnnoInfo annotation : parameter.annotations()) { Boolean visible = isVisible(annotation.sym()); if (visible == null) { continue; } String desc = sig.objectType(annotation.sym()); parameterAnnotations.add( new AnnotationInfo(desc, visible, annotationValues(annotation.values()))); } annotations.add(parameterAnnotations.build()); } return annotations.build(); } private String methodDescriptor(MethodInfo m, Function tenv) { ImmutableList typarams = ImmutableList.of(); ImmutableList.Builder fparams = ImmutableList.builder(); for (ParamInfo t : m.parameters()) { fparams.add(sig.signature(Erasure.erase(t.type(), tenv))); } TySig result = sig.signature(Erasure.erase(m.returnType(), tenv)); ImmutableList excns = ImmutableList.of(); return SigWriter.method(new MethodSig(typarams, fparams.build(), result, excns)); } private ClassFile.FieldInfo lowerField(FieldInfo f) { final String name = f.name(); Function tenv = new TyVarEnv(ImmutableMap.of()); String desc = SigWriter.type(sig.signature(Erasure.erase(f.type(), tenv))); String signature = sig.fieldSignature(f.type()); ImmutableList annotations = lowerAnnotations(f.annotations()); ImmutableList.Builder typeAnnotations = ImmutableList.builder(); lowerTypeAnnotations( typeAnnotations, f.type(), TargetType.FIELD, TypeAnnotationInfo.EMPTY_TARGET); return new ClassFile.FieldInfo( f.access(), name, desc, signature, f.value(), annotations, typeAnnotations.build()); } /** Creates inner class attributes for all referenced inner classes. */ private ImmutableList collectInnerClasses( SourceFile source, ClassSymbol origin, SourceTypeBoundClass info) { Set all = new LinkedHashSet<>(); addEnclosing(source, env, all, origin); for (ClassSymbol sym : info.children().values()) { addEnclosing(source, env, all, sym); } for (ClassSymbol sym : sig.classes) { addEnclosing(source, env, all, sym); } ImmutableList.Builder inners = ImmutableList.builder(); for (ClassSymbol innerSym : all) { inners.add(innerClass(env, innerSym)); } return inners.build(); } /** * Record all enclosing declarations of a symbol, to make sure the necessary InnerClass attributes * are added. * *

javac expects InnerClass attributes for enclosing classes to appear before their member * classes' entries. */ private void addEnclosing( SourceFile source, Env env, Set all, ClassSymbol sym) { TypeBoundClass info = env.get(sym); if (info == null) { throw TurbineError.format(source, ErrorKind.CLASS_FILE_NOT_FOUND, sym); } ClassSymbol owner = env.get(sym).owner(); if (owner != null) { addEnclosing(source, env, all, owner); all.add(sym); } } /** * Creates an inner class attribute, given an inner class that was referenced somewhere in the * class. */ private ClassFile.InnerClass innerClass( Env env, ClassSymbol innerSym) { TypeBoundClass inner = env.get(innerSym); String innerName = innerSym.binaryName().substring(inner.owner().binaryName().length() + 1); int access = inner.access(); access &= ~(TurbineFlag.ACC_SUPER | TurbineFlag.ACC_STRICT); return new ClassFile.InnerClass( innerSym.binaryName(), inner.owner().binaryName(), innerName, access); } /** Updates visibility, and unsets access bits that can only be set in InnerClass. */ private int classAccess(SourceTypeBoundClass info) { int access = info.access(); access &= ~(TurbineFlag.ACC_STATIC | TurbineFlag.ACC_PRIVATE | TurbineFlag.ACC_STRICT); if ((access & TurbineFlag.ACC_PROTECTED) != 0) { access &= ~TurbineFlag.ACC_PROTECTED; access |= TurbineFlag.ACC_PUBLIC; } return access; } /** * Looks up {@link TyVarInfo}s. * *

We could generalize {@link com.google.turbine.binder.lookup.Scope} instead, but this isn't * needed anywhere else. */ class TyVarEnv implements Function { private final Map tyParams; /** @param tyParams the initial lookup scope, e.g. a method's formal type parameters. */ public TyVarEnv(Map tyParams) { this.tyParams = tyParams; } @Override public TyVarInfo apply(TyVarSymbol sym) { TyVarInfo result = tyParams.get(sym); if (result != null) { return result; } // type variables can only be declared by methods and classes, // and we've already handled methods Symbol ownerSym = sym.owner(); if (ownerSym.symKind() != Symbol.Kind.CLASS) { throw new AssertionError(sym); } // anything that lexically encloses the class being lowered // must be in the same compilation unit, so we have source // information for it TypeBoundClass owner = env.get((ClassSymbol) ownerSym); return owner.typeParameterTypes().get(sym); } } private ImmutableList lowerAnnotations(ImmutableList annotations) { ImmutableList.Builder lowered = ImmutableList.builder(); for (AnnoInfo annotation : annotations) { AnnotationInfo anno = lowerAnnotation(annotation); if (anno == null) { continue; } lowered.add(anno); } return lowered.build(); } private AnnotationInfo lowerAnnotation(AnnoInfo annotation) { Boolean visible = isVisible(annotation.sym()); if (visible == null) { return null; } return new AnnotationInfo( sig.objectType(annotation.sym()), visible, annotationValues(annotation.values())); } /** * Returns true if the annotation is visible at runtime, false if it is not visible at runtime, * and {@code null} if it should not be retained in bytecode. */ @Nullable private Boolean isVisible(ClassSymbol sym) { RetentionPolicy retention = env.get(sym).annotationMetadata().retention(); switch (retention) { case CLASS: return false; case RUNTIME: return true; case SOURCE: return null; } throw new AssertionError(retention); } private ImmutableMap annotationValues(ImmutableMap values) { ImmutableMap.Builder result = ImmutableMap.builder(); for (Map.Entry entry : values.entrySet()) { result.put(entry.getKey(), annotationValue(entry.getValue())); } return result.build(); } private ElementValue annotationValue(Const value) { switch (value.kind()) { case CLASS_LITERAL: { TurbineClassValue classValue = (TurbineClassValue) value; return new ElementValue.ConstTurbineClassValue( SigWriter.type(sig.signature(classValue.type()))); } case ENUM_CONSTANT: { EnumConstantValue enumValue = (EnumConstantValue) value; return new ElementValue.EnumConstValue( sig.objectType(enumValue.sym().owner()), enumValue.sym().name()); } case ARRAY: { Const.ArrayInitValue arrayValue = (Const.ArrayInitValue) value; List values = new ArrayList<>(); for (Const element : arrayValue.elements()) { values.add(annotationValue(element)); } return new ElementValue.ArrayValue(values); } case ANNOTATION: { TurbineAnnotationValue annotationValue = (TurbineAnnotationValue) value; Boolean visible = isVisible(annotationValue.sym()); if (visible == null) { visible = true; } return new ElementValue.ConstTurbineAnnotationValue( new AnnotationInfo( sig.objectType(annotationValue.sym()), visible, annotationValues(annotationValue.values()))); } case PRIMITIVE: return new ElementValue.ConstValue((Const.Value) value); } throw new AssertionError(value.kind()); } /** Lower type annotations in a class declaration's signature. */ private ImmutableList classTypeAnnotations(SourceTypeBoundClass info) { ImmutableList.Builder result = ImmutableList.builder(); { if (info.superClassType() != null) { lowerTypeAnnotations( result, info.superClassType(), TargetType.SUPERTYPE, new TypeAnnotationInfo.SuperTypeTarget(-1)); } int idx = 0; for (Type i : info.interfaceTypes()) { lowerTypeAnnotations( result, i, TargetType.SUPERTYPE, new TypeAnnotationInfo.SuperTypeTarget(idx++)); } } typeParameterAnnotations( result, info.typeParameterTypes().values(), TargetType.CLASS_TYPE_PARAMETER, TargetType.CLASS_TYPE_PARAMETER_BOUND); return result.build(); } /** Lower type annotations in a method declaration's signature. */ private ImmutableList methodTypeAnnotations(MethodInfo m) { ImmutableList.Builder result = ImmutableList.builder(); typeParameterAnnotations( result, m.tyParams().values(), TargetType.METHOD_TYPE_PARAMETER, TargetType.METHOD_TYPE_PARAMETER_BOUND); { int idx = 0; for (Type e : m.exceptions()) { lowerTypeAnnotations(result, e, TargetType.METHOD_THROWS, new ThrowsTarget(idx++)); } } if (m.receiver() != null) { lowerTypeAnnotations( result, m.receiver().type(), TargetType.METHOD_RECEIVER_PARAMETER, TypeAnnotationInfo.EMPTY_TARGET); } lowerTypeAnnotations( result, m.returnType(), TargetType.METHOD_RETURN, TypeAnnotationInfo.EMPTY_TARGET); { int idx = 0; for (ParamInfo p : m.parameters()) { if (p.synthetic()) { continue; } lowerTypeAnnotations( result, p.type(), TargetType.METHOD_FORMAL_PARAMETER, new TypeAnnotationInfo.FormalParameterTarget(idx++)); } } return result.build(); } /** * Lower type annotations on class or method type parameters, either on the parameters themselves * or on bounds. */ private void typeParameterAnnotations( ImmutableList.Builder result, Iterable typeParameters, TargetType targetType, TargetType boundTargetType) { int typeParameterIndex = 0; for (TyVarInfo p : typeParameters) { for (AnnoInfo anno : groupRepeated(env, p.annotations())) { AnnotationInfo info = lowerAnnotation(anno); if (info == null) { continue; } result.add( new TypeAnnotationInfo( targetType, new TypeAnnotationInfo.TypeParameterTarget(typeParameterIndex), TypePath.root(), info)); } int boundIndex = 0; for (Type i : p.upperBound().bounds()) { if (boundIndex == 0 && isInterface(i, env)) { // super class bound index is always 0; interface bounds start at 1 boundIndex++; } lowerTypeAnnotations( result, i, boundTargetType, new TypeAnnotationInfo.TypeParameterBoundTarget(typeParameterIndex, boundIndex++)); } typeParameterIndex++; } } private boolean isInterface(Type type, Env env) { return type.tyKind() == TyKind.CLASS_TY && env.get(((ClassTy) type).sym()).kind() == TurbineTyKind.INTERFACE; } private void lowerTypeAnnotations( ImmutableList.Builder result, Type type, TargetType targetType, Target target) { new LowerTypeAnnotations(result, targetType, target) .lowerTypeAnnotations(type, TypePath.root()); } class LowerTypeAnnotations { private final ImmutableList.Builder result; private final TargetType targetType; private final Target target; public LowerTypeAnnotations( ImmutableList.Builder result, TargetType targetType, Target target) { this.result = result; this.targetType = targetType; this.target = target; } /** * Lower all type annotations present in a type. * *

Recursively descends into nested types, and accumulates a type path structure to locate * the annotation in the signature. */ private void lowerTypeAnnotations(Type type, TypePath path) { switch (type.tyKind()) { case TY_VAR: lowerTypeAnnotations(((TyVar) type).annos(), path); break; case CLASS_TY: lowerClassTypeTypeAnnotations((ClassTy) type, path); break; case ARRAY_TY: lowerArrayTypeAnnotations(type, path); break; case WILD_TY: lowerWildTyTypeAnnotations((WildTy) type, path); break; case PRIM_TY: lowerTypeAnnotations(((Type.PrimTy) type).annos(), path); break; case VOID_TY: break; default: throw new AssertionError(type.tyKind()); } } /** Lower a list of type annotations. */ private void lowerTypeAnnotations(ImmutableList annos, TypePath path) { for (AnnoInfo anno : groupRepeated(env, annos)) { AnnotationInfo info = lowerAnnotation(anno); if (info == null) { continue; } result.add(new TypeAnnotationInfo(targetType, target, path, info)); } } private void lowerWildTyTypeAnnotations(WildTy type, TypePath path) { switch (type.boundKind()) { case NONE: lowerTypeAnnotations(type.annotations(), path); break; case UPPER: case LOWER: lowerTypeAnnotations(type.annotations(), path); lowerTypeAnnotations(type.bound(), path.wild()); break; } } private void lowerArrayTypeAnnotations(Type type, TypePath path) { Type base = type; Deque flat = new ArrayDeque<>(); while (base instanceof ArrayTy) { ArrayTy arrayTy = (ArrayTy) base; flat.addFirst(arrayTy); base = arrayTy.elementType(); } for (ArrayTy arrayTy : flat) { lowerTypeAnnotations(arrayTy.annos(), path); path = path.array(); } lowerTypeAnnotations(base, path); } private void lowerClassTypeTypeAnnotations(ClassTy type, TypePath path) { for (SimpleClassTy simple : type.classes()) { lowerTypeAnnotations(simple.annos(), path); int idx = 0; for (Type a : simple.targs()) { lowerTypeAnnotations(a, path.typeArgument(idx++)); } path = path.nested(); } } } }