8334037: Local class creation in lambda in pre-construction context crashes javac

8333313: NullPointerException in lambda instantiating an inner local class in prologue
8333766: Stack overflow with anonymous class in super() parameter
8334679: Wrong bug number in regression test for JDK-8334252

Co-authored-by: Archie Cobbs <acobbs@openjdk.org>
Reviewed-by: jlahoda, vromero
This commit is contained in:
Maurizio Cimadamore 2024-06-26 09:12:02 +00:00
parent 7f6804ceb6
commit 4ce8822b6c
15 changed files with 793 additions and 751 deletions

View File

@ -60,8 +60,8 @@ public class CompileStates extends HashMap<Env<AttrContext>, CompileStates.Compi
FLOW(5),
TRANSTYPES(6),
TRANSPATTERNS(7),
UNLAMBDA(8),
LOWER(9),
LOWER(8),
UNLAMBDA(9),
GENERATE(10);
CompileState(int value) {

View File

@ -40,20 +40,15 @@ 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.resources.CompilerProperties.Notes;
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;
@ -72,7 +67,6 @@ import static com.sun.tools.javac.code.TypeTag.*;
import static com.sun.tools.javac.tree.JCTree.Tag.*;
import javax.lang.model.element.ElementKind;
import javax.lang.model.type.TypeKind;
import com.sun.tools.javac.main.Option;
@ -126,9 +120,6 @@ public class LambdaToMethod extends TreeTranslator {
/** deduplicate lambda implementation methods */
private final boolean deduplicateLambdas;
/** lambda proxy is a dynamic nestmate */
private final boolean nestmateLambdas;
/** Flag for alternate metafactories indicating the lambda object is intended to be serializable */
public static final int FLAG_SERIALIZABLE = 1 << 0;
@ -175,7 +166,6 @@ public class LambdaToMethod extends TreeTranslator {
debugLinesOrVars = lineDebugInfo || varDebugInfo;
verboseDeduplication = options.isSet("debug.dumpLambdaToMethodDeduplication");
deduplicateLambdas = options.getBoolean("deduplicateLambdas", true);
nestmateLambdas = Target.instance(context).runtimeUseNestAccess();
}
// </editor-fold>
@ -284,6 +274,7 @@ public class LambdaToMethod extends TreeTranslator {
this.attrEnv = env;
this.context = null;
this.contextMap = new HashMap<>();
cdef = analyzer.analyzeAndPreprocessClass((JCClassDecl) cdef);
return translate(cdef);
}
// </editor-fold>
@ -297,10 +288,6 @@ public class LambdaToMethod extends TreeTranslator {
*/
@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);
@ -418,9 +405,7 @@ public class LambdaToMethod extends TreeTranslator {
ListBuffer<JCExpression> syntheticInits = new ListBuffer<>();
if (localContext.methodReferenceReceiver != null) {
syntheticInits.append(localContext.methodReferenceReceiver);
} else if (!sym.isStatic()) {
if (!sym.isStatic()) {
syntheticInits.append(makeThis(
sym.owner.enclClass().asType(),
localContext.owner.enclClass()));
@ -433,11 +418,6 @@ public class LambdaToMethod extends TreeTranslator {
syntheticInits.append(captured_local);
}
}
// add captured outer this instances (used only when `this' capture itself is illegal)
for (Symbol fv : localContext.getSymbolMap(CAPTURED_OUTER_THIS).keySet()) {
JCExpression captured_local = make.QualThis(fv.type);
syntheticInits.append(captured_local);
}
//then, determine the arguments to the indy call
List<JCExpression> indy_args = translate(syntheticInits.toList(), localContext.prev);
@ -553,54 +533,6 @@ public class LambdaToMethod extends TreeTranslator {
}
}
/**
* Translate qualified `this' references within a lambda to the mapped identifier
* @param tree
*/
@Override
public void visitSelect(JCFieldAccess tree) {
if (context == null || !analyzer.lambdaFieldAccessFilter(tree)) {
super.visitSelect(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 {
super.visitSelect(tree);
}
} finally {
make.at(prevPos);
}
}
}
/**
* Translate instance creation expressions with implicit enclosing instances
* @param tree
*/
@Override
public void visitNewClass(JCNewClass tree) {
if (context == null || !analyzer.lambdaNewClassFilter(context, tree)) {
super.visitNewClass(tree);
} else {
int prevPos = make.pos;
try {
make.at(tree);
LambdaTranslationContext lambdaContext = (LambdaTranslationContext) context;
tree = lambdaContext.translate(tree);
super.visitNewClass(tree);
} finally {
make.at(prevPos);
}
}
}
@Override
public void visitVarDef(JCVariableDecl tree) {
LambdaTranslationContext lambdaContext = (LambdaTranslationContext)context;
@ -725,7 +657,7 @@ public class LambdaToMethod extends TreeTranslator {
deser.sym = kInfo.deserMethodSym;
deser.type = kInfo.deserMethodSym.type;
//System.err.printf("DESER: '%s'\n", deser);
return deser;
return lower.translateMethod(attrEnv, deser, make);
}
/** Make an attributed class instance creation expression.
@ -857,245 +789,8 @@ public class LambdaToMethod extends TreeTranslator {
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;
case TYPECAST: setVarargsIfNeeded(((JCTypeCast) tree).expr, 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 == 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.target = tree.target;
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 parameter type
Type parmType = implPTypes.head;
if (checkForIntersection) {
if (descPTypes.head.getKind() == TypeKind.INTERSECTION) {
parmType = samPTypes.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 (descPTypes.head.getKind() == TypeKind.TYPEVAR) {
TypeVar tv = (TypeVar) descPTypes.head;
if (tv.getUpperBound().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);
boolean protAccess =
isProtectedInSuperClassOfEnclosingClassInOtherPackage(tree.sym, owner);
Type rcvrType = tree.ownerAccessible && !protAccess ? 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.nil(), select,
convertArgs(tree.sym, args.toList(), tree.varargsElement)).
setType(tree.sym.erasure(types).getReturnType());
apply = transTypes.coerce(attrEnv, apply,
types.erasure(localContext.tree.referentType.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)
// but this may need to be patched up later with the proxy for the outer this
JCNewClass newClass = make.NewClass(null,
List.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(),
@ -1238,11 +933,6 @@ public class LambdaToMethod extends TreeTranslator {
*/
private int lambdaCount = 0;
/**
* List of types undergoing construction, i.e., in an early construction context.
*/
private List<ClassSymbol> typesUnderConstruction;
/**
* keep the count of lambda expression defined in given context (used to
* generate unambiguous names for serializable lambdas)
@ -1273,30 +963,10 @@ public class LambdaToMethod extends TreeTranslator {
private JCClassDecl analyzeAndPreprocessClass(JCClassDecl tree) {
frameStack = List.nil();
typesUnderConstruction = List.nil();
localClassDefs = new HashMap<>();
return translate(tree);
}
@Override
public void visitApply(JCMethodInvocation tree) {
super.visitApply(tree);
if (TreeInfo.isConstructorCall(tree)) {
Assert.check(typesUnderConstruction.head == currentClass());
typesUnderConstruction = typesUnderConstruction.tail; // end of early construction context
}
}
// where
private ClassSymbol currentClass() {
for (Frame frame : frameStack) {
if (frame.tree.hasTag(JCTree.Tag.CLASSDEF)) {
JCClassDecl cdef = (JCClassDecl) frame.tree;
return cdef.sym;
}
}
return null;
}
@Override
public void visitBlock(JCBlock tree) {
List<Frame> prevStack = frameStack;
@ -1329,21 +999,6 @@ public class LambdaToMethod extends TreeTranslator {
}
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();
final TypeSymbol outerInstanceSymbol = tree.sym.type.getEnclosingType().tsym;
while (localContext != null && !localContext.owner.isStatic()) {
if (localContext.tree.hasTag(LAMBDA)) {
JCTree block = capturedDecl(localContext.depth, outerInstanceSymbol);
if (block == null) break;
((LambdaTranslationContext)localContext)
.addSymbol(outerInstanceSymbol, CAPTURED_THIS);
}
localContext = localContext.prev;
}
}
}
frameStack = frameStack.prepend(new Frame(tree));
super.visitClassDef(tree);
@ -1398,16 +1053,7 @@ public class LambdaToMethod extends TreeTranslator {
@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;
}
analyzeLambda(tree, tree.wasMethodReference ? "mref.stat.1" : "lambda.stat");
}
private LambdaTranslationContext analyzeLambda(JCLambda tree, String statKey) {
@ -1434,87 +1080,16 @@ public class LambdaToMethod extends TreeTranslator {
@Override
public void visitMethodDef(JCMethodDecl tree) {
List<ClassSymbol> prevTypesUnderConstruction = typesUnderConstruction;
List<Frame> prevStack = frameStack;
try {
if (TreeInfo.isConstructor(tree)) // start early construction context (Object() notwithstanding)
typesUnderConstruction = typesUnderConstruction.prepend(currentClass());
frameStack = frameStack.prepend(new Frame(tree));
super.visitMethodDef(tree);
} finally {
}
finally {
frameStack = prevStack;
typesUnderConstruction = prevTypesUnderConstruction;
}
}
@Override
public void visitNewClass(JCNewClass tree) {
TypeSymbol def = tree.type.tsym;
boolean inReferencedClass = currentlyInClass(def);
boolean isLocal = def.isDirectlyOrIndirectlyLocal();
if ((inReferencedClass && isLocal || lambdaNewClassFilter(context(), tree))) {
TranslationContext<?> localContext = context();
final TypeSymbol outerInstanceSymbol = tree.type.getEnclosingType().tsym;
while (localContext != null && !localContext.owner.isStatic()) {
if (localContext.tree.hasTag(LAMBDA)) {
if (outerInstanceSymbol != null) {
JCTree block = capturedDecl(localContext.depth, outerInstanceSymbol);
if (block == null) break;
}
((LambdaTranslationContext)localContext)
.addSymbol(outerInstanceSymbol, CAPTURED_THIS);
}
localContext = localContext.prev;
}
}
super.visitNewClass(tree);
if (context() != null && !inReferencedClass && isLocal) {
LambdaTranslationContext lambdaContext = (LambdaTranslationContext)context();
captureLocalClassDefs(def, lambdaContext);
}
}
//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
@ -1531,15 +1106,9 @@ public class LambdaToMethod extends TreeTranslator {
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, Notes.MrefStat(rcontext.needsAltMetafactory(), null));
}
super.visitReference(tree);
if (dumpLambdaToMethodStats) {
log.note(tree, Notes.MrefStat(rcontext.needsAltMetafactory(), null));
}
}
@ -1773,42 +1342,6 @@ public class LambdaToMethod extends TreeTranslator {
&& sym.name != names.init;
}
/**
* This is used to filter out those select nodes that need to be adjusted
* when translating away lambda expressions - at the moment, this is the
* set of nodes that select `this' (qualified this)
*/
private boolean lambdaFieldAccessFilter(JCFieldAccess fAccess) {
return (context instanceof LambdaTranslationContext lambdaContext)
&& !fAccess.sym.isStatic()
&& fAccess.name == names._this
&& (fAccess.sym.owner.kind == TYP)
&& !lambdaContext.translatedSymbols.get(CAPTURED_OUTER_THIS).isEmpty();
}
/**
* 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;
@ -1918,18 +1451,6 @@ public class LambdaToMethod extends TreeTranslator {
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;
@ -1960,13 +1481,10 @@ public class LambdaToMethod extends TreeTranslator {
}
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(CAPTURED_OUTER_THIS, new LinkedHashMap<Symbol, Symbol>());
freeVarProcessedLocalClasses = new HashSet<>();
translatedSymbols.put(PARAM, new LinkedHashMap<>());
translatedSymbols.put(LOCAL_VAR, new LinkedHashMap<>());
translatedSymbols.put(CAPTURED_VAR, new LinkedHashMap<>());
translatedSymbols.put(CAPTURED_THIS, new LinkedHashMap<>());
}
/**
@ -2066,16 +1584,6 @@ public class LambdaToMethod extends TreeTranslator {
}
};
break;
case CAPTURED_OUTER_THIS:
Name name = names.fromString(sym.flatName().toString().replace('.', '$') + names.dollarThis);
ret = new VarSymbol(SYNTHETIC | FINAL | PARAMETER, 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) {
@Override
@ -2114,14 +1622,6 @@ public class LambdaToMethod extends TreeTranslator {
}
void addSymbol(Symbol sym, LambdaSymbolKind skind) {
if (skind == CAPTURED_THIS && sym != null && sym.kind == TYP && !typesUnderConstruction.isEmpty()) {
ClassSymbol currentClass = currentClass();
if (currentClass != null && typesUnderConstruction.contains(currentClass)) {
// reference must be to enclosing outer instance, mutate capture kind.
Assert.check(sym != currentClass); // should have been caught right in Attr
skind = CAPTURED_OUTER_THIS;
}
}
Map<Symbol, Symbol> transMap = getSymbolMap(skind);
if (!transMap.containsKey(sym)) {
transMap.put(sym, translate(sym, skind));
@ -2145,54 +1645,11 @@ public class LambdaToMethod extends TreeTranslator {
return t;
}
break;
case CAPTURED_OUTER_THIS:
Optional<Symbol> proxy = m.keySet().stream()
.filter(out -> lambdaIdent.sym.isMemberOf(out.type.tsym, types))
.reduce((a, b) -> a.isEnclosedBy((ClassSymbol)b) ? a : b);
if (proxy.isPresent()) {
// Transform outer instance variable references anchoring them to the captured synthetic.
Symbol tSym = m.get(proxy.get());
JCExpression t = make.Ident(tSym).setType(lambdaIdent.sym.owner.type);
t = make.Select(t, lambdaIdent.name);
t.setType(lambdaIdent.type);
TreeInfo.setSymbol(t, lambdaIdent.sym);
return t;
}
break;
}
}
return null;
}
/* Translate away qualified this expressions, anchoring them to synthetic parameters that
capture the qualified this handle. `fieldAccess' is guaranteed to one such.
*/
public JCTree translate(JCFieldAccess fieldAccess) {
Assert.check(fieldAccess.name == names._this);
Map<Symbol, Symbol> m = translatedSymbols.get(LambdaSymbolKind.CAPTURED_OUTER_THIS);
if (m.containsKey(fieldAccess.sym.owner)) {
Symbol tSym = m.get(fieldAccess.sym.owner);
JCExpression t = make.Ident(tSym).setType(fieldAccess.sym.owner.type);
return t;
}
return null;
}
/* Translate away naked new instance creation expressions with implicit enclosing instances,
anchoring them to synthetic parameters that stand proxy for the qualified outer this handle.
*/
public JCNewClass translate(JCNewClass newClass) {
Assert.check(newClass.clazz.type.tsym.hasOuterInstance() && newClass.encl == null);
Map<Symbol, Symbol> m = translatedSymbols.get(LambdaSymbolKind.CAPTURED_OUTER_THIS);
final Type enclosingType = newClass.clazz.type.getEnclosingType();
if (m.containsKey(enclosingType.tsym)) {
Symbol tSym = m.get(enclosingType.tsym);
JCExpression encl = make.Ident(tSym).setType(enclosingType);
newClass.encl = encl;
}
return newClass;
}
/**
* The translatedSym is not complete/accurate until the analysis is
* finished. Once the analysis is finished, the translatedSym is
@ -2230,10 +1687,6 @@ public class LambdaToMethod extends TreeTranslator {
params.append(make.VarDef((VarSymbol) thisSym, null));
parameterSymbols.append((VarSymbol) thisSym);
}
for (Symbol thisSym : getSymbolMap(CAPTURED_OUTER_THIS).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);
@ -2259,102 +1712,12 @@ public class LambdaToMethod extends TreeTranslator {
}
/**
* 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)
* Simple subclass modelling the translation context of a method reference.
*/
final class ReferenceTranslationContext extends TranslationContext<JCMemberReference> {
final boolean isSuper;
ReferenceTranslationContext(JCMemberReference tree) {
super(tree);
this.isSuper = tree.hasKind(ReferenceKind.SUPER);
}
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;
}
/**
* This method should be called only when target release <= 14
* where LambdaMetaFactory does not spin nestmate classes.
*
* This method should be removed when --release 14 is not supported.
*/
boolean isPrivateInOtherClass() {
assert !nestmateLambdas;
return (tree.sym.flags() & PRIVATE) != 0 &&
!types.isSameType(
types.erasure(tree.sym.enclClass().asType()),
types.erasure(owner.enclClass().asType()));
}
/**
* Erasure destroys the implementation parameter subtype
* relationship for intersection types.
* Have similar problems for union types too.
*/
boolean interfaceParameterIsIntersectionOrUnionType() {
List<Type> tl = tree.getDescriptorType(types).getParameterTypes();
for (; tl.nonEmpty(); tl = tl.tail) {
Type pt = tl.head;
if (isIntersectionOrUnionType(pt))
return true;
}
return false;
}
boolean isIntersectionOrUnionType(Type t) {
switch (t.getKind()) {
case INTERSECTION:
case UNION:
return true;
case TYPEVAR:
TypeVar tv = (TypeVar) t;
return isIntersectionOrUnionType(tv.getUpperBound());
}
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 interfaceParameterIsIntersectionOrUnionType() ||
isSuper ||
needsVarArgsConversion() ||
isArrayOp() ||
(!nestmateLambdas && isPrivateInOtherClass()) ||
isProtectedInSuperClassOfEnclosingClassInOtherPackage(tree.sym, owner) ||
!receiverAccessible() ||
(tree.getMode() == ReferenceMode.NEW &&
tree.kind != ReferenceKind.ARRAY_CTOR &&
(tree.sym.owner.isDirectlyOrIndirectlyLocal() || tree.sym.owner.isInner()));
}
Type generatedRefSig() {
return types.erasure(tree.sym.type);
}
Type bridgedRefSig() {
return types.erasure(types.findDescriptorSymbol(tree.target.tsym).type);
}
}
}
@ -2368,14 +1731,12 @@ public class LambdaToMethod extends TreeTranslator {
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)
CAPTURED_OUTER_THIS; // used when `this' capture is illegal, but outer this capture is legit (JDK-8129740)
CAPTURED_THIS; // class symbols to translated synthetic parameters (for captured member access)
boolean propagateAnnotations() {
switch (this) {
case CAPTURED_VAR:
case CAPTURED_THIS:
case CAPTURED_OUTER_THIS:
return false;
default:
return true;
@ -2417,12 +1778,6 @@ public class LambdaToMethod extends TreeTranslator {
}
}
private boolean isProtectedInSuperClassOfEnclosingClassInOtherPackage(Symbol targetReference,
Symbol currentClass) {
return ((targetReference.flags() & PROTECTED) != 0 &&
targetReference.packge() != currentClass.packge());
}
/**
* Signature Generation
*/

View File

@ -28,6 +28,8 @@ package com.sun.tools.javac.comp;
import java.util.*;
import java.util.stream.Collectors;
import com.sun.source.tree.LambdaExpressionTree.BodyKind;
import com.sun.source.tree.MemberReferenceTree.ReferenceMode;
import com.sun.tools.javac.code.*;
import com.sun.tools.javac.code.Kinds.KindSelector;
import com.sun.tools.javac.code.Scope.WriteableScope;
@ -35,7 +37,9 @@ import com.sun.tools.javac.jvm.*;
import com.sun.tools.javac.jvm.PoolConstant.LoadableConstant;
import com.sun.tools.javac.main.Option.PkgInfo;
import com.sun.tools.javac.resources.CompilerProperties.Fragments;
import com.sun.tools.javac.resources.CompilerProperties.Notes;
import com.sun.tools.javac.tree.*;
import com.sun.tools.javac.tree.JCTree.JCMemberReference.ReferenceKind;
import com.sun.tools.javac.util.*;
import com.sun.tools.javac.util.JCDiagnostic.DiagnosticPosition;
import com.sun.tools.javac.util.List;
@ -62,6 +66,9 @@ import com.sun.tools.javac.tree.JCTree.JCExpression;
import com.sun.tools.javac.tree.JCTree.JCExpressionStatement;
import static com.sun.tools.javac.tree.JCTree.JCOperatorExpression.OperandPos.LEFT;
import com.sun.tools.javac.tree.JCTree.JCSwitchExpression;
import javax.lang.model.type.TypeKind;
import static com.sun.tools.javac.tree.JCTree.Tag.*;
/** This pass translates away some syntactic sugar: inner classes,
@ -103,6 +110,7 @@ public class Lower extends TreeTranslator {
private final boolean optimizeOuterThis;
private final boolean useMatchException;
private final HashMap<TypePairs, String> typePairToName;
private int variableIndex = 0;
@SuppressWarnings("this-escape")
protected Lower(Context context) {
@ -170,6 +178,11 @@ public class Lower extends TreeTranslator {
*/
Map<Symbol,Symbol> actualSymbols;
/**
* The current expected return type.
*/
Type currentRestype;
/** The current method definition.
*/
JCMethodDecl currentMethodDef;
@ -186,12 +199,6 @@ public class Lower extends TreeTranslator {
*/
JCTree outermostMemberDef;
/** A map from local variable symbols to their translation (as per LambdaToMethod).
* This is required when a capturing local class is created from a lambda (in which
* case the captured symbols should be replaced with the translated lambda symbols).
*/
Map<Symbol, Symbol> lambdaTranslationMap = null;
/** A navigator class for assembling a mapping from local class symbols
* to class definition trees.
* There is only one case; all other cases simply traverse down the tree.
@ -1233,14 +1240,10 @@ public class Lower extends TreeTranslator {
make.at(tree.pos);
return makeLit(sym.type, cv);
}
if (lambdaTranslationMap != null && lambdaTranslationMap.get(sym) != null) {
return make.at(tree.pos).Ident(lambdaTranslationMap.get(sym));
} else {
// Otherwise replace the variable by its proxy.
sym = proxies.get(sym);
Assert.check(sym != null && (sym.flags_field & FINAL) != 0);
tree = make.at(tree.pos).Ident(sym);
}
// Otherwise replace the variable by its proxy.
sym = proxies.get(sym);
Assert.check(sym != null && (sym.flags_field & FINAL) != 0);
tree = make.at(tree.pos).Ident(sym);
}
JCExpression base = (tree.hasTag(SELECT)) ? ((JCFieldAccess) tree).selected : null;
switch (sym.kind) {
@ -1325,14 +1328,6 @@ public class Lower extends TreeTranslator {
accessBase(tree.pos(), sym), sym).setType(tree.type);
}
}
} else if (sym.owner.kind == MTH && lambdaTranslationMap != null) {
//sym is a local variable - check the lambda translation map to
//see if sym has been translated to something else in the current
//scope (by LambdaToMethod)
Symbol translatedSym = lambdaTranslationMap.get(sym.baseSymbol());
if (translatedSym != null) {
tree = make.at(tree.pos).Ident(translatedSym);
}
}
}
return tree;
@ -2779,15 +2774,21 @@ public class Lower extends TreeTranslator {
syms.methodClass);
}
Type prevRestype = currentRestype;
JCMethodDecl prevMethodDef = currentMethodDef;
MethodSymbol prevMethodSym = currentMethodSym;
int prevVariableIndex = variableIndex;
try {
currentRestype = types.erasure(tree.type.getReturnType());
currentMethodDef = tree;
currentMethodSym = tree.sym;
variableIndex = 0;
visitMethodDefInternal(tree);
} finally {
currentRestype = prevRestype;
currentMethodDef = prevMethodDef;
currentMethodSym = prevMethodSym;
variableIndex = prevVariableIndex;
}
}
@ -2866,16 +2867,7 @@ public class Lower extends TreeTranslator {
outerThisStack = prevOuterThisStack;
} else {
Map<Symbol, Symbol> prevLambdaTranslationMap =
lambdaTranslationMap;
try {
lambdaTranslationMap = (tree.sym.flags() & SYNTHETIC) != 0 &&
tree.sym.name.startsWith(names.lambda) ?
makeTranslationMap(tree) : null;
super.visitMethodDef(tree);
} finally {
lambdaTranslationMap = prevLambdaTranslationMap;
}
super.visitMethodDef(tree);
}
if (tree.name == names.init && ((tree.sym.flags_field & Flags.COMPACT_RECORD_CONSTRUCTOR) != 0 ||
(tree.sym.flags_field & (GENERATEDCONSTR | RECORD)) == (GENERATEDCONSTR | RECORD))) {
@ -2901,17 +2893,6 @@ public class Lower extends TreeTranslator {
}
result = tree;
}
//where
private Map<Symbol, Symbol> makeTranslationMap(JCMethodDecl tree) {
Map<Symbol, Symbol> translationMap = new HashMap<>();
for (JCVariableDecl vd : tree.params) {
Symbol p = vd.sym;
if (p != p.baseSymbol()) {
translationMap.put(p.baseSymbol(), p);
}
}
return translationMap;
}
public void visitTypeCast(JCTypeCast tree) {
tree.clazz = translate(tree.clazz);
@ -2973,7 +2954,7 @@ public class Lower extends TreeTranslator {
// preserving the side effects of the value
VarSymbol dollar_s = new VarSymbol(FINAL | SYNTHETIC,
names.fromString("tmp" + tree.pos + this.target.syntheticNameChar()),
names.fromString("tmp" + variableIndex++ + this.target.syntheticNameChar()),
types.erasure(tree.expr.type),
currentMethodSym);
JCStatement var = make.at(tree.pos())
@ -3132,13 +3113,7 @@ public class Lower extends TreeTranslator {
// If we have an anonymous class, create its flat version, rather
// than the class or interface following new.
if (tree.def != null) {
Map<Symbol, Symbol> prevLambdaTranslationMap = lambdaTranslationMap;
try {
lambdaTranslationMap = null;
translate(tree.def);
} finally {
lambdaTranslationMap = prevLambdaTranslationMap;
}
translate(tree.def);
tree.clazz = access(make_at(tree.clazz.pos()).Ident(tree.def.sym));
tree.def = null;
@ -3358,6 +3333,9 @@ public class Lower extends TreeTranslator {
return;
}
}
if (tree.args.stream().anyMatch(c -> c == null)) {
throw new AssertionError("Whooops before: " + tree);
}
result = tree;
}
@ -3845,6 +3823,7 @@ public class Lower extends TreeTranslator {
public void visitVarDef(JCVariableDecl tree) {
MethodSymbol oldMethodSym = currentMethodSym;
int prevVariableIndex = variableIndex;
tree.mods = translate(tree.mods);
tree.vartype = translate(tree.vartype);
if (currentMethodSym == null) {
@ -3854,9 +3833,13 @@ public class Lower extends TreeTranslator {
names.empty, null,
currentClass);
}
if (tree.init != null) tree.init = translate(tree.init, tree.type);
result = tree;
currentMethodSym = oldMethodSym;
try {
if (tree.init != null) tree.init = translate(tree.init, tree.type);
result = tree;
} finally {
currentMethodSym = oldMethodSym;
variableIndex = prevVariableIndex;
}
}
public void visitBlock(JCBlock tree) {
@ -3868,8 +3851,14 @@ public class Lower extends TreeTranslator {
names.empty, null,
currentClass);
}
super.visitBlock(tree);
currentMethodSym = oldMethodSym;
int prevVariableIndex = variableIndex;
try {
variableIndex = 0;
super.visitBlock(tree);
} finally {
currentMethodSym = oldMethodSym;
variableIndex = prevVariableIndex;
}
}
public void visitDoLoop(JCDoWhileLoop tree) {
@ -3896,11 +3885,355 @@ public class Lower extends TreeTranslator {
public void visitReturn(JCReturn tree) {
if (tree.expr != null)
tree.expr = translate(tree.expr,
types.erasure(currentMethodDef
.restype.type));
currentRestype);
result = tree;
}
@Override
public void visitLambda(JCLambda tree) {
Type prevRestype = currentRestype;
try {
currentRestype = types.erasure(tree.getDescriptorType(types)).getReturnType();
tree.body = tree.getBodyKind() == BodyKind.EXPRESSION ?
translate((JCExpression) tree.body, currentRestype) :
translate(tree.body);
} finally {
currentRestype = prevRestype;
}
result = tree;
}
@Override
public void visitReference(JCMemberReference tree) {
if (needsConversionToLambda(tree)) {
// Convert to a lambda, and process as such
MemberReferenceToLambda conv = new MemberReferenceToLambda(tree);
result = translate(conv.lambda());
} else {
super.visitReference(tree);
}
}
// where
boolean needsVarArgsConversion(JCMemberReference tree) {
return tree.varargsElement != null;
}
/**
* @return Is this an array operation like clone()
*/
boolean isArrayOp(JCMemberReference tree) {
return tree.sym.owner == syms.arrayClass;
}
boolean receiverAccessible(JCMemberReference tree) {
//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;
}
/**
* Erasure destroys the implementation parameter subtype
* relationship for intersection types.
* Have similar problems for union types too.
*/
boolean interfaceParameterIsIntersectionOrUnionType(JCMemberReference tree) {
List<Type> tl = tree.getDescriptorType(types).getParameterTypes();
for (; tl.nonEmpty(); tl = tl.tail) {
Type pt = tl.head;
if (isIntersectionOrUnionType(pt))
return true;
}
return false;
}
boolean isIntersectionOrUnionType(Type t) {
switch (t.getKind()) {
case INTERSECTION:
case UNION:
return true;
case TYPEVAR:
TypeVar tv = (TypeVar) t;
return isIntersectionOrUnionType(tv.getUpperBound());
}
return false;
}
private boolean isProtectedInSuperClassOfEnclosingClassInOtherPackage(Symbol targetReference,
Symbol currentClass) {
return ((targetReference.flags() & PROTECTED) != 0 &&
targetReference.packge() != currentClass.packge());
}
/**
* This method should be called only when target release <= 14
* where LambdaMetaFactory does not spin nestmate classes.
*
* This method should be removed when --release 14 is not supported.
*/
boolean isPrivateInOtherClass(JCMemberReference tree) {
assert !target.runtimeUseNestAccess();
return (tree.sym.flags() & PRIVATE) != 0 &&
!types.isSameType(
types.erasure(tree.sym.enclClass().asType()),
types.erasure(currentClass.asType()));
}
/**
* Does this reference need to be converted to a lambda
* (i.e. var args need to be expanded or "super" is used)
*/
boolean needsConversionToLambda(JCMemberReference tree) {
return interfaceParameterIsIntersectionOrUnionType(tree) ||
tree.hasKind(ReferenceKind.SUPER) ||
needsVarArgsConversion(tree) ||
isArrayOp(tree) ||
(!target.runtimeUseNestAccess() && isPrivateInOtherClass(tree)) ||
isProtectedInSuperClassOfEnclosingClassInOtherPackage(tree.sym, currentClass) ||
!receiverAccessible(tree) ||
(tree.getMode() == ReferenceMode.NEW &&
tree.kind != ReferenceKind.ARRAY_CTOR &&
(tree.sym.owner.isDirectlyOrIndirectlyLocal() || tree.sym.owner.isInner()));
}
/**
* Converts a method reference which cannot be used directly into a lambda
*/
private class MemberReferenceToLambda {
private final JCMemberReference tree;
private final ListBuffer<JCExpression> args = new ListBuffer<>();
private final ListBuffer<JCVariableDecl> params = new ListBuffer<>();
private final MethodSymbol owner = new MethodSymbol(0, names.empty, Type.noType, currentClass);
private JCExpression receiverExpression = null;
MemberReferenceToLambda(JCMemberReference tree) {
this.tree = tree;
}
JCExpression 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.target = tree.target;
slam.type = tree.type;
slam.pos = tree.pos;
slam.wasMethodReference = true;
if (receiverExpression != null) {
// use a let expression so that the receiver expression is evaluated eagerly
return make.at(tree.pos).LetExpr(
make.VarDef(rcvr, translate(receiverExpression)), slam).setType(tree.type);
} else {
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 = types.erasure(types.findDescriptorSymbol(tree.target.tsym).type);
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 = new VarSymbol(SYNTHETIC, names.fromString("rec$"), tree.getQualifierExpression().type, owner);
rcvr.pos = tree.pos;
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 = needsVarArgsConversion(tree) ? 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 parameter type
Type parmType = implPTypes.head;
if (checkForIntersection) {
if (descPTypes.head.getKind() == TypeKind.INTERSECTION) {
parmType = samPTypes.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 (descPTypes.head.getKind() == TypeKind.TYPEVAR) {
TypeVar tv = (TypeVar) descPTypes.head;
if (tv.getUpperBound().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;
}
private JCExpression makeReceiver(VarSymbol rcvr) {
if (rcvr == null) return null;
JCExpression rcvrExpr = make.Ident(rcvr);
boolean protAccess =
isProtectedInSuperClassOfEnclosingClassInOtherPackage(tree.sym, currentClass);
Type rcvrType = tree.ownerAccessible && !protAccess ? 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.nil(), select,
convertArgs(tree.sym, args.toList(), tree.varargsElement)).
setType(tree.sym.erasure(types).getReturnType());
apply = transTypes.coerce(attrEnv, apply,
types.erasure(tree.referentType.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)
// but this may need to be patched up later with the proxy for the outer this
JCNewClass newClass = make.NewClass(null,
List.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;
}
}
/**
* 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 == 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);
}
/**
* 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;
case TYPECAST: setVarargsIfNeeded(((JCTypeCast) tree).expr, varargsElement); break;
default: throw new AssertionError();
}
}
}
public void visitSwitch(JCSwitch tree) {
List<JCCase> cases = tree.patternSwitch ? addDefaultIfNeeded(tree.patternSwitch,
tree.wasEnumSelector,
@ -4018,7 +4351,7 @@ public class Lower extends TreeTranslator {
//switch ($selector != null ? $mapVar[$selector.ordinal()] : -1) {...}
//replacing case null with case -1:
VarSymbol dollar_s = new VarSymbol(FINAL|SYNTHETIC,
names.fromString("s" + tree.pos + this.target.syntheticNameChar()),
names.fromString("s" + variableIndex++ + this.target.syntheticNameChar()),
selector.type,
currentMethodSym);
JCStatement var = make.at(tree.pos()).VarDef(dollar_s, selector).setType(dollar_s.type);
@ -4175,13 +4508,13 @@ public class Lower extends TreeTranslator {
*/
VarSymbol dollar_s = new VarSymbol(FINAL|SYNTHETIC,
names.fromString("s" + tree.pos + target.syntheticNameChar()),
names.fromString("s" + variableIndex++ + target.syntheticNameChar()),
syms.stringType,
currentMethodSym);
stmtList.append(make.at(tree.pos()).VarDef(dollar_s, selector).setType(dollar_s.type));
VarSymbol dollar_tmp = new VarSymbol(SYNTHETIC,
names.fromString("tmp" + tree.pos + target.syntheticNameChar()),
names.fromString("tmp" + variableIndex++ + target.syntheticNameChar()),
syms.intType,
currentMethodSym);
JCVariableDecl dollar_tmp_def =
@ -4323,7 +4656,7 @@ public class Lower extends TreeTranslator {
while (constants.contains(replacementValue)) replacementValue++;
VarSymbol dollar_s = new VarSymbol(FINAL|SYNTHETIC,
names.fromString("s" + tree.pos + this.target.syntheticNameChar()),
names.fromString("s" + variableIndex++ + this.target.syntheticNameChar()),
selector.type,
currentMethodSym);
JCStatement var = make.at(tree.pos()).VarDef(dollar_s, selector).setType(dollar_s.type);
@ -4381,7 +4714,7 @@ public class Lower extends TreeTranslator {
TreeInfo.name(tree.selected) == names._super &&
!types.isDirectSuperInterface(((JCFieldAccess)tree.selected).selected.type.tsym, currentClass);
tree.selected = translate(tree.selected);
if (tree.name == names._class) {
if (tree.name == names._class && tree.selected.type.isPrimitiveOrVoid()) {
result = classOf(tree.selected);
}
else if (tree.name == names._super &&
@ -4457,6 +4790,7 @@ public class Lower extends TreeTranslator {
this.make = make;
endPosTable = env.toplevel.endPositions;
currentClass = null;
currentRestype = null;
currentMethodDef = null;
outermostClassDef = (cdef.hasTag(CLASSDEF)) ? (JCClassDecl)cdef : null;
outermostMemberDef = null;
@ -4486,6 +4820,7 @@ public class Lower extends TreeTranslator {
this.make = null;
endPosTable = null;
currentClass = null;
currentRestype = null;
currentMethodDef = null;
outermostClassDef = null;
outermostMemberDef = null;
@ -4505,4 +4840,23 @@ public class Lower extends TreeTranslator {
}
return translated.toList();
}
// needed for the lambda deserialization method, which is expressed as a big switch on strings
public JCMethodDecl translateMethod(Env<AttrContext> env, JCMethodDecl methodDecl, TreeMaker make) {
try {
this.attrEnv = env;
this.make = make;
this.currentClass = methodDecl.sym.enclClass();
proxies = new HashMap<>();
return translate(methodDecl);
} finally {
this.attrEnv = null;
this.make = null;
this.currentClass = null;
// the two fields below are set when visiting the method
this.currentMethodSym = null;
this.currentMethodDef = null;
this.proxies = null;
}
}
}

View File

@ -1618,14 +1618,6 @@ public class JavaCompiler {
compileStates.put(env, CompileState.TRANSPATTERNS);
if (scanner.hasLambdas) {
if (shouldStop(CompileState.UNLAMBDA))
return;
env.tree = LambdaToMethod.instance(context).translateTopLevelClass(env, env.tree, localMake);
compileStates.put(env, CompileState.UNLAMBDA);
}
if (shouldStop(CompileState.LOWER))
return;
@ -1647,6 +1639,16 @@ public class JavaCompiler {
if (shouldStop(CompileState.LOWER))
return;
if (scanner.hasLambdas) {
if (shouldStop(CompileState.UNLAMBDA))
return;
for (JCTree def : cdefs) {
LambdaToMethod.instance(context).translateTopLevelClass(env, def, localMake);
}
compileStates.put(env, CompileState.UNLAMBDA);
}
//generate code for each class
for (List<JCTree> l = cdefs; l.nonEmpty(); l = l.tail) {
JCClassDecl cdef = (JCClassDecl)l.head;

View File

@ -2013,6 +2013,7 @@ public abstract class JCTree implements Tree, Cloneable, DiagnosticPosition {
public JCTree body;
public boolean canCompleteNormally = true;
public ParameterKind paramKind;
public boolean wasMethodReference;
public JCLambda(List<JCVariableDecl> params,
JCTree body) {

View File

@ -344,11 +344,13 @@ public class TreeInfo {
@Override
public void visitClassDef(JCClassDecl tree) {
// don't descend any further
result = tree;
}
@Override
public void visitLambda(JCLambda tree) {
// don't descend any further
result = tree;
}
}

View File

@ -0,0 +1,42 @@
/*
* Copyright (c) 2024, 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.
*
* 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.
*/
/*
* @test
* @bug 8333766
* @summary Test for compiler crash when anonymous class created in early lambda
*/
public class AnonSuperLambdaCrash {
class Inner {
Inner() {
this(() -> new Object() { { AnonSuperLambdaCrash.this.hashCode(); } });
}
Inner(Runnable r) {
r.run();
}
}
public static void main(String[] args) {
new AnonSuperLambdaCrash().new Inner();
}
}

View File

@ -0,0 +1,43 @@
/*
* Copyright (c) 2024, 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.
*
* 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.
*/
/*
* @test
* @bug 8333313
* @summary Verify references to local classes declared in early construction contexts
* @enablePreview
*/
public class EarlyLocalTest1 {
class Test {
Test() {
class InnerLocal { }
Runnable r = () -> new InnerLocal();
r.run();
super();
}
}
public static void main(String[] args) {
new EarlyLocalTest1().new Test();
}
}

View File

@ -0,0 +1,47 @@
/*
* Copyright (c) 2024, 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.
*
* 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.
*/
/*
* @test
* @bug 8333313
* @summary Verify references to local classes declared in early construction contexts
* @enablePreview
*/
public class EarlyLocalTest4 {
class Test {
Test() {
class InnerLocal { }
Runnable r = new Runnable() {
public void run() {
new InnerLocal();
}
};
r.run();
super();
}
}
public static void main(String[] args) {
new EarlyLocalTest4().new Test();
}
}

View File

@ -0,0 +1,49 @@
/*
* Copyright (c) 2024, 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.
*
* 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.
*/
/*
* @test
* @bug 8333313
* @summary Verify references to local classes declared in early construction contexts
* @enablePreview
*/
import java.util.concurrent.atomic.AtomicReference;
public class EarlyLocalTest5 {
int y;
class Test extends AtomicReference<Runnable> {
Test(int x) {
class Foo implements Runnable {
public void run() {
System.out.println(x + y);
}
}
super(new Foo());
}
}
public static void main(String[] args) {
new EarlyLocalTest5().new Test(42);
}
}

View File

@ -0,0 +1,48 @@
/*
* Copyright (c) 2024, 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.
*
* 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.
*/
/*
* @test
* @bug 8333313
* @summary Verify references to local classes declared in early construction contexts
* @enablePreview
*/
import java.util.concurrent.atomic.AtomicReference;
public class EarlyLocalTest6 {
int y;
class Test extends AtomicReference<Runnable> {
Test(int x) {
super(new Runnable() {
public void run() {
System.out.println(x + y);
}
});
}
}
public static void main(String[] args) {
new EarlyLocalTest6().new Test(42);
}
}

View File

@ -0,0 +1,44 @@
/*
* Copyright (c) 2024, 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.
*
* 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.
*/
/*
* @test
* @bug 8333313
* @summary Verify references to local classes declared in early construction contexts
* @enablePreview
*/
import java.util.concurrent.atomic.AtomicReference;
public class EarlyLocalTest7 {
int y;
class Test extends AtomicReference<Runnable> {
Test(int x) {
super(() -> System.out.println(x + y));
}
}
public static void main(String[] args) {
new EarlyLocalTest7().new Test(42);
}
}

View File

@ -0,0 +1,57 @@
/*
* Copyright (c) 2024, 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.
*
* 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.
*/
/*
* @test
* @bug 8334037
* @summary Test for compiler crash when local class created in early lambda
* @enablePreview
*/
public class LambdaLocalEarlyCrash {
interface A { }
class Inner {
Inner() {
this(() -> {
class Local {
void g() {
m();
}
}
new Local().g(); // error
});
}
Inner(Runnable tr) {
tr.run();
}
}
void m() {
System.out.println("Hello");
}
public static void main(String[] args) {
new LambdaLocalEarlyCrash().new Inner();
}
}

View File

@ -22,7 +22,7 @@
*/
/*
* @test
* @bug 8194743
* @bug 8334252
* @summary Test lambda declared in early construction context
* @enablePreview
*/

View File

@ -69,29 +69,27 @@ class Universe {
}
Planet(String name, int moonsCount) {
this(name, moonsCount, java.lang.invoke.LambdaMetafactory.metafactory(name, Universe.Galaxy.this, Universe.Galaxy.SolarSystem.this));
this(name, moonsCount, ()->{
String n = name;
StringBuffer buf = new StringBuffer();
buf.append("This planet belongs to the galaxy " + Galaxy.this.name + " with " + starsCount + " stars\n");
buf.append("This planet belongs to the galaxy " + Universe.Galaxy.this.name + " with " + starsCount() + " stars\n");
buf.append("This planet belongs to the galaxy " + Galaxy.this.name() + " with " + starsCount() + " stars\n");
buf.append("This planet belongs to the galaxy " + Universe.Galaxy.this.name() + " with " + (Universe.Galaxy.this).starsCount() + " stars\n");
buf.append("This planet belongs to the solar system " + SolarSystem.this.name + " with " + planetsCount + " planets\n");
buf.append("This planet belongs to the solar system " + Galaxy.SolarSystem.this.name + " with " + planetsCount() + " planets\n");
buf.append("This planet belongs to the solar system " + (SolarSystem.this).name + " with " + planetsCount + " planets\n");
buf.append("This planet belongs to the solar system " + Universe.Galaxy.SolarSystem.this.name + " with " + Universe.Galaxy.SolarSystem.this.planetsCount + " planets\n");
buf.append("This planet belongs to the solar system " + Universe.Galaxy.SolarSystem.this.name.toLowerCase().toUpperCase() + " with " + Universe.Galaxy.SolarSystem.this.planetsCount + " planets\n");
buf.append("This planet belongs to the solar system " + copy(Universe.Galaxy.SolarSystem.this).name.toLowerCase().toUpperCase() + " with " + Universe.Galaxy.SolarSystem.this.planetsCount + " planets\n");
if (!buf.toString().equals(output)) throw new AssertionError("Unexpected value\n" + buf);
});
}
static final String output = "This planet belongs to the galaxy Mily way with 23456789 stars\nThis planet belongs to the galaxy Mily way with 23456789 stars\nThis planet belongs to the galaxy Mily way with 23456789 stars\nThis planet belongs to the galaxy Mily way with 23456789 stars\nThis planet belongs to the solar system Sun with 9 planets\nThis planet belongs to the solar system Sun with 9 planets\nThis planet belongs to the solar system Sun with 9 planets\nThis planet belongs to the solar system Sun with 9 planets\nThis planet belongs to the solar system SUN with 9 planets\nThis planet belongs to the solar system SUN with 9 planets\n";
public String toString() {
return "Planet " + name + " with " + moonsCount + " moon(s)";
}
/*synthetic*/ private static void lambda$new$0(/*synthetic*/ final String name, /*synthetic*/ final Universe.Galaxy Universe$Galaxy$this, /*synthetic*/ final Universe.Galaxy.SolarSystem Universe$Galaxy$SolarSystem$this) {
String n = name;
StringBuffer buf = new StringBuffer();
buf.append("This planet belongs to the galaxy " + Universe$Galaxy$this.name + " with " + Universe$Galaxy$this.starsCount + " stars\n");
buf.append("This planet belongs to the galaxy " + Universe$Galaxy$this.name + " with " + Universe$Galaxy$this.starsCount() + " stars\n");
buf.append("This planet belongs to the galaxy " + Universe$Galaxy$this.name() + " with " + Universe$Galaxy$this.starsCount() + " stars\n");
buf.append("This planet belongs to the galaxy " + Universe$Galaxy$this.name() + " with " + (Universe$Galaxy$this).starsCount() + " stars\n");
buf.append("This planet belongs to the solar system " + Universe$Galaxy$SolarSystem$this.name + " with " + Universe$Galaxy$SolarSystem$this.planetsCount + " planets\n");
buf.append("This planet belongs to the solar system " + Universe$Galaxy$SolarSystem$this.name + " with " + Universe$Galaxy$SolarSystem$this.planetsCount() + " planets\n");
buf.append("This planet belongs to the solar system " + (Universe$Galaxy$SolarSystem$this).name + " with " + Universe$Galaxy$SolarSystem$this.planetsCount + " planets\n");
buf.append("This planet belongs to the solar system " + Universe$Galaxy$SolarSystem$this.name + " with " + Universe$Galaxy$SolarSystem$this.planetsCount + " planets\n");
buf.append("This planet belongs to the solar system " + Universe$Galaxy$SolarSystem$this.name.toLowerCase().toUpperCase() + " with " + Universe$Galaxy$SolarSystem$this.planetsCount + " planets\n");
buf.append("This planet belongs to the solar system " + Universe$Galaxy$SolarSystem$this.copy(Universe$Galaxy$SolarSystem$this).name.toLowerCase().toUpperCase() + " with " + Universe$Galaxy$SolarSystem$this.planetsCount + " planets\n");
if (!buf.toString().equals(output)) throw new AssertionError("Unexpected value\n" + buf);
}
}
}
}