blob: d9c9c7ad9492e99e497beaf4b90de303332bdaed [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.cassandra.simulator.asm;
import java.util.Comparator;
import java.util.ListIterator;
import org.objectweb.asm.Label;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;
import org.objectweb.asm.tree.FrameNode;
import org.objectweb.asm.tree.InsnNode;
import org.objectweb.asm.tree.IntInsnNode;
import org.objectweb.asm.tree.LdcInsnNode;
import org.objectweb.asm.tree.LocalVariableNode;
import org.objectweb.asm.tree.MethodInsnNode;
import org.objectweb.asm.tree.MethodNode;
import org.objectweb.asm.tree.TryCatchBlockNode;
import static org.apache.cassandra.simulator.asm.TransformationKind.MONITOR;
/**
* For synchronized methods, we generate a new method that contains the source method's body, and the original method
* instead invoke preMonitorEnter before invoking the new hidden method.
*/
class MonitorMethodTransformer extends MethodNode
{
private final String className;
private final MethodWriterSink methodWriterSink;
private final ChanceSupplier monitorDelayChance;
private final String baseName;
private final boolean isInstanceMethod;
private int returnCode;
int maxLocalParams; // double counts long/double to match asm spec
public MonitorMethodTransformer(MethodWriterSink methodWriterSink, String className, int api, int access, String name, String descriptor, String signature, String[] exceptions, ChanceSupplier monitorDelayChance)
{
super(api, access, name, descriptor, signature, exceptions);
this.methodWriterSink = methodWriterSink;
this.className = className;
this.baseName = name;
this.isInstanceMethod = (access & Opcodes.ACC_STATIC) == 0;
this.monitorDelayChance = monitorDelayChance;
}
@Override
public void visitInsn(int opcode)
{
switch (opcode)
{
case Opcodes.RETURN:
case Opcodes.ARETURN:
case Opcodes.IRETURN:
case Opcodes.FRETURN:
case Opcodes.LRETURN:
case Opcodes.DRETURN:
if (returnCode != 0) assert returnCode == opcode;
else returnCode = opcode;
}
super.visitInsn(opcode);
}
int returnCode()
{
return returnCode;
}
// TODO (cleanup): this _should_ be possible to determine purely from the method signature
int loadParamsAndReturnInvokeCode()
{
if (isInstanceMethod)
instructions.add(new IntInsnNode(Opcodes.ALOAD, 0));
ListIterator<LocalVariableNode> it = localVariables.listIterator();
while (it.hasNext())
{
LocalVariableNode cur = it.next();
if (cur.index < maxLocalParams)
{
if (!isInstanceMethod || cur.index > 0)
{
int opcode;
switch (cur.desc.charAt(0))
{
case 'L':
case '[':
opcode = Opcodes.ALOAD;
break;
case 'J':
opcode = Opcodes.LLOAD;
break;
case 'D':
opcode = Opcodes.DLOAD;
break;
case 'F':
opcode = Opcodes.FLOAD;
break;
default:
opcode = Opcodes.ILOAD;
break;
}
instructions.add(new IntInsnNode(opcode, cur.index));
}
}
}
int invokeCode;
if (isInstanceMethod && (access & Opcodes.ACC_PRIVATE) != 0) invokeCode = Opcodes.INVOKESPECIAL;
else if (isInstanceMethod) invokeCode = Opcodes.INVOKEVIRTUAL;
else invokeCode = Opcodes.INVOKESTATIC;
return invokeCode;
}
void pushRef()
{
if (isInstanceMethod) instructions.add(new IntInsnNode(Opcodes.ALOAD, 0));
else instructions.add(new LdcInsnNode(org.objectweb.asm.Type.getType('L' + className + ';')));
}
void pop()
{
instructions.add(new InsnNode(Opcodes.POP));
}
void invokePreMonitorExit()
{
pushRef();
instructions.add(new MethodInsnNode(Opcodes.INVOKESTATIC, "org/apache/cassandra/simulator/systems/InterceptorOfSystemMethods$Global", "preMonitorExit", "(Ljava/lang/Object;)Ljava/lang/Object;", false));
}
void invokePreMonitorEnter()
{
pushRef();
instructions.add(new LdcInsnNode(monitorDelayChance.get()));
instructions.add(new MethodInsnNode(Opcodes.INVOKESTATIC, "org/apache/cassandra/simulator/systems/InterceptorOfSystemMethods$Global", "preMonitorEnter", "(Ljava/lang/Object;F)Ljava/lang/Object;", false));
}
void invokeMonitor(int insn)
{
instructions.add(new InsnNode(insn));
}
void reset(Label start, Label end)
{
instructions.clear();
tryCatchBlocks.clear();
if (visibleLocalVariableAnnotations != null)
visibleLocalVariableAnnotations.clear();
if (invisibleLocalVariableAnnotations != null)
invisibleLocalVariableAnnotations.clear();
Type[] args = Type.getArgumentTypes(desc);
// remove all local variables that aren't parameters and the `this` parameter
maxLocals = args.length == 1 && Type.VOID_TYPE.equals(args[0]) ? 0 : args.length;
if (isInstanceMethod) ++maxLocals;
// sort our local variables and remove those that aren't parameters
localVariables.sort(Comparator.comparingInt(c -> c.index));
ListIterator<LocalVariableNode> it = localVariables.listIterator();
while (it.hasNext())
{
LocalVariableNode cur = it.next();
if (cur.index >= maxLocals)
{
it.remove();
}
else
{
it.set(new LocalVariableNode(cur.name, cur.desc, cur.signature, getLabelNode(start), getLabelNode(end), cur.index));
switch (cur.desc.charAt(0))
{
case 'J':
case 'D':
// doubles and longs take two local variable positions
++maxLocals;
}
}
}
// save the number of pure-parameters for use elsewhere
maxLocalParams = maxLocals;
}
void writeOriginal()
{
access &= ~Opcodes.ACC_SYNCHRONIZED;
access |= Opcodes.ACC_SYNTHETIC;
name = baseName + "$unsync";
methodWriterSink.writeMethod(this);
}
// alternative approach (with writeInnerTryCatchSynchronized)
@SuppressWarnings("unused")
void writeOuterUnsynchronized()
{
access &= ~(Opcodes.ACC_SYNCHRONIZED | Opcodes.ACC_SYNTHETIC);
name = baseName;
Label start = new Label();
Label end = new Label();
reset(start, end);
maxStack = maxLocalParams;
instructions.add(getLabelNode(start));
invokePreMonitorEnter();
pop();
int invokeCode = loadParamsAndReturnInvokeCode();
instructions.add(new MethodInsnNode(invokeCode, className, baseName + "$catch", desc));
instructions.add(new InsnNode(returnCode()));
instructions.add(getLabelNode(end));
methodWriterSink.writeMethod(this);
}
// alternative approach (with writeOuterUnsynchronized)
@SuppressWarnings("unused")
void writeInnerTryCatchSynchronized()
{
access |= Opcodes.ACC_SYNCHRONIZED | Opcodes.ACC_SYNTHETIC;
name = baseName + "$catch";
Label start = new Label();
Label normal = new Label();
Label except = new Label();
Label end = new Label();
reset(start, end);
maxStack = Math.max(maxLocalParams, returnCode == Opcodes.RETURN ? 1 : 2); // must load self or class onto stack, and return value (if any)
++maxLocals;
tryCatchBlocks.add(new TryCatchBlockNode(getLabelNode(start), getLabelNode(normal), getLabelNode(except), null));
instructions.add(getLabelNode(start));
int invokeCode = loadParamsAndReturnInvokeCode();
instructions.add(new MethodInsnNode(invokeCode, className, baseName + "$unsync", desc));
instructions.add(getLabelNode(normal));
invokePreMonitorExit();
instructions.add(new InsnNode(returnCode()));
instructions.add(getLabelNode(except));
instructions.add(new FrameNode(Opcodes.F_SAME1, 0, null, 1, new Object[]{ "java/lang/Throwable" }));
instructions.add(new IntInsnNode(Opcodes.ASTORE, maxLocalParams));
invokePreMonitorExit();
instructions.add(new IntInsnNode(Opcodes.ALOAD, maxLocalParams));
instructions.add(new InsnNode(Opcodes.ATHROW));
instructions.add(getLabelNode(end));
methodWriterSink.writeSyntheticMethod(MONITOR, this);
}
void writeTryCatchMonitorEnterExit()
{
access |= Opcodes.ACC_SYNTHETIC;
name = baseName;
Label start = new Label();
Label inmonitor = new Label();
Label normal = new Label();
Label except = new Label(); // normal
Label normalRetExcept = new Label(); // normal return failed
Label exceptRetNormal = new Label(); // exceptional return success
Label exceptRetExcept = new Label(); // exceptional return failed
Label end = new Label();
reset(start, end);
++maxLocals; // add a local variable slot to save any exceptions into (at maxLocalParams position)
maxStack = Math.max(maxLocalParams, returnCode == Opcodes.RETURN ? 2 : 3); // must load self or class onto stack, and return value (if any)
tryCatchBlocks.add(new TryCatchBlockNode(getLabelNode(inmonitor), getLabelNode(normal), getLabelNode(except), null));
tryCatchBlocks.add(new TryCatchBlockNode(getLabelNode(normal), getLabelNode(normalRetExcept), getLabelNode(normalRetExcept), null));
tryCatchBlocks.add(new TryCatchBlockNode(getLabelNode(except), getLabelNode(exceptRetNormal), getLabelNode(exceptRetExcept), null));
// preMonitorEnter
// monitorenter
instructions.add(getLabelNode(start));
invokePreMonitorEnter();
invokeMonitor(Opcodes.MONITORENTER);
{
// try1 { val = original();
instructions.add(getLabelNode(inmonitor));
int invokeCode = loadParamsAndReturnInvokeCode();
instructions.add(new MethodInsnNode(invokeCode, className, baseName + "$unsync", desc));
{
// try2 { preMonitorExit(); monitorexit; return val; }
instructions.add(getLabelNode(normal));
invokePreMonitorExit();
invokeMonitor(Opcodes.MONITOREXIT);
instructions.add(new InsnNode(returnCode())); // success
// }
// catch2 { monitorexit; throw }
instructions.add(getLabelNode(normalRetExcept));
instructions.add(new FrameNode(Opcodes.F_SAME1, 0, null, 1, new Object[]{ "java/lang/Throwable" }));
instructions.add(new IntInsnNode(Opcodes.ASTORE, maxLocalParams));
pushRef();
invokeMonitor(Opcodes.MONITOREXIT);
instructions.add(new IntInsnNode(Opcodes.ALOAD, maxLocalParams));
instructions.add(new InsnNode(Opcodes.ATHROW));
// }
}
// catch1 { try3 { preMonitorExit; monitorexit; throw
instructions.add(getLabelNode(except));
instructions.add(new FrameNode(Opcodes.F_SAME1, 0, null, 1, new Object[]{ "java/lang/Throwable" }));
instructions.add(new IntInsnNode(Opcodes.ASTORE, maxLocalParams));
invokePreMonitorExit();
invokeMonitor(Opcodes.MONITOREXIT);
instructions.add(new IntInsnNode(Opcodes.ALOAD, maxLocalParams));
instructions.add(getLabelNode(exceptRetNormal));
instructions.add(new InsnNode(Opcodes.ATHROW));
instructions.add(getLabelNode(exceptRetExcept));
instructions.add(new FrameNode(Opcodes.F_SAME1, 0, null, 1, new Object[]{ "java/lang/Throwable" }));
instructions.add(new IntInsnNode(Opcodes.ASTORE, maxLocalParams));
pushRef();
invokeMonitor(Opcodes.MONITOREXIT);
instructions.add(new IntInsnNode(Opcodes.ALOAD, maxLocalParams));
instructions.add(new InsnNode(Opcodes.ATHROW));
}
instructions.add(getLabelNode(end));
methodWriterSink.writeSyntheticMethod(MONITOR, this);
}
@Override
public void visitEnd()
{
writeOriginal();
writeTryCatchMonitorEnterExit();
super.visitEnd();
}
}