| /* |
| * Copyright (c) 2013, 2018, Oracle and/or its affiliates. All rights reserved. |
| * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. |
| * |
| * This code is free software; you can redistribute it and/or modify it |
| * under the terms of the GNU General Public License version 2 only, as |
| * published by the Free Software Foundation. |
| * |
| * This code is distributed in the hope that it will be useful, but WITHOUT |
| * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or |
| * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License |
| * version 2 for more details (a copy is included in the LICENSE file that |
| * accompanied this code). |
| * |
| * You should have received a copy of the GNU General Public License version |
| * 2 along with this work; if not, write to the Free Software Foundation, |
| * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. |
| * |
| * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA |
| * or visit www.oracle.com if you need additional information or have any |
| * questions. |
| */ |
| |
| package vm.runtime.defmeth.shared; |
| |
| import vm.runtime.defmeth.shared.data.Clazz; |
| import java.io.PrintWriter; |
| import java.lang.instrument.ClassFileTransformer; |
| import java.lang.instrument.IllegalClassFormatException; |
| import java.lang.instrument.Instrumentation; |
| import java.lang.instrument.UnmodifiableClassException; |
| import java.security.ProtectionDomain; |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.List; |
| import java.util.regex.Matcher; |
| import java.util.regex.Pattern; |
| import nsk.share.Pair; |
| import nsk.share.TestFailure; |
| import vm.runtime.defmeth.shared.data.method.param.*; |
| |
| |
| /** |
| * Utility class with auxiliary miscellaneous methods. |
| */ |
| public class Util { |
| public static class Transformer { |
| private static Instrumentation inst; |
| |
| public static void premain(String agentArgs, Instrumentation inst) { |
| Transformer.inst = inst; |
| |
| /* |
| inst.addTransformer(new ClassFileTransformer() { |
| @Override |
| public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException { |
| System.out.println("Retransform (initial): " + className); |
| return classfileBuffer; |
| } |
| }); |
| */ |
| } |
| } |
| |
| /** |
| * Concatenate {@code strings} array interleaving with {@code sep} in between. |
| * |
| * @param sep |
| * @param strings |
| * @return |
| */ |
| public static String intersperse(String sep, String... strings) { |
| StringBuilder sb = new StringBuilder(); |
| if (strings.length == 0) { |
| return ""; |
| } else if (strings.length == 1) { |
| return strings[0]; |
| } else { |
| sb.append(strings[0]); |
| |
| for (int i=1; i<strings.length; i++) { |
| sb.append(sep).append(strings[i]); |
| } |
| |
| return sb.toString(); |
| } |
| } |
| |
| /** |
| * Construct array of names for an array of {@code Clazz} instances. |
| * |
| * @param clazzes |
| * @return |
| */ |
| public static String[] asStrings(Clazz[] clazzes) { |
| String[] result = new String[clazzes.length]; |
| for (int i = 0; i < clazzes.length; i++) { |
| result[i] = clazzes[i].intlName(); |
| } |
| |
| return result; |
| } |
| |
| /** |
| * Get the name of the test currently being executed |
| * |
| * @return name of the test being executed |
| */ |
| public static String getTestName() { |
| // Hack: examine stack trace and extract test method's name from there |
| try { |
| throw new Exception(); |
| } catch (Exception e) { |
| for (StackTraceElement elem : e.getStackTrace()) { |
| String className = elem.getClassName(); |
| String methodName = elem.getMethodName(); |
| |
| if (className.startsWith("vm.runtime.defmeth.") && |
| methodName.startsWith("test")) { |
| return String.format("%s.%s", |
| className.replaceAll(".*\\.", ""), methodName); |
| } |
| } |
| |
| return "Unknown"; |
| } |
| } |
| |
| /** |
| * Pretty-print {@code byte[] classFile} to stdout. |
| * |
| * @param classFile |
| */ |
| public static void printClassFile(byte[] classFile) { |
| int flags = jdk.internal.org.objectweb.asm.ClassReader.SKIP_DEBUG; |
| |
| classFile = classFile.clone(); |
| |
| // FIXME: workaround for V1_7 limiation of current ASM |
| // Artificially downgrade |
| if (classFile[7] == 52) { |
| System.out.println("WARNING: downgraded major verson from 52 to 0"); |
| classFile[7] = 0; |
| } |
| |
| jdk.internal.org.objectweb.asm.ClassReader cr = |
| new jdk.internal.org.objectweb.asm.ClassReader(classFile); |
| |
| cr.accept(new jdk.internal.org.objectweb.asm.util.TraceClassVisitor(new PrintWriter(System.out)), |
| flags); |
| |
| } |
| |
| /** |
| * Print ASM version (sequence of calls to ASM API to produce same class file) |
| * of {@code classFile} to stdout. |
| * |
| * @param classFile |
| */ |
| public static void asmifyClassFile(byte[] classFile) { |
| int flags = jdk.internal.org.objectweb.asm.ClassReader.SKIP_DEBUG; |
| |
| // FIXME: workaround for V1_7 limiation of current ASM |
| // Artificially downgrade |
| if (classFile[7] == 52) { |
| // Need to patch the bytecode, so make a copy of the class file |
| classFile = classFile.clone(); |
| |
| System.out.println("WARNING: downgraded major verson from 52 to 0"); |
| classFile[7] = 0; |
| } |
| |
| jdk.internal.org.objectweb.asm.ClassReader cr = |
| new jdk.internal.org.objectweb.asm.ClassReader(classFile); |
| |
| //cr.accept(new TraceClassVisitor(new PrintWriter(System.out)), flags); |
| cr.accept(new jdk.internal.org.objectweb.asm.util.TraceClassVisitor(null, |
| new jdk.internal.org.objectweb.asm.util.ASMifier(), |
| new PrintWriter(System.out)), flags); |
| } |
| |
| /** |
| * Parse method descriptor and split it into parameter types names and |
| * return type name. |
| * |
| * @param desc |
| * @return {@code Pair} of parameter types names and |
| */ |
| public static Pair<String[],String> parseDesc(String desc) { |
| Pattern p = Pattern.compile("\\((.*)\\)(.*)"); |
| Matcher m = p.matcher(desc); |
| |
| if (m.matches()) { |
| String opts = m.group(1); |
| String returnVal = m.group(2); |
| |
| return Pair.of(parseParams(opts), returnVal); |
| } else { |
| throw new IllegalArgumentException(desc); |
| } |
| |
| } |
| |
| /** |
| * Check whether a type isn't Void by it's name. |
| * |
| * @param type return type name |
| * @return |
| */ |
| public static boolean isNonVoid(String type) { |
| return !("V".equals(type)); |
| } |
| |
| /** |
| * Split a sequence of type names (in VM internal form). |
| * |
| * Example: |
| * "BCD[[ALA;I" => [ "B", "C", "D", "[[A", "LA;", "I" ] |
| * |
| * @param str |
| * @return |
| */ |
| public static String[] parseParams(String str) { |
| List<String> params = new ArrayList<>(); |
| |
| /* VM basic type notation: |
| B byte signed byte |
| C char Unicode character code point in the Basic Multilingual Plane, encoded with UTF-16 |
| D double double-precision floating-point value |
| F float single-precision floating-point value |
| I int integer |
| J long long integer |
| L Classname ; reference an instance of class Classname |
| S short signed short |
| Z boolean true or false |
| [ reference one array dimension |
| */ |
| int i = 0; |
| int start = 0; |
| while (i < str.length()) { |
| char c = str.charAt(i); |
| switch (c) { |
| case 'B': case 'C': case 'D': case 'F': |
| case 'I': case 'J': case 'S': case 'Z': |
| params.add(str.substring(start, i+1)); |
| start = i+1; |
| break; |
| case 'L': |
| int k = str.indexOf(';', i); |
| if (k != 1) { |
| params.add(str.substring(start, k+1)); |
| start = k+1; |
| i = k; |
| } else { |
| throw new IllegalArgumentException(str); |
| } |
| break; |
| case '[': |
| break; |
| default: |
| throw new IllegalArgumentException( |
| String.format("%d(%d): %c \'%s\'", i, start, c, str)); |
| } |
| |
| i++; |
| } |
| |
| if (start != str.length()) { |
| throw new IllegalArgumentException(str); |
| } |
| |
| return params.toArray(new String[0]); |
| } |
| |
| /** |
| * Returns default values for different types: |
| * - byte: 0 |
| * - short: 0 |
| * - int: 0 |
| * - long: 0L |
| * - char: \U0000 |
| * - boolean: false |
| * - float: 0.0f |
| * - double: 0.0d |
| * - array: null |
| * - Object: null |
| * |
| * @param types |
| * @return |
| */ |
| public static Param[] getDefaultValues(String[] types) { |
| List<Param> values = new ArrayList<>(); |
| |
| for (String type : types) { |
| switch (type) { |
| case "I": case "B": case "C": case "Z": case "S": |
| values.add(new IntParam(0)); |
| break; |
| case "J": |
| values.add(new LongParam(0L)); |
| break; |
| case "D": |
| values.add(new DoubleParam(0.0d)); |
| break; |
| case "F": |
| values.add(new FloatParam(0.0f)); |
| break; |
| default: |
| if (type.startsWith("L") || type.startsWith("[")) { |
| values.add(new NullParam()); |
| } else { |
| throw new IllegalArgumentException(Arrays.toString(types)); |
| } |
| break; |
| } |
| } |
| |
| return values.toArray(new Param[0]); |
| } |
| |
| /** |
| * Decode class name from internal VM representation into normal Java name. |
| * Internal class naming convention is extensively used to describe method type (descriptor). |
| * |
| * Examples: |
| * "Ljava/lang/Object" => "java.lang.Object" |
| * "I" => "int" |
| * "[[[C" => "char[][][]" |
| * |
| * @param name |
| * @return |
| */ |
| public static String decodeClassName(String name) { |
| switch (name) { |
| case "Z": return "boolean"; |
| case "B": return "byte"; |
| case "S": return "short"; |
| case "C": return "char"; |
| case "I": return "int"; |
| case "J": return "long"; |
| case "F": return "float"; |
| case "D": return "double"; |
| default: |
| if (name.startsWith("L")) { |
| // "Ljava/lang/String;" => "java.lang.String" |
| return name.substring(1, name.length()-1).replaceAll("/", "."); |
| } else if (name.startsWith("[")) { |
| // "[[[C" => "char[][][]" |
| return decodeClassName(name.substring(1)) + "[]"; |
| } else { |
| throw new IllegalArgumentException(name); |
| } |
| } |
| } |
| |
| /** |
| * Decode class name from internal VM format into regular name and resolve it using {@code cl} {@code ClassLoader}. |
| * It is used during conversion of method type from string representation to strongly typed variants (e.g. MethodType). |
| * |
| * @param name |
| * @param cl |
| * @return |
| */ |
| public static Class decodeClass(String name, ClassLoader cl) { |
| switch (name) { |
| case "Z": return boolean.class; |
| case "B": return byte.class; |
| case "S": return short.class; |
| case "C": return char.class; |
| case "I": return int.class; |
| case "J": return long.class; |
| case "F": return float.class; |
| case "D": return double.class; |
| case "V": return void.class; |
| default: |
| if (name.startsWith("L")) { |
| // "Ljava/lang/String;" => "java.lang.String" |
| String decodedName = name.substring(1, name.length()-1).replaceAll("/", "."); |
| try { |
| return cl.loadClass(decodedName); |
| } catch (Exception e) { |
| throw new Error(e); |
| } |
| } else if (name.startsWith("[")) { |
| // "[[[C" => "char[][][]" |
| //return decodeClassName(name.substring(1)) + "[]"; |
| throw new UnsupportedOperationException("Resolution of arrays isn't supported yet: "+name); |
| } else { |
| throw new IllegalArgumentException(name); |
| } |
| } |
| } |
| |
| /** |
| * Redefine a class with a new version. |
| * |
| * @param clz class for redefinition |
| */ |
| static public void retransformClass(final Class<?> clz, final byte[] classFile) { |
| ClassFileTransformer transformer = new ClassFileTransformer() { |
| @Override |
| public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException { |
| if (clz.getClassLoader() == loader && className.equals(clz.getName())) { |
| if (Constants.TRACE_CLASS_REDEF) System.out.println("RETRANSFORM: " + className); |
| |
| return classFile; |
| } else { |
| // leave the class as-is |
| return classfileBuffer; |
| } |
| } |
| }; |
| |
| Transformer.inst.addTransformer(transformer, true); |
| try { |
| Transformer.inst.retransformClasses(clz); |
| } catch (UnmodifiableClassException e) { |
| throw new TestFailure(e); |
| } finally { |
| Transformer.inst.removeTransformer(transformer); |
| } |
| } |
| |
| /** |
| * Redefine a class with a new version (class file in byte array). |
| * |
| * @param clz class for redefinition |
| * @param classFile new version as a byte array |
| * @return false if any errors occurred during class redefinition |
| */ |
| static public void redefineClass(Class<?> clz, byte[] classFile) { |
| if (clz == null) { |
| throw new IllegalArgumentException("clz == null"); |
| } |
| |
| if (classFile == null || classFile.length == 0) { |
| throw new IllegalArgumentException("Incorrect classFile"); |
| } |
| |
| if (Constants.TRACE_CLASS_REDEF) System.out.println("REDEFINE: "+clz.getName()); |
| |
| if (!redefineClassIntl(clz, classFile)) { |
| throw new TestFailure("redefineClass failed: "+clz.getName()); |
| } |
| } |
| |
| |
| native static public boolean redefineClassIntl(Class<?> clz, byte[] classFile); |
| |
| /** |
| * Get VM internal name of {@code Class<?> clz}. |
| * |
| * @param clz |
| * @return |
| */ |
| public static String getInternalName(Class<?> clz) { |
| if (!clz.isPrimitive()) { |
| return clz.getName().replaceAll("\\.", "/"); |
| } else { |
| throw new UnsupportedOperationException(); |
| } |
| } |
| } |