blob: ef6cdf7cc0fd11b414fed32cf1e5a228e47f144c [file] [log] [blame]
/*
* 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 com.google.common.base.Function;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.turbine.binder.bound.SourceTypeBoundClass;
import com.google.turbine.binder.bound.TypeBoundClass;
import com.google.turbine.binder.bound.TypeBoundClass.AnnoInfo;
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.lookup.Scope;
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.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.model.Const;
import com.google.turbine.model.TurbineFlag;
import com.google.turbine.model.TurbineVisibility;
import com.google.turbine.type.Type;
import com.google.turbine.type.Type.ClassTy;
import com.google.turbine.types.Erasure;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.annotation.Nullable;
/** Lowering from bound classes to bytecode. */
public class Lower {
/** Lowers all given classes to bytecode. */
public static Map<String, byte[]> lowerAll(
ImmutableMap<ClassSymbol, SourceTypeBoundClass> units,
CompoundEnv<ClassSymbol, BytecodeBoundClass> classpath) {
CompoundEnv<ClassSymbol, TypeBoundClass> env =
CompoundEnv.<ClassSymbol, TypeBoundClass>of(classpath).append(new SimpleEnv<>(units));
ImmutableMap.Builder<String, byte[]> result = ImmutableMap.builder();
for (ClassSymbol sym : units.keySet()) {
result.put(sym.binaryName(), new Lower().lower(units.get(sym), env, sym));
}
return result.build();
}
private final LowerSignature sig = new LowerSignature();
/** Lowers a class to bytecode. */
public byte[] lower(
SourceTypeBoundClass info, Env<ClassSymbol, TypeBoundClass> env, ClassSymbol sym) {
int access = classAccess(info);
String name = sig.descriptor(sym);
String signature = sig.classSignature(info);
String superName = sig.descriptor(info.superclass());
List<String> interfaces = new ArrayList<>();
for (ClassSymbol i : info.interfaces()) {
interfaces.add(sig.descriptor(i));
}
List<ClassFile.MethodInfo> methods = new ArrayList<>();
for (MethodInfo m : info.methods()) {
if (TurbineVisibility.fromAccess(m.access()) == TurbineVisibility.PRIVATE
&& !m.name().equals("<init>")) {
// TODO(cushon): drop private members earlier?
continue;
}
methods.add(lowerMethod(env, m, sym));
}
ImmutableList.Builder<ClassFile.FieldInfo> 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(env, f));
}
ImmutableList<AnnotationInfo> annotations = lowerAnnotations(env, info.annotations());
ImmutableList<ClassFile.InnerClass> inners = collectInnerClasses(sym, info, env);
ClassFile classfile =
new ClassFile(
access,
name,
signature,
superName,
interfaces,
methods,
fields.build(),
annotations,
inners);
return ClassWriter.writeClass(classfile);
}
private ClassFile.MethodInfo lowerMethod(
final Env<ClassSymbol, TypeBoundClass> env, final MethodInfo m, final ClassSymbol sym) {
int access = m.access();
Function<TyVarSymbol, TyVarInfo> tenv = new TyVarEnv(m.tyParams(), env);
String name = m.name();
String desc = methodDescriptor(m, tenv);
String signature = sig.methodSignature(env, m, sym);
ImmutableList.Builder<String> 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(), env) : null;
ImmutableList<AnnotationInfo> annotations = lowerAnnotations(env, m.annotations());
ImmutableList<ImmutableList<AnnotationInfo>> paramAnnotations = parameterAnnotations(env, m);
return new ClassFile.MethodInfo(
access,
name,
desc,
signature,
exceptions.build(),
defaultValue,
annotations,
paramAnnotations);
}
private ImmutableList<ImmutableList<AnnotationInfo>> parameterAnnotations(
Env<ClassSymbol, TypeBoundClass> env, MethodInfo m) {
ImmutableList.Builder<ImmutableList<AnnotationInfo>> annotations = ImmutableList.builder();
for (ParamInfo parameter : m.parameters()) {
if (parameter.synthetic()) {
continue;
}
if (parameter.annotations().isEmpty()) {
annotations.add(ImmutableList.of());
continue;
}
ImmutableList.Builder<AnnotationInfo> parameterAnnotations = ImmutableList.builder();
for (AnnoInfo annotation : parameter.annotations()) {
Boolean visible = isVisible(env, annotation.sym());
if (visible == null) {
continue;
}
String desc = descriptor(sig.descriptor(annotation.sym()));
parameterAnnotations.add(
new AnnotationInfo(desc, visible, annotationValues(annotation.values(), env)));
}
annotations.add(parameterAnnotations.build());
}
return annotations.build();
}
private String methodDescriptor(MethodInfo m, Function<TyVarSymbol, TyVarInfo> tenv) {
ImmutableList<Sig.TyParamSig> typarams = ImmutableList.of();
ImmutableList.Builder<TySig> 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<TySig> excns = ImmutableList.of();
return SigWriter.method(new MethodSig(typarams, fparams.build(), result, excns));
}
private ClassFile.FieldInfo lowerField(final Env<ClassSymbol, TypeBoundClass> env, FieldInfo f) {
final String name = f.name();
Function<TyVarSymbol, TyVarInfo> tenv = new TyVarEnv(Collections.emptyMap(), env);
String desc = SigWriter.type(sig.signature(Erasure.erase(f.type(), tenv)));
String signature = sig.fieldSignature(f.type());
ImmutableList<AnnotationInfo> annotations = lowerAnnotations(env, f.annotations());
return new ClassFile.FieldInfo(f.access(), name, desc, signature, f.value(), annotations);
}
/** Creates inner class attributes for all referenced inner classes. */
private ImmutableList<ClassFile.InnerClass> collectInnerClasses(
ClassSymbol origin, SourceTypeBoundClass info, Env<ClassSymbol, TypeBoundClass> env) {
Set<ClassSymbol> all = new LinkedHashSet<>();
addEnclosing(env, all, origin);
for (ClassSymbol sym : info.children().values()) {
addEnclosing(env, all, sym);
}
for (ClassSymbol sym : sig.classes) {
addEnclosing(env, all, sym);
}
ImmutableList.Builder<ClassFile.InnerClass> inners = ImmutableList.builder();
for (ClassSymbol innerSym : all) {
inners.add(innerClass(env, innerSym));
}
return inners.build();
}
private void addEnclosing(
Env<ClassSymbol, TypeBoundClass> env, Set<ClassSymbol> all, ClassSymbol sym) {
TypeBoundClass innerinfo = env.get(sym);
while (innerinfo.owner() != null) {
all.add(sym);
sym = innerinfo.owner();
innerinfo = env.get(sym);
}
}
/**
* Creates an inner class attribute, given an inner class that was referenced somewhere in the
* class.
*/
private ClassFile.InnerClass innerClass(
Env<ClassSymbol, TypeBoundClass> 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;
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);
if ((access & TurbineFlag.ACC_PROTECTED) != 0) {
access &= ~TurbineFlag.ACC_PROTECTED;
access |= TurbineFlag.ACC_PUBLIC;
}
return access;
}
/**
* Looks up {@link TyVarInfo}s.
*
* <p>We could generalize {@link Scope} instead, but this isn't needed anywhere else.
*/
static class TyVarEnv implements Function<TyVarSymbol, TyVarInfo> {
private final Env<ClassSymbol, TypeBoundClass> env;
private final Map<TyVarSymbol, TyVarInfo> tyParams;
/**
* @param tyParams the initial lookup scope, e.g. a method's formal type parameters.
* @param env the environment to look up a type variable's owning declaration in.
*/
public TyVarEnv(Map<TyVarSymbol, TyVarInfo> tyParams, Env<ClassSymbol, TypeBoundClass> env) {
this.tyParams = tyParams;
this.env = env;
}
@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
// TODO(cushon): remove this cast once we're reading type parameters from bytecode
TypeBoundClass owner = env.get((ClassSymbol) ownerSym);
if (!(owner instanceof SourceTypeBoundClass)) {
throw new AssertionError(sym);
}
return ((SourceTypeBoundClass) owner).typeParameterTypes().get(sym);
}
}
private ImmutableList<AnnotationInfo> lowerAnnotations(
Env<ClassSymbol, TypeBoundClass> env, ImmutableList<AnnoInfo> annotations) {
ImmutableList.Builder<AnnotationInfo> lowered = ImmutableList.builder();
outer:
for (AnnoInfo annotation : annotations) {
AnnotationInfo anno = lowerAnnotation(env, annotation);
if (anno == null) {
continue outer;
}
lowered.add(anno);
}
return lowered.build();
}
private AnnotationInfo lowerAnnotation(
Env<ClassSymbol, TypeBoundClass> env, AnnoInfo annotation) {
Boolean visible = isVisible(env, annotation.sym());
if (visible == null) {
return null;
}
return new AnnotationInfo(
descriptor(sig.descriptor(annotation.sym())),
visible,
annotationValues(annotation.values(), env));
}
private static String descriptor(String descriptor) {
return "L" + descriptor + ";";
}
/**
* 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 static Boolean isVisible(Env<ClassSymbol, TypeBoundClass> env, ClassSymbol sym) {
RetentionPolicy retention = env.get(sym).retention();
switch (retention) {
case CLASS:
return false;
case RUNTIME:
return true;
case SOURCE:
return null;
default:
throw new AssertionError(retention);
}
}
private ImmutableMap<String, ElementValue> annotationValues(
ImmutableMap<String, Const> values, Env<ClassSymbol, TypeBoundClass> env) {
ImmutableMap.Builder<String, ElementValue> result = ImmutableMap.builder();
for (Map.Entry<String, Const> entry : values.entrySet()) {
result.put(entry.getKey(), annotationValue(entry.getValue(), env));
}
return result.build();
}
private ElementValue annotationValue(Const value, Env<ClassSymbol, TypeBoundClass> env) {
switch (value.kind()) {
case CLASS_LITERAL:
{
Const.ClassValue classValue = (Const.ClassValue) value;
return new ElementValue.ConstClassValue(SigWriter.type(sig.signature(classValue.type())));
}
case ENUM_CONSTANT:
{
Const.EnumConstantValue enumValue = (Const.EnumConstantValue) value;
return new ElementValue.EnumConstValue(
descriptor(enumValue.sym().owner().binaryName()), enumValue.sym().name());
}
case ARRAY:
{
Const.ArrayInitValue arrayValue = (Const.ArrayInitValue) value;
List<ElementValue> values = new ArrayList<>();
for (Const element : arrayValue.elements()) {
values.add(annotationValue(element, env));
}
return new ElementValue.ArrayValue(values);
}
case ANNOTATION:
{
Const.AnnotationValue annotationValue = (Const.AnnotationValue) value;
Boolean visible = isVisible(env, annotationValue.sym());
if (visible == null) {
visible = true;
}
return new ElementValue.AnnotationValue(
new AnnotationInfo(
descriptor(annotationValue.sym().binaryName()),
visible,
annotationValues(annotationValue.values(), env)));
}
case PRIMITIVE:
return new ElementValue.ConstValue((Const.Value) value);
default:
throw new AssertionError(value.kind());
}
}
}