blob: 778e44c80894326bf9ff46e000a6ccecb70fc080 [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.EnumSet;
import java.util.List;
import java.util.function.Consumer;
import org.objectweb.asm.AnnotationVisitor;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.FieldVisitor;
import org.objectweb.asm.Handle;
import org.objectweb.asm.Label;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.tree.AbstractInsnNode;
import org.objectweb.asm.tree.MethodInsnNode;
import org.objectweb.asm.tree.MethodNode;
import static java.util.Collections.singletonList;
import static org.apache.cassandra.simulator.asm.Flag.DETERMINISTIC;
import static org.apache.cassandra.simulator.asm.Flag.GLOBAL_METHODS;
import static org.apache.cassandra.simulator.asm.Flag.MONITORS;
import static org.apache.cassandra.simulator.asm.Flag.NEMESIS;
import static org.apache.cassandra.simulator.asm.Flag.NO_PROXY_METHODS;
import static org.apache.cassandra.simulator.asm.TransformationKind.HASHCODE;
import static org.apache.cassandra.simulator.asm.TransformationKind.SYNCHRONIZED;
import static org.apache.cassandra.simulator.asm.Utils.deterministicToString;
import static org.apache.cassandra.simulator.asm.Utils.visitEachRefType;
import static org.apache.cassandra.simulator.asm.Utils.generateTryFinallyProxyCall;
import static org.objectweb.asm.Opcodes.ACC_PRIVATE;
import static org.objectweb.asm.Opcodes.ACC_STATIC;
import static org.objectweb.asm.Opcodes.ACC_SYNTHETIC;
import static org.objectweb.asm.Opcodes.INVOKESTATIC;
class ClassTransformer extends ClassVisitor implements MethodWriterSink
{
private static final List<AbstractInsnNode> DETERMINISM_SETUP = singletonList(new MethodInsnNode(INVOKESTATIC, "org/apache/cassandra/simulator/systems/InterceptibleThread", "enterDeterministicMethod", "()V", false));
private static final List<AbstractInsnNode> DETERMINISM_CLEANUP = singletonList(new MethodInsnNode(INVOKESTATIC, "org/apache/cassandra/simulator/systems/InterceptibleThread", "exitDeterministicMethod", "()V", false));
class DependentTypeVisitor extends MethodVisitor
{
public DependentTypeVisitor(int api, MethodVisitor methodVisitor)
{
super(api, methodVisitor);
}
@Override
public void visitTypeInsn(int opcode, String type)
{
super.visitTypeInsn(opcode, type);
Utils.visitIfRefType(type, dependentTypes);
}
@Override
public void visitFieldInsn(int opcode, String owner, String name, String descriptor)
{
super.visitFieldInsn(opcode, owner, name, descriptor);
Utils.visitIfRefType(descriptor, dependentTypes);
}
@Override
public void visitMethodInsn(int opcode, String owner, String name, String descriptor, boolean isInterface)
{
super.visitMethodInsn(opcode, owner, name, descriptor, isInterface);
Utils.visitEachRefType(descriptor, dependentTypes);
}
@Override
public void visitInvokeDynamicInsn(String name, String descriptor, Handle bootstrapMethodHandle, Object... bootstrapMethodArguments)
{
super.visitInvokeDynamicInsn(name, descriptor, bootstrapMethodHandle, bootstrapMethodArguments);
Utils.visitEachRefType(descriptor, dependentTypes);
}
@Override
public void visitLocalVariable(String name, String descriptor, String signature, Label start, Label end, int index)
{
super.visitLocalVariable(name, descriptor, signature, start, end, index);
Utils.visitIfRefType(descriptor, dependentTypes);
}
}
private final String className;
private final ChanceSupplier monitorDelayChance;
private final NemesisGenerator nemesis;
private final NemesisFieldKind.Selector nemesisFieldSelector;
private final Hashcode insertHashcode;
private final MethodLogger methodLogger;
private boolean isTransformed;
private boolean isCacheablyTransformed = true;
private final EnumSet<Flag> flags;
private final Consumer<String> dependentTypes;
private boolean updateVisibility = false;
ClassTransformer(int api, String className, EnumSet<Flag> flags, Consumer<String> dependentTypes)
{
this(api, new ClassWriter(0), className, flags, null, null, null, null, dependentTypes);
}
ClassTransformer(int api, String className, EnumSet<Flag> flags, ChanceSupplier monitorDelayChance, NemesisGenerator nemesis, NemesisFieldKind.Selector nemesisFieldSelector, Hashcode insertHashcode, Consumer<String> dependentTypes)
{
this(api, new ClassWriter(0), className, flags, monitorDelayChance, nemesis, nemesisFieldSelector, insertHashcode, dependentTypes);
}
private ClassTransformer(int api, ClassWriter classWriter, String className, EnumSet<Flag> flags, ChanceSupplier monitorDelayChance, NemesisGenerator nemesis, NemesisFieldKind.Selector nemesisFieldSelector, Hashcode insertHashcode, Consumer<String> dependentTypes)
{
super(api, classWriter);
if (flags.contains(NEMESIS) && (nemesis == null || nemesisFieldSelector == null))
throw new IllegalArgumentException();
if (flags.contains(MONITORS) && monitorDelayChance == null)
throw new IllegalArgumentException();
this.dependentTypes = dependentTypes;
this.className = className;
this.flags = flags;
this.monitorDelayChance = monitorDelayChance;
this.nemesis = nemesis;
this.nemesisFieldSelector = nemesisFieldSelector;
this.insertHashcode = insertHashcode;
this.methodLogger = MethodLogger.log(api, className);
}
public void setUpdateVisibility(boolean updateVisibility)
{
this.updateVisibility = updateVisibility;
}
/**
* Java 11 changed the way that classes defined in the same source file get access to private state (see https://openjdk.org/jeps/181),
* rather than trying to adapt to this, this method attempts to make the field/method/class public so that access
* is not restricted.
*/
private int makePublic(int access)
{
if (!updateVisibility)
return access;
// leave non-user created methods/fields/etc. alone
if (contains(access, Opcodes.ACC_BRIDGE) || contains(access, Opcodes.ACC_SYNTHETIC))
return access;
if (contains(access, Opcodes.ACC_PRIVATE))
{
access &= ~Opcodes.ACC_PRIVATE;
access |= Opcodes.ACC_PUBLIC;
}
else if (contains(access, Opcodes.ACC_PROTECTED))
{
access &= ~Opcodes.ACC_PROTECTED;
access |= Opcodes.ACC_PUBLIC;
}
else if (!contains(access, Opcodes.ACC_PUBLIC)) // package-protected
{
access |= Opcodes.ACC_PUBLIC;
}
return access;
}
private static boolean contains(int value, int mask)
{
return (value & mask) != 0;
}
@Override
public void visit(int version, int access, String name, String signature, String superName, String[] interfaces)
{
super.visit(version, makePublic(access), name, signature, superName, interfaces);
}
@Override
public FieldVisitor visitField(int access, String name, String descriptor, String signature, Object value)
{
if (dependentTypes != null)
Utils.visitIfRefType(descriptor, dependentTypes);
return super.visitField(makePublic(access), name, descriptor, signature, value);
}
@Override
public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions)
{
if (dependentTypes != null)
visitEachRefType(descriptor, dependentTypes);
EnumSet<Flag> flags = this.flags;
if (flags.isEmpty() || ((access & ACC_SYNTHETIC) != 0 && (name.endsWith("$unsync") || name.endsWith("$catch") || name.endsWith("$nemesis"))))
{
MethodVisitor visitor = super.visitMethod(access, name, descriptor, signature, exceptions);
if (dependentTypes != null && (access & (ACC_STATIC | ACC_SYNTHETIC)) != 0 && (name.equals("<clinit>") || name.startsWith("lambda$")))
visitor = new DependentTypeVisitor(api, visitor);
return visitor;
}
boolean isToString = false;
if (access == Opcodes.ACC_PUBLIC && name.equals("toString") && descriptor.equals("()Ljava/lang/String;") && !flags.contains(NO_PROXY_METHODS))
{
generateTryFinallyProxyCall(super.visitMethod(access, name, descriptor, signature, exceptions), className,
"toString$original", "()Ljava/lang/String;", access, true, false, DETERMINISM_SETUP, DETERMINISM_CLEANUP);
access = ACC_PRIVATE | ACC_SYNTHETIC;
name = "toString$original";
if (!flags.contains(DETERMINISTIC) || flags.contains(NEMESIS))
{
flags = EnumSet.copyOf(flags);
flags.add(DETERMINISTIC);
flags.remove(NEMESIS);
}
isToString = true;
}
access = makePublic(access);
MethodVisitor visitor;
if (flags.contains(MONITORS) && (access & Opcodes.ACC_SYNCHRONIZED) != 0)
{
visitor = new MonitorMethodTransformer(this, className, api, access, name, descriptor, signature, exceptions, monitorDelayChance);
witness(SYNCHRONIZED);
}
else
{
visitor = super.visitMethod(access, name, descriptor, signature, exceptions);
visitor = methodLogger.visitMethod(access, name, descriptor, visitor);
}
if (flags.contains(MONITORS))
visitor = new MonitorEnterExitParkTransformer(this, api, visitor, className, monitorDelayChance);
if (isToString)
visitor = deterministicToString(visitor);
if (flags.contains(GLOBAL_METHODS) || flags.contains(Flag.LOCK_SUPPORT) || flags.contains(Flag.DETERMINISTIC))
visitor = new GlobalMethodTransformer(flags, this, api, name, visitor);
if (flags.contains(NEMESIS))
visitor = new NemesisTransformer(this, api, name, visitor, nemesis, nemesisFieldSelector);
if (dependentTypes != null && (access & (ACC_STATIC | ACC_SYNTHETIC)) != 0 && (name.equals("<clinit>") || name.startsWith("lambda$")))
visitor = new DependentTypeVisitor(api, visitor);
return visitor;
}
@Override
public void visitEnd()
{
if (insertHashcode != null)
writeSyntheticMethod(HASHCODE, insertHashcode);
super.visitEnd();
methodLogger.visitEndOfClass();
}
public void writeMethod(MethodNode node)
{
writeMethod(null, node);
}
public void writeSyntheticMethod(TransformationKind kind, MethodNode node)
{
writeMethod(kind, node);
}
void writeMethod(TransformationKind kind, MethodNode node)
{
String[] exceptions = node.exceptions == null ? null : node.exceptions.toArray(new String[0]);
MethodVisitor visitor = super.visitMethod(node.access, node.name, node.desc, node.signature, exceptions);
visitor = methodLogger.visitMethod(node.access, node.name, node.desc, visitor);
if (kind != null)
witness(kind);
node.accept(visitor);
}
@Override
public AnnotationVisitor visitAnnotation(String descriptor, boolean visible)
{
return Utils.checkForSimulationAnnotations(api, descriptor, super.visitAnnotation(descriptor, visible), (flag, add) -> {
if (add) flags.add(flag);
else flags.remove(flag);
});
}
void readAndTransform(byte[] input)
{
ClassReader reader = new ClassReader(input);
reader.accept(this, 0);
}
void witness(TransformationKind kind)
{
isTransformed = true;
switch (kind)
{
case FIELD_NEMESIS:
case SIGNAL_NEMESIS:
isCacheablyTransformed = false;
}
methodLogger.witness(kind);
}
String className()
{
return className;
}
boolean isTransformed()
{
return isTransformed;
}
boolean isCacheablyTransformed()
{
return isCacheablyTransformed;
}
byte[] toBytes()
{
return ((ClassWriter) cv).toByteArray();
}
}