Skip to content

Commit

Permalink
[#802] Replace the runtime local variables tracker by a static one
Browse files Browse the repository at this point in the history
  • Loading branch information
sgodbillon authored and erwan committed May 5, 2011
1 parent 957ec38 commit 3c2b71e
Show file tree
Hide file tree
Showing 15 changed files with 308 additions and 528 deletions.
1 change: 1 addition & 0 deletions COPYING
Expand Up @@ -38,6 +38,7 @@ http://www.apache.org/licenses/LICENSE-2.0
- Signpost

http://www.gnu.org/licenses/lgpl.html
- Bytecodeparser
- c3p0
- Hibernate

Expand Down
1 change: 1 addition & 0 deletions framework/dependencies.yml
Expand Up @@ -8,6 +8,7 @@ transitiveDependencies: false
# This core dependencies are required by Play framework
require: &allDependencies
- antlr 2.7.6
- bytecodeparser 0.1
- c3p0 0.9.1.2
- cglib -> cglib-nodep 2.2
- com.google.code.gson -> gson 1.7.1
Expand Down
Binary file added framework/lib/bytecodeparser-0.1.jar
Binary file not shown.
6 changes: 3 additions & 3 deletions framework/src/play/CorePlugin.java
Expand Up @@ -16,7 +16,7 @@
import play.classloading.enhancers.ContinuationEnhancer;
import play.classloading.enhancers.ControllersEnhancer;
import play.classloading.enhancers.Enhancer;
import play.classloading.enhancers.LocalvariablesNamesEnhancer;
import play.classloading.enhancers.LVEnhancer;
import play.classloading.enhancers.MailerEnhancer;
import play.classloading.enhancers.PropertiesEnhancer;
import play.classloading.enhancers.SigEnhancer;
Expand Down Expand Up @@ -285,10 +285,10 @@ public void enhance(ApplicationClass applicationClass) throws Exception {
Class<?>[] enhancers = new Class[]{
SigEnhancer.class,
ControllersEnhancer.class,
LVEnhancer.class,
ContinuationEnhancer.class,
MailerEnhancer.class,
PropertiesEnhancer.class,
LocalvariablesNamesEnhancer.class
PropertiesEnhancer.class
};
for (Class<?> enhancer : enhancers) {
try {
Expand Down
2 changes: 0 additions & 2 deletions framework/src/play/Invoker.java
Expand Up @@ -16,7 +16,6 @@
import java.util.ArrayList;

import play.Play.Mode;
import play.classloading.enhancers.LocalvariablesNamesEnhancer.LocalVariablesNamesTracer;
import play.exceptions.PlayException;
import play.exceptions.UnexpectedException;
import play.libs.F;
Expand Down Expand Up @@ -211,7 +210,6 @@ public void before() {
*/
public void after() {
Play.pluginCollection.afterInvocation();
LocalVariablesNamesTracer.checkEmpty(); // detect bugs ....
}

/**
Expand Down
263 changes: 263 additions & 0 deletions framework/src/play/classloading/enhancers/LVEnhancer.java
@@ -0,0 +1,263 @@
package play.classloading.enhancers;

import java.util.Arrays;
import java.util.Stack;

import javassist.CtBehavior;
import javassist.CtClass;
import javassist.CtField;
import javassist.Modifier;
import javassist.NotFoundException;
import javassist.bytecode.BadBytecode;
import javassist.bytecode.Bytecode;
import javassist.bytecode.CodeAttribute;
import javassist.compiler.CompileError;
import javassist.compiler.Javac;
import bytecodeparser.analysis.decoders.DecodedMethodInvocationOp;
import bytecodeparser.analysis.decoders.DecodedMethodInvocationOp.MethodParams;
import bytecodeparser.analysis.opcodes.ExitOpcode;
import bytecodeparser.analysis.stack.StackAnalyzer;
import bytecodeparser.analysis.stack.StackAnalyzer.Frame;
import bytecodeparser.analysis.stack.StackAnalyzer.Frames;
import bytecodeparser.analysis.stack.StackAnalyzer.Frames.FrameIterator;
import bytecodeparser.utils.Utils;
import play.Logger;
import play.classloading.ApplicationClasses.ApplicationClass;
import play.exceptions.UnexpectedException;

public class LVEnhancer extends Enhancer {
@Override
public void enhanceThisClass(ApplicationClass applicationClass)
throws Exception {
CtClass ctClass = makeClass(applicationClass);
for(CtBehavior behavior : ctClass.getDeclaredMethods()) {
try {
if(behavior.isEmpty())
continue;
if(behavior.getMethodInfo().getCodeAttribute() == null)
continue;
if(Utils.getLocalVariableAttribute(behavior) == null)
continue;

StackAnalyzer parser = new StackAnalyzer(behavior);

// first, compute hash for parameter names
CtClass[] signatureTypes = behavior.getParameterTypes();
int memberShift = Modifier.isStatic(behavior.getModifiers()) ? 0 : 1;

if(signatureTypes.length > parser.context.localVariables.size() - memberShift) {
Logger.debug("ignoring method: %s %s (local vars numbers differs : %s != %s)", Modifier.toString(behavior.getModifiers()), behavior.getLongName(), signatureTypes.length, parser.context.localVariables.size() - memberShift);
continue;
}

StringBuffer signatureNames;
if(signatureTypes.length == 0)
signatureNames = new StringBuffer("new String[0];");
else {
signatureNames = new StringBuffer("new String[] {");

for(int i = memberShift; i < signatureTypes.length + memberShift; i++) {
if(i > memberShift)
signatureNames.append(",");

signatureNames.append("\"").append(parser.context.localVariables.get(i).name).append("\"");
}
signatureNames.append("};");
}

CtField signature = CtField.make("public static String[] $" + behavior.getName() + computeMethodHash(signatureTypes) + " = " + signatureNames.toString(), ctClass);
ctClass.addField(signature);
// end

Frames frames = parser.analyze();
CodeAttribute codeAttribute = behavior.getMethodInfo().getCodeAttribute();
FrameIterator iterator = frames.iterator();
while(iterator.hasNext()) {
Frame frame = iterator.next();
if(!frame.isAccessible) {
Logger.debug("WARNING : frame " + frame.index + " is NOT accessible");
continue;
}
if(frame.decodedOp instanceof DecodedMethodInvocationOp) {
DecodedMethodInvocationOp dmio = (DecodedMethodInvocationOp) frame.decodedOp;
StringBuffer stmt = new StringBuffer("{");
MethodParams methodParams = DecodedMethodInvocationOp.resolveParameters(frame);
stmt.append("String[] $$paramNames = new String[").append((methodParams.subject != null ? 1 : 0) + methodParams.params.length + (methodParams.varargs != null ? methodParams.varargs.length : 0)).append("];");
if(methodParams.subject != null && methodParams.subject.name != null)
stmt.append("$$paramNames[").append(0).append("] = \"").append(methodParams.subject.name).append("\";");
for(int i = 0; i < methodParams.params.length; i++)
if(methodParams.params[i] != null && methodParams.params[i].name != null)
stmt.append("$$paramNames[").append(i + (methodParams.subject != null ? 1 : 0)).append("] = \"").append(methodParams.params[i].name).append("\";");
if(methodParams.varargs != null)
for(int i = 0, j = methodParams.params.length + (methodParams.subject != null ? 1 : 0); i < methodParams.varargs.length; i++, j++)
if(methodParams.varargs[i] != null && methodParams.varargs[i].name != null)
stmt.append("$$paramNames[").append(j).append("] = \"").append(methodParams.varargs[i].name).append("\";");

stmt.append("play.classloading.enhancers.LVEnhancer.LVEnhancerRuntime.initMethodCall(\"" + dmio.getName() + "\", " + dmio.getNbParameters() + ", " + (methodParams.subject != null ? ("\"" + methodParams.subject + "\"") : "null") + ", $$paramNames);");
stmt.append("}");

insert(stmt.toString(), ctClass, behavior, codeAttribute, iterator, frame, false);
}
if(frame.decodedOp.op instanceof ExitOpcode) {
insert("play.classloading.enhancers.LVEnhancer.LVEnhancerRuntime.exitMethod(\""+ ctClass.getName() + "\", \"" + behavior.getName() + "\", \"" + behavior.getSignature() + "\");", ctClass, behavior, codeAttribute, iterator, frame, false);
}
if(iterator.isFirst()) {
insert("play.classloading.enhancers.LVEnhancer.LVEnhancerRuntime.enterMethod(\""+ ctClass.getName() + "\", \"" + behavior.getName() + "\", \"" + behavior.getSignature() + "\");", ctClass, behavior, codeAttribute, iterator, frame, false);
}
}
} catch(Exception e) {
throw new UnexpectedException("LVEnhancer: cannot enhance the behavior '" + behavior.getLongName() + "'", e);
//e.printStackTrace();
//new FileOutputStream("/tmp/" + ctClass.getName() + ".class").write(applicationClass.enhancedByteCode);
//System.exit(0);
}
}
applicationClass.enhancedByteCode = ctClass.toBytecode();
ctClass.defrost();
}

private static void insert(String stmt, CtClass ctClass, CtBehavior behavior, CodeAttribute codeAttribute, FrameIterator iterator, Frame frame, boolean after) throws CompileError, BadBytecode, NotFoundException {
Javac jv = new Javac(ctClass);
jv.recordLocalVariables(codeAttribute, frame.index);
jv.recordParams(behavior.getParameterTypes(), Modifier.isStatic(behavior.getModifiers()));
jv.setMaxLocals(codeAttribute.getMaxLocals());
jv.compileStmnt(stmt);

Bytecode b = jv.getBytecode();
int locals = b.getMaxLocals();
codeAttribute.setMaxLocals(locals);
iterator.insert(b.get(), after);
codeAttribute.setMaxStack(codeAttribute.computeMaxStack());
}

private 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) {
StringBuffer buffer = new StringBuffer();
for (String param : parameters) {
buffer.append(param);
}
Integer hash = buffer.toString().hashCode();
if (hash < 0) {
return -hash;
}
return hash;
}

public static class LVEnhancerRuntime {
private static ThreadLocal<Stack<MethodExecution>> methodParams = new ThreadLocal<Stack<MethodExecution>>();

public static void enterMethod(String clazz, String method, String signature) {
getCurrentMethodParams().push(new MethodExecution());
}

public static void exitMethod(String clazz, String method, String signature) {
getCurrentMethodParams().pop();
}

public static void initMethodCall(String method, int nbParams, String subject, String[] paramNames) {
getCurrentMethodParams().peek().currentNestedMethodCall = new MethodExecution(subject, paramNames, nbParams);
Logger.trace("initMethodCall for '" + method + "' with " + Arrays.toString(paramNames));
}

private static Stack<MethodExecution> getCurrentMethodParams() {
Stack<MethodExecution> result = methodParams.get();
if(result == null) {
result = new Stack<MethodExecution>();
methodParams.set(result);
}
return result;
}

public static ParamsNames getParamNames() {
Stack<MethodExecution> stack = getCurrentMethodParams();
if(stack.size() > 0) {
MethodExecution me = getCurrentMethodExecution();
return new ParamsNames(me.subject, me.paramsNames, me.varargsNames);
}
throw new UnexpectedException("empty methodParams!");
}

protected static LVEnhancer.MethodExecution getCurrentMethodExecution() {
Stack<MethodExecution> stack = getCurrentMethodParams();
if(stack.size() > 0)
return stack.get(stack.size() - 1).currentNestedMethodCall;
throw new UnexpectedException("empty methodParams!");
}

public static class ParamsNames {
public final String subject;
public final String[] params;
public final String[] varargs;

public boolean hasVarargs() {
return varargs != null;
}

public String[] mergeParamsAndVarargs() {
Logger.trace("ParamsNames: %s :: %s", Arrays.toString(params), Arrays.toString(varargs));
if(!hasVarargs())
return Arrays.copyOf(params, params.length);
String[] result = new String[params.length + varargs.length - 1];
for(int i = 0; i < params.length - 1; i++)
result[i] = params[i];
for(int i = 0, j = params.length - 1; i < varargs.length; i++, j++)
result[j] = varargs[i];
return result;
}

public ParamsNames(String subject, String[] params, String[] varargs) {
this.subject = subject;
this.params = params;
this.varargs = varargs;
}
}
}

protected static class MethodExecution {
protected String[] paramsNames;
protected String[] varargsNames;
protected String subject;
protected MethodExecution currentNestedMethodCall;

private MethodExecution() { }

private MethodExecution(String subject, String[] params, int nb) {
this.subject = subject;
paramsNames = Arrays.copyOfRange(params, 0, nb);
if(nb < params.length)
varargsNames = Arrays.copyOfRange(params, nb, params.length);
else varargsNames = null;
}
}
}

1 comment on commit 3c2b71e

@mbknor
Copy link
Member

@mbknor mbknor commented on 3c2b71e May 5, 2011

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This commit broke a test:

postANewJobAsCompany in samples-and-tests/jobboard

I tried to revert the commit and postANewJobAsCompany PASSED again

Please sign in to comment.