blob: d710ffe417d43dee6dbe335548e983e62e48409a [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.io.IOException;
import java.io.InputStream;
import java.util.Arrays;
import java.util.List;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import org.objectweb.asm.AnnotationVisitor;
import org.objectweb.asm.Label;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;
import org.objectweb.asm.tree.AbstractInsnNode;
import static org.apache.cassandra.simulator.asm.InterceptClasses.BYTECODE_VERSION;
import static org.objectweb.asm.Opcodes.ATHROW;
import static org.objectweb.asm.Opcodes.F_SAME1;
import static org.objectweb.asm.Opcodes.INVOKESTATIC;
public class Utils
{
public static String toInternalName(Class<?> clazz)
{
return toInternalName(clazz.getName());
}
public static String toInternalName(String className)
{
return className.replace('.', '/');
}
public static String toPath(Class<?> clazz)
{
return toInternalName(clazz) + ".class";
}
public static byte[] readDefinition(Class<?> clazz) throws IOException
{
return readDefinition(toPath(clazz));
}
public static byte[] readDefinition(String path) throws IOException
{
byte[] bytes = new byte[1024];
try (InputStream in = ClassLoader.getSystemResourceAsStream(path))
{
int count = 0;
while (true)
{
int add = in.read(bytes, count, bytes.length - count);
if (add < 0)
break;
if (add == 0)
bytes = Arrays.copyOf(bytes, bytes.length * 2);
count += add;
}
return Arrays.copyOf(bytes, count);
}
}
/**
* Generate a proxy method call, i.e. one whose only job is forwarding the parameters to a different method
* (and perhaps within a superclass, or another class entirely if static) with the same signature but perhaps
* different properties.
*/
private static long visitProxyCall(MethodVisitor visitor, String calleeClassName, String calleeMethodName, String descriptor, int access, boolean isInstanceMethod, boolean isInterface)
{
Type[] argTypes = Type.getArgumentTypes(descriptor);
Type returnType = Type.getReturnType(descriptor);
int stackSize = argTypes.length;
int localsSize = 0;
if (isInstanceMethod)
{
visitor.visitIntInsn(Opcodes.ALOAD, 0);
localsSize += 2;
stackSize += 1;
}
int i = 1;
for (Type type : argTypes)
{
int opcode;
switch (type.getDescriptor().charAt(0))
{
case 'L':
case '[':
opcode = Opcodes.ALOAD;
localsSize += 1;
break;
case 'J':
opcode = Opcodes.LLOAD;
localsSize += 2;
break;
case 'D':
opcode = Opcodes.DLOAD;
localsSize += 2;
break;
case 'F':
opcode = Opcodes.FLOAD;
localsSize += 1;
break;
default:
opcode = Opcodes.ILOAD;
localsSize += 1;
break;
}
visitor.visitIntInsn(opcode, i++);
}
int returnCode;
switch (returnType.getDescriptor().charAt(0))
{
case 'L':
case '[':
returnCode = Opcodes.ARETURN;
localsSize = Math.max(localsSize, 1);
break;
case 'J':
returnCode = Opcodes.LRETURN;
localsSize = Math.max(localsSize, 2);
break;
case 'D':
returnCode = Opcodes.DRETURN;
localsSize = Math.max(localsSize, 2);
break;
case 'F':
returnCode = Opcodes.FRETURN;
localsSize = Math.max(localsSize, 1);
break;
case 'V':
returnCode = Opcodes.RETURN;
break;
default:
returnCode = Opcodes.IRETURN;
localsSize = Math.max(localsSize, 1);
break;
}
int invokeCode;
if (isInstanceMethod && (access & Opcodes.ACC_PRIVATE) != 0 || calleeMethodName.equals("<init>")) invokeCode = Opcodes.INVOKESPECIAL;
else if (isInstanceMethod) invokeCode = Opcodes.INVOKEVIRTUAL;
else invokeCode = Opcodes.INVOKESTATIC;
visitor.visitMethodInsn(invokeCode, calleeClassName, calleeMethodName, descriptor, isInterface);
return localsSize | (((long)stackSize) << 28) | (((long) returnCode) << 56);
}
/**
* Generate a proxy method call, i.e. one whose only job is forwarding the parameters to a different method
* (and perhaps within a superclass, or another class entirely if static) with the same signature but perhaps
* different properties.
*/
public static void generateProxyCall(MethodVisitor visitor, String calleeClassName, String calleeMethodName, String descriptor, int access, boolean isInstanceMethod, boolean isInterface)
{
Label start = new Label(), end = new Label();
visitor.visitLabel(start);
long sizesAndReturnCode = visitProxyCall(visitor, calleeClassName, calleeMethodName, descriptor, access, isInstanceMethod, isInterface);
visitor.visitLabel(end);
visitor.visitInsn((int)(sizesAndReturnCode >>> 56) & 0xff);
visitor.visitMaxs((int)(sizesAndReturnCode >>> 28) & 0xfffffff, (int)(sizesAndReturnCode & 0xfffffff));
visitor.visitEnd();
}
/**
* Generate a proxy method call, i.e. one whose only job is forwarding the parameters to a different method
* (and perhaps within a superclass, or another class entirely if static) with the same signature but perhaps
* different properties.
*
* Invoke within a try/catch block, invoking the provided setup/cleanup instructions.
* As designed these must not assign any local variables, and the catch block must be exception free.
*/
public static void generateTryFinallyProxyCall(MethodVisitor visitor, String calleeClassName, String calleeMethodName, String descriptor, int access, boolean isInstanceMethod, boolean isInterface,
List<AbstractInsnNode> setup, List<AbstractInsnNode> cleanup)
{
Label startMethod = new Label(), startTry = new Label(), endTry = new Label(), startCatch = new Label(), endMethod = new Label();
visitor.visitLabel(startMethod);
visitor.visitTryCatchBlock(startTry, endTry, startCatch, null);
setup.forEach(i -> i.accept(visitor));
visitor.visitLabel(startTry);
long sizesAndReturnCode = visitProxyCall(visitor, calleeClassName, calleeMethodName, descriptor, access, isInstanceMethod, isInterface);
int returnCode = (int)(sizesAndReturnCode >>> 56) & 0xff;
visitor.visitLabel(endTry);
cleanup.forEach(i -> i.accept(visitor));
visitor.visitInsn(returnCode);
visitor.visitLabel(startCatch);
visitor.visitFrame(F_SAME1, 0, null, 1, new Object[] { "java/lang/Throwable" });
cleanup.forEach(i -> i.accept(visitor));
visitor.visitInsn(ATHROW);
visitor.visitLabel(endMethod);
if (isInstanceMethod)
visitor.visitLocalVariable("this", "L" + calleeClassName + ';', null, startMethod, endMethod, 0);
visitor.visitMaxs((int)(sizesAndReturnCode >>> 28) & 0xfffffff, (int)(sizesAndReturnCode & 0xfffffff));
visitor.visitEnd();
}
public static AnnotationVisitor checkForSimulationAnnotations(int api, String descriptor, AnnotationVisitor wrap, BiConsumer<Flag, Boolean> annotations)
{
if (!descriptor.equals("Lorg/apache/cassandra/utils/Simulate;"))
return wrap;
return new AnnotationVisitor(api, wrap)
{
@Override
public AnnotationVisitor visitArray(String name)
{
if (!name.equals("with") && !name.equals("without"))
return super.visitArray(name);
boolean add = name.equals("with");
return new AnnotationVisitor(api, super.visitArray(name))
{
@Override
public void visitEnum(String name, String descriptor, String value)
{
super.visitEnum(name, descriptor, value);
if (descriptor.equals("Lorg/apache/cassandra/utils/Simulate$With;"))
annotations.accept(Flag.valueOf(value), add);
}
};
}
};
}
public static MethodVisitor deterministicToString(MethodVisitor wrap)
{
return new MethodVisitor(BYTECODE_VERSION, wrap)
{
@Override
public void visitMethodInsn(int opcode, String owner, String name, String descriptor, boolean isInterface)
{
if (name.equals("hashCode") && owner.equals("java/lang/Object"))
{
super.visitMethodInsn(INVOKESTATIC, "java/lang/System", "identityHashCode", "(Ljava/lang/Object;)I", false);
}
else
{
super.visitMethodInsn(opcode, owner, name, descriptor, isInterface);
}
}
};
}
public static void visitEachRefType(String descriptor, Consumer<String> forEach)
{
Type[] argTypes = Type.getArgumentTypes(descriptor);
Type retType = Type.getReturnType(descriptor);
for (Type argType : argTypes)
visitIfRefType(argType.getDescriptor(), forEach);
visitIfRefType(retType.getDescriptor(), forEach);
}
public static void visitIfRefType(String descriptor, Consumer<String> forEach)
{
if (descriptor.charAt(0) != '[' && descriptor.charAt(descriptor.length() - 1) != ';')
{
if (descriptor.length() > 1)
forEach.accept(descriptor);
}
else
{
int i = 1;
while (descriptor.charAt(i) == '[') ++i;
if (descriptor.charAt(i) == 'L')
forEach.accept(descriptor.substring(i + 1, descriptor.length() - 1));
}
}
public static String descriptorToClassName(String desc)
{
// samples: "Ljdk/internal/misc/Unsafe;", "Lsun/misc/Unsafe;"
if (!(desc.startsWith("L") && desc.endsWith(";")))
throw new IllegalArgumentException("Unable to parse descriptor: " + desc);
return desc.substring(1, desc.length() - 1);
}
}