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