21 * questions. |
21 * questions. |
22 */ |
22 */ |
23 |
23 |
24 /* |
24 /* |
25 * @test |
25 * @test |
26 * @bug 8020968 |
26 * @bug 8020968 8147039 |
27 * @summary Sanity test for locals and operands |
27 * @summary Tests for locals and operands |
28 * @run main LocalsAndOperands |
28 * @run testng LocalsAndOperands |
29 */ |
29 */ |
30 |
30 |
|
31 import org.testng.annotations.*; |
31 import java.lang.StackWalker.StackFrame; |
32 import java.lang.StackWalker.StackFrame; |
32 import java.lang.reflect.*; |
33 import java.lang.reflect.*; |
33 import java.util.List; |
34 import java.util.*; |
34 import java.util.stream.Collectors; |
35 import java.util.stream.*; |
35 |
36 |
36 public class LocalsAndOperands { |
37 public class LocalsAndOperands { |
|
38 static final boolean debug = true; |
|
39 |
37 static Class<?> liveStackFrameClass; |
40 static Class<?> liveStackFrameClass; |
38 static Class<?> primitiveValueClass; |
41 static Class<?> primitiveValueClass; |
39 static StackWalker extendedWalker; |
42 static StackWalker extendedWalker; |
40 static Method getLocals; |
43 static Method getLocals; |
41 static Method getOperands; |
44 static Method getOperands; |
42 static Method getMonitors; |
45 static Method getMonitors; |
43 static Method primitiveType; |
46 static Method primitiveType; |
44 public static void main(String... args) throws Exception { |
47 |
45 liveStackFrameClass = Class.forName("java.lang.LiveStackFrame"); |
48 static { |
46 primitiveValueClass = Class.forName("java.lang.LiveStackFrame$PrimitiveValue"); |
49 try { |
47 |
50 liveStackFrameClass = Class.forName("java.lang.LiveStackFrame"); |
48 getLocals = liveStackFrameClass.getDeclaredMethod("getLocals"); |
51 primitiveValueClass = Class.forName("java.lang.LiveStackFrame$PrimitiveValue"); |
49 getLocals.setAccessible(true); |
52 |
50 |
53 getLocals = liveStackFrameClass.getDeclaredMethod("getLocals"); |
51 getOperands = liveStackFrameClass.getDeclaredMethod("getStack"); |
54 getLocals.setAccessible(true); |
52 getOperands.setAccessible(true); |
55 |
53 |
56 getOperands = liveStackFrameClass.getDeclaredMethod("getStack"); |
54 getMonitors = liveStackFrameClass.getDeclaredMethod("getMonitors"); |
57 getOperands.setAccessible(true); |
55 getMonitors.setAccessible(true); |
58 |
56 |
59 getMonitors = liveStackFrameClass.getDeclaredMethod("getMonitors"); |
57 primitiveType = primitiveValueClass.getDeclaredMethod("type"); |
60 getMonitors.setAccessible(true); |
58 primitiveType.setAccessible(true); |
61 |
59 |
62 primitiveType = primitiveValueClass.getDeclaredMethod("type"); |
60 Method method = liveStackFrameClass.getMethod("getStackWalker"); |
63 primitiveType.setAccessible(true); |
61 method.setAccessible(true); |
64 |
62 extendedWalker = (StackWalker) method.invoke(null); |
65 Method method = liveStackFrameClass.getMethod("getStackWalker"); |
63 new LocalsAndOperands(extendedWalker, true).test(); |
66 method.setAccessible(true); |
64 |
67 extendedWalker = (StackWalker) method.invoke(null); |
65 // no access to local and operands. |
68 } catch (Throwable t) { throw new RuntimeException(t); } |
66 new LocalsAndOperands(StackWalker.getInstance(), false).test(); |
69 } |
67 } |
70 |
68 |
71 /** Helper method to return a StackFrame's locals */ |
69 private final StackWalker walker; |
72 static Object[] invokeGetLocals(StackFrame arg) { |
70 private final boolean extended; |
73 try { |
71 LocalsAndOperands(StackWalker walker, boolean extended) { |
74 return (Object[]) getLocals.invoke(arg); |
72 this.walker = walker; |
75 } catch (Exception e) { throw new RuntimeException(e); } |
73 this.extended = extended; |
76 } |
74 } |
77 |
75 |
78 /***************** |
76 synchronized void test() throws Exception { |
79 * DataProviders * |
77 int x = 10; |
80 *****************/ |
78 char c = 'z'; |
81 |
79 String hi = "himom"; |
82 /** Calls testLocals() and provides LiveStackFrames for testLocals* methods */ |
80 long l = 1000000L; |
83 @DataProvider |
81 double d = 3.1415926; |
84 public static StackFrame[][] provider() { |
82 |
85 return new StackFrame[][] { |
83 List<StackWalker.StackFrame> frames = walker.walk(s -> s.collect(Collectors.toList())); |
86 new Tester().testLocals() |
84 if (extended) { |
87 }; |
85 for (StackWalker.StackFrame f : frames) { |
88 } |
86 System.out.println("frame: " + f); |
89 |
87 Object[] locals = (Object[]) getLocals.invoke(f); |
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); |
88 for (int i = 0; i < locals.length; i++) { |
143 for (int i = 0; i < locals.length; i++) { |
89 System.out.format(" local %d: %s type %s\n", i, locals[i], type(locals[i])); |
144 // Value |
90 |
145 String expected = Tester.LOCAL_VALUES[i]; |
91 // check for non-null locals in LocalsAndOperands.test() |
146 Object observed = locals[i]; |
92 if (f.getClassName().equals("LocalsAndOperands") && |
147 if (expected != null /* skip nulls in golden values */ && |
93 f.getMethodName().equals("test")) { |
148 !expected.equals(observed.toString())) { |
94 if (locals[i] == null) { |
149 System.err.println("Local value mismatch:"); |
95 throw new RuntimeException("kept-alive locals should not be null"); |
150 if (!debug) { dumpStackWithLocals(frames); } |
96 } |
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); |
97 } |
164 } |
98 } |
165 } |
99 |
166 } |
100 Object[] operands = (Object[]) getOperands.invoke(f); |
167 ); |
101 for (int i = 0; i < operands.length; i++) { |
168 } |
102 System.out.format(" operand %d: %s type %s%n", i, operands[i], |
169 |
103 type(operands[i])); |
170 /** |
104 } |
171 * Basic sanity check for locals and operands |
105 |
172 */ |
106 Object[] monitors = (Object[]) getMonitors.invoke(f); |
173 @Test(dataProvider = "provider") |
107 for (int i = 0; i < monitors.length; i++) { |
174 public static void sanityCheck(StackFrame... frames) { |
108 System.out.format(" monitor %d: %s%n", i, monitors[i]); |
175 if (debug) { |
109 } |
176 System.out.println("Running sanityCheck"); |
110 } |
177 } |
111 } else { |
178 try { |
112 for (StackFrame f : frames) { |
179 Stream<StackFrame> stream = Arrays.stream(frames); |
113 if (liveStackFrameClass.isInstance(f)) { |
180 if (debug) { |
114 throw new RuntimeException("should not be LiveStackFrame"); |
181 stream.forEach(LocalsAndOperands::printLocals); |
115 } |
182 } else { |
116 } |
183 System.out.println(stream.count() + " frames"); |
117 } |
184 } |
118 // Use local variables so they stay alive |
185 } catch (Throwable t) { |
119 System.out.println("Stayin' alive: "+x+" "+c+" "+hi+" "+l+" "+d); |
186 dumpStackWithLocals(frames); |
120 } |
187 throw t; |
121 |
188 } |
122 String type(Object o) throws Exception { |
189 } |
123 if (o == null) { |
190 |
124 return "null"; |
191 /** |
125 } else if (primitiveValueClass.isInstance(o)) { |
192 * Sanity check for locals and operands, including testng/jtreg frames |
126 char c = (char)primitiveType.invoke(o); |
193 */ |
127 return String.valueOf(c); |
194 @Test(dataProvider = "unfilteredProvider") |
128 } else { |
195 public static void unfilteredSanityCheck(StackFrame... frames) { |
129 return o.getClass().getName(); |
196 if (debug) { |
130 } |
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); } |
131 } |
361 } |
132 } |
362 } |