/*
* Javassist, a Java-bytecode translator toolkit.
* Copyright (C) 1999- Shigeru Chiba. All Rights Reserved.
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. Alternatively, the contents of this file may be used under
* the terms of the GNU Lesser General Public License Version 2.1 or later,
* or the Apache License Version 2.0.
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*/
package javassist.convert;
import javassist.
CannotCompileException;
import javassist.
CodeConverter.
ArrayAccessReplacementMethodNames;
import javassist.
CtClass;
import javassist.
NotFoundException;
import javassist.bytecode.
BadBytecode;
import javassist.bytecode.
CodeIterator;
import javassist.bytecode.
ConstPool;
import javassist.bytecode.
Descriptor;
import javassist.bytecode.
MethodInfo;
import javassist.bytecode.analysis.
Analyzer;
import javassist.bytecode.analysis.
Frame;
/**
* A transformer which replaces array access with static method invocations.
*
* @author <a href="kabir.khan@jboss.com">Kabir Khan</a>
* @author Jason T. Greene
* @version $Revision: 1.8 $
*/
public final class
TransformAccessArrayField extends
Transformer {
private final
String methodClassname;
private final
ArrayAccessReplacementMethodNames names;
private
Frame[]
frames;
private int
offset;
public
TransformAccessArrayField(
Transformer next,
String methodClassname,
ArrayAccessReplacementMethodNames names) throws
NotFoundException {
super(
next);
this.
methodClassname =
methodClassname;
this.
names =
names;
}
@
Override
public void
initialize(
ConstPool cp,
CtClass clazz,
MethodInfo minfo) throws
CannotCompileException {
/*
* This transformer must be isolated from other transformers, since some
* of them affect the local variable and stack maximums without updating
* the code attribute to reflect the changes. This screws up the
* data-flow analyzer, since it relies on consistent code state. Even
* if the attribute values were updated correctly, we would have to
* detect it, and redo analysis, which is not cheap. Instead, we are
* better off doing all changes in initialize() before everyone else has
* a chance to muck things up.
*/
CodeIterator iterator =
minfo.
getCodeAttribute().
iterator();
while (
iterator.
hasNext()) {
try {
int
pos =
iterator.
next();
int
c =
iterator.
byteAt(
pos);
if (
c ==
AALOAD)
initFrames(
clazz,
minfo);
if (
c ==
AALOAD ||
c ==
BALOAD ||
c ==
CALOAD ||
c ==
DALOAD
||
c ==
FALOAD ||
c ==
IALOAD ||
c ==
LALOAD
||
c ==
SALOAD) {
pos =
replace(
cp,
iterator,
pos,
c,
getLoadReplacementSignature(
c));
} else if (
c ==
AASTORE ||
c ==
BASTORE ||
c ==
CASTORE
||
c ==
DASTORE ||
c ==
FASTORE ||
c ==
IASTORE
||
c ==
LASTORE ||
c ==
SASTORE) {
pos =
replace(
cp,
iterator,
pos,
c,
getStoreReplacementSignature(
c));
}
} catch (
Exception e) {
throw new
CannotCompileException(
e);
}
}
}
@
Override
public void
clean() {
frames = null;
offset = -1;
}
@
Override
public int
transform(
CtClass tclazz, int
pos,
CodeIterator iterator,
ConstPool cp) throws
BadBytecode {
// Do nothing, see above comment
return
pos;
}
private
Frame getFrame(int
pos) throws
BadBytecode {
return
frames[
pos -
offset]; // Adjust pos
}
private void
initFrames(
CtClass clazz,
MethodInfo minfo) throws
BadBytecode {
if (
frames == null) {
frames = ((new
Analyzer())).
analyze(
clazz,
minfo);
offset = 0; // start tracking changes
}
}
private int
updatePos(int
pos, int
increment) {
if (
offset > -1)
offset +=
increment;
return
pos +
increment;
}
private
String getTopType(int
pos) throws
BadBytecode {
Frame frame =
getFrame(
pos);
if (
frame == null)
return null;
CtClass clazz =
frame.
peek().
getCtClass();
return
clazz != null ?
Descriptor.
toJvmName(
clazz) : null;
}
private int
replace(
ConstPool cp,
CodeIterator iterator, int
pos,
int
opcode,
String signature) throws
BadBytecode {
String castType = null;
String methodName =
getMethodName(
opcode);
if (
methodName != null) {
// See if the object must be cast
if (
opcode ==
AALOAD) {
castType =
getTopType(
iterator.
lookAhead());
// Do not replace an AALOAD instruction that we do not have a type for
// This happens when the state is guaranteed to be null (Type.UNINIT)
// So we don't really care about this case.
if (
castType == null)
return
pos;
if ("java/lang/Object".
equals(
castType))
castType = null;
}
// The gap may include extra padding
// Write a nop in case the padding pushes the instruction forward
iterator.
writeByte(
NOP,
pos);
CodeIterator.
Gap gap
=
iterator.
insertGapAt(
pos,
castType != null ? 5 : 2, false);
pos =
gap.
position;
int
mi =
cp.
addClassInfo(
methodClassname);
int
methodref =
cp.
addMethodrefInfo(
mi,
methodName,
signature);
iterator.
writeByte(
INVOKESTATIC,
pos);
iterator.
write16bit(
methodref,
pos + 1);
if (
castType != null) {
int
index =
cp.
addClassInfo(
castType);
iterator.
writeByte(
CHECKCAST,
pos + 3);
iterator.
write16bit(
index,
pos + 4);
}
pos =
updatePos(
pos,
gap.
length);
}
return
pos;
}
private
String getMethodName(int
opcode) {
String methodName = null;
switch (
opcode) {
case
AALOAD:
methodName =
names.
objectRead();
break;
case
BALOAD:
methodName =
names.
byteOrBooleanRead();
break;
case
CALOAD:
methodName =
names.
charRead();
break;
case
DALOAD:
methodName =
names.
doubleRead();
break;
case
FALOAD:
methodName =
names.
floatRead();
break;
case
IALOAD:
methodName =
names.
intRead();
break;
case
SALOAD:
methodName =
names.
shortRead();
break;
case
LALOAD:
methodName =
names.
longRead();
break;
case
AASTORE:
methodName =
names.
objectWrite();
break;
case
BASTORE:
methodName =
names.
byteOrBooleanWrite();
break;
case
CASTORE:
methodName =
names.
charWrite();
break;
case
DASTORE:
methodName =
names.
doubleWrite();
break;
case
FASTORE:
methodName =
names.
floatWrite();
break;
case
IASTORE:
methodName =
names.
intWrite();
break;
case
SASTORE:
methodName =
names.
shortWrite();
break;
case
LASTORE:
methodName =
names.
longWrite();
break;
}
if (
methodName.
equals(""))
methodName = null;
return
methodName;
}
private
String getLoadReplacementSignature(int
opcode) throws
BadBytecode {
switch (
opcode) {
case
AALOAD:
return "(Ljava/lang/Object;I)Ljava/lang/Object;";
case
BALOAD:
return "(Ljava/lang/Object;I)B";
case
CALOAD:
return "(Ljava/lang/Object;I)C";
case
DALOAD:
return "(Ljava/lang/Object;I)D";
case
FALOAD:
return "(Ljava/lang/Object;I)F";
case
IALOAD:
return "(Ljava/lang/Object;I)I";
case
SALOAD:
return "(Ljava/lang/Object;I)S";
case
LALOAD:
return "(Ljava/lang/Object;I)J";
}
throw new
BadBytecode(
opcode);
}
private
String getStoreReplacementSignature(int
opcode) throws
BadBytecode {
switch (
opcode) {
case
AASTORE:
return "(Ljava/lang/Object;ILjava/lang/Object;)V";
case
BASTORE:
return "(Ljava/lang/Object;IB)V";
case
CASTORE:
return "(Ljava/lang/Object;IC)V";
case
DASTORE:
return "(Ljava/lang/Object;ID)V";
case
FASTORE:
return "(Ljava/lang/Object;IF)V";
case
IASTORE:
return "(Ljava/lang/Object;II)V";
case
SASTORE:
return "(Ljava/lang/Object;IS)V";
case
LASTORE:
return "(Ljava/lang/Object;IJ)V";
}
throw new
BadBytecode(
opcode);
}
}