diff options
author | Ivan Dubrov <idubrov@guidewire.com> | 2014-04-24 17:02:24 -0700 |
---|---|---|
committer | Ivan Dubrov <idubrov@guidewire.com> | 2014-04-24 17:27:05 -0700 |
commit | 38ed95d3c0e7043351bcd98edcec840440c2f50c (patch) | |
tree | 2e96aab1587260c971f55ad2e3431ab69b6571ab /framework | |
parent | b4a131ffa7877b6c1853a3b0d6cf2dc1391b72d9 (diff) | |
download | dcevm-38ed95d3c0e7043351bcd98edcec840440c2f50c.tar.gz dcevm-38ed95d3c0e7043351bcd98edcec840440c2f50c.zip |
Extracting DCEVM patch & code into separate project
Making DCEVM to be toplevel project that clones HotSpot repository,
patches the code, builds JVM and tests it.
Diffstat (limited to 'framework')
6 files changed, 618 insertions, 0 deletions
diff --git a/framework/src/main/java/org/dcevm/ClassRedefinitionPolicy.java b/framework/src/main/java/org/dcevm/ClassRedefinitionPolicy.java new file mode 100644 index 00000000..32571863 --- /dev/null +++ b/framework/src/main/java/org/dcevm/ClassRedefinitionPolicy.java @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2010, 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 org.dcevm; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * @author Kerstin Breiteneder + */ +@Retention(value = RetentionPolicy.RUNTIME) +public @interface ClassRedefinitionPolicy { + + // Default value if no alias is set. + public final static class NoClass { + } + + Class<?> alias() default NoClass.class; +} diff --git a/framework/src/main/java/org/dcevm/HotSwapTool.java b/framework/src/main/java/org/dcevm/HotSwapTool.java new file mode 100644 index 00000000..b622cba9 --- /dev/null +++ b/framework/src/main/java/org/dcevm/HotSwapTool.java @@ -0,0 +1,233 @@ +/* + * Copyright (c) 2010, 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 org.dcevm; + +import org.objectweb.asm.ClassReader; +import org.objectweb.asm.ClassWriter; +import org.objectweb.asm.Type; + +import java.io.*; +import java.lang.instrument.UnmodifiableClassException; +import java.net.URL; +import java.util.HashMap; +import java.util.Hashtable; +import java.util.Map; + +/** + * @author Thomas Wuerthinger + * @author Kerstin Breiteneder + * @author Christoph Wimberger + * @author Ivan Dubrov + */ +public class HotSwapTool { + + /** + * Prefix for the version number in the class name. The class bytes are modified that this string including + * the following number is removed. This means that e.g. A___2 is treated as A anywhere in the source code. This is introduced + * to make the IDE not complain about multiple defined classes. + */ + public static final String IDENTIFIER = "___"; + private static final String CLASS_FILE_SUFFIX = ".class"; + private static Map<Class<?>, Integer> currentVersion = new Hashtable<Class<?>, Integer>(); + private static Redefiner redefiner; + private static int redefinitionCount; + private static long totalTime; + + static { + try { + //redefiner = new JDIRedefiner(4000); + redefiner = new InstrumentationRedefiner(); + } catch (Exception e) { + throw new IllegalStateException(e); + } + } + + /** + * Returns the current version of the inner classes of a specified outer class. + * + * @param baseClass the outer class whose version is queried + * @return the version of the inner classes of the specified outer class + */ + public static int getCurrentVersion(Class<?> baseClass) { + if (!currentVersion.containsKey(baseClass)) { + currentVersion.put(baseClass, 0); + } + return currentVersion.get(baseClass); + } + + /** + * Performs an explicit shutdown and disconnects from the VM. + */ + public static void shutdown() throws IOException { + redefiner.close(); + redefiner = null; + } + + private static Map<Class<?>, byte[]> buildRedefinitionMap(Map<String, File> classes) throws IOException, ClassNotFoundException { + // Collect rename rules + // Also, makes sure all classes are loaded in the VM, before they are redefined + final Map<String, String> typeMappings = new HashMap<String, String>(); + for (String name : classes.keySet()) { + Class<?> clazz = Class.forName(name); // FIXME: classloader? + ClassRedefinitionPolicy policy = clazz.getAnnotation(ClassRedefinitionPolicy.class); + Class<?> replacement = (policy != null && policy.alias() != ClassRedefinitionPolicy.NoClass.class) ? + policy.alias() : clazz; + typeMappings.put(Type.getInternalName(clazz), stripVersion(Type.getInternalName(replacement))); + + } + + Map<Class<?>, byte[]> classesMap = new HashMap<Class<?>, byte[]>(); + for (File file : classes.values()) { + loadAdaptedClass(file, typeMappings, classesMap); + } + return classesMap; + } + + private static void loadAdaptedClass(File file, Map<String, String> typeMappnigs, Map<Class<?>, byte[]> result) throws IOException, ClassNotFoundException { + + ClassWriter writer = new ClassWriter(ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS); + TestClassAdapter adapter = new TestClassAdapter(writer, typeMappnigs); + + InputStream in = new FileInputStream(file); + try { + new ClassReader(in).accept(adapter, ClassReader.EXPAND_FRAMES); + } finally { + try { + in.close(); + } catch (IOException e) { + // Ignore. + } + } + byte[] bytes = writer.toByteArray(); + String className = adapter.getClassName().replace('/', '.'); + result.put(Class.forName(className), bytes); // FIXME: ClassLoader... + } + + /** + * Redefines all inner classes of a outer class to a specified version. Inner classes who do not have a particular + * representation for a version remain unchanged. + * + * @param outerClass the outer class whose inner classes should be redefined + * @param versionNumber the target version number + */ + public static void toVersion(Class<?> outerClass, int versionNumber) { + assert versionNumber >= 0; + + if (versionNumber == getCurrentVersion(outerClass)) { + // Nothing to do! + return; + } + + Map<String, File> files = findClassesWithVersion(outerClass, versionNumber); + + try { + Map<Class<?>, byte[]> map = buildRedefinitionMap(files); + + long startTime = System.currentTimeMillis(); + redefiner.redefineClasses(map); + long curTime = System.currentTimeMillis() - startTime; + totalTime += curTime; + redefinitionCount++; + + } catch (UnmodifiableClassException e) { + throw new UnsupportedOperationException(e); + } catch (ClassNotFoundException e) { + throw new RuntimeException("Cannot redefine classes", e); + } catch (IOException e) { + throw new RuntimeException("Cannot redefine classes", e); + } + + setCurrentVersion(outerClass, versionNumber); + } + + private static Map<String, File> findClassesWithVersion(Class<?> baseClass, int version) { + Map<String, File> classes = new HashMap<String, File>(); + + String packageName = baseClass.getPackage().getName().replace('.', '/'); + URL url = baseClass.getClassLoader().getResource(packageName); + if (url == null) { + throw new IllegalArgumentException("Cannot find URL corresponding to the package '" + packageName + "'"); + } + File folder = new File(url.getFile()); + for (File f : folder.listFiles(IsClassFile.INSTANCE)) { + String fileName = f.getName(); + String simpleName = f.getName().substring(0, f.getName().length() - CLASS_FILE_SUFFIX.length()); + String name = baseClass.getPackage().getName() + '.' + simpleName; + + if (isInnerClass(name, baseClass) && parseClassVersion(fileName) == version) { + classes.put(name, f); + } + } + return classes; + } + + private enum IsClassFile implements FilenameFilter { + INSTANCE; + + @Override + public boolean accept(File dir, String name) { + return name.endsWith(CLASS_FILE_SUFFIX); + } + } + + private static boolean isInnerClass(String name, Class<?> baseClass) { + return name.startsWith(baseClass.getName() + "$"); + } + + private static void setCurrentVersion(Class<?> baseClass, int value) { + currentVersion.put(baseClass, value); + } + + /** + * Parse version of the class from the class name. Classes are named in the form of [Name]___[Version] + */ + private static int parseClassVersion(String name) { + int index = name.indexOf(IDENTIFIER); + if (index == -1) { + return 0; + } + return Integer.valueOf(name.substring(index + IDENTIFIER.length(), name.length() - CLASS_FILE_SUFFIX.length())); + } + + private static String stripVersion(String className) { + int index = className.indexOf(IDENTIFIER); + if (index == -1) { + return className; + } + return className.substring(0, index); + } + + public static void resetTimings() { + redefinitionCount = 0; + totalTime = 0; + } + + public static int getRedefinitionCount() { + return redefinitionCount; + } + + public static long getTotalTime() { + return totalTime; + } +} diff --git a/framework/src/main/java/org/dcevm/InstrumentationRedefiner.java b/framework/src/main/java/org/dcevm/InstrumentationRedefiner.java new file mode 100644 index 00000000..247fbb2e --- /dev/null +++ b/framework/src/main/java/org/dcevm/InstrumentationRedefiner.java @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2010, 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 org.dcevm; + + +import org.dcevm.agent.InstrumentationAgent; + +import java.io.IOException; +import java.lang.instrument.ClassDefinition; +import java.lang.instrument.Instrumentation; +import java.lang.instrument.UnmodifiableClassException; +import java.util.Map; + +public class InstrumentationRedefiner implements Redefiner { + public void redefineClasses(Map<Class<?>, byte[]> classes) throws ClassNotFoundException, UnmodifiableClassException { + Instrumentation instrumentation = InstrumentationAgent.INSTRUMENTATION; + if (instrumentation == null) { + throw new IllegalStateException("Instrumentation agent is not properly installed!"); + } + + ClassDefinition[] definitions = new ClassDefinition[classes.size()]; + int i = 0; + for (Map.Entry<Class<?>, byte[]> entry : classes.entrySet()) { + definitions[i++] = new ClassDefinition(entry.getKey(), entry.getValue()); + } + instrumentation.redefineClasses(definitions); + } + + @Override + public void close() throws IOException { + // Do nothing. + } +} diff --git a/framework/src/main/java/org/dcevm/JDIRedefiner.java b/framework/src/main/java/org/dcevm/JDIRedefiner.java new file mode 100644 index 00000000..6fc742d4 --- /dev/null +++ b/framework/src/main/java/org/dcevm/JDIRedefiner.java @@ -0,0 +1,153 @@ +/* + * Copyright (c) 2010, 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 org.dcevm; + +import com.sun.jdi.Bootstrap; +import com.sun.jdi.ReferenceType; +import com.sun.jdi.VirtualMachine; +import com.sun.jdi.VirtualMachineManager; +import com.sun.jdi.connect.AttachingConnector; +import com.sun.jdi.connect.Connector.Argument; +import com.sun.jdi.connect.IllegalConnectorArgumentsException; + +import java.io.IOException; +import java.lang.reflect.Field; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * Utility class for performing class redefinition using JDI. + * </li> + * </ul> + * + * @author Thomas Wuerthinger + * @author Kerstin Breiteneder + * @author Christoph Wimberger + * + */ +public class JDIRedefiner implements Redefiner { + + private static final String PORT_ARGUMENT_NAME = "port"; + private static final String TRANSPORT_NAME = "dt_socket"; + + private VirtualMachine vm; + + + /** Port at which to connect to the agent of the VM. **/ + public static final int PORT = 4000; + + public JDIRedefiner(int port) throws IOException { + vm = connect(port); + } + + @Override + public void close() throws IOException { + disconnect(); + } + + private VirtualMachine connect(int port) throws IOException { + VirtualMachineManager manager = Bootstrap.virtualMachineManager(); + + // Find appropiate connector + List<AttachingConnector> connectors = manager.attachingConnectors(); + AttachingConnector chosenConnector = null; + for (AttachingConnector c : connectors) { + if (c.transport().name().equals(TRANSPORT_NAME)) { + chosenConnector = c; + break; + } + } + if (chosenConnector == null) { + throw new IllegalStateException("Could not find socket connector"); + } + + // Set port argument + AttachingConnector connector = chosenConnector; + Map<String, Argument> defaults = connector.defaultArguments(); + Argument arg = defaults.get(PORT_ARGUMENT_NAME); + if (arg == null) { + throw new IllegalStateException("Could not find port argument"); + } + arg.setValue(Integer.toString(port)); + + // Attach + try { + System.out.println("Connector arguments: " + defaults); + return connector.attach(defaults); + } catch (IllegalConnectorArgumentsException e) { + throw new IllegalArgumentException("Illegal connector arguments", e); + } + } + + public void disconnect() { + if (vm != null) { + vm.dispose(); + vm = null; + } + } + + public void redefineClasses(Map<Class<?>, byte[]> classes) { + refreshAllClasses(); + List<ReferenceType> references = vm.allClasses(); + + Map<ReferenceType, byte[]> map = new HashMap<ReferenceType, byte[]>(classes.size()); + for (Map.Entry<Class<?>, byte[]> entry : classes.entrySet()) { + map.put(findReference(references, entry.getKey().getName()), entry.getValue()); + } + vm.redefineClasses(map); + } + + /** + * Call this method before calling allClasses() in order to refresh the JDI state of loaded classes. + * This is necessary because the JDI map of all loaded classes is only updated based on events received over JDWP (network connection) + * and therefore it is not necessarily up-to-date with the real state within the VM. + */ + private void refreshAllClasses() { + try { + Field f = vm.getClass().getDeclaredField("retrievedAllTypes"); + f.setAccessible(true); + f.set(vm, false); + } catch (IllegalArgumentException ex) { + Logger.getLogger(HotSwapTool.class.getName()).log(Level.SEVERE, null, ex); + } catch (IllegalAccessException ex) { + Logger.getLogger(HotSwapTool.class.getName()).log(Level.SEVERE, null, ex); + } catch (NoSuchFieldException ex) { + Logger.getLogger(HotSwapTool.class.getName()).log(Level.SEVERE, null, ex); + } catch (SecurityException ex) { + Logger.getLogger(HotSwapTool.class.getName()).log(Level.SEVERE, null, ex); + } + } + + private static ReferenceType findReference(List<ReferenceType> list, String name) { + for (ReferenceType ref : list) { + if (ref.name().equals(name)) { + return ref; + } + } + throw new IllegalArgumentException("Cannot find corresponding reference for class name '" + name + "'" ); + } +} diff --git a/framework/src/main/java/org/dcevm/Redefiner.java b/framework/src/main/java/org/dcevm/Redefiner.java new file mode 100644 index 00000000..cd183fe3 --- /dev/null +++ b/framework/src/main/java/org/dcevm/Redefiner.java @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2010, 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 org.dcevm; + +import java.io.Closeable; +import java.lang.instrument.UnmodifiableClassException; +import java.util.Map; + +/** + * Interface to the class redefinition implementation (JDI-based, Instrumenattion API based) + * + * @author Ivan Dubrov + */ +public interface Redefiner extends Closeable { + void redefineClasses(Map<Class<?>, byte[]> classes) throws ClassNotFoundException, UnmodifiableClassException; +} diff --git a/framework/src/main/java/org/dcevm/TestClassAdapter.java b/framework/src/main/java/org/dcevm/TestClassAdapter.java new file mode 100644 index 00000000..bd4b2daf --- /dev/null +++ b/framework/src/main/java/org/dcevm/TestClassAdapter.java @@ -0,0 +1,101 @@ +/* + * Copyright (c) 2010, 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 org.dcevm; + +import org.objectweb.asm.ClassVisitor; +import org.objectweb.asm.MethodVisitor; +import org.objectweb.asm.commons.Remapper; +import org.objectweb.asm.commons.RemappingClassAdapter; +import org.objectweb.asm.commons.RemappingMethodAdapter; + +import java.util.Map; + +/** + * @author Ivan Dubrov + */ +public class TestClassAdapter extends RemappingClassAdapter { + /** + * This suffix is automatically removed from the method. + */ + private final static String METHOD_SUFFIX = "___"; + + private boolean isObject; + + public TestClassAdapter(ClassVisitor cv, final Map<String, String> typeMappings) { + super(cv, new Remapper() { + @Override + public String map(String type) { + return typeMappings.containsKey(type) ? typeMappings.get(type) : type; + } + }); + } + + @Override + public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) { + // For java/lang/Object redefinition + String newName = remapper.mapType(name); + if (newName.equals("java/lang/Object")) { + superName = null; + isObject = true; + } + super.visit(version, access, name, signature, superName, interfaces); + } + + @Override + public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) { + return super.visitMethod(access, stripMethodSuffix(name), desc, signature, exceptions); + } + + /** + * Get renamed class name. + * + * @return + */ + public String getClassName() { + return remapper.mapType(className); + } + + protected MethodVisitor createRemappingMethodAdapter( + int access, + String newDesc, + MethodVisitor mv) + { + return new RemappingMethodAdapter(access, newDesc, mv, remapper) { + @Override + public void visitMethodInsn(int opcode, String owner, String name, String desc) { + if (name.equals("<init>") && isObject && owner.equals("java/lang/Object")) { + return; + } + + super.visitMethodInsn(opcode, owner, stripMethodSuffix(name), desc); + } + }; + } + + private static String stripMethodSuffix(String name) { + int pos = name.indexOf(METHOD_SUFFIX); + return (pos != -1) ? name.substring(0, pos) : name; + } +} + |