1 /* 2 * Copyright (c) 2013, 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 package vm.runtime.defmeth.shared; 25 26 import vm.runtime.defmeth.shared.data.Clazz; 27 import java.io.PrintWriter; 28 import java.lang.instrument.ClassFileTransformer; 29 import java.lang.instrument.IllegalClassFormatException; 30 import java.lang.instrument.Instrumentation; 31 import java.lang.instrument.UnmodifiableClassException; 32 import java.security.ProtectionDomain; 33 import java.util.ArrayList; 34 import java.util.Arrays; 35 import java.util.List; 36 import java.util.regex.Matcher; 37 import java.util.regex.Pattern; 38 import nsk.share.Pair; 39 import nsk.share.TestFailure; 40 import vm.runtime.defmeth.shared.data.method.param.*; 41 42 43 /** 44 * Utility class with auxiliary miscellaneous methods. 45 */ 46 public class Util { 47 public static class Transformer { 48 private static Instrumentation inst; 49 50 public static void premain(String agentArgs, Instrumentation inst) { 51 Transformer.inst = inst; 52 53 /* 54 inst.addTransformer(new ClassFileTransformer() { 55 @Override 56 public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException { 57 System.out.println("Retransform (initial): " + className); 58 return classfileBuffer; 59 } 60 }); 61 */ 62 } 63 } 64 65 /** 66 * Concatenate {@code strings} array interleaving with {@code sep} in between. 67 * 68 * @param sep 69 * @param strings 70 * @return 71 */ 72 public static String intersperse(String sep, String... strings) { 73 StringBuilder sb = new StringBuilder(); 74 if (strings.length == 0) { 75 return ""; 76 } else if (strings.length == 1) { 77 return strings[0]; 78 } else { 79 sb.append(strings[0]); 80 81 for (int i=1; i<strings.length; i++) { 82 sb.append(sep).append(strings[i]); 83 } 84 85 return sb.toString(); 86 } 87 } 88 89 /** 90 * Construct array of names for an array of {@code Clazz} instances. 91 * 92 * @param clazzes 93 * @return 94 */ 95 public static String[] asStrings(Clazz[] clazzes) { 96 String[] result = new String[clazzes.length]; 97 for (int i = 0; i < clazzes.length; i++) { 98 result[i] = clazzes[i].intlName(); 99 } 100 101 return result; 102 } 103 104 /** 105 * Get the name of the test currently being executed 106 * 107 * @return name of the test being executed 108 */ 109 public static String getTestName() { 110 // Hack: examine stack trace and extract test method's name from there 111 try { 112 throw new Exception(); 113 } catch (Exception e) { 114 for (StackTraceElement elem : e.getStackTrace()) { 115 String className = elem.getClassName(); 116 String methodName = elem.getMethodName(); 117 118 if (className.startsWith("vm.runtime.defmeth.") && 119 methodName.startsWith("test")) { 120 return String.format("%s.%s", 121 className.replaceAll(".*\\.", ""), methodName); 122 } 123 } 124 125 return "Unknown"; 126 } 127 } 128 129 /** 130 * Pretty-print {@code byte[] classFile} to stdout. 131 * 132 * @param classFile 133 */ 134 public static void printClassFile(byte[] classFile) { 135 int flags = jdk.internal.org.objectweb.asm.ClassReader.SKIP_DEBUG; 136 137 classFile = classFile.clone(); 138 139 // FIXME: workaround for V1_7 limiation of current ASM 140 // Artificially downgrade 141 if (classFile[7] == 52) { 142 System.out.println("WARNING: downgraded major verson from 52 to 0"); 143 classFile[7] = 0; 144 } 145 146 jdk.internal.org.objectweb.asm.ClassReader cr = 147 new jdk.internal.org.objectweb.asm.ClassReader(classFile); 148 149 cr.accept(new jdk.internal.org.objectweb.asm.util.TraceClassVisitor(new PrintWriter(System.out)), 150 flags); 151 152 } 153 154 /** 155 * Print ASM version (sequence of calls to ASM API to produce same class file) 156 * of {@code classFile} to stdout. 157 * 158 * @param classFile 159 */ 160 public static void asmifyClassFile(byte[] classFile) { 161 int flags = jdk.internal.org.objectweb.asm.ClassReader.SKIP_DEBUG; 162 163 // FIXME: workaround for V1_7 limiation of current ASM 164 // Artificially downgrade 165 if (classFile[7] == 52) { 166 // Need to patch the bytecode, so make a copy of the class file 167 classFile = classFile.clone(); 168 169 System.out.println("WARNING: downgraded major verson from 52 to 0"); 170 classFile[7] = 0; 171 } 172 173 jdk.internal.org.objectweb.asm.ClassReader cr = 174 new jdk.internal.org.objectweb.asm.ClassReader(classFile); 175 176 //cr.accept(new TraceClassVisitor(new PrintWriter(System.out)), flags); 177 cr.accept(new jdk.internal.org.objectweb.asm.util.TraceClassVisitor(null, 178 new jdk.internal.org.objectweb.asm.util.ASMifier(), 179 new PrintWriter(System.out)), flags); 180 } 181 182 /** 183 * Parse method descriptor and split it into parameter types names and 184 * return type name. 185 * 186 * @param desc 187 * @return {@code Pair} of parameter types names and 188 */ 189 public static Pair<String[],String> parseDesc(String desc) { 190 Pattern p = Pattern.compile("\\((.*)\\)(.*)"); 191 Matcher m = p.matcher(desc); 192 193 if (m.matches()) { 194 String opts = m.group(1); 195 String returnVal = m.group(2); 196 197 return Pair.of(parseParams(opts), returnVal); 198 } else { 199 throw new IllegalArgumentException(desc); 200 } 201 202 } 203 204 /** 205 * Check whether a type isn't Void by it's name. 206 * 207 * @param type return type name 208 * @return 209 */ 210 public static boolean isNonVoid(String type) { 211 return !("V".equals(type)); 212 } 213 214 /** 215 * Split a sequence of type names (in VM internal form). 216 * 217 * Example: 218 * "BCD[[ALA;I" => [ "B", "C", "D", "[[A", "LA;", "I" ] 219 * 220 * @param str 221 * @return 222 */ 223 public static String[] parseParams(String str) { 224 List<String> params = new ArrayList<>(); 225 226 /* VM basic type notation: 227 B byte signed byte 228 C char Unicode character code point in the Basic Multilingual Plane, encoded with UTF-16 229 D double double-precision floating-point value 230 F float single-precision floating-point value 231 I int integer 232 J long long integer 233 L Classname ; reference an instance of class Classname 234 S short signed short 235 Z boolean true or false 236 [ reference one array dimension 237 */ 238 int i = 0; 239 int start = 0; 240 while (i < str.length()) { 241 char c = str.charAt(i); 242 switch (c) { 243 case 'B': case 'C': case 'D': case 'F': 244 case 'I': case 'J': case 'S': case 'Z': 245 params.add(str.substring(start, i+1)); 246 start = i+1; 247 break; 248 case 'L': 249 int k = str.indexOf(';', i); 250 if (k != 1) { 251 params.add(str.substring(start, k+1)); 252 start = k+1; 253 i = k; 254 } else { 255 throw new IllegalArgumentException(str); 256 } 257 break; 258 case '[': 259 break; 260 default: 261 throw new IllegalArgumentException( 262 String.format("%d(%d): %c \'%s\'", i, start, c, str)); 263 } 264 265 i++; 266 } 267 268 if (start != str.length()) { 269 throw new IllegalArgumentException(str); 270 } 271 272 return params.toArray(new String[0]); 273 } 274 275 /** 276 * Returns default values for different types: 277 * - byte: 0 278 * - short: 0 279 * - int: 0 280 * - long: 0L 281 * - char: \U0000 282 * - boolean: false 283 * - float: 0.0f 284 * - double: 0.0d 285 * - array: null 286 * - Object: null 287 * 288 * @param types 289 * @return 290 */ 291 public static Param[] getDefaultValues(String[] types) { 292 List<Param> values = new ArrayList<>(); 293 294 for (String type : types) { 295 switch (type) { 296 case "I": case "B": case "C": case "Z": case "S": 297 values.add(new IntParam(0)); 298 break; 299 case "J": 300 values.add(new LongParam(0L)); 301 break; 302 case "D": 303 values.add(new DoubleParam(0.0d)); 304 break; 305 case "F": 306 values.add(new FloatParam(0.0f)); 307 break; 308 default: 309 if (type.startsWith("L") || type.startsWith("[")) { 310 values.add(new NullParam()); 311 } else { 312 throw new IllegalArgumentException(Arrays.toString(types)); 313 } 314 break; 315 } 316 } 317 318 return values.toArray(new Param[0]); 319 } 320 321 /** 322 * Decode class name from internal VM representation into normal Java name. 323 * Internal class naming convention is extensively used to describe method type (descriptor). 324 * 325 * Examples: 326 * "Ljava/lang/Object" => "java.lang.Object" 327 * "I" => "int" 328 * "[[[C" => "char[][][]" 329 * 330 * @param name 331 * @return 332 */ 333 public static String decodeClassName(String name) { 334 switch (name) { 335 case "Z": return "boolean"; 336 case "B": return "byte"; 337 case "S": return "short"; 338 case "C": return "char"; 339 case "I": return "int"; 340 case "J": return "long"; 341 case "F": return "float"; 342 case "D": return "double"; 343 default: 344 if (name.startsWith("L")) { 345 // "Ljava/lang/String;" => "java.lang.String" 346 return name.substring(1, name.length()-1).replaceAll("/", "."); 347 } else if (name.startsWith("[")) { 348 // "[[[C" => "char[][][]" 349 return decodeClassName(name.substring(1)) + "[]"; 350 } else { 351 throw new IllegalArgumentException(name); 352 } 353 } 354 } 355 356 /** 357 * Decode class name from internal VM format into regular name and resolve it using {@code cl} {@code ClassLoader}. 358 * It is used during conversion of method type from string representation to strongly typed variants (e.g. MethodType). 359 * 360 * @param name 361 * @param cl 362 * @return 363 */ 364 public static Class decodeClass(String name, ClassLoader cl) { 365 switch (name) { 366 case "Z": return boolean.class; 367 case "B": return byte.class; 368 case "S": return short.class; 369 case "C": return char.class; 370 case "I": return int.class; 371 case "J": return long.class; 372 case "F": return float.class; 373 case "D": return double.class; 374 case "V": return void.class; 375 default: 376 if (name.startsWith("L")) { 377 // "Ljava/lang/String;" => "java.lang.String" 378 String decodedName = name.substring(1, name.length()-1).replaceAll("/", "."); 379 try { 380 return cl.loadClass(decodedName); 381 } catch (Exception e) { 382 throw new Error(e); 383 } 384 } else if (name.startsWith("[")) { 385 // "[[[C" => "char[][][]" 386 //return decodeClassName(name.substring(1)) + "[]"; 387 throw new UnsupportedOperationException("Resolution of arrays isn't supported yet: "+name); 388 } else { 389 throw new IllegalArgumentException(name); 390 } 391 } 392 } 393 394 /** 395 * Redefine a class with a new version. 396 * 397 * @param clz class for redefinition 398 */ 399 static public void retransformClass(final Class<?> clz, final byte[] classFile) { 400 ClassFileTransformer transformer = new ClassFileTransformer() { 401 @Override 402 public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException { 403 if (clz.getClassLoader() == loader && className.equals(clz.getName())) { 404 if (Constants.TRACE_CLASS_REDEF) System.out.println("RETRANSFORM: " + className); 405 406 return classFile; 407 } else { 408 // leave the class as-is 409 return classfileBuffer; 410 } 411 } 412 }; 413 414 Transformer.inst.addTransformer(transformer, true); 415 try { 416 Transformer.inst.retransformClasses(clz); 417 } catch (UnmodifiableClassException e) { 418 throw new TestFailure(e); 419 } finally { 420 Transformer.inst.removeTransformer(transformer); 421 } 422 } 423 424 /** 425 * Redefine a class with a new version (class file in byte array). 426 * 427 * @param clz class for redefinition 428 * @param classFile new version as a byte array 429 * @return false if any errors occurred during class redefinition 430 */ 431 static public void redefineClass(Class<?> clz, byte[] classFile) { 432 if (clz == null) { 433 throw new IllegalArgumentException("clz == null"); 434 } 435 436 if (classFile == null || classFile.length == 0) { 437 throw new IllegalArgumentException("Incorrect classFile"); 438 } 439 440 if (Constants.TRACE_CLASS_REDEF) System.out.println("REDEFINE: "+clz.getName()); 441 442 if (!redefineClassIntl(clz, classFile)) { 443 throw new TestFailure("redefineClass failed: "+clz.getName()); 444 } 445 } 446 447 448 native static public boolean redefineClassIntl(Class<?> clz, byte[] classFile); 449 450 /** 451 * Get VM internal name of {@code Class<?> clz}. 452 * 453 * @param clz 454 * @return 455 */ 456 public static String getInternalName(Class<?> clz) { 457 if (!clz.isPrimitive()) { 458 return clz.getName().replaceAll("\\.", "/"); 459 } else { 460 throw new UnsupportedOperationException(); 461 } 462 } 463 }