LocalsAndOperands.java revision 14467:d2f46fdfc3ca
1/*
2 * Copyright (c) 2015, 2016 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 8020968 8147039
27 * @summary Tests for locals and operands
28 * @run testng LocalsAndOperands
29 */
30
31import org.testng.annotations.*;
32import java.lang.StackWalker.StackFrame;
33import java.lang.reflect.*;
34import java.util.*;
35import java.util.stream.*;
36
37public class LocalsAndOperands {
38    static final boolean debug = true;
39
40    static Class<?> liveStackFrameClass;
41    static Class<?> primitiveValueClass;
42    static StackWalker extendedWalker;
43    static Method getLocals;
44    static Method getOperands;
45    static Method getMonitors;
46    static Method primitiveType;
47
48    static {
49        try {
50            liveStackFrameClass = Class.forName("java.lang.LiveStackFrame");
51            primitiveValueClass = Class.forName("java.lang.LiveStackFrame$PrimitiveValue");
52
53            getLocals = liveStackFrameClass.getDeclaredMethod("getLocals");
54            getLocals.setAccessible(true);
55
56            getOperands = liveStackFrameClass.getDeclaredMethod("getStack");
57            getOperands.setAccessible(true);
58
59            getMonitors = liveStackFrameClass.getDeclaredMethod("getMonitors");
60            getMonitors.setAccessible(true);
61
62            primitiveType = primitiveValueClass.getDeclaredMethod("type");
63            primitiveType.setAccessible(true);
64
65            Method method = liveStackFrameClass.getMethod("getStackWalker");
66            method.setAccessible(true);
67            extendedWalker = (StackWalker) method.invoke(null);
68        } catch (Throwable t) { throw new RuntimeException(t); }
69    }
70
71    /** Helper method to return a StackFrame's locals */
72    static Object[] invokeGetLocals(StackFrame arg) {
73        try {
74            return (Object[]) getLocals.invoke(arg);
75        } catch (Exception e) { throw new RuntimeException(e); }
76    }
77
78    /*****************
79     * DataProviders *
80     *****************/
81
82    /** Calls testLocals() and provides LiveStackFrames for testLocals* methods */
83    @DataProvider
84    public static StackFrame[][] provider() {
85        return new StackFrame[][] {
86            new Tester().testLocals()
87        };
88    }
89
90    /**
91     * Calls testLocalsKeepAlive() and provides LiveStackFrames for testLocals* methods.
92     * Local variables in testLocalsKeepAlive() are ensured to not become dead.
93     */
94    @DataProvider
95    public static StackFrame[][] keepAliveProvider() {
96        return new StackFrame[][] {
97            new Tester().testLocalsKeepAlive()
98        };
99    }
100
101    /**
102     * Provides StackFrames from a StackWalker without the LOCALS_AND_OPERANDS
103     * option.
104     */
105    @DataProvider
106    public static StackFrame[][] noLocalsProvider() {
107        // Use default StackWalker
108        return new StackFrame[][] {
109            new Tester(StackWalker.getInstance(), true).testLocals()
110        };
111    }
112
113    /**
114     * Calls testLocals() and provides LiveStackFrames for *all* called methods,
115     * including test infrastructure (jtreg, testng, etc)
116     *
117     */
118    @DataProvider
119    public static StackFrame[][] unfilteredProvider() {
120        return new StackFrame[][] {
121            new Tester(extendedWalker, false).testLocals()
122        };
123    }
124
125    /****************
126     * Test methods *
127     ****************/
128
129    /**
130     * Check for expected local values and types in the LiveStackFrame
131     */
132    @Test(dataProvider = "keepAliveProvider")
133    public static void checkLocalValues(StackFrame... frames) {
134        if (debug) {
135            System.out.println("Running checkLocalValues");
136            dumpStackWithLocals(frames);
137        }
138        Arrays.stream(frames).filter(f -> f.getMethodName()
139                                           .equals("testLocalsKeepAlive"))
140                                           .forEach(
141            f -> {
142                Object[] locals = invokeGetLocals(f);
143                for (int i = 0; i < locals.length; i++) {
144                    // Value
145                    String expected = Tester.LOCAL_VALUES[i];
146                    Object observed = locals[i];
147                    if (expected != null /* skip nulls in golden values */ &&
148                            !expected.equals(observed.toString())) {
149                        System.err.println("Local value mismatch:");
150                        if (!debug) { dumpStackWithLocals(frames); }
151                        throw new RuntimeException("local " + i + " value is " +
152                                observed + ", expected " + expected);
153                    }
154
155                    // Type
156                    expected = Tester.LOCAL_TYPES[i];
157                    observed = type(locals[i]);
158                    if (expected != null /* skip nulls in golden values */ &&
159                            !expected.equals(observed)) {
160                        System.err.println("Local type mismatch:");
161                        if (!debug) { dumpStackWithLocals(frames); }
162                        throw new RuntimeException("local " + i + " type is " +
163                                observed + ", expected " + expected);
164                    }
165                }
166            }
167        );
168    }
169
170    /**
171     * Basic sanity check for locals and operands
172     */
173    @Test(dataProvider = "provider")
174    public static void sanityCheck(StackFrame... frames) {
175        if (debug) {
176            System.out.println("Running sanityCheck");
177        }
178        try {
179            Stream<StackFrame> stream = Arrays.stream(frames);
180            if (debug) {
181                stream.forEach(LocalsAndOperands::printLocals);
182            } else {
183                System.out.println(stream.count() + " frames");
184            }
185        } catch (Throwable t) {
186            dumpStackWithLocals(frames);
187            throw t;
188        }
189    }
190
191    /**
192     * Sanity check for locals and operands, including testng/jtreg frames
193     */
194    @Test(dataProvider = "unfilteredProvider")
195    public static void unfilteredSanityCheck(StackFrame... frames) {
196        if (debug) {
197            System.out.println("Running unfilteredSanityCheck");
198        }
199        try {
200            Stream<StackFrame> stream = Arrays.stream(frames);
201            if (debug) {
202                stream.forEach(f -> { System.out.println(f + ": " +
203                        invokeGetLocals(f).length + " locals"); } );
204            } else {
205                System.out.println(stream.count() + " frames");
206            }
207        } catch (Throwable t) {
208            dumpStackWithLocals(frames);
209            throw t;
210        }
211    }
212
213    /**
214     * Test that LiveStackFrames are not provided with the default StackWalker
215     * options.
216     */
217    @Test(dataProvider = "noLocalsProvider")
218    public static void withoutLocalsAndOperands(StackFrame... frames) {
219        for (StackFrame frame : frames) {
220            if (liveStackFrameClass.isInstance(frame)) {
221                throw new RuntimeException("should not be LiveStackFrame");
222            }
223        }
224    }
225
226    static class Tester {
227        private StackWalker walker;
228        private boolean filter = true; // Filter out testng/jtreg/etc frames?
229
230        Tester() {
231            this.walker = extendedWalker;
232        }
233
234        Tester(StackWalker walker, boolean filter) {
235            this.walker = walker;
236            this.filter = filter;
237        }
238
239        /**
240         * Perform stackwalk without keeping local variables alive and return an
241         * array of the collected StackFrames
242         */
243        private synchronized StackFrame[] testLocals() {
244            // Unused local variables will become dead
245            int x = 10;
246            char c = 'z';
247            String hi = "himom";
248            long l = 1000000L;
249            double d =  3.1415926;
250
251            if (filter) {
252                return walker.walk(s -> s.filter(f -> TEST_METHODS.contains(f
253                        .getMethodName())).collect(Collectors.toList()))
254                        .toArray(new StackFrame[0]);
255            } else {
256                return walker.walk(s -> s.collect(Collectors.toList()))
257                        .toArray(new StackFrame[0]);
258            }
259        }
260
261        /**
262         * Perform stackwalk, keeping local variables alive, and return a list of
263         * the collected StackFrames
264         */
265        private synchronized StackFrame[] testLocalsKeepAlive() {
266            int x = 10;
267            char c = 'z';
268            String hi = "himom";
269            long l = 1000000L;
270            double d =  3.1415926;
271
272            List<StackWalker.StackFrame> frames;
273            if (filter) {
274                frames = walker.walk(s -> s.filter(f -> TEST_METHODS.contains(f
275                        .getMethodName())).collect(Collectors.toList()));
276            } else {
277                frames = walker.walk(s -> s.collect(Collectors.toList()));
278            }
279
280            // Use local variables so they stay alive
281            System.out.println("Stayin' alive: "+x+" "+c+" "+hi+" "+l+" "+d);
282            return frames.toArray(new StackFrame[0]); // FIXME: convert to Array here
283        }
284
285        // Expected values for locals in testLocals() & testLocalsKeepAlive()
286        // TODO: use real values instead of Strings, rebuild doubles & floats, etc
287        private final static String[] LOCAL_VALUES = new String[] {
288            null, // skip, LocalsAndOperands$Tester@XXX identity is different each run
289            "10",
290            "122",
291            "himom",
292            "0",
293            null, // skip, fix in 8156073
294            null, // skip, fix in 8156073
295            null, // skip, fix in 8156073
296            "0"
297        };
298
299        // Expected types for locals in testLocals() & testLocalsKeepAlive()
300        // TODO: use real types
301        private final static String[] LOCAL_TYPES = new String[] {
302            null, // skip
303            "I",
304            "I",
305            "java.lang.String",
306            "I",
307            "I",
308            "I",
309            "I",
310            "I"
311        };
312
313        final static Map NUM_LOCALS = Map.of("testLocals", 8,
314                                             "testLocalsKeepAlive",
315                                             LOCAL_VALUES.length);
316        private final static Collection<String> TEST_METHODS = NUM_LOCALS.keySet();
317    }
318
319    /**
320     * Print stack trace with locals
321     */
322    public static void dumpStackWithLocals(StackFrame...frames) {
323        Arrays.stream(frames).forEach(LocalsAndOperands::printLocals);
324    }
325
326    /**
327     * Print the StackFrame and an indexed list of its locals
328     */
329    public static void printLocals(StackWalker.StackFrame frame) {
330        try {
331            System.out.println(frame);
332            Object[] locals = (Object[]) getLocals.invoke(frame);
333            for (int i = 0; i < locals.length; i++) {
334                System.out.format("  local %d: %s type %s\n", i, locals[i], type(locals[i]));
335            }
336
337            Object[] operands = (Object[]) getOperands.invoke(frame);
338            for (int i = 0; i < operands.length; i++) {
339                System.out.format("  operand %d: %s type %s%n", i, operands[i],
340                                  type(operands[i]));
341            }
342
343            Object[] monitors = (Object[]) getMonitors.invoke(frame);
344            for (int i = 0; i < monitors.length; i++) {
345                System.out.format("  monitor %d: %s%n", i, monitors[i]);
346            }
347        } catch (Exception e) { throw new RuntimeException(e); }
348    }
349
350    private static String type(Object o) {
351        try {
352            if (o == null) {
353                return "null";
354            } else if (primitiveValueClass.isInstance(o)) {
355                char c = (char)primitiveType.invoke(o);
356                return String.valueOf(c);
357            } else {
358                return o.getClass().getName();
359            }
360        } catch(Exception e) { throw new RuntimeException(e); }
361    }
362}
363