/* * 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; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; import org.fakereplace.api.environment.CurrentEnvironment; import org.fakereplace.core.Transformer; import org.fakereplace.data.BaseClassData; import org.fakereplace.data.ClassDataStore; import org.fakereplace.data.FieldData; import org.fakereplace.logging.Logger; import org.fakereplace.manip.data.AddedFieldData; import org.fakereplace.manip.util.Boxing; import org.fakereplace.manip.util.ManipulationDataStore; import org.fakereplace.runtime.FieldDataStore; import org.fakereplace.runtime.FieldReferenceDataStore; import org.fakereplace.util.DescriptorUtils; import javassist.bytecode.Bytecode; import javassist.bytecode.ClassFile; import javassist.bytecode.CodeIterator; import javassist.bytecode.ConstPool; import javassist.bytecode.MethodInfo; import javassist.bytecode.Opcode; public class FieldManipulator implements ClassManipulator { private static final String FIELD_DATA_STORE_CLASS = FieldDataStore.class.getName(); private static final Logger log = Logger.getLogger(FieldManipulator.class); /** * added field information by class */ private final ManipulationDataStore<AddedFieldData> data = new ManipulationDataStore<AddedFieldData>(); public void addField(AddedFieldData dt) { data.add(dt.getClassName(), dt); } public boolean transformClass(ClassFile file, ClassLoader loader, boolean modifiableClass, final Set<MethodInfo> modifiedMethods) { Map<String, Set<AddedFieldData>> addedFieldData = data.getManipulationData(loader); if (addedFieldData.isEmpty()) { return false; } Map<Integer, AddedFieldData> fieldAccessLocations = new HashMap<Integer, AddedFieldData>(); // first we need to scan the constant pool looking for // CONST_Fieldref structures ConstPool pool = file.getConstPool(); for (int i = 1; i < pool.getSize(); ++i) { // we have a field reference if (pool.getTag(i) == ConstPool.CONST_Fieldref) { String className = pool.getFieldrefClassName(i); String fieldName = pool.getFieldrefName(i); String descriptor = pool.getFieldrefType(i); boolean handled = false; if (addedFieldData.containsKey(className)) { for (AddedFieldData data : addedFieldData.get(className)) { if (fieldName.equals(data.getName())) { // store the location in the const pool of the method ref fieldAccessLocations.put(i, data); handled = true; break; } } } if (!handled && CurrentEnvironment.getEnvironment().isClassReplaceable(className, loader)) { //may be an added field //if the field does not actually exist yet we just assume it is about to come into existence //and rewrite it anyway BaseClassData data = ClassDataStore.instance().getBaseClassData(loader, className); if(data != null) { FieldData field = data.getField(fieldName); if (field == null) { //this is a new field //lets deal with it int fieldNo = FieldReferenceDataStore.instance().getFieldNo(fieldName, descriptor); AddedFieldData fieldData = new AddedFieldData(fieldNo, fieldName, descriptor, className, loader); fieldAccessLocations.put(i, fieldData); Transformer.getManipulator().rewriteInstanceFieldAccess(fieldData); addedFieldData = this.data.getManipulationData(loader); } } } } } // this means we found an instance of the call, now we have to iterate // through the methods and replace instances of the call if (!fieldAccessLocations.isEmpty()) { List<MethodInfo> methods = file.getMethods(); for (MethodInfo m : methods) { try { // ignore abstract methods if (m.getCodeAttribute() == null) { continue; } CodeIterator it = m.getCodeAttribute().iterator(); while (it.hasNext()) { // loop through the bytecode int index = it.next(); int op = it.byteAt(index); // if the bytecode is a field access if (op == Opcode.PUTFIELD || op == Opcode.GETFIELD || op == Opcode.GETSTATIC || op == Opcode.PUTSTATIC) { int val = it.s16bitAt(index + 1); // if the field access is for an added field if (fieldAccessLocations.containsKey(val)) { AddedFieldData data = fieldAccessLocations.get(val); int arrayPos = file.getConstPool().addIntegerInfo(data.getArrayIndex()); // write over the field access with nop it.writeByte(Opcode.NOP, index); it.writeByte(Opcode.NOP, index + 1); it.writeByte(Opcode.NOP, index + 2); if (op == Opcode.PUTFIELD) { Bytecode b = new Bytecode(file.getConstPool()); if (data.getDescriptor().charAt(0) != 'L' && data.getDescriptor().charAt(0) != '[') { Boxing.box(b, data.getDescriptor().charAt(0)); } b.addLdc(arrayPos); b.addInvokestatic(FIELD_DATA_STORE_CLASS, "setValue", "(Ljava/lang/Object;Ljava/lang/Object;I)V"); it.insertEx(b.get()); } else if (op == Opcode.GETFIELD) { Bytecode b = new Bytecode(file.getConstPool()); b.addLdc(arrayPos); b.addInvokestatic(FIELD_DATA_STORE_CLASS, "getValue", "(Ljava/lang/Object;I)Ljava/lang/Object;"); if (DescriptorUtils.isPrimitive(data.getDescriptor())) { Boxing.unbox(b, data.getDescriptor().charAt(0)); } else { b.addCheckcast(DescriptorUtils.getTypeStringFromDescriptorFormat(data.getDescriptor())); } it.insertEx(b.get()); } else if (op == Opcode.PUTSTATIC) { Bytecode b = new Bytecode(file.getConstPool()); if (data.getDescriptor().charAt(0) != 'L' && data.getDescriptor().charAt(0) != '[') { Boxing.box(b, data.getDescriptor().charAt(0)); } b.addLdc(file.getConstPool().addClassInfo(data.getClassName())); b.add(Opcode.SWAP); b.addLdc(arrayPos); b.addInvokestatic(FIELD_DATA_STORE_CLASS, "setValue", "(Ljava/lang/Object;Ljava/lang/Object;I)V"); it.insertEx(b.get()); } else if (op == Opcode.GETSTATIC) { Bytecode b = new Bytecode(file.getConstPool()); b.addLdc(file.getConstPool().addClassInfo(data.getClassName())); b.addLdc(arrayPos); b.addInvokestatic(FIELD_DATA_STORE_CLASS, "getValue", "(Ljava/lang/Object;I)Ljava/lang/Object;"); if (DescriptorUtils.isPrimitive(data.getDescriptor())) { Boxing.unbox(b, data.getDescriptor().charAt(0)); } else { b.addCheckcast(DescriptorUtils.getTypeStringFromDescriptorFormat(data.getDescriptor())); } it.insertEx(b.get()); } modifiedMethods.add(m); } } } } catch (Exception e) { log.error("Bad byte code transforming " + file.getName(), e); e.printStackTrace(); } } return true; } else { return false; } } public void clearRewrites(String className, ClassLoader loader) { data.remove(className, loader); } }