blob: e50d19271b844661d29b61b989f759180b7c89eb [file] [log] [blame]
/*
* Copyright (c) 2010, 2014, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package com.sun.tools.javac.comp;
import com.sun.tools.javac.tree.*;
import com.sun.tools.javac.tree.JCTree.*;
import com.sun.tools.javac.tree.JCTree.JCMemberReference.ReferenceKind;
import com.sun.tools.javac.tree.TreeMaker;
import com.sun.tools.javac.tree.TreeTranslator;
import com.sun.tools.javac.code.Attribute;
import com.sun.tools.javac.code.Kinds;
import com.sun.tools.javac.code.Scope;
import com.sun.tools.javac.code.Symbol;
import com.sun.tools.javac.code.Symbol.ClassSymbol;
import com.sun.tools.javac.code.Symbol.DynamicMethodSymbol;
import com.sun.tools.javac.code.Symbol.MethodSymbol;
import com.sun.tools.javac.code.Symbol.TypeSymbol;
import com.sun.tools.javac.code.Symbol.VarSymbol;
import com.sun.tools.javac.code.Symtab;
import com.sun.tools.javac.code.Type;
import com.sun.tools.javac.code.Type.MethodType;
import com.sun.tools.javac.code.Type.TypeVar;
import com.sun.tools.javac.code.Types;
import com.sun.tools.javac.comp.LambdaToMethod.LambdaAnalyzerPreprocessor.*;
import com.sun.tools.javac.comp.Lower.BasicFreeVarCollector;
import com.sun.tools.javac.jvm.*;
import com.sun.tools.javac.util.*;
import com.sun.tools.javac.util.JCDiagnostic.DiagnosticPosition;
import com.sun.source.tree.MemberReferenceTree.ReferenceMode;
import java.util.EnumMap;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;
import static com.sun.tools.javac.comp.LambdaToMethod.LambdaSymbolKind.*;
import static com.sun.tools.javac.code.Flags.*;
import static com.sun.tools.javac.code.Kinds.*;
import static com.sun.tools.javac.code.TypeTag.*;
import static com.sun.tools.javac.tree.JCTree.Tag.*;
import javax.lang.model.type.TypeKind;
/**
* This pass desugars lambda expressions into static methods
*
* <p><b>This is NOT part of any supported API.
* If you write code that depends on this, you do so at your own risk.
* This code and its internal interfaces are subject to change or
* deletion without notice.</b>
*/
public class LambdaToMethod extends TreeTranslator {
private Attr attr;
private JCDiagnostic.Factory diags;
private Log log;
private Lower lower;
private Names names;
private Symtab syms;
private Resolve rs;
private TreeMaker make;
private Types types;
private TransTypes transTypes;
private Env<AttrContext> attrEnv;
/** the analyzer scanner */
private LambdaAnalyzerPreprocessor analyzer;
/** map from lambda trees to translation contexts */
private Map<JCTree, TranslationContext<?>> contextMap;
/** current translation context (visitor argument) */
private TranslationContext<?> context;
/** info about the current class being processed */
private KlassInfo kInfo;
/** dump statistics about lambda code generation */
private boolean dumpLambdaToMethodStats;
/** force serializable representation, for stress testing **/
private final boolean forceSerializable;
/** Flag for alternate metafactories indicating the lambda object is intended to be serializable */
public static final int FLAG_SERIALIZABLE = 1 << 0;
/** Flag for alternate metafactories indicating the lambda object has multiple targets */
public static final int FLAG_MARKERS = 1 << 1;
/** Flag for alternate metafactories indicating the lambda object requires multiple bridges */
public static final int FLAG_BRIDGES = 1 << 2;
// <editor-fold defaultstate="collapsed" desc="Instantiating">
protected static final Context.Key<LambdaToMethod> unlambdaKey =
new Context.Key<LambdaToMethod>();
public static LambdaToMethod instance(Context context) {
LambdaToMethod instance = context.get(unlambdaKey);
if (instance == null) {
instance = new LambdaToMethod(context);
}
return instance;
}
private LambdaToMethod(Context context) {
context.put(unlambdaKey, this);
diags = JCDiagnostic.Factory.instance(context);
log = Log.instance(context);
lower = Lower.instance(context);
names = Names.instance(context);
syms = Symtab.instance(context);
rs = Resolve.instance(context);
make = TreeMaker.instance(context);
types = Types.instance(context);
transTypes = TransTypes.instance(context);
analyzer = new LambdaAnalyzerPreprocessor();
Options options = Options.instance(context);
dumpLambdaToMethodStats = options.isSet("dumpLambdaToMethodStats");
attr = Attr.instance(context);
forceSerializable = options.isSet("forceSerializable");
}
// </editor-fold>
private class KlassInfo {
/**
* list of methods to append
*/
private ListBuffer<JCTree> appendedMethodList;
/**
* list of deserialization cases
*/
private final Map<String, ListBuffer<JCStatement>> deserializeCases;
/**
* deserialize method symbol
*/
private final MethodSymbol deserMethodSym;
/**
* deserialize method parameter symbol
*/
private final VarSymbol deserParamSym;
private final JCClassDecl clazz;
private KlassInfo(JCClassDecl clazz) {
this.clazz = clazz;
appendedMethodList = new ListBuffer<>();
deserializeCases = new HashMap<String, ListBuffer<JCStatement>>();
MethodType type = new MethodType(List.of(syms.serializedLambdaType), syms.objectType,
List.<Type>nil(), syms.methodClass);
deserMethodSym = makePrivateSyntheticMethod(STATIC, names.deserializeLambda, type, clazz.sym);
deserParamSym = new VarSymbol(FINAL, names.fromString("lambda"),
syms.serializedLambdaType, deserMethodSym);
}
private void addMethod(JCTree decl) {
appendedMethodList = appendedMethodList.prepend(decl);
}
}
// <editor-fold defaultstate="collapsed" desc="translate methods">
@Override
public <T extends JCTree> T translate(T tree) {
TranslationContext<?> newContext = contextMap.get(tree);
return translate(tree, newContext != null ? newContext : context);
}
<T extends JCTree> T translate(T tree, TranslationContext<?> newContext) {
TranslationContext<?> prevContext = context;
try {
context = newContext;
return super.translate(tree);
}
finally {
context = prevContext;
}
}
<T extends JCTree> List<T> translate(List<T> trees, TranslationContext<?> newContext) {
ListBuffer<T> buf = new ListBuffer<>();
for (T tree : trees) {
buf.append(translate(tree, newContext));
}
return buf.toList();
}
public JCTree translateTopLevelClass(Env<AttrContext> env, JCTree cdef, TreeMaker make) {
this.make = make;
this.attrEnv = env;
this.context = null;
this.contextMap = new HashMap<JCTree, TranslationContext<?>>();
return translate(cdef);
}
// </editor-fold>
// <editor-fold defaultstate="collapsed" desc="visitor methods">
/**
* Visit a class.
* Maintain the translatedMethodList across nested classes.
* Append the translatedMethodList to the class after it is translated.
* @param tree
*/
@Override
public void visitClassDef(JCClassDecl tree) {
if (tree.sym.owner.kind == PCK) {
//analyze class
tree = analyzer.analyzeAndPreprocessClass(tree);
}
KlassInfo prevKlassInfo = kInfo;
try {
kInfo = new KlassInfo(tree);
super.visitClassDef(tree);
if (!kInfo.deserializeCases.isEmpty()) {
int prevPos = make.pos;
try {
make.at(tree);
kInfo.addMethod(makeDeserializeMethod(tree.sym));
} finally {
make.at(prevPos);
}
}
//add all translated instance methods here
List<JCTree> newMethods = kInfo.appendedMethodList.toList();
tree.defs = tree.defs.appendList(newMethods);
for (JCTree lambda : newMethods) {
tree.sym.members().enter(((JCMethodDecl)lambda).sym);
}
result = tree;
} finally {
kInfo = prevKlassInfo;
}
}
/**
* Translate a lambda into a method to be inserted into the class.
* Then replace the lambda site with an invokedynamic call of to lambda
* meta-factory, which will use the lambda method.
* @param tree
*/
@Override
public void visitLambda(JCLambda tree) {
LambdaTranslationContext localContext = (LambdaTranslationContext)context;
MethodSymbol sym = localContext.translatedSym;
MethodType lambdaType = (MethodType) sym.type;
{
Symbol owner = localContext.owner;
ListBuffer<Attribute.TypeCompound> ownerTypeAnnos = new ListBuffer<Attribute.TypeCompound>();
ListBuffer<Attribute.TypeCompound> lambdaTypeAnnos = new ListBuffer<Attribute.TypeCompound>();
for (Attribute.TypeCompound tc : owner.getRawTypeAttributes()) {
if (tc.position.onLambda == tree) {
lambdaTypeAnnos.append(tc);
} else {
ownerTypeAnnos.append(tc);
}
}
if (lambdaTypeAnnos.nonEmpty()) {
owner.setTypeAttributes(ownerTypeAnnos.toList());
sym.setTypeAttributes(lambdaTypeAnnos.toList());
}
}
//create the method declaration hoisting the lambda body
JCMethodDecl lambdaDecl = make.MethodDef(make.Modifiers(sym.flags_field),
sym.name,
make.QualIdent(lambdaType.getReturnType().tsym),
List.<JCTypeParameter>nil(),
localContext.syntheticParams,
lambdaType.getThrownTypes() == null ?
List.<JCExpression>nil() :
make.Types(lambdaType.getThrownTypes()),
null,
null);
lambdaDecl.sym = sym;
lambdaDecl.type = lambdaType;
//translate lambda body
//As the lambda body is translated, all references to lambda locals,
//captured variables, enclosing members are adjusted accordingly
//to refer to the static method parameters (rather than i.e. acessing to
//captured members directly).
lambdaDecl.body = translate(makeLambdaBody(tree, lambdaDecl));
//Add the method to the list of methods to be added to this class.
kInfo.addMethod(lambdaDecl);
//now that we have generated a method for the lambda expression,
//we can translate the lambda into a method reference pointing to the newly
//created method.
//
//Note that we need to adjust the method handle so that it will match the
//signature of the SAM descriptor - this means that the method reference
//should be added the following synthetic arguments:
//
// * the "this" argument if it is an instance method
// * enclosing locals captured by the lambda expression
ListBuffer<JCExpression> syntheticInits = new ListBuffer<>();
if (localContext.methodReferenceReceiver != null) {
syntheticInits.append(localContext.methodReferenceReceiver);
} else if (!sym.isStatic()) {
syntheticInits.append(makeThis(
sym.owner.enclClass().asType(),
localContext.owner.enclClass()));
}
//add captured locals
for (Symbol fv : localContext.getSymbolMap(CAPTURED_VAR).keySet()) {
if (fv != localContext.self) {
JCTree captured_local = make.Ident(fv).setType(fv.type);
syntheticInits.append((JCExpression) captured_local);
}
}
//then, determine the arguments to the indy call
List<JCExpression> indy_args = translate(syntheticInits.toList(), localContext.prev);
//build a sam instance using an indy call to the meta-factory
int refKind = referenceKind(sym);
//convert to an invokedynamic call
result = makeMetafactoryIndyCall(context, refKind, sym, indy_args);
}
private JCIdent makeThis(Type type, Symbol owner) {
VarSymbol _this = new VarSymbol(PARAMETER | FINAL | SYNTHETIC,
names._this,
type,
owner);
return make.Ident(_this);
}
/**
* Translate a method reference into an invokedynamic call to the
* meta-factory.
* @param tree
*/
@Override
public void visitReference(JCMemberReference tree) {
ReferenceTranslationContext localContext = (ReferenceTranslationContext)context;
//first determine the method symbol to be used to generate the sam instance
//this is either the method reference symbol, or the bridged reference symbol
Symbol refSym = localContext.isSignaturePolymorphic()
? localContext.sigPolySym
: tree.sym;
//the qualifying expression is treated as a special captured arg
JCExpression init;
switch(tree.kind) {
case IMPLICIT_INNER: /** Inner :: new */
case SUPER: /** super :: instMethod */
init = makeThis(
localContext.owner.enclClass().asType(),
localContext.owner.enclClass());
break;
case BOUND: /** Expr :: instMethod */
init = tree.getQualifierExpression();
init = attr.makeNullCheck(init);
break;
case UNBOUND: /** Type :: instMethod */
case STATIC: /** Type :: staticMethod */
case TOPLEVEL: /** Top level :: new */
case ARRAY_CTOR: /** ArrayType :: new */
init = null;
break;
default:
throw new InternalError("Should not have an invalid kind");
}
List<JCExpression> indy_args = init==null? List.<JCExpression>nil() : translate(List.of(init), localContext.prev);
//build a sam instance using an indy call to the meta-factory
result = makeMetafactoryIndyCall(localContext, localContext.referenceKind(), refSym, indy_args);
}
/**
* Translate identifiers within a lambda to the mapped identifier
* @param tree
*/
@Override
public void visitIdent(JCIdent tree) {
if (context == null || !analyzer.lambdaIdentSymbolFilter(tree.sym)) {
super.visitIdent(tree);
} else {
int prevPos = make.pos;
try {
make.at(tree);
LambdaTranslationContext lambdaContext = (LambdaTranslationContext) context;
JCTree ltree = lambdaContext.translate(tree);
if (ltree != null) {
result = ltree;
} else {
//access to untranslated symbols (i.e. compile-time constants,
//members defined inside the lambda body, etc.) )
super.visitIdent(tree);
}
} finally {
make.at(prevPos);
}
}
}
@Override
public void visitVarDef(JCVariableDecl tree) {
LambdaTranslationContext lambdaContext = (LambdaTranslationContext)context;
if (context != null && lambdaContext.getSymbolMap(LOCAL_VAR).containsKey(tree.sym)) {
tree.init = translate(tree.init);
tree.sym = (VarSymbol) lambdaContext.getSymbolMap(LOCAL_VAR).get(tree.sym);
result = tree;
} else if (context != null && lambdaContext.getSymbolMap(TYPE_VAR).containsKey(tree.sym)) {
JCExpression init = translate(tree.init);
VarSymbol xsym = (VarSymbol)lambdaContext.getSymbolMap(TYPE_VAR).get(tree.sym);
int prevPos = make.pos;
try {
result = make.at(tree).VarDef(xsym, init);
} finally {
make.at(prevPos);
}
// Replace the entered symbol for this variable
Scope sc = tree.sym.owner.members();
if (sc != null) {
sc.remove(tree.sym);
sc.enter(xsym);
}
} else {
super.visitVarDef(tree);
}
}
// </editor-fold>
// <editor-fold defaultstate="collapsed" desc="Translation helper methods">
private JCBlock makeLambdaBody(JCLambda tree, JCMethodDecl lambdaMethodDecl) {
return tree.getBodyKind() == JCLambda.BodyKind.EXPRESSION ?
makeLambdaExpressionBody((JCExpression)tree.body, lambdaMethodDecl) :
makeLambdaStatementBody((JCBlock)tree.body, lambdaMethodDecl, tree.canCompleteNormally);
}
private JCBlock makeLambdaExpressionBody(JCExpression expr, JCMethodDecl lambdaMethodDecl) {
Type restype = lambdaMethodDecl.type.getReturnType();
boolean isLambda_void = expr.type.hasTag(VOID);
boolean isTarget_void = restype.hasTag(VOID);
boolean isTarget_Void = types.isSameType(restype, types.boxedClass(syms.voidType).type);
int prevPos = make.pos;
try {
if (isTarget_void) {
//target is void:
// BODY;
JCStatement stat = make.at(expr).Exec(expr);
return make.Block(0, List.<JCStatement>of(stat));
} else if (isLambda_void && isTarget_Void) {
//void to Void conversion:
// BODY; return null;
ListBuffer<JCStatement> stats = new ListBuffer<>();
stats.append(make.at(expr).Exec(expr));
stats.append(make.Return(make.Literal(BOT, null).setType(syms.botType)));
return make.Block(0, stats.toList());
} else {
//non-void to non-void conversion:
// return (TYPE)BODY;
JCExpression retExpr = transTypes.coerce(attrEnv, expr, restype);
return make.at(retExpr).Block(0, List.<JCStatement>of(make.Return(retExpr)));
}
} finally {
make.at(prevPos);
}
}
private JCBlock makeLambdaStatementBody(JCBlock block, final JCMethodDecl lambdaMethodDecl, boolean completeNormally) {
final Type restype = lambdaMethodDecl.type.getReturnType();
final boolean isTarget_void = restype.hasTag(VOID);
boolean isTarget_Void = types.isSameType(restype, types.boxedClass(syms.voidType).type);
class LambdaBodyTranslator extends TreeTranslator {
@Override
public void visitClassDef(JCClassDecl tree) {
//do NOT recurse on any inner classes
result = tree;
}
@Override
public void visitLambda(JCLambda tree) {
//do NOT recurse on any nested lambdas
result = tree;
}
@Override
public void visitReturn(JCReturn tree) {
boolean isLambda_void = tree.expr == null;
if (isTarget_void && !isLambda_void) {
//Void to void conversion:
// { TYPE $loc = RET-EXPR; return; }
VarSymbol loc = makeSyntheticVar(0, names.fromString("$loc"), tree.expr.type, lambdaMethodDecl.sym);
JCVariableDecl varDef = make.VarDef(loc, tree.expr);
result = make.Block(0, List.<JCStatement>of(varDef, make.Return(null)));
} else if (!isTarget_void || !isLambda_void) {
//non-void to non-void conversion:
// return (TYPE)RET-EXPR;
tree.expr = transTypes.coerce(attrEnv, tree.expr, restype);
result = tree;
} else {
result = tree;
}
}
}
JCBlock trans_block = new LambdaBodyTranslator().translate(block);
if (completeNormally && isTarget_Void) {
//there's no return statement and the lambda (possibly inferred)
//return type is java.lang.Void; emit a synthetic return statement
trans_block.stats = trans_block.stats.append(make.Return(make.Literal(BOT, null).setType(syms.botType)));
}
return trans_block;
}
private JCMethodDecl makeDeserializeMethod(Symbol kSym) {
ListBuffer<JCCase> cases = new ListBuffer<>();
ListBuffer<JCBreak> breaks = new ListBuffer<>();
for (Map.Entry<String, ListBuffer<JCStatement>> entry : kInfo.deserializeCases.entrySet()) {
JCBreak br = make.Break(null);
breaks.add(br);
List<JCStatement> stmts = entry.getValue().append(br).toList();
cases.add(make.Case(make.Literal(entry.getKey()), stmts));
}
JCSwitch sw = make.Switch(deserGetter("getImplMethodName", syms.stringType), cases.toList());
for (JCBreak br : breaks) {
br.target = sw;
}
JCBlock body = make.Block(0L, List.<JCStatement>of(
sw,
make.Throw(makeNewClass(
syms.illegalArgumentExceptionType,
List.<JCExpression>of(make.Literal("Invalid lambda deserialization"))))));
JCMethodDecl deser = make.MethodDef(make.Modifiers(kInfo.deserMethodSym.flags()),
names.deserializeLambda,
make.QualIdent(kInfo.deserMethodSym.getReturnType().tsym),
List.<JCTypeParameter>nil(),
List.of(make.VarDef(kInfo.deserParamSym, null)),
List.<JCExpression>nil(),
body,
null);
deser.sym = kInfo.deserMethodSym;
deser.type = kInfo.deserMethodSym.type;
//System.err.printf("DESER: '%s'\n", deser);
return deser;
}
/** Make an attributed class instance creation expression.
* @param ctype The class type.
* @param args The constructor arguments.
* @param cons The constructor symbol
*/
JCNewClass makeNewClass(Type ctype, List<JCExpression> args, Symbol cons) {
JCNewClass tree = make.NewClass(null,
null, make.QualIdent(ctype.tsym), args, null);
tree.constructor = cons;
tree.type = ctype;
return tree;
}
/** Make an attributed class instance creation expression.
* @param ctype The class type.
* @param args The constructor arguments.
*/
JCNewClass makeNewClass(Type ctype, List<JCExpression> args) {
return makeNewClass(ctype, args,
rs.resolveConstructor(null, attrEnv, ctype, TreeInfo.types(args), List.<Type>nil()));
}
private void addDeserializationCase(int implMethodKind, Symbol refSym, Type targetType, MethodSymbol samSym,
DiagnosticPosition pos, List<Object> staticArgs, MethodType indyType) {
String functionalInterfaceClass = classSig(targetType);
String functionalInterfaceMethodName = samSym.getSimpleName().toString();
String functionalInterfaceMethodSignature = typeSig(types.erasure(samSym.type));
String implClass = classSig(types.erasure(refSym.owner.type));
String implMethodName = refSym.getQualifiedName().toString();
String implMethodSignature = typeSig(types.erasure(refSym.type));
JCExpression kindTest = eqTest(syms.intType, deserGetter("getImplMethodKind", syms.intType), make.Literal(implMethodKind));
ListBuffer<JCExpression> serArgs = new ListBuffer<>();
int i = 0;
for (Type t : indyType.getParameterTypes()) {
List<JCExpression> indexAsArg = new ListBuffer<JCExpression>().append(make.Literal(i)).toList();
List<Type> argTypes = new ListBuffer<Type>().append(syms.intType).toList();
serArgs.add(make.TypeCast(types.erasure(t), deserGetter("getCapturedArg", syms.objectType, argTypes, indexAsArg)));
++i;
}
JCStatement stmt = make.If(
deserTest(deserTest(deserTest(deserTest(deserTest(
kindTest,
"getFunctionalInterfaceClass", functionalInterfaceClass),
"getFunctionalInterfaceMethodName", functionalInterfaceMethodName),
"getFunctionalInterfaceMethodSignature", functionalInterfaceMethodSignature),
"getImplClass", implClass),
"getImplMethodSignature", implMethodSignature),
make.Return(makeIndyCall(
pos,
syms.lambdaMetafactory,
names.altMetafactory,
staticArgs, indyType, serArgs.toList(), samSym.name)),
null);
ListBuffer<JCStatement> stmts = kInfo.deserializeCases.get(implMethodName);
if (stmts == null) {
stmts = new ListBuffer<>();
kInfo.deserializeCases.put(implMethodName, stmts);
}
/****
System.err.printf("+++++++++++++++++\n");
System.err.printf("*functionalInterfaceClass: '%s'\n", functionalInterfaceClass);
System.err.printf("*functionalInterfaceMethodName: '%s'\n", functionalInterfaceMethodName);
System.err.printf("*functionalInterfaceMethodSignature: '%s'\n", functionalInterfaceMethodSignature);
System.err.printf("*implMethodKind: %d\n", implMethodKind);
System.err.printf("*implClass: '%s'\n", implClass);
System.err.printf("*implMethodName: '%s'\n", implMethodName);
System.err.printf("*implMethodSignature: '%s'\n", implMethodSignature);
****/
stmts.append(stmt);
}
private JCExpression eqTest(Type argType, JCExpression arg1, JCExpression arg2) {
JCBinary testExpr = make.Binary(JCTree.Tag.EQ, arg1, arg2);
testExpr.operator = rs.resolveBinaryOperator(null, JCTree.Tag.EQ, attrEnv, argType, argType);
testExpr.setType(syms.booleanType);
return testExpr;
}
private JCExpression deserTest(JCExpression prev, String func, String lit) {
MethodType eqmt = new MethodType(List.of(syms.objectType), syms.booleanType, List.<Type>nil(), syms.methodClass);
Symbol eqsym = rs.resolveQualifiedMethod(null, attrEnv, syms.objectType, names.equals, List.of(syms.objectType), List.<Type>nil());
JCMethodInvocation eqtest = make.Apply(
List.<JCExpression>nil(),
make.Select(deserGetter(func, syms.stringType), eqsym).setType(eqmt),
List.<JCExpression>of(make.Literal(lit)));
eqtest.setType(syms.booleanType);
JCBinary compound = make.Binary(JCTree.Tag.AND, prev, eqtest);
compound.operator = rs.resolveBinaryOperator(null, JCTree.Tag.AND, attrEnv, syms.booleanType, syms.booleanType);
compound.setType(syms.booleanType);
return compound;
}
private JCExpression deserGetter(String func, Type type) {
return deserGetter(func, type, List.<Type>nil(), List.<JCExpression>nil());
}
private JCExpression deserGetter(String func, Type type, List<Type> argTypes, List<JCExpression> args) {
MethodType getmt = new MethodType(argTypes, type, List.<Type>nil(), syms.methodClass);
Symbol getsym = rs.resolveQualifiedMethod(null, attrEnv, syms.serializedLambdaType, names.fromString(func), argTypes, List.<Type>nil());
return make.Apply(
List.<JCExpression>nil(),
make.Select(make.Ident(kInfo.deserParamSym).setType(syms.serializedLambdaType), getsym).setType(getmt),
args).setType(type);
}
/**
* Create new synthetic method with given flags, name, type, owner
*/
private MethodSymbol makePrivateSyntheticMethod(long flags, Name name, Type type, Symbol owner) {
return new MethodSymbol(flags | SYNTHETIC | PRIVATE, name, type, owner);
}
/**
* Create new synthetic variable with given flags, name, type, owner
*/
private VarSymbol makeSyntheticVar(long flags, String name, Type type, Symbol owner) {
return makeSyntheticVar(flags, names.fromString(name), type, owner);
}
/**
* Create new synthetic variable with given flags, name, type, owner
*/
private VarSymbol makeSyntheticVar(long flags, Name name, Type type, Symbol owner) {
return new VarSymbol(flags | SYNTHETIC, name, type, owner);
}
/**
* Set varargsElement field on a given tree (must be either a new class tree
* or a method call tree)
*/
private void setVarargsIfNeeded(JCTree tree, Type varargsElement) {
if (varargsElement != null) {
switch (tree.getTag()) {
case APPLY: ((JCMethodInvocation)tree).varargsElement = varargsElement; break;
case NEWCLASS: ((JCNewClass)tree).varargsElement = varargsElement; break;
default: throw new AssertionError();
}
}
}
/**
* Convert method/constructor arguments by inserting appropriate cast
* as required by type-erasure - this is needed when bridging a lambda/method
* reference, as the bridged signature might require downcast to be compatible
* with the generated signature.
*/
private List<JCExpression> convertArgs(Symbol meth, List<JCExpression> args, Type varargsElement) {
Assert.check(meth.kind == Kinds.MTH);
List<Type> formals = types.erasure(meth.type).getParameterTypes();
if (varargsElement != null) {
Assert.check((meth.flags() & VARARGS) != 0);
}
return transTypes.translateArgs(args, formals, varargsElement, attrEnv);
}
// </editor-fold>
/**
* Converts a method reference which cannot be used directly into a lambda
*/
private class MemberReferenceToLambda {
private final JCMemberReference tree;
private final ReferenceTranslationContext localContext;
private final Symbol owner;
private final ListBuffer<JCExpression> args = new ListBuffer<>();
private final ListBuffer<JCVariableDecl> params = new ListBuffer<>();
private JCExpression receiverExpression = null;
MemberReferenceToLambda(JCMemberReference tree, ReferenceTranslationContext localContext, Symbol owner) {
this.tree = tree;
this.localContext = localContext;
this.owner = owner;
}
JCLambda lambda() {
int prevPos = make.pos;
try {
make.at(tree);
//body generation - this can be either a method call or a
//new instance creation expression, depending on the member reference kind
VarSymbol rcvr = addParametersReturnReceiver();
JCExpression expr = (tree.getMode() == ReferenceMode.INVOKE)
? expressionInvoke(rcvr)
: expressionNew();
JCLambda slam = make.Lambda(params.toList(), expr);
slam.targets = tree.targets;
slam.type = tree.type;
slam.pos = tree.pos;
return slam;
} finally {
make.at(prevPos);
}
}
/**
* Generate the parameter list for the converted member reference.
*
* @return The receiver variable symbol, if any
*/
VarSymbol addParametersReturnReceiver() {
Type samDesc = localContext.bridgedRefSig();
List<Type> samPTypes = samDesc.getParameterTypes();
List<Type> descPTypes = tree.getDescriptorType(types).getParameterTypes();
// Determine the receiver, if any
VarSymbol rcvr;
switch (tree.kind) {
case BOUND:
// The receiver is explicit in the method reference
rcvr = addParameter("rec$", tree.getQualifierExpression().type, false);
receiverExpression = attr.makeNullCheck(tree.getQualifierExpression());
break;
case UNBOUND:
// The receiver is the first parameter, extract it and
// adjust the SAM and unerased type lists accordingly
rcvr = addParameter("rec$", samDesc.getParameterTypes().head, false);
samPTypes = samPTypes.tail;
descPTypes = descPTypes.tail;
break;
default:
rcvr = null;
break;
}
List<Type> implPTypes = tree.sym.type.getParameterTypes();
int implSize = implPTypes.size();
int samSize = samPTypes.size();
// Last parameter to copy from referenced method, exclude final var args
int last = localContext.needsVarArgsConversion() ? implSize - 1 : implSize;
// Failsafe -- assure match-up
boolean checkForIntersection = tree.varargsElement != null || implSize == descPTypes.size();
// Use parameter types of the implementation method unless the unerased
// SAM parameter type is an intersection type, in that case use the
// erased SAM parameter type so that the supertype relationship
// the implementation method parameters is not obscured.
// Note: in this loop, the lists implPTypes, samPTypes, and descPTypes
// are used as pointers to the current parameter type information
// and are thus not usable afterwards.
for (int i = 0; implPTypes.nonEmpty() && i < last; ++i) {
// By default use the implementation method parmeter type
Type parmType = implPTypes.head;
// If the unerased parameter type is a type variable whose
// bound is an intersection (eg. <T extends A & B>) then
// use the SAM parameter type
if (checkForIntersection && descPTypes.head.getKind() == TypeKind.TYPEVAR) {
TypeVar tv = (TypeVar) descPTypes.head;
if (tv.bound.getKind() == TypeKind.INTERSECTION) {
parmType = samPTypes.head;
}
}
addParameter("x$" + i, parmType, true);
// Advance to the next parameter
implPTypes = implPTypes.tail;
samPTypes = samPTypes.tail;
descPTypes = descPTypes.tail;
}
// Flatten out the var args
for (int i = last; i < samSize; ++i) {
addParameter("xva$" + i, tree.varargsElement, true);
}
return rcvr;
}
JCExpression getReceiverExpression() {
return receiverExpression;
}
private JCExpression makeReceiver(VarSymbol rcvr) {
if (rcvr == null) return null;
JCExpression rcvrExpr = make.Ident(rcvr);
Type rcvrType = tree.ownerAccessible ? tree.sym.enclClass().type : tree.expr.type;
if (rcvrType == syms.arrayClass.type) {
// Map the receiver type to the actually type, not just "array"
rcvrType = tree.getQualifierExpression().type;
}
if (!rcvr.type.tsym.isSubClass(rcvrType.tsym, types)) {
rcvrExpr = make.TypeCast(make.Type(rcvrType), rcvrExpr).setType(rcvrType);
}
return rcvrExpr;
}
/**
* determine the receiver of the method call - the receiver can
* be a type qualifier, the synthetic receiver parameter or 'super'.
*/
private JCExpression expressionInvoke(VarSymbol rcvr) {
JCExpression qualifier =
(rcvr != null) ?
makeReceiver(rcvr) :
tree.getQualifierExpression();
//create the qualifier expression
JCFieldAccess select = make.Select(qualifier, tree.sym.name);
select.sym = tree.sym;
select.type = tree.sym.erasure(types);
//create the method call expression
JCExpression apply = make.Apply(List.<JCExpression>nil(), select,
convertArgs(tree.sym, args.toList(), tree.varargsElement)).
setType(tree.sym.erasure(types).getReturnType());
apply = transTypes.coerce(apply, localContext.generatedRefSig().getReturnType());
setVarargsIfNeeded(apply, tree.varargsElement);
return apply;
}
/**
* Lambda body to use for a 'new'.
*/
private JCExpression expressionNew() {
if (tree.kind == ReferenceKind.ARRAY_CTOR) {
//create the array creation expression
JCNewArray newArr = make.NewArray(
make.Type(types.elemtype(tree.getQualifierExpression().type)),
List.of(make.Ident(params.first())),
null);
newArr.type = tree.getQualifierExpression().type;
return newArr;
} else {
//create the instance creation expression
//note that method reference syntax does not allow an explicit
//enclosing class (so the enclosing class is null)
JCNewClass newClass = make.NewClass(null,
List.<JCExpression>nil(),
make.Type(tree.getQualifierExpression().type),
convertArgs(tree.sym, args.toList(), tree.varargsElement),
null);
newClass.constructor = tree.sym;
newClass.constructorType = tree.sym.erasure(types);
newClass.type = tree.getQualifierExpression().type;
setVarargsIfNeeded(newClass, tree.varargsElement);
return newClass;
}
}
private VarSymbol addParameter(String name, Type p, boolean genArg) {
VarSymbol vsym = new VarSymbol(PARAMETER | SYNTHETIC, names.fromString(name), p, owner);
vsym.pos = tree.pos;
params.append(make.VarDef(vsym, null));
if (genArg) {
args.append(make.Ident(vsym));
}
return vsym;
}
}
private MethodType typeToMethodType(Type mt) {
Type type = types.erasure(mt);
return new MethodType(type.getParameterTypes(),
type.getReturnType(),
type.getThrownTypes(),
syms.methodClass);
}
/**
* Generate an indy method call to the meta factory
*/
private JCExpression makeMetafactoryIndyCall(TranslationContext<?> context,
int refKind, Symbol refSym, List<JCExpression> indy_args) {
JCFunctionalExpression tree = context.tree;
//determine the static bsm args
MethodSymbol samSym = (MethodSymbol) types.findDescriptorSymbol(tree.type.tsym);
List<Object> staticArgs = List.<Object>of(
typeToMethodType(samSym.type),
new Pool.MethodHandle(refKind, refSym, types),
typeToMethodType(tree.getDescriptorType(types)));
//computed indy arg types
ListBuffer<Type> indy_args_types = new ListBuffer<>();
for (JCExpression arg : indy_args) {
indy_args_types.append(arg.type);
}
//finally, compute the type of the indy call
MethodType indyType = new MethodType(indy_args_types.toList(),
tree.type,
List.<Type>nil(),
syms.methodClass);
Name metafactoryName = context.needsAltMetafactory() ?
names.altMetafactory : names.metafactory;
if (context.needsAltMetafactory()) {
ListBuffer<Object> markers = new ListBuffer<>();
for (Type t : tree.targets.tail) {
if (t.tsym != syms.serializableType.tsym) {
markers.append(t.tsym);
}
}
int flags = context.isSerializable() ? FLAG_SERIALIZABLE : 0;
boolean hasMarkers = markers.nonEmpty();
boolean hasBridges = context.bridges.nonEmpty();
if (hasMarkers) {
flags |= FLAG_MARKERS;
}
if (hasBridges) {
flags |= FLAG_BRIDGES;
}
staticArgs = staticArgs.append(flags);
if (hasMarkers) {
staticArgs = staticArgs.append(markers.length());
staticArgs = staticArgs.appendList(markers.toList());
}
if (hasBridges) {
staticArgs = staticArgs.append(context.bridges.length() - 1);
for (Symbol s : context.bridges) {
Type s_erasure = s.erasure(types);
if (!types.isSameType(s_erasure, samSym.erasure(types))) {
staticArgs = staticArgs.append(s.erasure(types));
}
}
}
if (context.isSerializable()) {
int prevPos = make.pos;
try {
make.at(kInfo.clazz);
addDeserializationCase(refKind, refSym, tree.type, samSym,
tree, staticArgs, indyType);
} finally {
make.at(prevPos);
}
}
}
return makeIndyCall(tree, syms.lambdaMetafactory, metafactoryName, staticArgs, indyType, indy_args, samSym.name);
}
/**
* Generate an indy method call with given name, type and static bootstrap
* arguments types
*/
private JCExpression makeIndyCall(DiagnosticPosition pos, Type site, Name bsmName,
List<Object> staticArgs, MethodType indyType, List<JCExpression> indyArgs,
Name methName) {
int prevPos = make.pos;
try {
make.at(pos);
List<Type> bsm_staticArgs = List.of(syms.methodHandleLookupType,
syms.stringType,
syms.methodTypeType).appendList(bsmStaticArgToTypes(staticArgs));
Symbol bsm = rs.resolveInternalMethod(pos, attrEnv, site,
bsmName, bsm_staticArgs, List.<Type>nil());
DynamicMethodSymbol dynSym =
new DynamicMethodSymbol(methName,
syms.noSymbol,
bsm.isStatic() ?
ClassFile.REF_invokeStatic :
ClassFile.REF_invokeVirtual,
(MethodSymbol)bsm,
indyType,
staticArgs.toArray());
JCFieldAccess qualifier = make.Select(make.QualIdent(site.tsym), bsmName);
qualifier.sym = dynSym;
qualifier.type = indyType.getReturnType();
JCMethodInvocation proxyCall = make.Apply(List.<JCExpression>nil(), qualifier, indyArgs);
proxyCall.type = indyType.getReturnType();
return proxyCall;
} finally {
make.at(prevPos);
}
}
//where
private List<Type> bsmStaticArgToTypes(List<Object> args) {
ListBuffer<Type> argtypes = new ListBuffer<>();
for (Object arg : args) {
argtypes.append(bsmStaticArgToType(arg));
}
return argtypes.toList();
}
private Type bsmStaticArgToType(Object arg) {
Assert.checkNonNull(arg);
if (arg instanceof ClassSymbol) {
return syms.classType;
} else if (arg instanceof Integer) {
return syms.intType;
} else if (arg instanceof Long) {
return syms.longType;
} else if (arg instanceof Float) {
return syms.floatType;
} else if (arg instanceof Double) {
return syms.doubleType;
} else if (arg instanceof String) {
return syms.stringType;
} else if (arg instanceof Pool.MethodHandle) {
return syms.methodHandleType;
} else if (arg instanceof MethodType) {
return syms.methodTypeType;
} else {
Assert.error("bad static arg " + arg.getClass());
return null;
}
}
/**
* Get the opcode associated with this method reference
*/
private int referenceKind(Symbol refSym) {
if (refSym.isConstructor()) {
return ClassFile.REF_newInvokeSpecial;
} else {
if (refSym.isStatic()) {
return ClassFile.REF_invokeStatic;
} else if ((refSym.flags() & PRIVATE) != 0) {
return ClassFile.REF_invokeSpecial;
} else if (refSym.enclClass().isInterface()) {
return ClassFile.REF_invokeInterface;
} else {
return ClassFile.REF_invokeVirtual;
}
}
}
// <editor-fold defaultstate="collapsed" desc="Lambda/reference analyzer">
/**
* This visitor collects information about translation of a lambda expression.
* More specifically, it keeps track of the enclosing contexts and captured locals
* accessed by the lambda being translated (as well as other useful info).
* It also translates away problems for LambdaToMethod.
*/
class LambdaAnalyzerPreprocessor extends TreeTranslator {
/** the frame stack - used to reconstruct translation info about enclosing scopes */
private List<Frame> frameStack;
/**
* keep the count of lambda expression (used to generate unambiguous
* names)
*/
private int lambdaCount = 0;
/**
* keep the count of lambda expression defined in given context (used to
* generate unambiguous names for serializable lambdas)
*/
private class SyntheticMethodNameCounter {
private Map<String, Integer> map = new HashMap<>();
int getIndex(StringBuilder buf) {
String temp = buf.toString();
Integer count = map.get(temp);
if (count == null) {
count = 0;
}
++count;
map.put(temp, count);
return count;
}
}
private SyntheticMethodNameCounter syntheticMethodNameCounts =
new SyntheticMethodNameCounter();
private Map<Symbol, JCClassDecl> localClassDefs;
/**
* maps for fake clinit symbols to be used as owners of lambda occurring in
* a static var init context
*/
private Map<ClassSymbol, Symbol> clinits =
new HashMap<ClassSymbol, Symbol>();
private JCClassDecl analyzeAndPreprocessClass(JCClassDecl tree) {
frameStack = List.nil();
localClassDefs = new HashMap<Symbol, JCClassDecl>();
return translate(tree);
}
@Override
public void visitBlock(JCBlock tree) {
List<Frame> prevStack = frameStack;
try {
if (frameStack.nonEmpty() && frameStack.head.tree.hasTag(CLASSDEF)) {
frameStack = frameStack.prepend(new Frame(tree));
}
super.visitBlock(tree);
}
finally {
frameStack = prevStack;
}
}
@Override
public void visitClassDef(JCClassDecl tree) {
List<Frame> prevStack = frameStack;
int prevLambdaCount = lambdaCount;
SyntheticMethodNameCounter prevSyntheticMethodNameCounts =
syntheticMethodNameCounts;
Map<ClassSymbol, Symbol> prevClinits = clinits;
DiagnosticSource prevSource = log.currentSource();
try {
log.useSource(tree.sym.sourcefile);
lambdaCount = 0;
syntheticMethodNameCounts = new SyntheticMethodNameCounter();
prevClinits = new HashMap<ClassSymbol, Symbol>();
if (tree.sym.owner.kind == MTH) {
localClassDefs.put(tree.sym, tree);
}
if (directlyEnclosingLambda() != null) {
tree.sym.owner = owner();
if (tree.sym.hasOuterInstance()) {
//if a class is defined within a lambda, the lambda must capture
//its enclosing instance (if any)
TranslationContext<?> localContext = context();
while (localContext != null) {
if (localContext.tree.getTag() == LAMBDA) {
((LambdaTranslationContext)localContext)
.addSymbol(tree.sym.type.getEnclosingType().tsym, CAPTURED_THIS);
}
localContext = localContext.prev;
}
}
}
frameStack = frameStack.prepend(new Frame(tree));
super.visitClassDef(tree);
}
finally {
log.useSource(prevSource.getFile());
frameStack = prevStack;
lambdaCount = prevLambdaCount;
syntheticMethodNameCounts = prevSyntheticMethodNameCounts;
clinits = prevClinits;
}
}
@Override
public void visitIdent(JCIdent tree) {
if (context() != null && lambdaIdentSymbolFilter(tree.sym)) {
if (tree.sym.kind == VAR &&
tree.sym.owner.kind == MTH &&
tree.type.constValue() == null) {
TranslationContext<?> localContext = context();
while (localContext != null) {
if (localContext.tree.getTag() == LAMBDA) {
JCTree block = capturedDecl(localContext.depth, tree.sym);
if (block == null) break;
((LambdaTranslationContext)localContext)
.addSymbol(tree.sym, CAPTURED_VAR);
}
localContext = localContext.prev;
}
} else if (tree.sym.owner.kind == TYP) {
TranslationContext<?> localContext = context();
while (localContext != null) {
if (localContext.tree.hasTag(LAMBDA)) {
JCTree block = capturedDecl(localContext.depth, tree.sym);
if (block == null) break;
switch (block.getTag()) {
case CLASSDEF:
JCClassDecl cdecl = (JCClassDecl)block;
((LambdaTranslationContext)localContext)
.addSymbol(cdecl.sym, CAPTURED_THIS);
break;
default:
Assert.error("bad block kind");
}
}
localContext = localContext.prev;
}
}
}
super.visitIdent(tree);
}
@Override
public void visitLambda(JCLambda tree) {
analyzeLambda(tree, "lambda.stat");
}
private void analyzeLambda(JCLambda tree, JCExpression methodReferenceReceiver) {
// Translation of the receiver expression must occur first
JCExpression rcvr = translate(methodReferenceReceiver);
LambdaTranslationContext context = analyzeLambda(tree, "mref.stat.1");
if (rcvr != null) {
context.methodReferenceReceiver = rcvr;
}
}
private LambdaTranslationContext analyzeLambda(JCLambda tree, String statKey) {
List<Frame> prevStack = frameStack;
try {
LambdaTranslationContext context = new LambdaTranslationContext(tree);
if (dumpLambdaToMethodStats) {
log.note(tree, statKey, context.needsAltMetafactory(), context.translatedSym);
}
frameStack = frameStack.prepend(new Frame(tree));
for (JCVariableDecl param : tree.params) {
context.addSymbol(param.sym, PARAM);
frameStack.head.addLocal(param.sym);
}
contextMap.put(tree, context);
super.visitLambda(tree);
context.complete();
return context;
}
finally {
frameStack = prevStack;
}
}
@Override
public void visitMethodDef(JCMethodDecl tree) {
List<Frame> prevStack = frameStack;
try {
frameStack = frameStack.prepend(new Frame(tree));
super.visitMethodDef(tree);
}
finally {
frameStack = prevStack;
}
}
@Override
public void visitNewClass(JCNewClass tree) {
TypeSymbol def = tree.type.tsym;
boolean inReferencedClass = currentlyInClass(def);
boolean isLocal = def.isLocal();
if ((inReferencedClass && isLocal || lambdaNewClassFilter(context(), tree))) {
TranslationContext<?> localContext = context();
while (localContext != null) {
if (localContext.tree.getTag() == LAMBDA) {
((LambdaTranslationContext)localContext)
.addSymbol(tree.type.getEnclosingType().tsym, CAPTURED_THIS);
}
localContext = localContext.prev;
}
}
if (context() != null && !inReferencedClass && isLocal) {
LambdaTranslationContext lambdaContext = (LambdaTranslationContext)context();
captureLocalClassDefs(def, lambdaContext);
}
super.visitNewClass(tree);
}
//where
void captureLocalClassDefs(Symbol csym, final LambdaTranslationContext lambdaContext) {
JCClassDecl localCDef = localClassDefs.get(csym);
if (localCDef != null && lambdaContext.freeVarProcessedLocalClasses.add(csym)) {
BasicFreeVarCollector fvc = lower.new BasicFreeVarCollector() {
@Override
void addFreeVars(ClassSymbol c) {
captureLocalClassDefs(c, lambdaContext);
}
@Override
void visitSymbol(Symbol sym) {
if (sym.kind == VAR &&
sym.owner.kind == MTH &&
((VarSymbol)sym).getConstValue() == null) {
TranslationContext<?> localContext = context();
while (localContext != null) {
if (localContext.tree.getTag() == LAMBDA) {
JCTree block = capturedDecl(localContext.depth, sym);
if (block == null) break;
((LambdaTranslationContext)localContext).addSymbol(sym, CAPTURED_VAR);
}
localContext = localContext.prev;
}
}
}
};
fvc.scan(localCDef);
}
}
//where
boolean currentlyInClass(Symbol csym) {
for (Frame frame : frameStack) {
if (frame.tree.hasTag(JCTree.Tag.CLASSDEF)) {
JCClassDecl cdef = (JCClassDecl) frame.tree;
if (cdef.sym == csym) {
return true;
}
}
}
return false;
}
/**
* Method references to local class constructors, may, if the local
* class references local variables, have implicit constructor
* parameters added in Lower; As a result, the invokedynamic bootstrap
* information added in the LambdaToMethod pass will have the wrong
* signature. Hooks between Lower and LambdaToMethod have been added to
* handle normal "new" in this case. This visitor converts potentially
* affected method references into a lambda containing a normal
* expression.
*
* @param tree
*/
@Override
public void visitReference(JCMemberReference tree) {
ReferenceTranslationContext rcontext = new ReferenceTranslationContext(tree);
contextMap.put(tree, rcontext);
if (rcontext.needsConversionToLambda()) {
// Convert to a lambda, and process as such
MemberReferenceToLambda conv = new MemberReferenceToLambda(tree, rcontext, owner());
analyzeLambda(conv.lambda(), conv.getReceiverExpression());
} else {
super.visitReference(tree);
if (dumpLambdaToMethodStats) {
log.note(tree, "mref.stat", rcontext.needsAltMetafactory(), null);
}
}
}
@Override
public void visitSelect(JCFieldAccess tree) {
if (context() != null && tree.sym.kind == VAR &&
(tree.sym.name == names._this ||
tree.sym.name == names._super)) {
// A select of this or super means, if we are in a lambda,
// we much have an instance context
TranslationContext<?> localContext = context();
while (localContext != null) {
if (localContext.tree.hasTag(LAMBDA)) {
JCClassDecl clazz = (JCClassDecl)capturedDecl(localContext.depth, tree.sym);
if (clazz == null) break;
((LambdaTranslationContext)localContext).addSymbol(clazz.sym, CAPTURED_THIS);
}
localContext = localContext.prev;
}
}
super.visitSelect(tree);
}
@Override
public void visitVarDef(JCVariableDecl tree) {
TranslationContext<?> context = context();
LambdaTranslationContext ltc = (context != null && context instanceof LambdaTranslationContext)?
(LambdaTranslationContext)context :
null;
if (ltc != null) {
if (frameStack.head.tree.hasTag(LAMBDA)) {
ltc.addSymbol(tree.sym, LOCAL_VAR);
}
// Check for type variables (including as type arguments).
// If they occur within class nested in a lambda, mark for erasure
Type type = tree.sym.asType();
if (inClassWithinLambda() && !types.isSameType(types.erasure(type), type)) {
ltc.addSymbol(tree.sym, TYPE_VAR);
}
}
List<Frame> prevStack = frameStack;
try {
if (tree.sym.owner.kind == MTH) {
frameStack.head.addLocal(tree.sym);
}
frameStack = frameStack.prepend(new Frame(tree));
super.visitVarDef(tree);
}
finally {
frameStack = prevStack;
}
}
/**
* Return a valid owner given the current declaration stack
* (required to skip synthetic lambda symbols)
*/
private Symbol owner() {
return owner(false);
}
@SuppressWarnings("fallthrough")
private Symbol owner(boolean skipLambda) {
List<Frame> frameStack2 = frameStack;
while (frameStack2.nonEmpty()) {
switch (frameStack2.head.tree.getTag()) {
case VARDEF:
if (((JCVariableDecl)frameStack2.head.tree).sym.isLocal()) {
frameStack2 = frameStack2.tail;
break;
}
JCClassDecl cdecl = (JCClassDecl)frameStack2.tail.head.tree;
return initSym(cdecl.sym,
((JCVariableDecl)frameStack2.head.tree).sym.flags() & STATIC);
case BLOCK:
JCClassDecl cdecl2 = (JCClassDecl)frameStack2.tail.head.tree;
return initSym(cdecl2.sym,
((JCBlock)frameStack2.head.tree).flags & STATIC);
case CLASSDEF:
return ((JCClassDecl)frameStack2.head.tree).sym;
case METHODDEF:
return ((JCMethodDecl)frameStack2.head.tree).sym;
case LAMBDA:
if (!skipLambda)
return ((LambdaTranslationContext)contextMap
.get(frameStack2.head.tree)).translatedSym;
default:
frameStack2 = frameStack2.tail;
}
}
Assert.error();
return null;
}
private Symbol initSym(ClassSymbol csym, long flags) {
boolean isStatic = (flags & STATIC) != 0;
if (isStatic) {
/* static clinits are generated in Gen, so we need to use a fake
* one. Attr creates a fake clinit method while attributing
* lambda expressions used as initializers of static fields, so
* let's use that one.
*/
MethodSymbol clinit = attr.removeClinit(csym);
if (clinit != null) {
clinits.put(csym, clinit);
return clinit;
}
/* if no clinit is found at Attr, then let's try at clinits.
*/
clinit = (MethodSymbol)clinits.get(csym);
if (clinit == null) {
/* no luck, let's create a new one
*/
clinit = makePrivateSyntheticMethod(STATIC,
names.clinit,
new MethodType(List.<Type>nil(), syms.voidType,
List.<Type>nil(), syms.methodClass),
csym);
clinits.put(csym, clinit);
}
return clinit;
} else {
//get the first constructor and treat it as the instance init sym
for (Symbol s : csym.members_field.getElementsByName(names.init)) {
return s;
}
}
Assert.error("init not found");
return null;
}
private JCTree directlyEnclosingLambda() {
if (frameStack.isEmpty()) {
return null;
}
List<Frame> frameStack2 = frameStack;
while (frameStack2.nonEmpty()) {
switch (frameStack2.head.tree.getTag()) {
case CLASSDEF:
case METHODDEF:
return null;
case LAMBDA:
return frameStack2.head.tree;
default:
frameStack2 = frameStack2.tail;
}
}
Assert.error();
return null;
}
private boolean inClassWithinLambda() {
if (frameStack.isEmpty()) {
return false;
}
List<Frame> frameStack2 = frameStack;
boolean classFound = false;
while (frameStack2.nonEmpty()) {
switch (frameStack2.head.tree.getTag()) {
case LAMBDA:
return classFound;
case CLASSDEF:
classFound = true;
frameStack2 = frameStack2.tail;
break;
default:
frameStack2 = frameStack2.tail;
}
}
// No lambda
return false;
}
/**
* Return the declaration corresponding to a symbol in the enclosing
* scope; the depth parameter is used to filter out symbols defined
* in nested scopes (which do not need to undergo capture).
*/
private JCTree capturedDecl(int depth, Symbol sym) {
int currentDepth = frameStack.size() - 1;
for (Frame block : frameStack) {
switch (block.tree.getTag()) {
case CLASSDEF:
ClassSymbol clazz = ((JCClassDecl)block.tree).sym;
if (sym.isMemberOf(clazz, types)) {
return currentDepth > depth ? null : block.tree;
}
break;
case VARDEF:
if (((JCVariableDecl)block.tree).sym == sym &&
sym.owner.kind == MTH) { //only locals are captured
return currentDepth > depth ? null : block.tree;
}
break;
case BLOCK:
case METHODDEF:
case LAMBDA:
if (block.locals != null && block.locals.contains(sym)) {
return currentDepth > depth ? null : block.tree;
}
break;
default:
Assert.error("bad decl kind " + block.tree.getTag());
}
currentDepth--;
}
return null;
}
private TranslationContext<?> context() {
for (Frame frame : frameStack) {
TranslationContext<?> context = contextMap.get(frame.tree);
if (context != null) {
return context;
}
}
return null;
}
/**
* This is used to filter out those identifiers that needs to be adjusted
* when translating away lambda expressions
*/
private boolean lambdaIdentSymbolFilter(Symbol sym) {
return (sym.kind == VAR || sym.kind == MTH)
&& !sym.isStatic()
&& sym.name != names.init;
}
/**
* This is used to filter out those new class expressions that need to
* be qualified with an enclosing tree
*/
private boolean lambdaNewClassFilter(TranslationContext<?> context, JCNewClass tree) {
if (context != null
&& tree.encl == null
&& tree.def == null
&& !tree.type.getEnclosingType().hasTag(NONE)) {
Type encl = tree.type.getEnclosingType();
Type current = context.owner.enclClass().type;
while (!current.hasTag(NONE)) {
if (current.tsym.isSubClass(encl.tsym, types)) {
return true;
}
current = current.getEnclosingType();
}
return false;
} else {
return false;
}
}
private class Frame {
final JCTree tree;
List<Symbol> locals;
public Frame(JCTree tree) {
this.tree = tree;
}
void addLocal(Symbol sym) {
if (locals == null) {
locals = List.nil();
}
locals = locals.prepend(sym);
}
}
/**
* This class is used to store important information regarding translation of
* lambda expression/method references (see subclasses).
*/
private abstract class TranslationContext<T extends JCFunctionalExpression> {
/** the underlying (untranslated) tree */
final T tree;
/** points to the adjusted enclosing scope in which this lambda/mref expression occurs */
final Symbol owner;
/** the depth of this lambda expression in the frame stack */
final int depth;
/** the enclosing translation context (set for nested lambdas/mref) */
final TranslationContext<?> prev;
/** list of methods to be bridged by the meta-factory */
final List<Symbol> bridges;
TranslationContext(T tree) {
this.tree = tree;
this.owner = owner();
this.depth = frameStack.size() - 1;
this.prev = context();
ClassSymbol csym =
types.makeFunctionalInterfaceClass(attrEnv, names.empty, tree.targets, ABSTRACT | INTERFACE);
this.bridges = types.functionalInterfaceBridges(csym);
}
/** does this functional expression need to be created using alternate metafactory? */
boolean needsAltMetafactory() {
return tree.targets.length() > 1 ||
isSerializable() ||
bridges.length() > 1;
}
/** does this functional expression require serialization support? */
boolean isSerializable() {
if (forceSerializable) {
return true;
}
for (Type target : tree.targets) {
if (types.asSuper(target, syms.serializableType.tsym) != null) {
return true;
}
}
return false;
}
/**
* @return Name of the enclosing method to be folded into synthetic
* method name
*/
String enclosingMethodName() {
return syntheticMethodNameComponent(owner.name);
}
/**
* @return Method name in a form that can be folded into a
* component of a synthetic method name
*/
String syntheticMethodNameComponent(Name name) {
if (name == null) {
return "null";
}
String methodName = name.toString();
if (methodName.equals("<clinit>")) {
methodName = "static";
} else if (methodName.equals("<init>")) {
methodName = "new";
}
return methodName;
}
}
/**
* This class retains all the useful information about a lambda expression;
* the contents of this class are filled by the LambdaAnalyzer visitor,
* and the used by the main translation routines in order to adjust references
* to captured locals/members, etc.
*/
private class LambdaTranslationContext extends TranslationContext<JCLambda> {
/** variable in the enclosing context to which this lambda is assigned */
final Symbol self;
/** variable in the enclosing context to which this lambda is assigned */
final Symbol assignedTo;
Map<LambdaSymbolKind, Map<Symbol, Symbol>> translatedSymbols;
/** the synthetic symbol for the method hoisting the translated lambda */
MethodSymbol translatedSym;
List<JCVariableDecl> syntheticParams;
/**
* to prevent recursion, track local classes processed
*/
final Set<Symbol> freeVarProcessedLocalClasses;
/**
* For method references converted to lambdas. The method
* reference receiver expression. Must be treated like a captured
* variable.
*/
JCExpression methodReferenceReceiver;
LambdaTranslationContext(JCLambda tree) {
super(tree);
Frame frame = frameStack.head;
switch (frame.tree.getTag()) {
case VARDEF:
assignedTo = self = ((JCVariableDecl) frame.tree).sym;
break;
case ASSIGN:
self = null;
assignedTo = TreeInfo.symbol(((JCAssign) frame.tree).getVariable());
break;
default:
assignedTo = self = null;
break;
}
// This symbol will be filled-in in complete
this.translatedSym = makePrivateSyntheticMethod(0, null, null, owner.enclClass());
translatedSymbols = new EnumMap<>(LambdaSymbolKind.class);
translatedSymbols.put(PARAM, new LinkedHashMap<Symbol, Symbol>());
translatedSymbols.put(LOCAL_VAR, new LinkedHashMap<Symbol, Symbol>());
translatedSymbols.put(CAPTURED_VAR, new LinkedHashMap<Symbol, Symbol>());
translatedSymbols.put(CAPTURED_THIS, new LinkedHashMap<Symbol, Symbol>());
translatedSymbols.put(TYPE_VAR, new LinkedHashMap<Symbol, Symbol>());
freeVarProcessedLocalClasses = new HashSet<>();
}
/**
* For a serializable lambda, generate a disambiguating string
* which maximizes stability across deserialization.
*
* @return String to differentiate synthetic lambda method names
*/
private String serializedLambdaDisambiguation() {
StringBuilder buf = new StringBuilder();
// Append the enclosing method signature to differentiate
// overloaded enclosing methods. For lambdas enclosed in
// lambdas, the generated lambda method will not have type yet,
// but the enclosing method's name will have been generated
// with this same method, so it will be unique and never be
// overloaded.
Assert.check(
owner.type != null ||
directlyEnclosingLambda() != null);
if (owner.type != null) {
buf.append(typeSig(owner.type));
buf.append(":");
}
// Add target type info
buf.append(types.findDescriptorSymbol(tree.type.tsym).owner.flatName());
buf.append(" ");
// Add variable assigned to
if (assignedTo != null) {
buf.append(assignedTo.flatName());
buf.append("=");
}
//add captured locals info: type, name, order
for (Symbol fv : getSymbolMap(CAPTURED_VAR).keySet()) {
if (fv != self) {
buf.append(typeSig(fv.type));
buf.append(" ");
buf.append(fv.flatName());
buf.append(",");
}
}
return buf.toString();
}
/**
* For a non-serializable lambda, generate a simple method.
*
* @return Name to use for the synthetic lambda method name
*/
private Name lambdaName() {
return names.lambda.append(names.fromString(enclosingMethodName() + "$" + lambdaCount++));
}
/**
* For a serializable lambda, generate a method name which maximizes
* name stability across deserialization.
*
* @return Name to use for the synthetic lambda method name
*/
private Name serializedLambdaName() {
StringBuilder buf = new StringBuilder();
buf.append(names.lambda);
// Append the name of the method enclosing the lambda.
buf.append(enclosingMethodName());
buf.append('$');
// Append a hash of the disambiguating string : enclosing method
// signature, etc.
String disam = serializedLambdaDisambiguation();
buf.append(Integer.toHexString(disam.hashCode()));
buf.append('$');
// The above appended name components may not be unique, append
// a count based on the above name components.
buf.append(syntheticMethodNameCounts.getIndex(buf));
String result = buf.toString();
//System.err.printf("serializedLambdaName: %s -- %s\n", result, disam);
return names.fromString(result);
}
/**
* Translate a symbol of a given kind into something suitable for the
* synthetic lambda body
*/
Symbol translate(final Symbol sym, LambdaSymbolKind skind) {
Symbol ret;
switch (skind) {
case CAPTURED_THIS:
ret = sym; // self represented
break;
case TYPE_VAR:
// Just erase the type var
ret = new VarSymbol(sym.flags(), sym.name,
types.erasure(sym.type), sym.owner);
/* this information should also be kept for LVT generation at Gen
* a Symbol with pos < startPos won't be tracked.
*/
((VarSymbol)ret).pos = ((VarSymbol)sym).pos;
break;
case CAPTURED_VAR:
ret = new VarSymbol(SYNTHETIC | FINAL | PARAMETER, sym.name, types.erasure(sym.type), translatedSym) {
@Override
public Symbol baseSymbol() {
//keep mapping with original captured symbol
return sym;
}
};
break;
case LOCAL_VAR:
ret = new VarSymbol(sym.flags() & FINAL, sym.name, sym.type, translatedSym);
((VarSymbol) ret).pos = ((VarSymbol) sym).pos;
break;
case PARAM:
ret = new VarSymbol((sym.flags() & FINAL) | PARAMETER, sym.name, types.erasure(sym.type), translatedSym);
((VarSymbol) ret).pos = ((VarSymbol) sym).pos;
break;
default:
Assert.error(skind.name());
throw new AssertionError();
}
if (ret != sym) {
ret.setDeclarationAttributes(sym.getRawAttributes());
ret.setTypeAttributes(sym.getRawTypeAttributes());
}
return ret;
}
void addSymbol(Symbol sym, LambdaSymbolKind skind) {
Map<Symbol, Symbol> transMap = getSymbolMap(skind);
if (!transMap.containsKey(sym)) {
transMap.put(sym, translate(sym, skind));
}
}
Map<Symbol, Symbol> getSymbolMap(LambdaSymbolKind skind) {
Map<Symbol, Symbol> m = translatedSymbols.get(skind);
Assert.checkNonNull(m);
return m;
}
JCTree translate(JCIdent lambdaIdent) {
for (Map<Symbol, Symbol> m : translatedSymbols.values()) {
if (m.containsKey(lambdaIdent.sym)) {
Symbol tSym = m.get(lambdaIdent.sym);
JCTree t = make.Ident(tSym).setType(lambdaIdent.type);
tSym.setTypeAttributes(lambdaIdent.sym.getRawTypeAttributes());
return t;
}
}
return null;
}
/**
* The translatedSym is not complete/accurate until the analysis is
* finished. Once the analysis is finished, the translatedSym is
* "completed" -- updated with type information, access modifiers,
* and full parameter list.
*/
void complete() {
if (syntheticParams != null) {
return;
}
boolean inInterface = translatedSym.owner.isInterface();
boolean thisReferenced = !getSymbolMap(CAPTURED_THIS).isEmpty();
// If instance access isn't needed, make it static.
// Interface instance methods must be default methods.
// Lambda methods are private synthetic.
// Inherit ACC_STRICT from the enclosing method, or, for clinit,
// from the class.
translatedSym.flags_field = SYNTHETIC | LAMBDA_METHOD |
owner.flags_field & STRICTFP |
owner.owner.flags_field & STRICTFP |
PRIVATE |
(thisReferenced? (inInterface? DEFAULT : 0) : STATIC);
//compute synthetic params
ListBuffer<JCVariableDecl> params = new ListBuffer<>();
ListBuffer<VarSymbol> parameterSymbols = new ListBuffer<>();
// The signature of the method is augmented with the following
// synthetic parameters:
//
// 1) reference to enclosing contexts captured by the lambda expression
// 2) enclosing locals captured by the lambda expression
for (Symbol thisSym : getSymbolMap(CAPTURED_VAR).values()) {
params.append(make.VarDef((VarSymbol) thisSym, null));
parameterSymbols.append((VarSymbol) thisSym);
}
for (Symbol thisSym : getSymbolMap(PARAM).values()) {
params.append(make.VarDef((VarSymbol) thisSym, null));
parameterSymbols.append((VarSymbol) thisSym);
}
syntheticParams = params.toList();
translatedSym.params = parameterSymbols.toList();
// Compute and set the lambda name
translatedSym.name = isSerializable()
? serializedLambdaName()
: lambdaName();
//prepend synthetic args to translated lambda method signature
translatedSym.type = types.createMethodTypeWithParameters(
generatedLambdaSig(),
TreeInfo.types(syntheticParams));
}
Type generatedLambdaSig() {
return types.erasure(tree.getDescriptorType(types));
}
}
/**
* This class retains all the useful information about a method reference;
* the contents of this class are filled by the LambdaAnalyzer visitor,
* and the used by the main translation routines in order to adjust method
* references (i.e. in case a bridge is needed)
*/
private final class ReferenceTranslationContext extends TranslationContext<JCMemberReference> {
final boolean isSuper;
final Symbol sigPolySym;
ReferenceTranslationContext(JCMemberReference tree) {
super(tree);
this.isSuper = tree.hasKind(ReferenceKind.SUPER);
this.sigPolySym = isSignaturePolymorphic()
? makePrivateSyntheticMethod(tree.sym.flags(),
tree.sym.name,
bridgedRefSig(),
tree.sym.enclClass())
: null;
}
/**
* Get the opcode associated with this method reference
*/
int referenceKind() {
return LambdaToMethod.this.referenceKind(tree.sym);
}
boolean needsVarArgsConversion() {
return tree.varargsElement != null;
}
/**
* @return Is this an array operation like clone()
*/
boolean isArrayOp() {
return tree.sym.owner == syms.arrayClass;
}
boolean receiverAccessible() {
//hack needed to workaround 292 bug (7087658)
//when 292 issue is fixed we should remove this and change the backend
//code to always generate a method handle to an accessible method
return tree.ownerAccessible;
}
/**
* The VM does not support access across nested classes (8010319).
* Were that ever to change, this should be removed.
*/
boolean isPrivateInOtherClass() {
return (tree.sym.flags() & PRIVATE) != 0 &&
!types.isSameType(
types.erasure(tree.sym.enclClass().asType()),
types.erasure(owner.enclClass().asType()));
}
/**
* Signature polymorphic methods need special handling.
* e.g. MethodHandle.invoke() MethodHandle.invokeExact()
*/
final boolean isSignaturePolymorphic() {
return tree.sym.kind == MTH &&
types.isSignaturePolymorphic((MethodSymbol)tree.sym);
}
/**
* Erasure destroys the implementation parameter subtype
* relationship for intersection types
*/
boolean interfaceParameterIsIntersectionType() {
List<Type> tl = tree.getDescriptorType(types).getParameterTypes();
if (tree.kind == ReferenceKind.UNBOUND) {
tl = tl.tail;
}
for (; tl.nonEmpty(); tl = tl.tail) {
Type pt = tl.head;
if (pt.getKind() == TypeKind.TYPEVAR) {
TypeVar tv = (TypeVar) pt;
if (tv.bound.getKind() == TypeKind.INTERSECTION) {
return true;
}
}
}
return false;
}
/**
* Does this reference need to be converted to a lambda
* (i.e. var args need to be expanded or "super" is used)
*/
final boolean needsConversionToLambda() {
return interfaceParameterIsIntersectionType() ||
isSuper ||
needsVarArgsConversion() ||
isArrayOp() ||
isPrivateInOtherClass() ||
!receiverAccessible() ||
(tree.getMode() == ReferenceMode.NEW &&
tree.kind != ReferenceKind.ARRAY_CTOR &&
(tree.sym.owner.isLocal() || tree.sym.owner.isInner()));
}
Type generatedRefSig() {
return types.erasure(tree.sym.type);
}
Type bridgedRefSig() {
return types.erasure(types.findDescriptorSymbol(tree.targets.head.tsym).type);
}
}
}
// </editor-fold>
/*
* These keys provide mappings for various translated lambda symbols
* and the prevailing order must be maintained.
*/
enum LambdaSymbolKind {
PARAM, // original to translated lambda parameters
LOCAL_VAR, // original to translated lambda locals
CAPTURED_VAR, // variables in enclosing scope to translated synthetic parameters
CAPTURED_THIS, // class symbols to translated synthetic parameters (for captured member access)
TYPE_VAR; // original to translated lambda type variables
}
/**
* ****************************************************************
* Signature Generation
* ****************************************************************
*/
private String typeSig(Type type) {
L2MSignatureGenerator sg = new L2MSignatureGenerator();
sg.assembleSig(type);
return sg.toString();
}
private String classSig(Type type) {
L2MSignatureGenerator sg = new L2MSignatureGenerator();
sg.assembleClassSig(type);
return sg.toString();
}
/**
* Signature Generation
*/
private class L2MSignatureGenerator extends Types.SignatureGenerator {
/**
* An output buffer for type signatures.
*/
StringBuilder sb = new StringBuilder();
L2MSignatureGenerator() {
super(types);
}
@Override
protected void append(char ch) {
sb.append(ch);
}
@Override
protected void append(byte[] ba) {
sb.append(new String(ba));
}
@Override
protected void append(Name name) {
sb.append(name.toString());
}
@Override
public String toString() {
return sb.toString();
}
}
}