/*
* Copyright 2012, Thomas Kerber
*
* 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 milk.jpatch.code;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import milk.jpatch.Util;
import org.apache.bcel.classfile.Method;
import org.apache.bcel.generic.ConstantPoolGen;
import org.apache.bcel.generic.FieldInstruction;
import org.apache.bcel.generic.GETFIELD;
import org.apache.bcel.generic.GETSTATIC;
import org.apache.bcel.generic.Instruction;
import org.apache.bcel.generic.InstructionHandle;
import org.apache.bcel.generic.InstructionList;
import org.apache.bcel.generic.LocalVariableInstruction;
import org.apache.bcel.generic.PUTFIELD;
import org.apache.bcel.generic.PUTSTATIC;
/**
* Denotes a code segment initializing a field.
* @author Thomas Kerber
* @version 1.0.0
*/
public class FieldInitCodeSegment implements Serializable{
private static final long serialVersionUID = -2308147557332096828L;
/**
* The instruction list.
*/
public final InstructionList il;
/**
* The fields name.
*/
public final String fieldName;
/**
* The new local variable indexes.
*
* If these indexes are already defined, they are changed in the code.
*/
// TODO: add something similar to CodeAddDelta.
public final int[] newLocalVars;
/**
*
* @param il The instruction list.
* @param fieldName The fields name.
* @param newLocalVars The new local variable indexes.
*/
public FieldInitCodeSegment(InstructionList il, String fieldName,
int[] newLocalVars){
this.il = il;
this.fieldName = fieldName;
this.newLocalVars = newLocalVars;
}
/**
* Generates...
* @param methods The class methods.
* @param static_ Whether or not to generate static fics.
* @param cpg The cpool gen.
* @return The fics.
*/
public static List<FieldInitCodeSegment> generate(Method[] methods,
boolean static_, ConstantPoolGen cpg){
List<FieldInitCodeSegment> ret =
new ArrayList<FieldInitCodeSegment>();
List<Method> initMethods = new ArrayList<Method>();
for(Method m : methods){
if(m.isStatic() != static_)
continue;
if((!static_ && m.getName().equals("<init>")) ||
(static_ && m.getName().equals("<cinit>")))
initMethods.add(m);
}
List<List<FieldInitCodeSegment>> ficss =
new ArrayList<List<FieldInitCodeSegment>>();
for(Method m : initMethods)
ficss.add(generate(new InstructionList(m.getCode().getCode()),
Util.getFixedLocalVariableLength(m), static_, cpg));
// Field names contained in _all_ each methods fics. (The rest can't be
// equal for every method anyhow and are ignored)
Set<String> fieldNames = null;
for(List<FieldInitCodeSegment> ficsL : ficss){
Set<String> thisMethodFieldNames = new HashSet<String>();
for(FieldInitCodeSegment fics : ficsL){
thisMethodFieldNames.add(fics.fieldName);
}
if(fieldNames == null)
fieldNames = thisMethodFieldNames;
else
fieldNames.retainAll(thisMethodFieldNames);
}
outer: for(String fieldName : fieldNames){
FieldInitCodeSegment cmpTo = null;
{
List<FieldInitCodeSegment> ficsL = ficss.get(0);
for(FieldInitCodeSegment fics : ficsL){
if(fics.fieldName.equals(fieldName)){
cmpTo = fics;
break;
}
}
}
for(int i = 1; i < ficss.size(); i++){
FieldInitCodeSegment cmpWith = null;
{
List<FieldInitCodeSegment> ficsL = ficss.get(i);
for(FieldInitCodeSegment fics : ficsL){
if(fics.fieldName.equals(fieldName)){
cmpWith = fics;
break;
}
}
}
if(!cmpTo.equals(cmpWith))
continue outer;
}
ret.add(cmpTo);
}
return ret;
}
/**
* Generates...
* @param il The instruction list to generate from.
* @param static_ Whether this is <clinit> or <init>.
* @param cpg The cpool.
* @return The init code segments
*/
public static List<FieldInitCodeSegment> generate(InstructionList il,
int paramCount, boolean static_, ConstantPoolGen cpg){
List<FieldInitCodeSegment> ret =
new ArrayList<FieldInitCodeSegment>();
/*
* An init code segment is a segment which:
*
* a) modifies the stack by +- 0.
* *b) lies in top-level code.
* *c) ends with a putfield to a static field if static_ is true, or
* to a non-static field if static_ is false. The putfield must
* be to a field which is not accessed more than once.
* d) contains only local variables which are not used elsewhere.
* e) does not access any other fields, unless the fields are static
* and are accessed in the <init> method.
* f) does not contain any jump instructions.
*/
List<InstructionHandle> topLevelCode = CodeUtil.
getTopLevelInstructions(il);
InstructionHandle[] ihl = il.getInstructionHandles();
List<Integer> shortlistedPutfields = new ArrayList<Integer>();
for(int i = 0; i < topLevelCode.size(); i++){
if(topLevelCode.get(i) == null)
continue;
if(topLevelCode.get(i).getInstruction() instanceof PUTFIELD)
shortlistedPutfields.add(i);
}
// Remove of field is accessed more than once.
for(int i = 0; i < shortlistedPutfields.size(); i++){
String fieldName =
((PUTFIELD)topLevelCode.get(i).getInstruction()).
getFieldName(cpg);
for(InstructionHandle ih : ihl){
if(ih == topLevelCode.get(shortlistedPutfields.get(i)))
continue;
Instruction in = ih.getInstruction();
if(in instanceof FieldInstruction){
if(!static_ && (in instanceof GETSTATIC ||
in instanceof PUTSTATIC))
continue;
else if(static_ && (in instanceof GETFIELD ||
in instanceof PUTFIELD))
continue;
if(((FieldInstruction) in).getFieldName(cpg).equals(
fieldName)){
shortlistedPutfields.remove(i--);
break;
}
}
}
}
for(int i = 0; i < shortlistedPutfields.size(); i++){
// Already start one back so as to avoid the last putfield
// triggering the "no field access" rule.
int pos = shortlistedPutfields.get(i) - 1;
int endPos = shortlistedPutfields.get(i);
int stackModif = 2;
// Go backwards, adding to the stack. If at any time one of the
// Conditions f or e are violated, or a null or index -1 is
// encountered in top level code, this isn't field init code.
// every time stackModif is 0, the condition d is checked, and if
// successful, this is final and is split off into an instruction
// list.
outer: while(true){
if(pos == -1)
break;
InstructionHandle instrH = topLevelCode.get(pos);
if(instrH == null)
break;
Instruction instr = instrH.getInstruction();
if(CodeUtil.isJump(instr))
break;
if(instr instanceof FieldInstruction)
if(!(!static_ &&
(instr instanceof PUTSTATIC ||
instr instanceof GETSTATIC)))
break;
stackModif -= CodeUtil.getInstructionStackModif(instr,
cpg.getConstantPool());
if(stackModif == 0){
List<Integer> localVariableIndexes =
new ArrayList<Integer>();
// Gather list of local variable indexes in the block.
for(int f = pos; f <= endPos; f++){
Instruction instr2 = topLevelCode.get(f).
getInstruction();
if(instr2 instanceof LocalVariableInstruction){
int localIndex = ((LocalVariableInstruction)instr2).
getIndex();
// Ignore parameters. (Yes, even though they can
// theoretically be edited. That may induce possible
// errors, but is far more convinient)
if(localIndex < paramCount)
continue;
localVariableIndexes.add(localIndex);
}
}
// And compare against the entire method code.
icmp: for(InstructionHandle ih : ihl){
// Ignore handles in the block.
for(int f = pos; f <= endPos; f++)
if(topLevelCode.get(f) == ih)
continue icmp;
Instruction instr2 = ih.getInstruction();
if(!(instr2 instanceof LocalVariableInstruction))
continue;
int localIndex = ((LocalVariableInstruction)instr2).
getIndex();
for(Integer blockIndex : localVariableIndexes)
if(blockIndex == localIndex)
// Jackpot. If won't work.
continue outer;
}
// If it made it through: congratulations! Your
// FieldInitCodeSegment may now be generated!
InstructionList newList = new InstructionList();
for(int f = pos; f <= endPos; f++){
newList.append(topLevelCode.get(f).getInstruction());
}
String fieldName = ((FieldInstruction)topLevelCode.
get(endPos).getInstruction()).getFieldName(cpg);
int[] localVariableArr = Util.toIntArray(
localVariableIndexes);
ret.add(new FieldInitCodeSegment(newList, fieldName,
localVariableArr));
}
}
}
return ret;
}
@Override
public boolean equals(Object o){
if(o == null)
return false;
if(this == o)
return true;
if(!(o instanceof FieldInitCodeSegment))
return false;
FieldInitCodeSegment other = (FieldInitCodeSegment)o;
if(!this.fieldName.equals(other.fieldName))
return false;
if(this.il.size() != other.il.size())
return false;
Instruction[] ins1 = il.getInstructions();
Instruction[] ins2 = other.il.getInstructions();
for(int i = 0; i < il.size(); i++){
if(!(ins1[i] instanceof LocalVariableInstruction &&
ins2[i] instanceof LocalVariableInstruction)){
if(!ins1[i].equals(ins2[i]))
return false;
}
else{
LocalVariableInstruction lvi1 =
(LocalVariableInstruction)ins1[i];
LocalVariableInstruction lvi2 =
(LocalVariableInstruction)ins2[i];
int lvindex1 = lvi1.getIndex();
int lvindex2 = lvi2.getIndex();
// Indexes get replaced with their index in the newLocalVars
// array. That way, the same local vars are matched, even if
// they have a different index.
int nlvindex1 = -1;
int nlvindex2 = -1;
for(int f = 0; f < newLocalVars.length; f++){
if(newLocalVars[f] == lvindex1){
nlvindex1 = f;
break;
}
}
for(int f = 0; f < other.newLocalVars.length; f++){
if(other.newLocalVars[f] == lvindex2){
nlvindex2 = f;
break;
}
}
if(nlvindex1 != nlvindex2)
return false;
lvi1.setIndex(nlvindex1);
lvi2.setIndex(nlvindex2);
boolean eq = lvi1.toString().equals(lvi2.toString());
lvi1.setIndex(lvindex1);
lvi2.setIndex(lvindex2);
if(!eq)
return false;
}
}
return true;
}
}