| /* | |
| * Copyright (c) 2015, 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 8025636 | |
| * @library /test/lib/ | |
| * @modules jdk.compiler | |
| * @compile LambdaStackTrace.java | |
| * @run main LambdaStackTrace | |
| * @summary Synthetic frames should be hidden in exceptions | |
| */ | |
| import jdk.test.lib.compiler.CompilerUtils; | |
| import java.io.IOException; | |
| import java.lang.classfile.ClassFile; | |
| import java.lang.constant.ClassDesc; | |
| import java.lang.constant.MethodTypeDesc; | |
| import java.lang.reflect.InvocationTargetException; | |
| import java.lang.reflect.Method; | |
| import java.nio.charset.Charset; | |
| import java.nio.file.Files; | |
| import java.nio.file.Path; | |
| import java.util.ArrayList; | |
| import static java.lang.constant.ConstantDescs.CD_Object; | |
| import static java.lang.constant.ConstantDescs.CD_String; | |
| import static java.lang.classfile.ClassFile.*; | |
| public class LambdaStackTrace { | |
| static Path CLASSES = Path.of(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/1937396743.run(<Unknown>: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. | |
| Files.write(CLASSES.resolve("Maker.class"), generateMaker()); | |
| Files.write(CLASSES.resolve("StringMaker.class"), generateStringMaker()); | |
| } | |
| private static byte[] generateMaker() { | |
| // interface Maker { | |
| // Object make(); | |
| // } | |
| return ClassFile.of().build(ClassDesc.of("Maker"), clb -> clb | |
| .withVersion(JAVA_7_VERSION, 0) | |
| .withFlags(ACC_INTERFACE | ACC_ABSTRACT) | |
| .withSuperclass(CD_Object) | |
| .withMethod("make", MethodTypeDesc.of(CD_Object), | |
| ACC_PUBLIC | ACC_ABSTRACT, mb -> {}) | |
| ); | |
| } | |
| private static byte[] generateStringMaker() { | |
| // interface StringMaker extends Maker { | |
| // String make(); | |
| // } | |
| return ClassFile.of().build(ClassDesc.of("StringMaker"), clb -> clb | |
| .withVersion(JAVA_7_VERSION, 0) | |
| .withFlags(ACC_INTERFACE | ACC_ABSTRACT) | |
| .withSuperclass(CD_Object) | |
| .withInterfaceSymbols(ClassDesc.of("Maker")) | |
| .withMethod("make", MethodTypeDesc.of(CD_String), | |
| ACC_PUBLIC | ACC_ABSTRACT, mb -> {}) | |
| ); | |
| } | |
| static void emitCode(Path file) throws IOException { | |
| ArrayList<String> 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("}"); | |
| Files.write(file, scratch, Charset.defaultCharset()); | |
| } | |
| static void compileCaller() throws IOException { | |
| Path src = Path.of("src"); | |
| Files.createDirectories(src); | |
| Path caller = src.resolve("Caller.java"); | |
| emitCode(caller); | |
| CompilerUtils.compile(src, CLASSES, "-cp", CLASSES.toAbsolutePath().toString()); | |
| } | |
| 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"); | |
| } | |
| } |