/*
* Copyright 2016, Stuart Douglas, and individual contributors as indicated
* by the @authors tag.
*
* Licensed 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.fakereplace.manip.util;
import javassist.bytecode.BadBytecode;
import javassist.bytecode.Bytecode;
import javassist.bytecode.ClassFile;
import javassist.bytecode.CodeAttribute;
import javassist.bytecode.CodeIterator;
import javassist.bytecode.DuplicateMemberException;
import javassist.bytecode.MethodInfo;
import javassist.bytecode.Opcode;
import org.fakereplace.data.MethodData;
import org.fakereplace.util.DescriptorUtils;
/**
* Class that holds various static helper methods that manipulate the bytecode
*
* @author stuart
*/
public class ManipulationUtils {
private ManipulationUtils() {
}
/**
* This class changes a block of code to return either a boxed version of a
* Primitive type or null if the method is void
*
* @author stuart
*/
public static class MethodReturnRewriter {
public static void rewriteFakeMethod(CodeIterator methodBody, String methodDescriptor) {
String ret = DescriptorUtils.getReturnType(methodDescriptor);
// if the return type is larger than one then it is not a primitive
// so it does not need to be boxed
if (ret.length() != 1) {
return;
}
// void methods are special
if (ret.equals("V")) {
while (methodBody.hasNext()) {
try {
int index = methodBody.next();
int opcode = methodBody.byteAt(index);
// replace a RETURN opcode with
// ACONST_NULL
// ARETURN
// to return a null value
if (opcode == Opcode.RETURN) {
Bytecode code = new Bytecode(methodBody.get().getConstPool());
methodBody.writeByte(Opcode.ARETURN, index);
code.add(Opcode.ACONST_NULL);
methodBody.insertAt(index, code.get());
}
} catch (BadBytecode e) {
throw new RuntimeException(e);
}
}
} else {
while (methodBody.hasNext()) {
try {
int index = methodBody.next();
int opcode = methodBody.byteAt(index);
switch (opcode) {
case Opcode.IRETURN:
case Opcode.LRETURN:
case Opcode.DRETURN:
case Opcode.FRETURN:
// write a NOP over the old return instruction
// insert the boxing code to get an object on the stack
methodBody.writeByte(Opcode.ARETURN, index);
Bytecode b = new Bytecode(methodBody.get().getConstPool());
Boxing.box(b, ret.charAt(0));
methodBody.insertAt(index, b.get());
}
} catch (BadBytecode e) {
throw new RuntimeException(e);
}
}
}
}
/**
* Gets the correct return instruction for a proxy method
*
*/
public static void addReturnProxyMethod(String methodDescriptor, Bytecode b) {
String ret = DescriptorUtils.getReturnType(methodDescriptor);
// if the return type is larger than one then it is not a primitive
// so just do an ARETURN
if (ret.length() != 1) {
b.addCheckcast(DescriptorUtils.getReturnTypeInJvmFormat(methodDescriptor));
b.add(Opcode.ARETURN);
return;
}
// void methods are special
if (ret.equals("V")) {
b.add(Opcode.RETURN);
return;
} else {
// unbox the primitive type
char tp = ret.charAt(0);
Boxing.unbox(b, tp);
if (tp == 'F') {
b.add(Opcode.FRETURN);
} else if (tp == 'D') {
b.add(Opcode.DRETURN);
} else if (tp == 'J') {
b.add(Opcode.LRETURN);
} else {
b.add(Opcode.IRETURN);
}
return;
}
}
}
/**
* add a bogus constructor call to a bytecode sequence so a constructor can
* pass bytecode validation
*
*/
public static boolean addBogusConstructorCall(ClassFile file, Bytecode code) {
MethodInfo constructorToCall = null;
for (Object meth : file.getMethods()) {
MethodInfo m = (MethodInfo) meth;
if (m.getName().equals("<init>")) {
constructorToCall = m;
break;
}
}
if (constructorToCall == null) {
return false;
}
// push this onto the stack
code.add(Bytecode.ALOAD_0);
String[] params = DescriptorUtils.descriptorStringToParameterArray(constructorToCall.getDescriptor());
for (String p : params) {
// int char short boolean byte
if (p.equals("I") || p.equals("C") || p.equals("S") || p.equals("Z") || p.equals("B")) {
// push integer 0
code.add(Opcode.ICONST_0);
}
// long
else if (p.equals("J")) {
code.add(Opcode.LCONST_0);
}
// double
else if (p.equals("D")) {
code.add(Opcode.DCONST_0);
}
// float
else if (p.equals("F")) {
code.add(Opcode.FCONST_0);
}
// arrays and reference types
else {
code.add(Opcode.ACONST_NULL);
}
}
// all our args should be pushed onto the stack, call the constructor
code.addInvokespecial(file.getName(), "<init>", constructorToCall.getDescriptor());
code.addNew(NoSuchMethodError.class.getName());
code.add(Opcode.DUP);
code.addInvokespecial(NoSuchMethodError.class.getName(), "<init>", "()V");
code.add(Opcode.ATHROW);
return true;
}
/**
* inserts a 16 bit offset into the bytecode
*
*/
public static void add16bit(Bytecode b, int value) {
value = value % 65536;
b.add(value >> 8);
b.add(value % 256);
}
public static void pushParametersIntoArray(Bytecode bc, String methodDescriptor) {
String[] params = DescriptorUtils.descriptorStringToParameterArray(methodDescriptor);
// now we need an array:
bc.addIconst(params.length);
bc.addAnewarray("java.lang.Object");
// now we have our array sitting on top of the stack
// we need to stick our parameters into it. We do this is reverse
// as we can't pull them from the bottom of the stack
for (int i = params.length - 1; i >= 0; --i) {
if (DescriptorUtils.isWide(params[i])) {
// dup the array below the wide
bc.add(Opcode.DUP_X2);
// now do it again so we have two copies
bc.add(Opcode.DUP_X2);
// now pop it, the is the equivalent of a wide swap
bc.add(Opcode.POP);
} else {
// duplicate the array to place 3
bc.add(Opcode.DUP_X1);
// now swap
bc.add(Opcode.SWAP);
}
// now the parameter is above the array
// box it if nessesary
if (DescriptorUtils.isPrimitive(params[i])) {
Boxing.box(bc, params[i].charAt(0));
}
// add the array index
bc.addIconst(i);
bc.add(Opcode.SWAP);
bc.add(Opcode.AASTORE);
// we still have the array on the top of the stack becuase we
// duplicated it earlier
}
}
}