package org.hotswap.agent.plugin.jvm;
import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import org.hotswap.agent.annotation.LoadEvent;
import org.hotswap.agent.annotation.OnClassLoadEvent;
import org.hotswap.agent.annotation.Plugin;
import org.hotswap.agent.command.Command;
import org.hotswap.agent.config.PluginManager;
import org.hotswap.agent.javassist.CannotCompileException;
import org.hotswap.agent.javassist.CtClass;
import org.hotswap.agent.javassist.CtConstructor;
import org.hotswap.agent.javassist.CtField;
import org.hotswap.agent.javassist.CtMethod;
import org.hotswap.agent.javassist.Modifier;
import org.hotswap.agent.javassist.NotFoundException;
import org.hotswap.agent.javassist.expr.ExprEditor;
import org.hotswap.agent.javassist.expr.FieldAccess;
import org.hotswap.agent.logging.AgentLogger;
/**
* ClassInitPlugin initializes static (class) variables after class redefinition. Initializes new enumeration values.
*
* @author Vladimir Dvorak
*/
@Plugin(name = "ClassInitPlugin",
description = "Initialize empty static fields (left by DCEVM) using code from <clinit> method.",
testedVersions = {"DCEVM"})
public class ClassInitPlugin {
private static AgentLogger LOGGER = AgentLogger.getLogger(ClassInitPlugin.class);
private static final String HOTSWAP_AGENT_CLINIT_METHOD = "__ha_clinit";
public static boolean reloadFlag;
@OnClassLoadEvent(classNameRegexp = ".*", events = LoadEvent.REDEFINE)
public static void patch(final CtClass ctClass, final ClassLoader classLoader, final Class<?> originalClass) throws IOException, CannotCompileException, NotFoundException {
if (isSyntheticClass(originalClass)) {
return;
}
final String className = ctClass.getName();
try {
CtMethod origMethod = ctClass.getDeclaredMethod(HOTSWAP_AGENT_CLINIT_METHOD);
ctClass.removeMethod(origMethod);
} catch (org.hotswap.agent.javassist.NotFoundException ex) {
// swallow
}
CtConstructor clinit = ctClass.getClassInitializer();
if (clinit != null) {
LOGGER.debug("Adding __ha_clinit to class: {}", className);
CtConstructor haClinit = new CtConstructor(clinit, ctClass, null);
haClinit.getMethodInfo().setName(HOTSWAP_AGENT_CLINIT_METHOD);
haClinit.setModifiers(Modifier.PUBLIC | Modifier.STATIC);
ctClass.addConstructor(haClinit);
final boolean reinitializeStatics[] = new boolean[] { false };
haClinit.instrument(
new ExprEditor() {
public void edit(FieldAccess f) throws CannotCompileException {
try {
if (f.isStatic() && f.isWriter()) {
Field originalField = null;
try {
originalField = originalClass.getDeclaredField(f.getFieldName());
} catch (NoSuchFieldException e) {
LOGGER.debug("New field will be initialized {}", f.getFieldName());
reinitializeStatics[0] = true;
}
if (originalField != null) {
// ENUM$VALUES is last in enumeration
if (originalClass.isEnum() && "ENUM$VALUES".equals(f.getFieldName())) {
if (reinitializeStatics[0]) {
LOGGER.debug("New field will be initialized {}", f.getFieldName());
} else {
reinitializeStatics[0] = checkOldEnumValues(ctClass, originalClass);
}
} else {
LOGGER.debug("Skipping old field {}", f.getFieldName());
f.replace("{}");
}
}
}
} catch (Exception e) {
LOGGER.error("Patching __ha_clinit method failed.", e);
}
}
}
);
if (reinitializeStatics[0]) {
PluginManager.getInstance().getScheduler().scheduleCommand(new Command() {
@Override
public void executeCommand() {
try {
Class<?> clazz = classLoader.loadClass(className);
Method m = clazz.getDeclaredMethod(HOTSWAP_AGENT_CLINIT_METHOD, new Class[] {});
if (m != null) {
m.invoke(null, new Object[] {});
}
} catch (Exception e) {
LOGGER.error("Error initializing redefined class {}", e, className);
} finally {
reloadFlag = false;
}
}
}, 150); // Hack : init should be done after dependant class redefinition. Since the class can
// be proxied by syntetic proxy, the class init must be scheduled after proxy redefinition.
// Currently proxy redefinition (in ProxyPlugin) is scheduled with 100ms delay, therefore
// the class init must be scheduled after it.
} else {
reloadFlag = false;
}
}
}
private static boolean checkOldEnumValues(CtClass ctClass, Class<?> originalClass) {
if (ctClass.isEnum()) {
// Check if some field from original enumeration was deleted
Enum<?>[] enumConstants = (Enum<?>[]) originalClass.getEnumConstants();
for (Enum<?> en : enumConstants) {
try {
CtField existing = ctClass.getDeclaredField(en.toString());
} catch (NotFoundException e) {
LOGGER.debug("Enum field deleted. ENUM$VALUES will be reinitialized {}", en.toString());
return true;
}
}
} else {
LOGGER.error("Patching __ha_clinit method failed. Enum type expected {}", ctClass.getName());
}
return false;
}
private static boolean isSyntheticClass(Class<?> classBeingRedefined) {
return classBeingRedefined.getSimpleName().contains("$$_javassist")
|| classBeingRedefined.getName().startsWith("com.sun.proxy.$Proxy")
|| classBeingRedefined.getSimpleName().contains("$$");
}
}