/* * Copyright (c) 2015, 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 8025636 * @summary Synthetic frames should be hidden in exceptions * @modules java.base/jdk.internal.org.objectweb.asm * jdk.compiler * @compile -XDignore.symbol.file LUtils.java LambdaStackTrace.java * @run main LambdaStackTrace */ import jdk.internal.org.objectweb.asm.ClassWriter; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.ArrayList; import static jdk.internal.org.objectweb.asm.Opcodes.ACC_ABSTRACT; import static jdk.internal.org.objectweb.asm.Opcodes.ACC_INTERFACE; import static jdk.internal.org.objectweb.asm.Opcodes.ACC_PUBLIC; import static jdk.internal.org.objectweb.asm.Opcodes.V1_7; public class LambdaStackTrace { static File classes = new File(System.getProperty("test.classes")); public static void main(String[] args) throws Exception { testBasic(); testBridgeMethods(); } /** * Test the simple case */ private static void testBasic() throws Exception { try { Runnable r = () -> { throw new RuntimeException(); }; r.run(); } catch (Exception ex) { // Before 8025636 the stacktrace would look like: // at LambdaStackTrace.lambda$main$0(LambdaStackTrace.java:37) // at LambdaStackTrace$$Lambda$1/1937396743.run(:1000000) // at LambdaStackTrace.testBasic(LambdaStackTrace.java:40) // at ... // // We are verifying that the middle frame above is gone. verifyFrames(ex.getStackTrace(), "LambdaStackTrace\\..*", "LambdaStackTrace.testBasic"); } } /** * Test the more complicated case with bridge methods. * * We set up the following interfaces: * * interface Maker { * Object make(); * } * interface StringMaker extends Maker { * String make(); * } * * And we will use them like so: * * StringMaker sm = () -> { throw new RuntimeException(); }; * sm.make(); * ((Maker)m).make(); * * The first call is a "normal" interface call, the second will use a * bridge method. In both cases the generated lambda frame should * be removed from the stack trace. */ private static void testBridgeMethods() throws Exception { // setup generateInterfaces(); compileCaller(); // test StackTraceElement[] frames = call("Caller", "callStringMaker"); verifyFrames(frames, "Caller\\..*", "Caller.callStringMaker"); frames = call("Caller", "callMaker"); verifyFrames(frames, "Caller\\..*", "Caller.callMaker"); } private static void generateInterfaces() throws IOException { // We can't let javac compile these interfaces because in > 1.8 it will insert // bridge methods into the interfaces - we want code that looks like <= 1.7, // so we generate it. try (FileOutputStream fw = new FileOutputStream(new File(classes, "Maker.class"))) { fw.write(generateMaker()); } try (FileOutputStream fw = new FileOutputStream(new File(classes, "StringMaker.class"))) { fw.write(generateStringMaker()); } } private static byte[] generateMaker() { // interface Maker { // Object make(); // } ClassWriter cw = new ClassWriter(0); cw.visit(V1_7, ACC_INTERFACE | ACC_ABSTRACT, "Maker", null, "java/lang/Object", null); cw.visitMethod(ACC_PUBLIC | ACC_ABSTRACT, "make", "()Ljava/lang/Object;", null, null); cw.visitEnd(); return cw.toByteArray(); } private static byte[] generateStringMaker() { // interface StringMaker extends Maker { // String make(); // } ClassWriter cw = new ClassWriter(0); cw.visit(V1_7, ACC_INTERFACE | ACC_ABSTRACT, "StringMaker", null, "java/lang/Object", new String[]{"Maker"}); cw.visitMethod(ACC_PUBLIC | ACC_ABSTRACT, "make", "()Ljava/lang/String;", null, null); cw.visitEnd(); return cw.toByteArray(); } static void emitCode(File f) { ArrayList scratch = new ArrayList<>(); scratch.add("public class Caller {"); scratch.add(" public static void callStringMaker() {"); scratch.add(" StringMaker sm = () -> { throw new RuntimeException(); };"); scratch.add(" sm.make();"); scratch.add(" }"); scratch.add(" public static void callMaker() {"); scratch.add(" StringMaker sm = () -> { throw new RuntimeException(); };"); scratch.add(" ((Maker) sm).make();"); // <-- This will call the bridge method scratch.add(" }"); scratch.add("}"); LUtils.createFile(f, scratch); } static void compileCaller() { File caller = new File(classes, "Caller.java"); emitCode(caller); LUtils.compile("-cp", classes.getAbsolutePath(), "-d", classes.getAbsolutePath(), caller.getAbsolutePath()); } private static void verifyFrames(StackTraceElement[] stack, String... patterns) throws Exception { for (int i = 0; i < patterns.length; i++) { String cm = stack[i].getClassName() + "." + stack[i].getMethodName(); if (!cm.matches(patterns[i])) { System.err.println("Actual trace did not match expected trace at frame " + i); System.err.println("Expected frame patterns:"); for (int j = 0; j < patterns.length; j++) { System.err.println(" " + j + ": " + patterns[j]); } System.err.println("Actual frames:"); for (int j = 0; j < patterns.length; j++) { System.err.println(" " + j + ": " + stack[j]); } throw new Exception("Incorrect stack frames found"); } } } private static StackTraceElement[] call(String clazz, String method) throws Exception { Class c = Class.forName(clazz); try { Method m = c.getDeclaredMethod(method); m.invoke(null); } catch(InvocationTargetException ex) { return ex.getTargetException().getStackTrace(); } throw new Exception("Expected exception to be thrown"); } }