blob: 5d997429116bebfb9c3c7b0224223f7b2e75fdcd [file] [log] [blame]
/**********************************************************************
* This file is part of "Object Teams Dynamic Runtime Environment"
*
* Copyright 2009, 2018 Oliver Frank and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Please visit http://www.eclipse.org/objectteams for updates and contact.
*
* Contributors:
* Oliver Frank - Initial API and implementation
* Stephan Herrmann - Initial API and implementation
**********************************************************************/
package org.eclipse.objectteams.otredyn.bytecode.asm;
import java.io.File;
import java.io.FileOutputStream;
import java.lang.instrument.IllegalClassFormatException;
import java.util.ArrayList;
import java.util.List;
import org.eclipse.jdt.annotation.NonNull;
import org.eclipse.jdt.annotation.Nullable;
import org.eclipse.objectteams.otredyn.bytecode.AbstractBoundClass;
import org.eclipse.objectteams.otredyn.bytecode.AbstractTeam;
import org.eclipse.objectteams.otredyn.bytecode.Field;
import org.eclipse.objectteams.otredyn.bytecode.IBytecodeProvider;
import org.eclipse.objectteams.otredyn.bytecode.Method;
import org.eclipse.objectteams.otredyn.bytecode.RedefineStrategyFactory;
import org.eclipse.objectteams.otredyn.bytecode.asm.verify.OTCheckClassAdapter;
import org.eclipse.objectteams.otredyn.runtime.TeamManager;
import org.eclipse.objectteams.otredyn.transformer.jplis.ObjectTeamsTransformer;
import org.eclipse.objectteams.otredyn.transformer.names.ClassNames;
import org.eclipse.objectteams.otredyn.transformer.names.ConstantMembers;
import org.eclipse.objectteams.runtime.IReweavingTask;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;
/**
* This class implements the bytecode manipulating part of {@link AbstractBoundClass}.
* It uses ASM to manipulate the bytecode.
* @author Oliver Frank
*/
class AsmWritableBoundClass extends AsmBoundClass {
private static boolean dumping = false;
private static boolean verifying = false;
static {
if(System.getProperty("ot.dump")!=null)
dumping = true;
if (System.getProperty("objectteams.otdre.verify") != null)
verifying = true;
}
private ClassWriter writer;
private MultiClassAdapter multiAdapter;
private ClassReader reader;
private boolean isTransformed;
private boolean isTransformedForMemberAccess;
private boolean isTransformedStatic;
private List<AbstractTransformableClassNode> nodes;
private boolean isFirstTransformation = true;
private boolean isTransformationActive;
private Boolean superIsWeavable;
protected AsmWritableBoundClass(@NonNull String name, String id, IBytecodeProvider bytecodeProvider, ClassLoader loader) {
super(name, id, bytecodeProvider, loader);
}
/**
* Adds a field to the class
*
* @param field defines name and type of the field
* @param access access flags for the field
* @see AddFieldAdapter
*/
private void addField(Field field, int access) {
assert (isTransformationActive) : "No transformation active";
String desc = field.getSignature();
multiAdapter.addVisitor(new AddFieldAdapter(writer, field.getName(), access, desc));
}
/**
* Adds an empty method to the class
* @param method
* @param access
* @param signature
* @param exceptions
* @param superToCall may be null, else the super class to which a super-call should be inserted
* @param addThrow if true the empty method will throw an IllegalStateException.
* @see AddEmptyMethodAdapter
*/
private void addEmptyMethod(Method method, int access, String signature, String[] exceptions, String superToCall, boolean addThrow) {
assert (isTransformationActive) : "No transformation active";
String desc = method.getSignature();
Type[] args = Type.getArgumentTypes(desc);
multiAdapter.addVisitor(new AddEmptyMethodAdapter(writer, method.getName(),
access, desc, exceptions, signature, args.length + 1, superToCall, addThrow));
}
/**
* Adds an interface to the class
* @see AddInterfaceAdapter
* @param name
*/
private void addInterface(String name) {
assert (isTransformationActive) : "No transformation active";
multiAdapter.setToplevelVisitor(new AddInterfaceAdapter(writer, name));
}
/**
* This method must be called before any transformation
* can be done. It makes it possible to collect all transformations
* and transform the bytecode only one time at the end.
*/
@Override
protected void startTransformation() {
reader = new ClassReader(allocateAndGetBytecode());
writer = getClassWriter();
multiAdapter = new MultiClassAdapter(writer);
if (nodes == null)
nodes = new ArrayList<AbstractTransformableClassNode>();
isTransformationActive = true;
}
LoaderAwareClassWriter getClassWriter() {
int flags = ClassWriter.COMPUTE_FRAMES;
// DEBUG: when frame computation throws an exception, enable dumping of class file without frames computed:
// if (getName().contains("JUnitLaunchConfigurationDelegate"))
// flags = 0;
return new LoaderAwareClassWriter(reader, flags, this.loader);
}
/**
* Is the class manipulated right now.
*/
@Override
public boolean isTransformationActive() {
return isTransformationActive;
}
/**
* Executes all pending transformations.
* @throws IllegalClassFormatException various bytecode problems, e.g., unexpected RET instruction etc.
*/
@Override
protected void endTransformation(final Class<?> definedClass) throws IllegalClassFormatException {
assert (isTransformationActive) : "No transformation active";
try {
if (multiAdapter == null || nodes == null)
return;
if (multiAdapter.hasVisitors() || !nodes.isEmpty()) {
// //TODO (ofra): Do everything in one transformation
// Do all transformation with the Core API of ASM
try {
reader.accept(multiAdapter, ClassReader.SKIP_FRAMES);
} catch (RuntimeException e) {
e.printStackTrace();
IllegalClassFormatException ex = new IllegalClassFormatException("Cannot transform class "+this+":"+e.getMessage());
try { ex.initCause(e); } catch (Throwable t) { t.printStackTrace(); }
throw ex;
}
setBytecode(writer.toByteArray());
//Do all transformations with the Tree API of ASM
for (AbstractTransformableClassNode node : nodes) {
reader = new ClassReader(allocateAndGetBytecode());
reader.accept(node, ClassReader.SKIP_FRAMES);
if (node.transform()) {
writer = getClassWriter();
node.accept(writer);
byte[] bytes = writer.toByteArray();
setBytecode(bytes);
if (verifying) {
OTCheckClassAdapter.verify(node, bytes, this.loader);
}
}
}
dump();
reader = null;
writer = null;
multiAdapter = null;
nodes = null;
//Check, if this is the first transformation for this class
if (!this.isFirstTransformation) {
// It is not the first transformation, so redefine the class
try {
redefine(definedClass);
} catch (ClassNotFoundException cnfe) {
if (ObjectTeamsTransformer.initiatedByThrowAwayLoader.get() == Boolean.TRUE) {
scheduleRetry(definedClass);
return;
} else {
throw new RuntimeException("OTDRE: Failed to redefine class: "+this.getName(), cnfe);
}
} catch (Throwable t) {
// t.printStackTrace(System.out);
// if redefinition failed (ClassCircularity?) install a runnable for deferred redefinition:
scheduleRetry(definedClass);
return;
}
}
} else {
reader = null;
writer = null;
multiAdapter = null;
nodes = null;
}
releaseBytecode();
} finally {
isTransformationActive = false;
isFirstTransformation = false;
}
}
private void scheduleRetry(final Class<?> definedClass) {
final Runnable previousTask = TeamManager.pendingTasks.get();
TeamManager.pendingTasks.set(new Runnable() {
public void run() {
if (previousTask != null)
previousTask.run();
try {
redefine(definedClass);
} catch (ClassNotFoundException e) {
e.printStackTrace(); // should never get here, since we expect CNFE already on the first attempt
}
}
@Override
public String toString() {
return "Retry "+AsmWritableBoundClass.this.toString();
}
});
}
protected void superTransformation(Class<?> definedClass) throws IllegalClassFormatException {
AbstractTeam mySuper = getSuperclass();
if (mySuper != null && mySuper.isLoaded()) {
boolean superNeedsWeaving = false;
synchronized (mySuper.openBindingTasks) {
superNeedsWeaving = !mySuper.openBindingTasks.isEmpty();
}
if (superNeedsWeaving) // no locks held during the following call!
mySuper.handleTaskList(definedClass != null ? definedClass.getSuperclass() : null);
}
}
/**
* Creates the dispatch code in the original method.
* @see CreateDispatchCodeInOrgMethodAdapter
*/
@Override
protected void createDispatchCodeInOrgMethod(Method boundMethod,
int joinPointId, int boundMethodId) {
assert (isTransformationActive) : "No transformation active";
nodes.add(new CreateDispatchCodeInOrgMethodAdapter(boundMethod,
joinPointId, boundMethodId));
}
/**
* Creates the dispatch code in the method callAllBindings.
* @see CreateDispatchCodeInCallAllBindingsAdapter
*/
@Override
protected void createDispatchCodeInCallAllBindings(int joinpointId,
int boundMethodId) {
assert (isTransformationActive) : "No transformation active";
nodes.add(new CreateDispatchCodeInCallAllBindingsAdapter(joinpointId,
boundMethodId));
}
/**
* Moves the code of the original method to callOrig or callOrigStatic.
* @see MoveCodeToCallOrigAdapter
*/
@Override
protected void moveCodeToCallOrig(Method boundMethod, int boundMethodId, boolean baseSuperRequired) {
if (boundMethod.getName().equals("<init>")) return; // don't move constructor code
assert (isTransformationActive) : "No transformation active";
nodes.add(new MoveCodeToCallOrigAdapter(this, boundMethod, boundMethodId, baseSuperRequired, this.weavingContext));
}
/**
* Creates a super call in callOrig.
* @see CreateSuperCallInCallOrigAdapter
*/
@Override
protected void createSuperCallInCallOrig(int boundMethodId) {
assert (isTransformationActive) : "No transformation active";
nodes.add(new CreateSuperCallInCallOrigAdapter(
getInternalSuperClassName(), boundMethodId));
}
/**
* Creates a call of callAllBindings in the original method.
* @see CreateCallAllBindingsCallInOrgMethod
*/
@Override
protected void createCallAllBindingsCallInOrgMethod(Method boundMethod,
int boundMethodId, boolean needToAddMethod) {
assert (isTransformationActive) : "No transformation active";
if (needToAddMethod) {
String desc = boundMethod.getSignature();
Type[] args = Type.getArgumentTypes(desc);
multiAdapter.addVisitor(new AddEmptyMethodAdapter(writer, boundMethod.getName(),
boundMethod.getAccessFlags(), desc, null, boundMethod.getSignature(), args.length+1/*maxLocals*/, null, false));
nodes.add(new CreateSpecificSuperCallInCallOrigAdapter(this, getInternalSuperClassName(), boundMethod, boundMethodId));
}
nodes.add(new CreateCallAllBindingsCallInOrgMethod(boundMethod,
boundMethodId));
}
@Override
protected void replaceWickedSuperCalls(AbstractBoundClass superclass, Method targetMethod) {
ReplaceWickedSuperCallsAdapter.register(nodes, superclass, targetMethod);
}
/**
* Prepares a the class for a decapsulation of one of its methods
*/
@Override
protected void weaveMethodAccess(Method method, int accessId) {
nodes.add(new CreateMethodAccessAdapter(method, accessId));
}
/**
* Prepares a the class for a decapsulation of one of its fields
*/
@Override
protected void weaveFieldAccess(Field field, int accessId) {
nodes.add(new CreateFieldAccessAdapter(field, accessId));
}
/**
* Write the bytecode in the directory ./otdyn to the hard disk,
* if the system property "ot.dump" is set.
*/
private void dump() {
if (!dumping)
return;
String name = getName().replaceAll("/", ".");
File dir = new File("otdyn");
if (!dir.exists())
dir.mkdir();
String filename = "otdyn/" + name + ".class";
try (FileOutputStream fos = new FileOutputStream(filename)) {
fos.write(allocateAndGetBytecode());
} catch (Exception e) {
e.printStackTrace();
}
}
private int n = 0; // counts dump files for this class
public void dump(byte[] bytecode, String postfix) {
if (!dumping)
return;
String name = getName().replaceAll("/", ".");
File dir = new File("otdyn");
if (!dir.exists())
dir.mkdir();
String filename = "otdyn/" + name + postfix+".#"+(n++);
try (FileOutputStream fos = new FileOutputStream(filename)) {
fos.write(bytecode);
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* Redefines the class
* @param definedClass previously defined class if available
* @throws ClassNotFoundException may signal missing OTEquinoxAgent
*/
private void redefine(Class<?> definedClass) throws ClassNotFoundException {
try {
Class<?> clazz = definedClass != null ? definedClass : this.loader.loadClass(this.getName()); // boot classes may have null classloader, can't be redefined anyway?
byte[] bytecode = allocateAndGetBytecode();
dump(bytecode, "redef");
RedefineStrategyFactory.getRedefineStrategy().redefine(clazz, bytecode);
} catch (ClassNotFoundException cnfe) {
throw cnfe;
} catch (Throwable t) {
throw new RuntimeException("OTDRE: Error occured while dynamically redefining class " + getName()+"\n"+t.getMessage(), t);
}
}
/**
* Do all transformations needed at load time
*/
@Override
protected void prepareAsPossibleBaseClass() {
if (!isFirstTransformation)
return;
addInterface(ClassNames.I_BOUND_BASE_SLASH);
int methodModifiers = Opcodes.ACC_PUBLIC;
if (isInterface())
methodModifiers |= Opcodes.ACC_ABSTRACT;
if (!isInterface() && !isSuperWeavable(true))
addField(ConstantMembers.roleSet, Opcodes.ACC_PUBLIC);
String internalWeavableDirectSuperClassName = getInternalWeavableSuperClassName(false);
addEmptyMethod(ConstantMembers.callOrig, methodModifiers, null, null, internalWeavableDirectSuperClassName, true);
addEmptyMethod(ConstantMembers.callAllBindingsClient, methodModifiers, null, null, internalWeavableDirectSuperClassName, true);
// the methods callOrigStatic and accessStatic have to already exist to call it in a concrete team
if (!isInterface()) {
addEmptyMethod(getCallOrigStatic(), Opcodes.ACC_PUBLIC + Opcodes.ACC_STATIC, null, null, null, false);
addEmptyMethod(ConstantMembers.accessStatic, Opcodes.ACC_PUBLIC + Opcodes.ACC_STATIC, null, null, null, false);
}
addEmptyMethod(ConstantMembers.access, methodModifiers, null, null, getInternalWeavableSuperClassName(true), true);
addEmptyMethod(ConstantMembers.addOrRemoveRole, methodModifiers, null, null, getInternalWeavableSuperClassName(true), true);
if (!isInterface())
multiAdapter.addVisitor(new AddAfterClassLoadingHook(this.writer, this));
if (AddThreadNotificationAdapter.shouldNotify(this))
multiAdapter.addVisitor(new AddThreadNotificationAdapter(this.writer, this));
}
/** Get the suitable variant of _OT$callOrigStatic, respecting synth args for static role methods. */
Method getCallOrigStatic() {
if (isRole())
return ConstantMembers.callOrigStaticRoleVersion(getEnclosingClassName());
else
return ConstantMembers.callOrigStatic;
}
/**
* Prepares the class for implicit team activation by adding appropriate calls to
* _OT$implicitlyActivate(), _OT$implicitlyDeactivate() into all relevant methods.
*/
@Override
protected void prepareTeamActivation() {
if (!isFirstTransformation || isInterface())
return;
if (isTeam() || isRole())
multiAdapter.addVisitor(new AddImplicitActivationAdapter(this.writer, this));
AddGlobalTeamActivationAdapter.checkAddVisitor(this.multiAdapter, this.writer);
}
@Override
protected void prepareLiftingParticipant() {
if (isTeam() && LiftingParticipantAdapter.isLiftingParticipantConfigured(this.loader)) {
multiAdapter.addVisitor(new LiftingParticipantAdapter(this.writer));
}
}
/**
* Prepares the methods callAllBindings and callOrig with an empty
* switch statement
*/
@Override
protected void prepareForFirstTransformation() {
if (!isTransformed && !isInterface()) {
nodes.add(new CreateSwitchAdapter(ConstantMembers.callOrig, getInternalWeavableSuperClassName(false)));
nodes.add(new CreateSwitchForCallAllBindingsNode());
nodes.add(new CreateAddRemoveRoleMethod());
isTransformed = true;
hierarchyIsCallinAffected = true;
propagateCallinInfraToSubclasses();
}
}
/**
* If subclasses have previously added emtpy methods (callOrig,callAllBindings,access),
* those need to be change to a super call, so they don't interfere with real callin dispatch in super
* (likely on behalf of a different sub class).
*/
protected void propagateCallinInfraToSubclasses() {
for (AbstractBoundClass sub : subclasses.keySet()) {
if (sub instanceof AsmWritableBoundClass && !sub.isAnonymous()) {
AsmWritableBoundClass writableSub = (AsmWritableBoundClass) sub;
if (!writableSub.hierarchyIsCallinAffected) {
writableSub.hierarchyIsCallinAffected = true;
writableSub.createSuperCalls();
writableSub.propagateCallinInfraToSubclasses();
}
}
}
}
/**
* Prepares the method callOrigStatic with an empty
* switch statement
*/
@Override
protected void prepareForFirstStaticTransformation() {
if (!isTransformedStatic && !isInterface()) {
nodes.add(new CreateSwitchAdapter(getCallOrigStatic(), isRole()));
isTransformedStatic = true;
}
}
@Override
protected void createSuperCalls() {
final String internalSuperClassName = getInternalSuperClassName();
final Runnable operation = () -> {
nodes.add(new CreateSuperCallAdapter(internalSuperClassName, ConstantMembers.callAllBindingsClient));
nodes.add(new CreateSuperCallAdapter(internalSuperClassName, ConstantMembers.callOrig));
nodes.add(new CreateSuperCallAdapter(internalSuperClassName, ConstantMembers.access));
};
if (isTransformationActive || !isTransformed) {
// either include in the current transformation or schedule for an upcoming transformation.
if (nodes == null)
nodes = new ArrayList<AbstractTransformableClassNode>();
operation.run();
// note: order of adapters in nodes is not relevant, because the methods to be augmented
// are created by visitors of the multiAdapter, see prepareAsPossibleBaseClass().
} else if (this.weavingContext != null) {
IReweavingTask task = new IReweavingTask() {
@Override public void reweave(@Nullable Class<?> definedClass) throws IllegalClassFormatException {
startTransaction();
if (!isTransformationActive) {
startTransformation();
}
operation.run();
commitTransaction(definedClass);
}
};
if (!weavingContext.scheduleReweaving(this.getName(), task))
try {
handleTaskList(null);
} catch (IllegalClassFormatException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
} else {
new IllegalStateException("weavingContext unexpectedly null").printStackTrace();
}
}
/**
* Prepares the methods access and accessStatic with an empty
* switch statement
*/
@Override
protected void prepareForFirstMemberAccess() {
if (!isTransformedForMemberAccess && !isInterface()) {
String internalWeavableSuperClassName = getInternalWeavableSuperClassName(true);
nodes.add(new CreateSwitchForAccessAdapter(ConstantMembers.access, internalWeavableSuperClassName, this));
nodes.add(new CreateSwitchForAccessAdapter(ConstantMembers.accessStatic, internalWeavableSuperClassName, this));
isTransformedForMemberAccess = true;
}
}
/**
* Was the class already transformed?
*/
@Override
public boolean isFirstTransformation() {
return isFirstTransformation;
}
public void restartTransformation() {
this.isFirstTransformation = true;
}
@Override
protected boolean isSuperWeavable(boolean considerSupers) {
if (this.superIsWeavable == null)
this.superIsWeavable = weavingContext.isWeavable(getSuperClassName(), considerSupers, false);
return this.superIsWeavable;
}
@Override
public String toString() {
StringBuilder buf = new StringBuilder();
toDebugString(buf, "");
return buf.toString();
}
public void toDebugString(StringBuilder buf, String indent) {
buf.append(indent).append("AsmWritableBoundClass ").append(getName()).append('\n');
buf.append(indent).append("[\n");
buf.append(indent).append(" isTransformed=").append(isTransformed).append('\n');
buf.append(indent).append(" isTransformedForMemberAccess=").append(isTransformedForMemberAccess).append('\n');
buf.append(indent).append(" isTransformedStatic=").append(isTransformedStatic).append('\n');
buf.append(indent).append(" isFirstTransformation=").append(isFirstTransformation).append('\n');
buf.append(indent).append(" isTransformationActive=").append(isTransformationActive).append('\n');
buf.append(indent).append(" boundBaseClasses=").append(boundBaseClasses).append('\n');
buf.append(indent).append(" openBindingTasks=").append(openBindingTasks).append('\n');
buf.append(indent).append(" parsed=").append(parsed).append('\n');
buf.append(indent).append(" isUnweavable=").append(isUnweavable).append('\n');
buf.append(indent).append(" subclasses=\n");
for (AbstractBoundClass sub : subclasses.keySet())
sub.toDebugString(buf, indent+" ");
buf.append(indent).append("]\n");
}
}