blob: 8ded6f6e44f66cc3917cbfa94e9ea9a0bd394ed3 [file] [log] [blame]
/**********************************************************************
* This file is part of "Object Teams Dynamic Runtime Environment"
*
* Copyright 2014 Stephan Herrmann.
*
* 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:
* Stephan Herrmann - Initial API and implementation
**********************************************************************/
package org.eclipse.objectteams.otredyn.bytecode.asm;
import static org.eclipse.objectteams.otredyn.bytecode.asm.AsmBoundClass.ASM_API;
import org.eclipse.objectteams.otredyn.transformer.names.ClassNames;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.Label;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;
import org.objectweb.asm.commons.AdviceAdapter;
/**
* Add code into all direct implementors of Runnable.run() / Thread.run()
* to inform the TeamThreadManager about new and ended threads.
* (See org.eclipse.objectteams.otre.ThreadActivation in the old OTRE).
*/
public class AddThreadNotificationAdapter extends ClassVisitor {
protected static final String THREAD_DESC = "L"+ClassNames.THREAD_SLASH+";";
protected static final String VOID_DESC = "()V";
// Runnable / Thread:
protected static final String RUN = "run", RUN_DESC = VOID_DESC;
// Thread:
protected static final String CURRENT_THREAD = "currentThread", CURRENT_THREAD_DESC = "()"+THREAD_DESC;
// TeamThreadManager:
protected static final String NEW_THREAD_STARTED = "newThreadStarted", NEW_THREAD_STARTED_DESC = "(Z"+THREAD_DESC+")Z";
protected static final String THREAD_ENDED = "threadEnded", THREAD_ENDED_DESC = VOID_DESC;
// any implementor:
protected static final String INIT = "<init>";
// new field inserted by this adapter:
protected static final String CREATION_THREAD = "_OT$creationThread";
private AsmBoundClass clazz;
public AddThreadNotificationAdapter(ClassVisitor cv, AsmBoundClass clazz) {
super(ASM_API, cv);
this.clazz = clazz;
}
@Override
public void visitEnd() {
cv.visitField(Opcodes.ACC_PRIVATE, CREATION_THREAD, THREAD_DESC, null, null);
super.visitEnd();
}
@Override
public MethodVisitor visitMethod(int access, String methodName, String desc, String signature, String[] exceptions) {
if (INIT.equals(methodName)) {
// into each constructor ...
final MethodVisitor methodVisitor = cv.visitMethod(access, methodName, desc, null, null);
return new AdviceAdapter(this.api, methodVisitor, access, methodName, desc) {
@Override
public void visitMethodInsn(int opcode, String owner, String name, String desc, boolean itfc) {
super.visitMethodInsn(opcode, owner, name, desc, itfc);
// ... that contains a super(..) call (rather than this(..)):
if (opcode == Opcodes.INVOKESPECIAL && INIT.equals(name) && owner.equals(clazz.getInternalSuperClassName())) {
// insert:
// this._OT$creationThread = Thread.currentThread();
methodVisitor.visitIntInsn(Opcodes.ALOAD, 0);
methodVisitor.visitMethodInsn(Opcodes.INVOKESTATIC, ClassNames.THREAD_SLASH, CURRENT_THREAD, CURRENT_THREAD_DESC, false);
methodVisitor.visitFieldInsn(Opcodes.PUTFIELD, clazz.getInternalName(), CREATION_THREAD, THREAD_DESC);
}
}
};
} else if (RUN.equals(methodName) && RUN_DESC.equals(desc)) {
final MethodVisitor methodVisitor = cv.visitMethod(access, methodName, desc, null, null);
return new AdviceAdapter(this.api, methodVisitor, access, methodName, desc) {
Label start = new Label(); // start of method (scope of new local)
Label end = new Label(); // end of method
int isThreadStartIdx; // new local: boolean _OT$isThreadStart
@Override
protected void onMethodEnter() {
methodVisitor.visitLabel(start);
isThreadStartIdx=newLocal(Type.BOOLEAN_TYPE);
methodVisitor.visitLocalVariable("_OT$isThreadStart", "Z", null, start, end, isThreadStartIdx);
// TeamThreadManager.newThreadStarted(false, this._OT$creationThread)
methodVisitor.visitInsn(Opcodes.ICONST_0);
methodVisitor.visitIntInsn(Opcodes.ALOAD, 0);
methodVisitor.visitFieldInsn(Opcodes.GETFIELD, clazz.getInternalName(), CREATION_THREAD, THREAD_DESC);
methodVisitor.visitMethodInsn(Opcodes.INVOKESTATIC, ClassNames.TEAM_THREAD_MANAGER_SLASH,
NEW_THREAD_STARTED, NEW_THREAD_STARTED_DESC, false);
methodVisitor.visitIntInsn(Opcodes.ISTORE, isThreadStartIdx);
// this._OT$creationThread = null; // avoid leak
methodVisitor.visitIntInsn(Opcodes.ALOAD, 0);
methodVisitor.visitInsn(Opcodes.ACONST_NULL);
methodVisitor.visitFieldInsn(Opcodes.PUTFIELD, clazz.getInternalName(), CREATION_THREAD, THREAD_DESC);
}
@Override
protected void onMethodExit(int opcode) {
insertThreadEndedNotification();
}
@Override
public void endMethod() {
methodVisitor.visitLabel(end);
// insert another threadEnded notification as a handler for Throwable
Label handler = new Label();
methodVisitor.visitLabel(handler);
insertThreadEndedNotification();
methodVisitor.visitInsn(Opcodes.ATHROW); // rethrow caught exception
methodVisitor.visitTryCatchBlock(start, end, handler, ClassNames.THROWABLE_SLASH);
methodVisitor.visitMaxs(0, 0);
}
void insertThreadEndedNotification() {
Label skip = new Label();
// insert:
// if (_OT$isThreadStart) TeamThreadManager.threadEnded();
methodVisitor.visitIntInsn(Opcodes.ILOAD, isThreadStartIdx);
methodVisitor.visitJumpInsn(Opcodes.IFEQ, skip);
methodVisitor.visitMethodInsn(Opcodes.INVOKESTATIC, ClassNames.TEAM_THREAD_MANAGER_SLASH,
THREAD_ENDED, THREAD_ENDED_DESC, false);
methodVisitor.visitLabel(skip);
}
};
}
return null;
}
public static boolean shouldNotify(AsmWritableBoundClass clazz) {
String[] interfaceNames = clazz.getSuperInterfaceNames();
if (interfaceNames != null) {
for (int i = 0; i < interfaceNames.length; i++) {
if (ClassNames.RUNNABLE_SLASH.equals(interfaceNames[i]))
return true;
}
}
if (ClassNames.THREAD_SLASH.equals(clazz.getInternalSuperClassName()))
return true;
// not traversing super chains, currently. FIXME: Should indeed traverse super interfaces to find Runnable!!
return false;
}
}