--- /dev/null 2018-05-14 10:15:02.574213890 -0700 +++ new/test/hotspot/jtreg/vmTestbase/vm/runtime/defmeth/shared/Util.java 2018-05-21 10:53:09.013502405 -0700 @@ -0,0 +1,463 @@ +/* + * 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 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 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 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(); + } + } +}