From e1c01bf5809b761b5a0e0d4a5f6fa917b4867caf Mon Sep 17 00:00:00 2001 From: Anton Keks Date: Sat, 17 Dec 2016 20:01:31 +0200 Subject: [PATCH] Rename old LocalvariablesNamesEnhancerJava7, which also generates parameter names fields. This class can be dropped when we drop Java 7 support. Plain LocalvariablesNamesEnhancer will not do it anymore, because parameter names are available using standard API in Java 8. --- framework/src/play/CorePlugin.java | 2 +- .../LocalvariablesNamesEnhancer.java | 127 +-------- .../LocalvariablesNamesEnhancerJava7.java | 249 ++++++++++++++++++ framework/src/play/utils/Java.java | 10 +- 4 files changed, 259 insertions(+), 129 deletions(-) create mode 100644 framework/src/play/classloading/enhancers/LocalvariablesNamesEnhancerJava7.java diff --git a/framework/src/play/CorePlugin.java b/framework/src/play/CorePlugin.java index d2354748a2..3dff948b5e 100644 --- a/framework/src/play/CorePlugin.java +++ b/framework/src/play/CorePlugin.java @@ -291,7 +291,7 @@ protected Enhancer[] defaultEnhancers() { new SigEnhancer(), new ControllersEnhancer(), new MailerEnhancer(), - new LocalvariablesNamesEnhancer() + Play.classes.java8 ? new LocalvariablesNamesEnhancer() : new LocalvariablesNamesEnhancerJava7() }; } diff --git a/framework/src/play/classloading/enhancers/LocalvariablesNamesEnhancer.java b/framework/src/play/classloading/enhancers/LocalvariablesNamesEnhancer.java index b431eaa6cd..b277ed44de 100644 --- a/framework/src/play/classloading/enhancers/LocalvariablesNamesEnhancer.java +++ b/framework/src/play/classloading/enhancers/LocalvariablesNamesEnhancer.java @@ -1,10 +1,10 @@ package play.classloading.enhancers; -import javassist.*; +import javassist.CtClass; +import javassist.CtMethod; import javassist.bytecode.*; import play.Logger; import play.classloading.ApplicationClasses.ApplicationClass; -import play.libs.F.T2; import java.lang.reflect.Field; import java.util.*; @@ -16,7 +16,7 @@ public class LocalvariablesNamesEnhancer extends Enhancer { @Override public void enhanceThisClass(ApplicationClass applicationClass) throws Exception { - if (isAnon(applicationClass)) { + if (isAnon(applicationClass) || isScala(applicationClass)) { return; } @@ -37,72 +37,9 @@ public void enhanceThisClass(ApplicationClass applicationClass) throws Exception if (codeAttribute == null || javassist.Modifier.isAbstract(method.getModifiers())) { continue; } + LocalVariableAttribute localVariableAttribute = (LocalVariableAttribute) codeAttribute.getAttribute("LocalVariableTable"); - List> parameterNames = new ArrayList<>(); - if (localVariableAttribute == null) { - if(method.getParameterTypes().length > 0) - continue; - } else { - if(localVariableAttribute.tableLength() < method.getParameterTypes().length + (Modifier.isStatic(method.getModifiers()) ? 0 : 1)) { - Logger.warn("weird: skipping method %s %s as its number of local variables is incorrect (lv=%s || lv.length=%s || params.length=%s || (isStatic? %s)", method.getReturnType().getName(), method.getLongName(), localVariableAttribute, localVariableAttribute != null ? localVariableAttribute.tableLength() : -1, method.getParameterTypes().length, Modifier.isStatic(method.getModifiers())); - } - for(int i=0; i(localVariableAttribute.startPc(i) + localVariableAttribute.index(i), localVariableAttribute.variableName(i))); - } - } - Collections.sort(parameterNames, new Comparator>() { - @Override - public int compare(T2 o1, T2 o2) { - return o1._1.compareTo(o2._1); - } - - }); - } - List names = new ArrayList<>(); - for (int i = 0; i < method.getParameterTypes().length + (Modifier.isStatic(method.getModifiers()) ? 0 : 1); i++) { - if (localVariableAttribute == null) { - continue; - } - try { - String name = parameterNames.get(i)._2; - if (!"this".equals(name)) { - names.add(name); - } - } catch (Exception e) { - Logger.warn(e, "While applying localvariables to %s.%s, param %s", ctClass.getName(), method.getName(), i); - } - } - StringBuilder iv = new StringBuilder(); - if (names.isEmpty()) { - iv.append("new String[0];"); - } else { - iv.append("new String[] {"); - for (Iterator i = names.iterator(); i.hasNext();) { - iv.append("\""); - String aliasedName = i.next(); - if (aliasedName.contains("$")) { - aliasedName = aliasedName.substring(0, aliasedName.indexOf("$")); - } - iv.append(aliasedName); - iv.append("\""); - if (i.hasNext()) { - iv.append(","); - } - } - iv.append("};"); - } - - String sigField = "$" + method.getName() + LocalVariablesNamesTracer.computeMethodHash(method.getParameterTypes()); - try { // #1198 - ctClass.getDeclaredField(sigField); - } catch (NotFoundException nfe) { - CtField signature = CtField.make("public static String[] " + sigField + " = " + iv, ctClass); - ctClass.addField(signature); - } - - if (localVariableAttribute == null || isScala(applicationClass)) { continue; } @@ -122,18 +59,11 @@ public int compare(T2 o1, T2 o2) { aliasedName = aliasedName.substring(0, aliasedName.indexOf("$")); } - if ("this".equals(name)) { continue; } - /* DEBUG - IO.write(ctClass.toBytecode(), new File("/tmp/lv_"+applicationClass.name+".class")); - ctClass.defrost(); - */ - try { - // The instruction at which this local variable has been created Integer pc = localVariableAttribute.startPc(i); @@ -191,7 +121,7 @@ public int compare(T2 o1, T2 o2) { } - private static Bytecode makeBytecodeForLVStore(CtMethod method, String sig, String name, int slot) { + static Bytecode makeBytecodeForLVStore(CtMethod method, String sig, String name, int slot) { Bytecode b = new Bytecode(method.getMethodInfo().getConstPool()); b.addLdc(name); if("I".equals(sig) || "B".equals(sig) || "C".equals(sig) || "S".equals(sig) || "Z".equals(sig)) @@ -228,49 +158,6 @@ public interface LocalVariablesSupport { */ public static class LocalVariablesNamesTracer { - public static Integer computeMethodHash(CtClass[] parameters) { - String[] names = new String[parameters.length]; - for (int i = 0; i < parameters.length; i++) { - names[i] = parameters[i].getName(); - } - return computeMethodHash(names); - } - - public static Integer computeMethodHash(Class[] parameters) { - String[] names = new String[parameters.length]; - for (int i = 0; i < parameters.length; i++) { - Class param = parameters[i]; - names[i] = ""; - if (param.isArray()) { - int level = 1; - param = param.getComponentType(); - // Array of array - while (param.isArray()) { - level++; - param = param.getComponentType(); - } - names[i] = param.getName(); - for (int j = 0; j < level; j++) { - names[i] += "[]"; - } - } else { - names[i] = param.getName(); - } - } - return computeMethodHash(names); - } - - public static Integer computeMethodHash(String[] parameters) { - StringBuilder buffer = new StringBuilder(); - for (String param : parameters) { - buffer.append(param); - } - Integer hash = buffer.toString().hashCode(); - if (hash < 0) { - return -hash; - } - return hash; - } static final ThreadLocal>> localVariables = new ThreadLocal<>(); public static void checkEmpty() { @@ -378,9 +265,9 @@ public static void setLocalVariablesStateAfterAwait(Stack> s } } - private static final Map storeByCode = new HashMap<>(); + static final Map storeByCode = new HashMap<>(); - /** + /* * Useful instructions */ static { diff --git a/framework/src/play/classloading/enhancers/LocalvariablesNamesEnhancerJava7.java b/framework/src/play/classloading/enhancers/LocalvariablesNamesEnhancerJava7.java new file mode 100644 index 0000000000..993cc80f89 --- /dev/null +++ b/framework/src/play/classloading/enhancers/LocalvariablesNamesEnhancerJava7.java @@ -0,0 +1,249 @@ +package play.classloading.enhancers; + +import javassist.*; +import javassist.bytecode.Bytecode; +import javassist.bytecode.CodeAttribute; +import javassist.bytecode.CodeIterator; +import javassist.bytecode.LocalVariableAttribute; +import play.Logger; +import play.classloading.ApplicationClasses.ApplicationClass; +import play.exceptions.UnexpectedException; +import play.libs.F.T2; + +import java.lang.reflect.Method; +import java.util.*; + +/** + * Track names of local variables + generate signature fields for Java 7 support + */ +public class LocalvariablesNamesEnhancerJava7 extends LocalvariablesNamesEnhancer { + + @Override + public void enhanceThisClass(ApplicationClass applicationClass) throws Exception { + if (isAnon(applicationClass)) { + return; + } + + CtClass ctClass = makeClass(applicationClass); + if (!ctClass.subtypeOf(classPool.get(LocalVariablesSupport.class.getName())) && !ctClass.getName().matches("^controllers\\..*\\$class$")) { + return; + } + + for (CtMethod method : ctClass.getDeclaredMethods()) { + + if (method.getName().contains("$")) { + // Generated method, skip + continue; + } + + // Signatures names + CodeAttribute codeAttribute = (CodeAttribute) method.getMethodInfo().getAttribute("Code"); + if (codeAttribute == null || Modifier.isAbstract(method.getModifiers())) { + continue; + } + LocalVariableAttribute localVariableAttribute = (LocalVariableAttribute) codeAttribute.getAttribute("LocalVariableTable"); + List> parameterNames = new ArrayList<>(); + + if (localVariableAttribute == null) { + if(method.getParameterTypes().length > 0) + continue; + } else { + if(localVariableAttribute.tableLength() < method.getParameterTypes().length + (Modifier.isStatic(method.getModifiers()) ? 0 : 1)) { + Logger.warn("weird: skipping method %s %s as its number of local variables is incorrect (lv=%s || lv.length=%s || params.length=%s || (isStatic? %s)", method.getReturnType().getName(), method.getLongName(), localVariableAttribute, localVariableAttribute != null ? localVariableAttribute.tableLength() : -1, method.getParameterTypes().length, Modifier.isStatic(method.getModifiers())); + } + for(int i=0; i(localVariableAttribute.startPc(i) + localVariableAttribute.index(i), localVariableAttribute.variableName(i))); + } + } + Collections.sort(parameterNames, new Comparator>() { + @Override + public int compare(T2 o1, T2 o2) { + return o1._1.compareTo(o2._1); + } + + }); + } + List names = new ArrayList<>(); + for (int i = 0; i < method.getParameterTypes().length + (Modifier.isStatic(method.getModifiers()) ? 0 : 1); i++) { + if (localVariableAttribute == null) { + continue; + } + try { + String name = parameterNames.get(i)._2; + if (!"this".equals(name)) { + names.add(name); + } + } catch (Exception e) { + Logger.warn(e, "While applying localvariables to %s.%s, param %s", ctClass.getName(), method.getName(), i); + } + } + StringBuilder iv = new StringBuilder(); + if (names.isEmpty()) { + iv.append("new String[0];"); + } else { + iv.append("new String[] {"); + for (Iterator i = names.iterator(); i.hasNext();) { + iv.append("\""); + String aliasedName = i.next(); + if (aliasedName.contains("$")) { + aliasedName = aliasedName.substring(0, aliasedName.indexOf("$")); + } + iv.append(aliasedName); + iv.append("\""); + if (i.hasNext()) { + iv.append(","); + } + } + iv.append("};"); + } + + String sigField = "$" + method.getName() + computeMethodHash(method.getParameterTypes()); + try { // #1198 + ctClass.getDeclaredField(sigField); + } catch (NotFoundException nfe) { + CtField signature = CtField.make("public static String[] " + sigField + " = " + iv, ctClass); + ctClass.addField(signature); + } + + if (localVariableAttribute == null || isScala(applicationClass)) { + continue; + } + + // OK. + // Here after each local variable creation instruction, + // we insert a call to play.utils.LocalVariables.addVariable('var', var) + // without breaking everything... + for (int i = 0; i < localVariableAttribute.tableLength(); i++) { + + // name of the local variable + String name = localVariableAttribute.getConstPool().getUtf8Info(localVariableAttribute.nameIndex(i)); + + // Normalize the variable name + // For several reasons, both variables name and name$1 will be aliased to name + String aliasedName = name; + if (aliasedName.contains("$")) { + aliasedName = aliasedName.substring(0, aliasedName.indexOf("$")); + } + + + if ("this".equals(name)) { + continue; + } + + /* DEBUG + IO.write(ctClass.toBytecode(), new File("/tmp/lv_"+applicationClass.name+".class")); + ctClass.defrost(); + */ + + try { + + // The instruction at which this local variable has been created + Integer pc = localVariableAttribute.startPc(i); + + // Move to the next instruction (insertionPc) + CodeIterator codeIterator = codeAttribute.iterator(); + codeIterator.move(pc); + pc = codeIterator.next(); + + Bytecode b = makeBytecodeForLVStore(method, localVariableAttribute.signature(i), name, localVariableAttribute.index(i)); + codeIterator.insert(pc, b.get()); + codeAttribute.setMaxStack(codeAttribute.computeMaxStack()); + + // Bon chaque instruction de cette méthode + while (codeIterator.hasNext()) { + int index = codeIterator.next(); + int op = codeIterator.byteAt(index); + + // DEBUG + // printOp(op); + + int varNumber = -1; + // The variable changes + if (storeByCode.containsKey(op)) { + varNumber = storeByCode.get(op); + if (varNumber == -2) { + varNumber = codeIterator.byteAt(index + 1); + } + } + + // Si c'est un store de la variable en cours d'examination + // et que c'est dans la frame d'utilisation de cette variable on trace l'affectation. + // (en fait la frame commence à localVariableAttribute.startPc(i)-1 qui est la première affectation + // mais aussi l'initialisation de la variable qui est deja tracé plus haut, donc on commence à localVariableAttribute.startPc(i)) + if (varNumber == localVariableAttribute.index(i) && index < localVariableAttribute.startPc(i) + localVariableAttribute.codeLength(i)) { + b = makeBytecodeForLVStore(method, localVariableAttribute.signature(i), aliasedName, varNumber); + codeIterator.insertEx(b.get()); + codeAttribute.setMaxStack(codeAttribute.computeMaxStack()); + } + } + } catch (Exception e) { + // Well probably a compiled optimizer (I hope so) + } + + } + + // init variable tracer + method.insertBefore("play.classloading.enhancers.LocalvariablesNamesEnhancer.LocalVariablesNamesTracer.enter();"); + method.insertAfter("play.classloading.enhancers.LocalvariablesNamesEnhancer.LocalVariablesNamesTracer.exit();", true); + + } + + // Done. + applicationClass.enhancedByteCode = ctClass.toBytecode(); + ctClass.defrost(); + } + + public static Integer computeMethodHash(CtClass[] parameters) { + String[] names = new String[parameters.length]; + for (int i = 0; i < parameters.length; i++) { + names[i] = parameters[i].getName(); + } + return computeMethodHash(names); + } + + public static Integer computeMethodHash(Class[] parameters) { + String[] names = new String[parameters.length]; + for (int i = 0; i < parameters.length; i++) { + Class param = parameters[i]; + names[i] = ""; + if (param.isArray()) { + int level = 1; + param = param.getComponentType(); + // Array of array + while (param.isArray()) { + level++; + param = param.getComponentType(); + } + names[i] = param.getName(); + for (int j = 0; j < level; j++) { + names[i] += "[]"; + } + } else { + names[i] = param.getName(); + } + } + return computeMethodHash(names); + } + + public static Integer computeMethodHash(String[] parameters) { + StringBuilder buffer = new StringBuilder(); + for (String param : parameters) { + buffer.append(param); + } + Integer hash = buffer.toString().hashCode(); + if (hash < 0) { + return -hash; + } + return hash; + } + + public static String[] parameterNames(Method method) { + try { + return (String[]) method.getDeclaringClass().getDeclaredField("$" + method.getName() + computeMethodHash(method.getParameterTypes())).get(null); + } + catch (Exception e) { + throw new UnexpectedException("Cannot read parameter names for " + method, e); + } + } +} diff --git a/framework/src/play/utils/Java.java b/framework/src/play/utils/Java.java index 9e37b8ba67..5d8cb5c728 100644 --- a/framework/src/play/utils/Java.java +++ b/framework/src/play/utils/Java.java @@ -6,7 +6,7 @@ import javassist.bytecode.SourceFileAttribute; import play.Play; import play.classloading.ApplicationClassloaderState; -import play.classloading.enhancers.LocalvariablesNamesEnhancer.LocalVariablesNamesTracer; +import play.classloading.enhancers.LocalvariablesNamesEnhancerJava7; import play.data.binding.Binder; import play.data.binding.ParamNode; import play.data.binding.RootParamNode; @@ -202,13 +202,7 @@ public static String[] parameterNames(Method method) throws Exception { return names; } else { - // Java 7 and below support via LocalVariablesNamesEnhancer - try { - return (String[]) method.getDeclaringClass().getDeclaredField("$" + method.getName() + LocalVariablesNamesTracer.computeMethodHash(method.getParameterTypes())).get(null); - } - catch (Exception e) { - throw new UnexpectedException("Cannot read parameter names for " + method, e); - } + return LocalvariablesNamesEnhancerJava7.parameterNames(method); } }