blob: 47987c1734084e684f6d2ab7c006a25d7642f9af [file] [log] [blame]
/**********************************************************************
* This file is part of the "Object Teams Runtime Environment"
*
* Copyright 2009 Stephan Herrmann
*
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Please visit http://www.objectteams.org for updates and contact.
*
* Contributors:
* Stephan Herrmann - Initial API and implementation
**********************************************************************/
package org.eclipse.objectteams.otre;
import java.lang.reflect.Field;
import org.objectteams.Team;
import org.apache.bcel.Constants;
import org.apache.bcel.classfile.Method;
import org.apache.bcel.generic.ALOAD;
import org.apache.bcel.generic.BranchInstruction;
import org.apache.bcel.generic.ClassGen;
import org.apache.bcel.generic.ConstantPoolGen;
import org.apache.bcel.generic.DUP;
import org.apache.bcel.generic.GOTO;
import org.apache.bcel.generic.IFNULL;
import org.apache.bcel.generic.INVOKESPECIAL;
import org.apache.bcel.generic.InstructionFactory;
import org.apache.bcel.generic.InstructionHandle;
import org.apache.bcel.generic.InstructionList;
import org.apache.bcel.generic.LDC;
import org.apache.bcel.generic.MethodGen;
import org.apache.bcel.generic.NEW;
import org.apache.bcel.generic.NOP;
import org.apache.bcel.generic.ObjectType;
import org.apache.bcel.generic.POP;
import org.apache.bcel.generic.Type;
import org.eclipse.objectteams.otre.jplis.JPLISEnhancer;
/**
* If the property ot.lifting.participant is set transform all lift methods and insert
* static calls to createRole(Team,Object,String)Object; to the registered lifting participant
* before creating a new role. If createRole returns non-null then that value is taken
* as the new role, otherwise lifting proceeds as normal, i.e., normally creates a new role.
*
* @author stephan
* @since 1.3.1
*/
public class LiftingParticipantTransformation extends ObjectTeamsTransformation {
private static String PARTICIPANT_NAME = System.getProperty("ot.lifting.participant");
private static boolean checked = false;
final private static String LIFT_PREFIX = "_OT$liftTo";
private static final String WRONG_ROLE_EXCEPTION = "org.objectteams.WrongRoleException";
private static final String LIFTING_FAILED_EXCEPTION = "org.objectteams.LiftingFailedException";
private static final String LIFTING_VETO_EXCEPTION = "org.objectteams.LiftingVetoException";
private static final ObjectType iLiftingParticipant = new ObjectType("org.objectteams.ILiftingParticipant");
private static final String CREATE_ROLE_METHOD = "createRole";
private static final String LIFTING_PARTICIPANT_FIELD = "_OT$liftingParticipant";
public LiftingParticipantTransformation(Object loader) { super(loader); }
public void doTransformCode(ClassGen cg)
{
if (!classNeedsTeamExtensions(cg)) return;
synchronized (LiftingParticipantTransformation.class) {
try {
checkInitParticipant();
} catch (Exception e) {
new IllegalArgumentException("Lifting participant "+PARTICIPANT_NAME+" is invalid.", e).printStackTrace();
PARTICIPANT_NAME = null;
}
if (PARTICIPANT_NAME == null)
return;
}
factory = new InstructionFactory(cg);
ConstantPoolGen cpg = cg.getConstantPool();
String class_name = cg.getClassName();
// FIXME(SH): evaluate inclusion/exclusion filter per className
Method[] methods = cg.getMethods();
for (int i=0; i<methods.length; i++) {
Method m = methods[i];
if (!m.getName().startsWith(LIFT_PREFIX))
continue;
cg.replaceMethod(m, m = weaveLiftingParticipant(m, class_name, cpg));
JPLISEnhancer.requireClassFileVersionLessThan51(cg);
}
}
private void checkInitParticipant() throws Exception {
if (checked) return;
checked = true;
Field participantField = Team.class.getField(LIFTING_PARTICIPANT_FIELD);
Object participant = participantField.get(null);
if (PARTICIPANT_NAME != null)
{
// initialize from property "ot.lifting.participant"
if (participant != null)
throw new IllegalStateException("liftingParticipant already installed.");
// install a shared instance into class Team:
Class<?> participantClass = ClassLoaderAccess.loadClass(loader, PARTICIPANT_NAME);
participantField.set(null, participantClass.newInstance());
}
else if (participant != null)
{
// field was already initialized by a third party
// fetch the class name to signal that transformations are needed.
PARTICIPANT_NAME = participant.getClass().getName();
}
}
private Method weaveLiftingParticipant(Method m, String className, ConstantPoolGen cpg) {
MethodGen mg = newMethodGen(m, className, cpg);
InstructionList il = mg.getInstructionList();
InstructionHandle[] ihs = il.getInstructionHandles();
for (int i=0; i<ihs.length; i++) {
InstructionHandle ih = ihs[i];
if (ih.getInstruction() instanceof NEW)
{
NEW newInstr = (NEW) ih.getInstruction();
Type newType = newInstr.getType(cpg);
String newTypeName = newType.toString();
// don't transform creation of these exceptions:
if (newTypeName.equals(LIFTING_FAILED_EXCEPTION)) continue;
if (newTypeName.equals(LIFTING_VETO_EXCEPTION)) continue;
if (newTypeName.equals(WRONG_ROLE_EXCEPTION)) continue;
ih.setInstruction(new NOP()); // keep this handle for the enclosing switch
InstructionList inset = new InstructionList();
// fetch instance of lifting participant from Team._OT$liftingParticipant
inset.append(factory.createFieldAccess(teamClassType.getClassName(),
LIFTING_PARTICIPANT_FIELD,
iLiftingParticipant,
Constants.GETSTATIC));
inset.append(new ALOAD(0)); // load the team
inset.append(new ALOAD(1)); // load the base
inset.append(new LDC(cpg.addString(newTypeName))); // load the role class name
inset.append(factory.createInvoke(iLiftingParticipant.getClassName(), // receiver type
CREATE_ROLE_METHOD, // method
object, // return type
new Type[] {teamType, object, string},// arg types
Constants.INVOKEINTERFACE));
inset.append(new DUP()); // keep value after null-check
BranchInstruction isNull = new IFNULL(null);
inset.append(isNull);
inset.append(factory.createCast(object, newType));
// let goto skip: 0: new R, 1: dup, 2: aload_0, 3: aload_1, (4: cast if needed), 4: o. 5: invokespecial<init>
int invokeOffset = 4;
if (!(ihs[i+invokeOffset].getInstruction() instanceof INVOKESPECIAL))
invokeOffset++;
inset.append(new GOTO(ihs[i+invokeOffset+1])); // one past above sequence
// continue here if null, i.e., perform the original new-instruction
InstructionHandle goOn = inset.append(new POP()); // discard dup'ed value from above
isNull.setTarget(goOn);
inset.append(newInstr); // re-insert deleted first instruction
il.append(ih, inset);
}
}
return mg.getMethod();
}
}