LambdaStackTrace.java revision 12973:2e63fa2efdb1
1/*
2 * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation.
8 *
9 * This code is distributed in the hope that it will be useful, but WITHOUT
10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
12 * version 2 for more details (a copy is included in the LICENSE file that
13 * accompanied this code).
14 *
15 * You should have received a copy of the GNU General Public License version
16 * 2 along with this work; if not, write to the Free Software Foundation,
17 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
18 *
19 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
20 * or visit www.oracle.com if you need additional information or have any
21 * questions.
22 */
23
24/*
25 * @test
26 * @bug 8025636
27 * @summary Synthetic frames should be hidden in exceptions
28 * @modules java.base/jdk.internal.org.objectweb.asm
29 *          jdk.compiler
30 * @compile -XDignore.symbol.file LUtils.java LambdaStackTrace.java
31 * @run main LambdaStackTrace
32 */
33
34import jdk.internal.org.objectweb.asm.ClassWriter;
35
36import java.io.File;
37import java.io.FileOutputStream;
38import java.io.IOException;
39import java.lang.reflect.InvocationTargetException;
40import java.lang.reflect.Method;
41import java.util.ArrayList;
42
43import static jdk.internal.org.objectweb.asm.Opcodes.ACC_ABSTRACT;
44import static jdk.internal.org.objectweb.asm.Opcodes.ACC_INTERFACE;
45import static jdk.internal.org.objectweb.asm.Opcodes.ACC_PUBLIC;
46import static jdk.internal.org.objectweb.asm.Opcodes.V1_7;
47
48public class LambdaStackTrace {
49
50    static File classes = new File(System.getProperty("test.classes"));
51
52    public static void main(String[] args) throws Exception {
53        testBasic();
54        testBridgeMethods();
55    }
56
57    /**
58     * Test the simple case
59     */
60    private static void testBasic() throws Exception {
61        try {
62            Runnable r = () -> {
63                throw new RuntimeException();
64            };
65            r.run();
66        } catch (Exception ex) {
67            // Before 8025636 the stacktrace would look like:
68            //  at LambdaStackTrace.lambda$main$0(LambdaStackTrace.java:37)
69            //  at LambdaStackTrace$$Lambda$1/1937396743.run(<Unknown>:1000000)
70            //  at LambdaStackTrace.testBasic(LambdaStackTrace.java:40)
71            //  at ...
72            //
73            // We are verifying that the middle frame above is gone.
74
75            verifyFrames(ex.getStackTrace(),
76                    "LambdaStackTrace\\..*",
77                    "LambdaStackTrace.testBasic");
78        }
79    }
80
81    /**
82     * Test the more complicated case with bridge methods.
83     *
84     * We set up the following interfaces:
85     *
86     * interface Maker {
87     *   Object make();
88     * }
89     * interface StringMaker extends Maker {
90     *   String make();
91     * }
92     *
93     * And we will use them like so:
94     *
95     * StringMaker sm = () -> { throw new RuntimeException(); };
96     * sm.make();
97     * ((Maker)m).make();
98     *
99     * The first call is a "normal" interface call, the second will use a
100     * bridge method. In both cases the generated lambda frame should
101     * be removed from the stack trace.
102     */
103    private static void testBridgeMethods() throws Exception {
104        // setup
105        generateInterfaces();
106        compileCaller();
107
108        // test
109        StackTraceElement[] frames = call("Caller", "callStringMaker");
110        verifyFrames(frames,
111                "Caller\\..*",
112                "Caller.callStringMaker");
113
114        frames = call("Caller", "callMaker");
115        verifyFrames(frames,
116                "Caller\\..*",
117                "Caller.callMaker");
118    }
119
120    private static void generateInterfaces() throws IOException {
121        // We can't let javac compile these interfaces because in > 1.8 it will insert
122        // bridge methods into the interfaces - we want code that looks like <= 1.7,
123        // so we generate it.
124        try (FileOutputStream fw = new FileOutputStream(new File(classes, "Maker.class"))) {
125            fw.write(generateMaker());
126        }
127        try (FileOutputStream fw = new FileOutputStream(new File(classes, "StringMaker.class"))) {
128            fw.write(generateStringMaker());
129        }
130    }
131
132    private static byte[] generateMaker() {
133        // interface Maker {
134        //   Object make();
135        // }
136        ClassWriter cw = new ClassWriter(0);
137        cw.visit(V1_7, ACC_INTERFACE | ACC_ABSTRACT, "Maker", null, "java/lang/Object", null);
138        cw.visitMethod(ACC_PUBLIC | ACC_ABSTRACT, "make",
139                "()Ljava/lang/Object;", null, null);
140        cw.visitEnd();
141        return cw.toByteArray();
142    }
143
144    private static byte[] generateStringMaker() {
145        // interface StringMaker extends Maker {
146        //   String make();
147        // }
148        ClassWriter cw = new ClassWriter(0);
149        cw.visit(V1_7, ACC_INTERFACE | ACC_ABSTRACT, "StringMaker", null, "java/lang/Object", new String[]{"Maker"});
150        cw.visitMethod(ACC_PUBLIC | ACC_ABSTRACT, "make",
151                "()Ljava/lang/String;", null, null);
152        cw.visitEnd();
153        return cw.toByteArray();
154    }
155
156
157    static void emitCode(File f) {
158        ArrayList<String> scratch = new ArrayList<>();
159        scratch.add("public class Caller {");
160        scratch.add("    public static void callStringMaker() {");
161        scratch.add("        StringMaker sm = () -> { throw new RuntimeException(); };");
162        scratch.add("        sm.make();");
163        scratch.add("    }");
164        scratch.add("    public static void callMaker() {");
165        scratch.add("        StringMaker sm = () -> { throw new RuntimeException(); };");
166        scratch.add("        ((Maker) sm).make();");  // <-- This will call the bridge method
167        scratch.add("    }");
168        scratch.add("}");
169        LUtils.createFile(f, scratch);
170    }
171
172    static void compileCaller() {
173        File caller = new File(classes, "Caller.java");
174        emitCode(caller);
175        LUtils.compile("-cp", classes.getAbsolutePath(), "-d", classes.getAbsolutePath(), caller.getAbsolutePath());
176    }
177
178    private static void verifyFrames(StackTraceElement[] stack, String... patterns) throws Exception {
179        for (int i = 0; i < patterns.length; i++) {
180            String cm = stack[i].getClassName() + "." + stack[i].getMethodName();
181            if (!cm.matches(patterns[i])) {
182                System.err.println("Actual trace did not match expected trace at frame " + i);
183                System.err.println("Expected frame patterns:");
184                for (int j = 0; j < patterns.length; j++) {
185                    System.err.println("  " + j + ": " + patterns[j]);
186                }
187                System.err.println("Actual frames:");
188                for (int j = 0; j < patterns.length; j++) {
189                    System.err.println("  " + j + ": " + stack[j]);
190                }
191                throw new Exception("Incorrect stack frames found");
192            }
193        }
194    }
195
196    private static StackTraceElement[] call(String clazz, String method) throws Exception {
197        Class<?> c = Class.forName(clazz);
198        try {
199            Method m = c.getDeclaredMethod(method);
200            m.invoke(null);
201        } catch(InvocationTargetException ex) {
202            return ex.getTargetException().getStackTrace();
203        }
204        throw new Exception("Expected exception to be thrown");
205    }
206}
207