# HG changeset patch # User jjg # Date 1382133814 25200 # Node ID 7de97abc4a5c59d009f18df180fcf50c48d06ec3 # Parent 7af634b1fc5b1bafbe078f37bbfb5706857bcf46 8026749: Missing LV table in lambda bodies Reviewed-by: vromero, jlahoda diff -r 7af634b1fc5b -r 7de97abc4a5c src/share/classes/com/sun/tools/javac/code/Flags.java --- a/src/share/classes/com/sun/tools/javac/code/Flags.java Thu Oct 17 19:10:19 2013 -0700 +++ b/src/share/classes/com/sun/tools/javac/code/Flags.java Fri Oct 18 15:03:34 2013 -0700 @@ -270,6 +270,11 @@ */ public static final long POTENTIALLY_AMBIGUOUS = 1L<<48; + /** + * Flag that marks a synthetic method body for a lambda expression + */ + public static final long LAMBDA_METHOD = 1L<<49; + /** Modifier masks. */ public static final int @@ -378,7 +383,8 @@ NOT_IN_PROFILE(Flags.NOT_IN_PROFILE), BAD_OVERRIDE(Flags.BAD_OVERRIDE), SIGNATURE_POLYMORPHIC(Flags.SIGNATURE_POLYMORPHIC), - THROWS(Flags.THROWS); + THROWS(Flags.THROWS), + LAMBDA_METHOD(Flags.LAMBDA_METHOD); Flag(long flag) { this.value = flag; diff -r 7af634b1fc5b -r 7de97abc4a5c src/share/classes/com/sun/tools/javac/comp/Flow.java --- a/src/share/classes/com/sun/tools/javac/comp/Flow.java Thu Oct 17 19:10:19 2013 -0700 +++ b/src/share/classes/com/sun/tools/javac/comp/Flow.java Fri Oct 18 15:03:34 2013 -0700 @@ -1718,9 +1718,9 @@ if (tree.body == null) { return; } - /* MemberEnter can generate synthetic methods, ignore them + /* Ignore synthetic methods, except for translated lambda methods. */ - if ((tree.sym.flags() & SYNTHETIC) != 0) { + if ((tree.sym.flags() & (SYNTHETIC | LAMBDA_METHOD)) == SYNTHETIC) { return; } @@ -1795,7 +1795,7 @@ protected void initParam(JCVariableDecl def) { inits.incl(def.sym.adr); uninits.excl(def.sym.adr); - } + } public void visitVarDef(JCVariableDecl tree) { boolean track = trackable(tree.sym); diff -r 7af634b1fc5b -r 7de97abc4a5c src/share/classes/com/sun/tools/javac/comp/LambdaToMethod.java --- a/src/share/classes/com/sun/tools/javac/comp/LambdaToMethod.java Thu Oct 17 19:10:19 2013 -0700 +++ b/src/share/classes/com/sun/tools/javac/comp/LambdaToMethod.java Fri Oct 18 15:03:34 2013 -0700 @@ -30,6 +30,7 @@ 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.Flags; import com.sun.tools.javac.code.Kinds; import com.sun.tools.javac.code.Scope; import com.sun.tools.javac.code.Symbol; @@ -1315,7 +1316,9 @@ ListBuffer paramBuff = new ListBuffer(); int i = 0; for (List l = ptypes; l.nonEmpty(); l = l.tail) { - paramBuff.append(make.Param(make.paramName(i++), l.head, owner)); + JCVariableDecl param = make.Param(make.paramName(i++), l.head, owner); + param.sym.pos = tree.pos; + paramBuff.append(param); } List params = paramBuff.toList(); @@ -1755,7 +1758,7 @@ ((VarSymbol)ret).pos = ((VarSymbol)sym).pos; break; case CAPTURED_VAR: - ret = new VarSymbol(SYNTHETIC | FINAL, name, types.erasure(sym.type), translatedSym) { + ret = new VarSymbol(SYNTHETIC | FINAL | PARAMETER, name, types.erasure(sym.type), translatedSym) { @Override public Symbol baseSymbol() { //keep mapping with original captured symbol @@ -1763,8 +1766,17 @@ } }; break; + case LOCAL_VAR: + ret = new VarSymbol(FINAL, name, types.erasure(sym.type), translatedSym); + ((VarSymbol) ret).pos = ((VarSymbol) sym).pos; + break; + case PARAM: + ret = new VarSymbol(FINAL | PARAMETER, name, types.erasure(sym.type), translatedSym); + ((VarSymbol) ret).pos = ((VarSymbol) sym).pos; + break; default: ret = makeSyntheticVar(FINAL, name, types.erasure(sym.type), translatedSym); + ((VarSymbol) ret).pos = ((VarSymbol) sym).pos; } if (ret != sym) { ret.setDeclarationAttributes(sym.getRawAttributes()); @@ -1845,7 +1857,7 @@ // If instance access isn't needed, make it static. // Interface instance methods must be default methods. // Lambda methods are private synthetic. - translatedSym.flags_field = SYNTHETIC | + translatedSym.flags_field = SYNTHETIC | LAMBDA_METHOD | PRIVATE | (thisReferenced? (inInterface? DEFAULT : 0) : STATIC); diff -r 7af634b1fc5b -r 7de97abc4a5c src/share/classes/com/sun/tools/javac/jvm/Gen.java --- a/src/share/classes/com/sun/tools/javac/jvm/Gen.java Thu Oct 17 19:10:19 2013 -0700 +++ b/src/share/classes/com/sun/tools/javac/jvm/Gen.java Fri Oct 18 15:03:34 2013 -0700 @@ -2892,7 +2892,8 @@ @Override public void visitMethodDef(JCMethodDecl tree) { - if ((tree.sym.flags() & (SYNTHETIC | GENERATEDCONSTR)) != 0) { + if ((tree.sym.flags() & (SYNTHETIC | GENERATEDCONSTR)) != 0 + && (tree.sym.flags() & LAMBDA_METHOD) == 0) { return; } if (tree.name.equals(names.clinit)) { @@ -2906,6 +2907,7 @@ return; } currentMethod = tree.sym; + super.visitMethodDef(tree); } diff -r 7af634b1fc5b -r 7de97abc4a5c test/tools/javac/lambda/LocalVariableTable.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test/tools/javac/lambda/LocalVariableTable.java Fri Oct 18 15:03:34 2013 -0700 @@ -0,0 +1,220 @@ +/* + * Copyright (c) 2013, 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 8025998 8026749 + * @summary Missing LV table in lambda bodies + * @compile -g LocalVariableTable.java + * @run main LocalVariableTable + */ + +import java.io.*; +import java.lang.annotation.*; +import java.util.*; +import com.sun.tools.classfile.*; + +/* + * The test checks that a LocalVariableTable attribute is generated for the + * method bodies representing lambda expressions, and checks that the expected + * set of entries is found in the attribute. + * + * Since the bug was about missing entries in the LVT, not malformed entries, + * the test is not intended to be a detailed test of the contents of each + * LocalVariableTable entry: it is assumed that if a entry is present, it + * will have the correct contents. + * + * The test looks for test cases represented by nested classes whose + * name begins with "Lambda". Each such class contains a lambda expression + * that will mapped into a lambda method, and because the test is compiled + * with -g, these methods should have a LocalVariableTable. The set of + * expected names in the LVT is provided in an annotation on the class for + * the test case. + */ +public class LocalVariableTable { + public static void main(String... args) throws Exception { + new LocalVariableTable().run(); + } + + void run() throws Exception { + // the declared classes are returned in an unspecified order, + // so for neatness, sort them by name before processing them + Class[] classes = getClass().getDeclaredClasses(); + Arrays.sort(classes, (c1, c2) -> c1.getName().compareTo(c2.getName())); + + for (Class c : classes) { + if (c.getSimpleName().startsWith("Lambda")) + check(c); + } + if (errors > 0) + throw new Exception(errors + " errors found"); + } + + /** Check an individual test case. */ + void check(Class c) throws Exception { + System.err.println("Checking " + c.getSimpleName()); + + Expect expect = c.getAnnotation(Expect.class); + if (expect == null) { + error("@Expect not found for class " + c.getSimpleName()); + return; + } + + ClassFile cf = ClassFile.read(getClass().getResource(c.getName() + ".class").openStream()); + Method m = getLambdaMethod(cf); + if (m == null) { + error("lambda method not found"); + return; + } + + Code_attribute code = (Code_attribute) m.attributes.get(Attribute.Code); + if (code == null) { + error("Code attribute not found"); + return; + } + + LocalVariableTable_attribute lvt = + (LocalVariableTable_attribute) code.attributes.get(Attribute.LocalVariableTable); + if (lvt == null) { + error("LocalVariableTable attribute not found"); + return; + } + + Set foundNames = new LinkedHashSet<>(); + for (LocalVariableTable_attribute.Entry e: lvt.local_variable_table) { + foundNames.add(cf.constant_pool.getUTF8Value(e.name_index)); + } + + Set expectNames = new LinkedHashSet<>(Arrays.asList(expect.value())); + if (!foundNames.equals(expectNames)) { + Set foundOnly = new LinkedHashSet<>(foundNames); + foundOnly.removeAll(expectNames); + for (String s: foundOnly) + error("Unexpected name found: " + s); + Set expectOnly = new LinkedHashSet<>(expectNames); + expectOnly.removeAll(foundNames); + for (String s: expectOnly) + error("Expected name not found: " + s); + } + } + + /** Get a method whose name begins "lambda$...". */ + Method getLambdaMethod(ClassFile cf) throws ConstantPoolException { + for (Method m: cf.methods) { + if (m.getName(cf.constant_pool).startsWith("lambda$")) + return m; + } + return null; + } + + /** Report an error. */ + void error(String msg) { + System.err.println("Error: " + msg); + errors++; + } + + int errors; + + /** + * Annotation used to provide the set of names expected in the LVT attribute. + */ + @Retention(RetentionPolicy.RUNTIME) + @interface Expect { + String[] value(); + } + + /** Functional interface with nullary method. */ + interface Run0 { + public void run(); + } + + /** Functional interface with 1-ary method. */ + interface Run1 { + public void run(int a0); + } + + /** Functional interface with 2-ary method. */ + interface Run2 { + public void run(int a0, int a1); + } + + /* + * ---------- Test cases --------------------------------------------------- + */ + + @Expect({ "x" }) + static class Lambda_Args0_Local1 { + Run0 r = () -> { int x = 0; }; + } + + @Expect({ "x", "this" }) + static class Lambda_Args0_Local1_this { + int v; + Run0 r = () -> { int x = v; }; + } + + @Expect({ "a" }) + static class Lambda_Args1_Local0 { + Run1 r = (a) -> { }; + } + + @Expect({ "a", "x" }) + static class Lambda_Args1_Local1 { + Run1 r = (a) -> { int x = a; }; + } + + @Expect({ "a", "x" }) + static class Lambda_Args1_Local1_Captured1 { + void m() { + int v = 0; + Run1 r = (a) -> { int x = a + v; }; + } + } + + @Expect({ "a1", "a2", "x1", "x2", "this" }) + static class Lambda_Args2_Local2_Captured2_this { + int v; + void m() { + int v1 = 0; + int v2 = 0; + Run2 r = (a1, a2) -> { + int x1 = a1 + v1 + v; + int x2 = a2 + v2 + v; + }; + } + } + + @Expect({ "e" }) + static class Lambda_Try_Catch { + private static Runnable asUncheckedRunnable(Closeable c) { + return () -> { + try { + c.close(); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + }; + } + } +} +