/* This file is part of the db4o object database http://www.db4o.com Copyright (C) 2004 - 2011 Versant Corporation http://www.versant.com db4o is free software; you can redistribute it and/or modify it under the terms of version 3 of the GNU General Public License as published by the Free Software Foundation. db4o is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see http://www.gnu.org/licenses/. */ package com.db4o.ta.instrumentation; import java.util.Comparator; import java.util.Iterator; import java.util.TreeMap; import EDU.purdue.cs.bloat.editor.ClassEditor; import EDU.purdue.cs.bloat.editor.EditorVisitor; import EDU.purdue.cs.bloat.editor.FieldEditor; import EDU.purdue.cs.bloat.editor.Instruction; import EDU.purdue.cs.bloat.editor.LocalVariable; import EDU.purdue.cs.bloat.editor.MemberRef; import EDU.purdue.cs.bloat.editor.MethodEditor; import EDU.purdue.cs.bloat.editor.NameAndType; import EDU.purdue.cs.bloat.editor.Opcode; import EDU.purdue.cs.bloat.editor.Type; import com.db4o.activation.*; import com.db4o.foundation.*; import com.db4o.instrumentation.core.*; import com.db4o.instrumentation.util.*; import com.db4o.ta.*; /** * @exclude */ class InstrumentFieldAccessEdit implements BloatClassEdit { private ClassFilter _filter; public InstrumentFieldAccessEdit(ClassFilter filter) { _filter = filter; } public InstrumentationStatus enhance(ClassEditor ce, ClassLoader origLoader, BloatLoaderContext loaderContext) { if(isAlreadyInstrumented(ce)) { return InstrumentationStatus.FAILED; } return instrumentAllMethods(ce, origLoader, loaderContext); } private boolean isAlreadyInstrumented(ClassEditor ce) { return BloatUtil.implementsDirectly(ce, ActivatableInstrumented.class); } private InstrumentationStatus instrumentAllMethods(final ClassEditor ce, final ClassLoader origLoader, final BloatLoaderContext loaderContext) { final MemberRef activateMethod = createMethodReference(ce.type(), TransparentActivationInstrumentationConstants.ACTIVATE_METHOD_NAME, new Type[]{}, Type.VOID); final MemberRef bindMethod = createMethodReference(ce.type(), TransparentActivationInstrumentationConstants.BIND_METHOD_NAME, new Type[]{ Type.getType(Activator.class) }, Type.VOID); final ObjectByRef instrumented = new ObjectByRef(InstrumentationStatus.NOT_INSTRUMENTED); ce.visit(new EditorVisitor() { public void visitClassEditor(ClassEditor editor) { editor.addInterface(ActivatableInstrumented.class); } public void visitFieldEditor(FieldEditor editor) { } public void visitMethodEditor(MethodEditor editor) { if(editor.isAbstract()) { return; } MemberRef methodRef = editor.memberRef(); if(methodRef.equals(activateMethod) || methodRef.equals(bindMethod)) { return; } TreeMap fieldAccessIndexes = new TreeMap(new Comparator() { public int compare(Object o1, Object o2) { return -((Comparable)o1).compareTo(o2); } }); for(int codeIdx = 0; codeIdx < editor.codeLength(); codeIdx++) { Object curCode = editor.codeElementAt(codeIdx); MemberRef fieldRef = fieldRef(curCode); if(fieldRef == null || !accept(fieldRef)) { continue; } boolean writeAccess = ((Instruction)curCode).origOpcode() == Opcode.opc_putfield; fieldAccessIndexes.put( new Integer(codeIdx), new FieldAccess( fieldRef, writeAccess ? ActivationPurpose.WRITE : ActivationPurpose.READ)); } if(fieldAccessIndexes.isEmpty()) { return; } try { int modifiedCount = 0; for (Iterator idxIter = fieldAccessIndexes.keySet().iterator(); idxIter.hasNext();) { Integer idx = ((Integer) idxIter.next()); FieldAccess fieldAccess = (FieldAccess)fieldAccessIndexes.get(idx); if (instrumentFieldAccess(loaderContext, editor, idx, fieldAccess)) { modifiedCount++; } } editor.commit(); if (modifiedCount > 0) { instrumented.value = InstrumentationStatus.INSTRUMENTED; } } catch (ClassNotFoundException e) { instrumented.value = InstrumentationStatus.FAILED; return; } } private boolean instrumentFieldAccess( final BloatLoaderContext loaderContext, MethodEditor editor, Integer idx, FieldAccess fieldAccess) throws ClassNotFoundException { MemberRef fieldRef = fieldAccess.fieldRef; ClassEditor fieldParentClassEditor = loaderContext.classEditor(fieldRef.declaringClass()); if (!isPersistentField(loaderContext, fieldParentClassEditor, fieldRef)) { return false; } if(editor.isConstructor()) { if(editor.declaringClass().name().equals(fieldParentClassEditor.name())) { return false; } } final MemberRef targetActivateMethod = createMethodReference(fieldRef.declaringClass(), TransparentActivationInstrumentationConstants.ACTIVATE_METHOD_NAME, new Type[]{ Type.getType(ActivationPurpose.class)}, Type.VOID); if(targetActivateMethod == null) { return false; } int insertionPoint = idx.intValue(); if (ActivationPurpose.WRITE == fieldAccess.purpose) { LoadStoreInstructions instructions = BloatUtil.loadStoreInstructionsFor(fieldRef.type()); LocalVariable temp = editor.newLocal(fieldRef.type()); editor.insertCodeAt(new Instruction(instructions.store, temp), insertionPoint++); insertionPoint = insertActivateCall(editor, targetActivateMethod, insertionPoint, ActivationPurpose.WRITE); editor.insertCodeAt(new Instruction(instructions.load, temp), insertionPoint); } else { insertActivateCall(editor, targetActivateMethod, insertionPoint, ActivationPurpose.READ); } return true; } private int insertActivateCall(MethodEditor editor, final MemberRef targetActivateMethod, int insertionPoint, ActivationPurpose purpose) { editor.insertCodeAt(new Instruction(Opcode.opc_dup), insertionPoint); editor.insertCodeAt(new Instruction(Opcode.opc_getstatic, purposeFieldFor(purpose)), ++insertionPoint); editor.insertCodeAt(new Instruction(Opcode.opc_invokevirtual, targetActivateMethod), ++insertionPoint); return ++insertionPoint; } private MemberRef purposeFieldFor(ActivationPurpose purpose) { String fieldName = ActivationPurpose.READ == purpose ? "READ" : "WRITE"; return createMemberRef(Type.getType(ActivationPurpose.class), fieldName, Type.getType(ActivationPurpose.class)); } private boolean isPersistentField( final BloatLoaderContext loaderContext, final ClassEditor ce, MemberRef fieldRef) throws ClassNotFoundException { FieldEditor fieldEdit = loaderContext.field(ce, fieldRef.name(), fieldRef.type()); return !fieldEdit.isTransient() && !fieldEdit.isStatic(); } private boolean accept(MemberRef fieldRef) { String className = fieldRef.declaringClass().className(); String normalizedClassName = BloatUtil.normalizeClassName(className); try { final Class<?> clazz = origLoader.loadClass(normalizedClassName); if (clazz.isEnum()) { return false; } return _filter.accept(clazz); } catch (ClassNotFoundException e) { // TODO: sensible error notification. e.printStackTrace(); return false; } } private MemberRef fieldRef(Object code) { if(!(code instanceof Instruction)) { return null; } Instruction curInstr = (Instruction)code; if(curInstr.origOpcode() == Opcode.opc_getfield || curInstr.origOpcode() == Opcode.opc_putfield) { return (MemberRef) curInstr.operand(); } return null; } }); if(((InstrumentationStatus)instrumented.value).isInstrumented()) { ce.commit(); } return (InstrumentationStatus) instrumented.value; } private MemberRef createMethodReference(Type parent, String name, Type[] args, Type ret) { return createMemberRef(parent, name, Type.getType(args, ret)); } private MemberRef createMemberRef(Type parent, String name, Type type) { NameAndType nameAndType = new NameAndType(name, type); return new MemberRef(parent, nameAndType); } }