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 }