1 /* 2 * Copyright (c) 2015, 2017, 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 8156073 27 * @summary Tests for locals and operands 28 * @modules java.base/java.lang:open 29 * @run testng/othervm -Xint -DtestUnused=true LocalsAndOperands 30 * @run testng/othervm -Xcomp LocalsAndOperands 31 * @run testng/othervm -Xcomp -XX:-TieredCompilation LocalsAndOperands 32 */ 33 34 import org.testng.annotations.*; 35 import static org.testng.Assert.*; 36 import java.lang.StackWalker.StackFrame; 37 import static java.lang.StackWalker.Option.*; 38 import java.lang.reflect.*; 39 import java.util.*; 40 import java.util.stream.*; 41 42 public class LocalsAndOperands { 43 static final boolean debug = false; 44 static final boolean is32bit; 45 static final boolean testUnused; 46 47 static Class<?> liveStackFrameClass; 48 static Class<?> primitiveSlotClass; 49 static Class<?> primitiveSlot32Class; 50 static Class<?> primitiveSlot64Class; 51 52 static StackWalker extendedWalker; 53 static Method getLocals; 54 static Method getOperands; 55 static Method getMonitors; 56 static Method primitiveSize; 57 static Method primitiveLongValue; 58 static Method primitiveIntValue; 59 static Method getExtendedWalker; 60 61 private static final long LOWER_LONG_VAL = 4L; // Lower bits 62 private static final long UPPER_LONG_VAL = 0x123400000000L; // Upper bits 63 private static final long NEG_LONG_VAL = Long.MIN_VALUE; 64 65 private static final double LOWER_DOUBLE_VAL = Double.longBitsToDouble(0xABCDL); 66 private static final double UPPER_DOUBLE_VAL = Double.longBitsToDouble(0x432100000000L); 67 68 static { 69 try { 70 liveStackFrameClass = Class.forName("java.lang.LiveStackFrame"); 71 primitiveSlotClass = Class.forName("java.lang.LiveStackFrame$PrimitiveSlot"); 72 primitiveSlot32Class = Class.forName("java.lang.LiveStackFrameInfo$PrimitiveSlot32"); 73 primitiveSlot64Class = Class.forName("java.lang.LiveStackFrameInfo$PrimitiveSlot64"); 74 75 getLocals = liveStackFrameClass.getDeclaredMethod("getLocals"); 76 getLocals.setAccessible(true); 77 78 getOperands = liveStackFrameClass.getDeclaredMethod("getStack"); 79 getOperands.setAccessible(true); 80 81 getMonitors = liveStackFrameClass.getDeclaredMethod("getMonitors"); 82 getMonitors.setAccessible(true); 83 84 primitiveSize = primitiveSlotClass.getDeclaredMethod("size"); 85 primitiveSize.setAccessible(true); 86 87 primitiveLongValue = primitiveSlotClass.getDeclaredMethod("longValue"); 88 primitiveLongValue.setAccessible(true); 89 90 primitiveIntValue = primitiveSlotClass.getDeclaredMethod("intValue"); 91 primitiveIntValue.setAccessible(true); 92 93 getExtendedWalker = liveStackFrameClass.getMethod("getStackWalker", Set.class); 94 getExtendedWalker.setAccessible(true); 95 extendedWalker = (StackWalker) getExtendedWalker.invoke(null, 96 EnumSet.noneOf(StackWalker.Option.class)); 97 98 String dataModel = System.getProperty("sun.arch.data.model"); 99 if ("32".equals(dataModel)) { 100 is32bit = true; 101 } else if ("64".equals(dataModel)) { 102 is32bit= false; 103 } else { 104 throw new RuntimeException("Weird data model:" + dataModel); 105 } 106 System.out.println("VM bits: " + dataModel); 107 108 testUnused = System.getProperty("testUnused") != null; 109 } catch (Throwable t) { throw new RuntimeException(t); } 110 } 111 112 /** Helper method to return a StackFrame's locals */ 113 static Object[] invokeGetLocals(StackFrame arg) { 114 try { 115 return (Object[]) getLocals.invoke(arg); 116 } catch (Exception e) { throw new RuntimeException(e); } 117 } 118 119 /***************** 120 * DataProviders * 121 *****************/ 122 123 /** Calls KnownLocalsTester.testLocals* and provides LiveStackFrames */ 124 @DataProvider 125 public static StackFrame[][] knownLocalsProvider() { 126 List<StackFrame[]> list = new ArrayList<>(3); 127 list.add(new KnownLocalsTester().testLocalsKeepAlive()); 128 list.add(new KnownLocalsTester().testLocalsKeepAliveArgs(0xA, 'z', 129 "himom", 0x3FF00000000L + 0xFFFF, Math.PI)); 130 if (testUnused) { 131 list.add(new KnownLocalsTester().testLocalsUnused()); 132 } 133 return list.toArray(new StackFrame[1][1]); 134 } 135 136 /**************** 137 * Test methods * 138 ****************/ 139 140 /** 141 * Check for expected local values in the LiveStackFrame 142 */ 143 @Test(dataProvider = "knownLocalsProvider") 144 public static void checkLocalValues(StackFrame... frames) { 145 dumpFramesIfDebug(frames); 146 try { 147 Stream.of(frames) 148 .filter(f -> KnownLocalsTester.TEST_METHODS.contains(f.getMethodName())) 149 .forEach(LocalsAndOperands::checkFrameLocals); 150 } catch (Exception e) { dumpFramesIfNotDebug(frames); throw e; } 151 } 152 153 /** 154 * Check the locals in the given StackFrame against the expected values. 155 */ 156 private static void checkFrameLocals(StackFrame f) { 157 Object[] expectedArray = KnownLocalsTester.LOCAL_VALUES; 158 Object[] locals = invokeGetLocals(f); 159 160 for (int i = 0; i < locals.length; i++) { 161 Object expected = expectedArray[i]; 162 Object observed = locals[i]; 163 164 if (expected == null) { /* skip nulls in golden values */ 165 continue; 166 } else if (expected instanceof KnownLocalsTester.TwoSlotValue) { 167 // confirm integrity of expected values 168 assertEquals(expectedArray[i+1], null, 169 "Malformed array of expected values - slot after TwoSlotValue should be null"); 170 assertLongIsInSlots(locals[i], locals[i+1], 171 ((KnownLocalsTester.TwoSlotValue)expected).value); 172 i++; // skip following slot 173 } else if (primitiveSlotClass.isInstance(observed)) { // single slot primitive 174 assertTrue(primitiveValueEquals(observed, expected), 175 "Local value mismatch: local " + i + " value is " + 176 observed + ", expected " + expected); 177 } else if (expected instanceof Class) { 178 assertTrue(((Class)expected).isInstance(observed), 179 "Local value mismatch: local " + i + " expected instancof " + 180 expected + " but got " + observed); 181 } else if (expected instanceof String) { 182 assertEquals(expected, observed, "Local value mismatch: local " + 183 i + " value is " + observed + ", expected " + expected); 184 } else { 185 throw new RuntimeException("Unrecognized expected local value " + 186 i + ": " + expected); 187 } 188 } 189 } 190 191 /** 192 * Sanity check for locals and operands, including testng/jtreg frames 193 * using all StackWalker options. 194 */ 195 @Test 196 public synchronized void fullStackSanityCheck() throws Throwable { 197 if (debug) { 198 System.out.println("Running fullStackSanityCheck"); 199 } 200 StackWalker sw = (StackWalker) getExtendedWalker.invoke(null, 201 EnumSet.of(SHOW_HIDDEN_FRAMES, SHOW_REFLECT_FRAMES, 202 RETAIN_CLASS_REFERENCE)); 203 sw.forEach(f -> { 204 if (debug) { 205 printLocals(f); 206 } else { 207 try { 208 System.out.println(" " + f + ": " + 209 ((Object[]) getLocals.invoke(f)).length + " locals, " + 210 ((Object[]) getOperands.invoke(f)).length + " operands, " + 211 ((Object[]) getMonitors.invoke(f)).length + " monitors"); 212 } catch (IllegalAccessException|InvocationTargetException t) { 213 throw new RuntimeException(t); 214 } 215 } 216 }); 217 } 218 219 /** 220 * Test that LiveStackFrames are not provided with the default StackWalker 221 * options. 222 */ 223 @Test 224 public static void noLocalsSanityCheck() { 225 StackWalker sw = StackWalker.getInstance(); 226 sw.forEach(f -> { 227 assertFalse(liveStackFrameClass.isInstance(f), 228 "should not be LiveStackFrame"); 229 }); 230 } 231 232 /** 233 * Class stack-walking methods with a known set of methods and local variables. 234 */ 235 static class KnownLocalsTester { 236 private StackWalker walker; 237 238 KnownLocalsTester() { 239 this.walker = extendedWalker; 240 } 241 242 /** 243 * Perform stackwalk without keeping local variables alive and return an 244 * array of the collected StackFrames 245 */ 246 private synchronized StackFrame[] testLocalsUnused() { 247 // Unused local variables will become dead 248 int x = 0xA; 249 char c = 'z'; // 0x7A 250 String hi = "himom"; 251 long l = 0x3FF00000000L + 0xFFFFL; 252 double d = Math.PI; 253 254 return walker.walk(s -> 255 s.filter(f -> TEST_METHODS.contains(f.getMethodName())) 256 .toArray(StackFrame[]::new) 257 ); 258 } 259 260 /** 261 * Perform stackwalk, keeping local variables alive, and return a list of 262 * the collected StackFrames 263 */ 264 private synchronized StackFrame[] testLocalsKeepAlive() { 265 int x = 0xA; 266 char c = 'z'; // 0x7A 267 String hi = "himom"; 268 long l = 0x3FF00000000L + 0xFFFFL; 269 double d = Math.PI; 270 271 StackFrame[] frames = walker.walk(s -> 272 s.filter(f -> TEST_METHODS.contains(f.getMethodName())) 273 .toArray(StackFrame[]::new) 274 ); 275 276 // Use local variables so they stay alive 277 System.out.println("Stayin' alive: "+this+" "+x+" "+c+" "+hi+" "+l+" "+d); 278 return frames; 279 } 280 281 /** 282 * Perform stackwalk, keeping method arguments alive, and return a list of 283 * the collected StackFrames 284 */ 285 private synchronized StackFrame[] testLocalsKeepAliveArgs(int x, char c, 286 String hi, long l, 287 double d) { 288 StackFrame[] frames = walker.walk(s -> 289 s.filter(f -> TEST_METHODS.contains(f.getMethodName())) 290 .toArray(StackFrame[]::new) 291 ); 292 293 // Use local variables so they stay alive 294 System.out.println("Stayin' alive: "+this+" "+x+" "+c+" "+hi+" "+l+" "+d); 295 return frames; 296 } 297 298 // An expected two-slot local (i.e. long or double) 299 static class TwoSlotValue { 300 public long value; 301 public TwoSlotValue(long value) { this.value = value; } 302 } 303 304 // Expected values for locals in KnownLocalsTester.testLocals* methods 305 private final static Object[] LOCAL_VALUES = new Object[] { 306 LocalsAndOperands.KnownLocalsTester.class, 307 Integer.valueOf(0xA), 308 Integer.valueOf(0x7A), 309 "himom", 310 new TwoSlotValue(0x3FF00000000L + 0xFFFFL), 311 null, // 2nd slot 312 new TwoSlotValue(Double.doubleToRawLongBits(Math.PI)), 313 null, // 2nd slot 314 Integer.valueOf(0) 315 }; 316 317 private final static List<String> TEST_METHODS = 318 List.of("testLocalsUnused", 319 "testLocalsKeepAlive", 320 "testLocalsKeepAliveArgs"); 321 } 322 323 /* Simpler tests of long & double arguments */ 324 325 @Test 326 public static void testUsedLongArg() throws Exception { 327 usedLong(LOWER_LONG_VAL); 328 usedLong(UPPER_LONG_VAL); 329 usedLong(NEG_LONG_VAL); 330 } 331 332 private static void usedLong(long longArg) throws Exception { 333 StackFrame[] frames = extendedWalker.walk(s -> 334 s.filter(f -> "usedLong".equals(f.getMethodName())) 335 .toArray(StackFrame[]::new) 336 ); 337 try { 338 dumpFramesIfDebug(frames); 339 340 Object[] locals = (Object[]) getLocals.invoke(frames[0]); 341 assertLongIsInSlots(locals[0], locals[1], longArg); 342 System.out.println("Stayin' alive: " + longArg); 343 } catch (Exception t) { 344 dumpFramesIfNotDebug(frames); 345 throw t; 346 } 347 } 348 349 @Test 350 public static void testUnusedLongArg() throws Exception { 351 if (testUnused) { 352 unusedLong(NEG_LONG_VAL); 353 } 354 } 355 356 private static void unusedLong(long longArg) throws Exception { 357 StackFrame[] frames = extendedWalker.walk(s -> 358 s.filter(f -> "unusedLong".equals(f.getMethodName())) 359 .toArray(StackFrame[]::new) 360 ); 361 try { 362 dumpFramesIfDebug(frames); 363 364 final Object[] locals = (Object[]) getLocals.invoke(frames[0]); 365 assertLongIsInSlots(locals[0], locals[1], NEG_LONG_VAL); 366 } catch (Exception t) { 367 dumpFramesIfNotDebug(frames); 368 throw t; 369 } 370 } 371 372 @Test 373 public static void testUsedDoubleArg() throws Exception { 374 usedDouble(LOWER_DOUBLE_VAL); 375 usedDouble(UPPER_DOUBLE_VAL); 376 } 377 378 private static void usedDouble(double doubleArg) throws Exception { 379 StackFrame[] frames = extendedWalker.walk(s -> 380 s.filter(f -> "usedDouble".equals(f.getMethodName())) 381 .toArray(StackFrame[]::new) 382 ); 383 try { 384 dumpFramesIfDebug(frames); 385 386 Object[] locals = (Object[]) getLocals.invoke(frames[0]); 387 assertDoubleIsInSlots(locals[0], locals[1], doubleArg); 388 System.out.println("Stayin' alive: " + doubleArg); 389 } catch (Exception t) { 390 dumpFramesIfNotDebug(frames); 391 throw t; 392 } 393 } 394 395 /******************* 396 * Utility Methods * 397 *******************/ 398 399 /** 400 * Print stack trace with locals 401 */ 402 public static void dumpStackWithLocals(StackFrame...frames) { 403 Stream.of(frames).forEach(LocalsAndOperands::printLocals); 404 } 405 406 public static void dumpFramesIfDebug(StackFrame...frames) { 407 if (debug) { dumpStackWithLocals(frames); } 408 } 409 410 public static void dumpFramesIfNotDebug(StackFrame...frames) { 411 if (!debug) { dumpStackWithLocals(frames); } 412 } 413 414 /** 415 * Print the StackFrame and an indexed list of its locals 416 */ 417 public static void printLocals(StackWalker.StackFrame frame) { 418 try { 419 System.out.println("Locals for: " + frame); 420 Object[] locals = (Object[]) getLocals.invoke(frame); 421 for (int i = 0; i < locals.length; i++) { 422 String localStr = null; 423 424 if (primitiveSlot64Class.isInstance(locals[i])) { 425 localStr = String.format("0x%X", 426 (Long)primitiveLongValue.invoke(locals[i])); 427 } else if (primitiveSlot32Class.isInstance(locals[i])) { 428 localStr = String.format("0x%X", 429 (Integer)primitiveIntValue.invoke(locals[i])); 430 } else if (locals[i] != null) { 431 localStr = locals[i].toString(); 432 } 433 System.out.format(" local %d: %s type %s\n", i, localStr, type(locals[i])); 434 } 435 436 Object[] operands = (Object[]) getOperands.invoke(frame); 437 for (int i = 0; i < operands.length; i++) { 438 System.out.format(" operand %d: %s type %s%n", i, operands[i], 439 type(operands[i])); 440 } 441 442 Object[] monitors = (Object[]) getMonitors.invoke(frame); 443 for (int i = 0; i < monitors.length; i++) { 444 System.out.format(" monitor %d: %s%n", i, monitors[i]); 445 } 446 } catch (Exception e) { throw new RuntimeException(e); } 447 } 448 449 private static String type(Object o) { 450 try { 451 if (o == null) { 452 return "null"; 453 } else if (primitiveSlotClass.isInstance(o)) { 454 int s = (int)primitiveSize.invoke(o); 455 return s + "-byte primitive"; 456 } else { 457 return o.getClass().getName(); 458 } 459 } catch(Exception e) { throw new RuntimeException(e); } 460 } 461 462 /* 463 * Check if the PrimitiveValue "primVal" contains the specified value, 464 * either a Long or an Integer. 465 */ 466 static boolean primitiveValueEquals(Object primVal, Object expectedVal) { 467 try { 468 if (expectedVal instanceof Long) { 469 assertFalse(is32bit); 470 assertTrue(primitiveSlot64Class.isInstance(primVal)); 471 assertTrue(8 == (int)primitiveSize.invoke(primVal)); 472 return Objects.equals(primitiveLongValue.invoke(primVal), expectedVal); 473 } else if (expectedVal instanceof Integer) { 474 int expectedInt = (Integer)expectedVal; 475 if (is32bit) { 476 assertTrue(primitiveSlot32Class.isInstance(primVal), 477 "expected a PrimitiveSlot32 on 32-bit VM"); 478 assertTrue(4 == (int)primitiveSize.invoke(primVal)); 479 return expectedInt == (int)primitiveIntValue.invoke(primVal); 480 } else { 481 assertTrue(primitiveSlot64Class.isInstance(primVal), 482 "expected a PrimitiveSlot64 on 64-bit VM"); 483 assertTrue(8 == (int)primitiveSize.invoke(primVal)); 484 // Look for int expectedVal in high- or low-order 32 bits 485 long primValLong = (long)primitiveLongValue.invoke(primVal); 486 return (int)(primValLong & 0x00000000FFFFFFFFL) == expectedInt || 487 (int)(primValLong >>> 32) == expectedInt; 488 } 489 } else { 490 throw new RuntimeException("Called with non-Integer/Long: " + expectedVal); 491 } 492 } catch (IllegalAccessException|InvocationTargetException e) { 493 throw new RuntimeException(e); 494 } 495 496 } 497 498 /* 499 * Assert that the expected 2-slot long value is stored somewhere in the 500 * pair of slots. 501 * Throw exception if long value isn't in the two slots given. 502 * Accounts for 32 vs 64 bit, but is lax on endianness (accepts either) 503 */ 504 static void assertLongIsInSlots(Object primVal0, Object primVal1, long expected) { 505 try { 506 if (is32bit) { 507 int upper = (int)(expected & 0xFFFFFFFFL); 508 int lower = (int)(expected >> 32); 509 510 if (!((primitiveValueEquals(primVal0, upper) && 511 primitiveValueEquals(primVal1, lower)) || 512 (primitiveValueEquals(primVal0, lower) && 513 primitiveValueEquals(primVal1, upper)))) { 514 throw new RuntimeException(String.format("0x%X and 0x%X of 0x%016X not found in 0x%X and 0x%X", 515 upper, lower, expected, 516 (int)primitiveIntValue.invoke(primVal0), 517 (int)primitiveIntValue.invoke(primVal1))); 518 } 519 } else { 520 if (!(primitiveValueEquals(primVal0, expected) || 521 primitiveValueEquals(primVal1, expected))) { 522 throw new RuntimeException(String.format("0x%016X not found in 0x%016X or 0x%016X", 523 expected, 524 (long)primitiveLongValue.invoke(primVal0), 525 (long)primitiveLongValue.invoke(primVal1))); 526 } 527 } 528 } catch (IllegalAccessException|InvocationTargetException e) { 529 throw new RuntimeException(e); 530 } 531 } 532 533 static void assertDoubleIsInSlots(Object primVal0, Object primVal1, double expected) { 534 assertLongIsInSlots(primVal0, primVal1, Double.doubleToRawLongBits(expected)); 535 } 536 }