package com.francetelecom.rd.stubs.engine;
/*
* #%L
* Matos
* $Id:$
* $HeadURL:$
* %%
* Copyright (C) 2008 - 2014 Orange SA
* %%
* 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.
* #L%
*/
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* The goal of this class is to patch native methods into stubs raising an
* exception.
* @author Pierre Cregut
*
*/
public class PatchNativeMethod {
/**
* If the mode is true, then there is something to patch.
*/
private boolean mode = false;
private Map<String, Integer> expectedIndexes;
private int constantpoolsize;
private int runtime_entry;
private int init_runtime_entry;
private int init_object_entry;
private List<int[]> finalStaticFields = new ArrayList<int[]>();
private List<Integer> fieldsEntries = new ArrayList<Integer>();
private int this_entry;
/** Code size regular meth. */
public final int CODE_LENGTH = 8;
/** Code size constructor */
public final int CODE_CONST_LENGTH = 5;
/** Opcode for nop */
public final byte NOP = 0;
/** Opcode for aconst_null */
public final byte ACNULL = 1;
/** Opcode for a_load */
public static final byte A_LOAD= (byte) 0x2a;
/** Opcode for dup */
public static final byte DUP = (byte) 0x59;
/** opcode for return */
public static final byte RETURN= (byte) 0xb1;
/** Opcode for put_static */
public final byte PUT_STATIC = (byte) 0xb3;
/** Opcode for invoke_special */
public static final byte INVOKESPECIAL= (byte) 0xb7;
/** Opcode for new */
public static final byte NEW = (byte) 0xbb;
/** Opcode for athrow */
public static final byte ATHROW = (byte) 0xbf;
private static final String INIT_STRING = "<init>";
private static final String RUNTIME_STRING = "java/lang/RuntimeException";
private static final String SIG_STRING = "()V";
private static final String CODE_STRING = "Code";
private static final String OBJECT_STRING = "java/lang/Object";
private static final String CLINIT_STRING = "<clinit>";
private static final String [] expectedStrings = {
INIT_STRING, RUNTIME_STRING, SIG_STRING, CODE_STRING, OBJECT_STRING
};
private int [] positions;
private String [] strings;
private byte [] buf = new byte [1024];
private boolean isEnum;
/**
* Constructor
*/
public PatchNativeMethod() {
expectedIndexes = new HashMap<String, Integer>();
positions = new int[expectedStrings.length];
for(int i=0; i<expectedStrings.length; i++) {
expectedIndexes.put(expectedStrings[i],i);
positions[i] = -1;
}
}
/**
* Register a new UTF8 string. Check if it is one of the searched ones.
* @param pos
* @param utfString
*/
public void check(int pos, String utfString) {
strings[pos] = utfString;
if (expectedIndexes.containsKey(utfString)) {
positions[expectedIndexes.get(utfString)] = pos;
}
}
/**
* Access to one of the searched strings. It outputs the necessary index as
* a short on the stream.
* @param out
* @param constant
* @throws IOException
*/
private void referConstant(DataOutputStream out, String constant) throws IOException {
out.writeShort((short) positions[expectedIndexes.get(constant)]);
}
/**
* This version of skipBytes will feed the buffer until we skip the right number of
* bytes.
* @param is The stream to read from
* @param i The number of bytes to skip
* @throws IOException if stream is exhausted.
*/
public static void forceSkip(DataInputStream is, int i) throws IOException {
int done;
while((done = is.skipBytes(i)) != i) {
if (done == 0) throw new RuntimeException("Skip bytes");
i -= done;
}
}
/**
* Prepare the patcher. We look for native methods.
* @param code
* @throws IOException
*/
void prepare(DataInputStream code) throws IOException {
int mod = code.readShort();
// if (Modifier.isInterface(mod)) return;
isEnum = (0x4000 & mod) != 0;
this_entry = code.readShort();
forceSkip(code,2);
int itfs = code.readShort();
forceSkip(code,itfs*2);
int fields = code.readShort();
for(int i=0; i < fields; i++) {
int modField = code.readShort();
int name = code.readShort();
int descr = code.readShort();
if(Modifier.isStatic(modField) && Modifier.isFinal(modField) && !strings[descr].equals("Ljava/lang/String;") && (strings[descr].charAt(0)=='L' || strings[descr].charAt(0)=='[')) {
// System.err.println("FIELD " + strings[name] + ": " + strings[descr] + " MUST BE INITIALIZED.");
finalStaticFields.add(new int []{name,descr});
}
// forceSkip(code,6);
int atts = code.readShort();
for(int j=0;j<atts;j++) skipAttribute(code);
}
int methods = code.readShort();
for(int i=0; i < methods; i++) {
int modMethod = code.readShort();
int name = code.readShort();
if (strings[name].equals(CLINIT_STRING) && !isEnum) mode = true;
code.readShort(); // int desc =
if (Modifier.isNative(modMethod)) {
mode = true;
// System.err.println("*********** NATIVE **********");
// System.err.println("Method " + strings[name] + " " + strings[desc] + " " + countArgs(desc));
}
int atts = code.readShort();
for(int j=0;j<atts;j++) skipAttribute(code);
}
}
/**
* Go through an attribute.
* @param code
* @throws IOException
*/
private void skipAttribute(DataInputStream code) throws IOException {
forceSkip(code,2);
int size = code.readInt();
forceSkip(code,size);
}
/**
* Modify the constant pool. We need to have access to RuntimeException.<init>()
* and RuntimeException as a class.
* @param out
* @throws IOException
*/
public void addConstants(DataOutputStream out) throws IOException {
for(int i=0; i < expectedStrings.length; i++) {
if(positions[i] == -1) {
positions[i] = constantpoolsize++;
out.writeByte(PatchConstantPool.CONSTANT_Utf8);
out.writeUTF(expectedStrings[i]);
}
}
// Class
out.writeByte(PatchConstantPool.CONSTANT_Class);
referConstant(out,RUNTIME_STRING);
// NameAndType
out.writeByte(PatchConstantPool.CONSTANT_NameAndType);
referConstant(out,INIT_STRING);
referConstant(out,SIG_STRING);
// Method
out.writeByte(PatchConstantPool.CONSTANT_Methodref);
out.writeShort((short) constantpoolsize);
out.writeShort((short) constantpoolsize+1);
// Class Object
out.writeByte(PatchConstantPool.CONSTANT_Class);
referConstant(out,OBJECT_STRING);
// Object Init Method
out.writeByte(PatchConstantPool.CONSTANT_Methodref);
out.writeShort((short) constantpoolsize+3);
out.writeShort((short) constantpoolsize+1);
runtime_entry = constantpoolsize;
init_runtime_entry = constantpoolsize + 2;
init_object_entry = constantpoolsize + 4;
constantpoolsize += 5;
for(int [] nameType : finalStaticFields) {
out.writeByte(PatchConstantPool.CONSTANT_NameAndType);
out.writeShort(nameType[0]);
out.writeShort(nameType[1]);
out.writeByte(PatchConstantPool.CONSTANT_Fieldref);
out.writeShort(this_entry);
out.writeShort(constantpoolsize);
fieldsEntries.add(constantpoolsize+1);
constantpoolsize +=2;
}
}
/**
* Copy the body of the class and change the methods if needed.
* @param code
* @param out
* @throws IOException
*/
public void dumpCode(DataInputStream code, DataOutputStream out) throws IOException {
transfer(6,code,out);
int itfs = code.readShort();
out.writeShort(itfs);
transfer(itfs*2,code,out);
int fields = code.readShort();
out.writeShort(fields);
for(int i=0; i < fields; i++) {
transfer(6,code,out);
int atts = code.readShort();
out.writeShort(atts);
for(int j=0;j<atts;j++) dumpAttribute(code,out);
}
int methods = code.readShort();
out.writeShort(methods);
for(int i=0; i < methods; i++) {
int modMethod = code.readShort();
out.writeShort(modMethod & ~ Modifier.NATIVE);
int name = code.readShort();
out.writeShort(name);
int descr = code.readShort();
out.writeShort(descr);
boolean isNative = Modifier.isNative(modMethod);
boolean isClinit = strings[name].equals(CLINIT_STRING) && !isEnum;
int argCount = countArgs(descr);
if (! Modifier.isStatic(modMethod)) argCount++;
int atts = code.readShort();
out.writeShort((isNative) ? atts + 1 : atts);
for(int j=0;j<atts;j++) dumpAttribute(code,out,isClinit);
if(isClinit) dumpStaticInit(out);
else if(isNative) {
if (strings[name].equals("<init>")) {
dumpConstructorCode(out,argCount);
} else {
dumpMethodCode(out, argCount);
}
}
}
int atts = code.readShort();
out.writeShort(atts);
for(int j=0;j<atts;j++) dumpAttribute(code,out);
}
/**
* Take a method descriptor and counts the number of arguments (for correct locals).
* @param descrIdx
* @return
*/
private int countArgs(int descrIdx) {
String descr = strings[descrIdx];
int pos = 1;
int args = 0;
char c;
while((c = descr.charAt(pos)) != ')') {
if (c == 'L') pos = descr.indexOf(';', pos);
pos++;
if ((c == 'J') || (c == 'D')) args++;
if (c != '[') args++;
}
return args;
}
/**
* Dumps an attribute (generic)
* @param code
* @param out
* @throws IOException
*/
private void dumpAttribute(DataInputStream code, DataOutputStream out) throws IOException {
transfer(2,code,out);
int size = code.readInt();
out.writeInt(size);
transfer(size,code,out);
}
/**
* Dumps an attribute unless it is a code attribute and the flag is set (then discard).
* @param code
* @param out
* @throws IOException
*/
private void dumpAttribute(DataInputStream code, DataOutputStream out, boolean isClinit) throws IOException {
int attName = code.readShort();
int size = code.readInt();
if (isClinit && strings[attName].equals(CODE_STRING)) {
forceSkip(code,size);
} else {
out.writeShort(attName);
out.writeInt(size);
transfer(size,code,out);
}
}
/**
* Transfer n bytes from inputstream to outputstream.
* @param i
* @param code
* @param out
* @throws IOException
*/
private void transfer(int i, DataInputStream code, DataOutputStream out) throws IOException {
if (buf.length < i) buf = new byte[i];
code.readFully(buf, 0, i);
out.write(buf,0,i);
}
/**
* Register the pool size in the patcher.
* @param size
*/
public void setConstantPoolSize(int size) {
constantpoolsize = size;
strings = new String[size];
}
/**
* Change the code of a method.
* @param out
* @param argCount
* @throws IOException
*/
public void dumpMethodCode(DataOutputStream out, int argCount) throws IOException {
referConstant(out, CODE_STRING);
out.writeInt(CODE_LENGTH + 12);
out.writeShort(2); // MAX STACK = 2
out.writeShort(argCount); // MAX LOCALS = 1
out.writeInt(CODE_LENGTH); // CODE LENGTH
out.writeByte(NEW);
out.writeShort((short) runtime_entry);
out.writeByte(DUP);
out.writeByte(INVOKESPECIAL);
out.writeShort(init_runtime_entry);
out.writeByte(ATHROW);
out.writeShort(0); // Exception table size
out.writeShort(0); // Code attributes table size
}
/**
* Dumps the code for a constructor.
* @param out
* @param argCount
* @throws IOException
*/
public void dumpConstructorCode(DataOutputStream out, int argCount) throws IOException {
referConstant(out, CODE_STRING);
out.writeInt(CODE_CONST_LENGTH + 12);
out.writeShort(2); // MAX STACK = 2
out.writeShort(argCount); // MAX LOCALS = 1
out.writeInt(CODE_CONST_LENGTH); // CODE LENGTH
out.writeByte(A_LOAD);
out.writeByte(INVOKESPECIAL);
out.writeShort(init_object_entry);
out.writeByte(RETURN);
out.writeShort(0); // Exception table size
out.writeShort(0); // Code attributes table size
}
/**
* @param out
*/
private void dumpStaticInit(DataOutputStream out) throws IOException {
int codeLength = fieldsEntries.size() * 4 + 1;
referConstant(out, CODE_STRING);
out.writeInt(codeLength + 12);
out.writeShort(2); // MAX STACK = 2
out.writeShort(0); // MAX LOCALS = 1
out.writeInt(codeLength); // CODE LENGTH
for(int entry: fieldsEntries) {
out.writeByte(ACNULL);
out.writeByte(PUT_STATIC);
out.writeShort(entry);
}
out.writeByte(RETURN);
out.writeShort(0); // Exception table size
out.writeShort(0); // Code attributes table size
}
/**
* Modifies the pool size.
* @param result
*/
public void updatePoolSize(byte[] result) {
result[8] = (byte) ((constantpoolsize >> 8) & 0xff);
result[9] = (byte) (constantpoolsize & 0xff);
}
/**
* Check if we need to patch.
* @return
*/
public boolean needModification() {
return mode;
}
}