/** * Copyright 2014 SAP AG * * 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.aim.mainagent.instrumentor; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import javassist.CannotCompileException; import javassist.CtBehavior; import javassist.CtClass; import org.aim.api.exceptions.InstrumentationException; import org.aim.api.instrumentation.AbstractEnclosingProbe; import org.aim.api.instrumentation.description.internal.InstrumentationSet; import org.aim.description.restrictions.Restriction; import org.aim.logging.AIMLogger; import org.aim.logging.AIMLoggerFactory; import org.aim.mainagent.builder.ProbeBuilder; import org.aim.mainagent.builder.Snippet; import org.aim.mainagent.probes.IncrementalInstrumentationProbe; import org.aim.mainagent.utils.JavassistWrapper; import org.aim.mainagent.utils.Utils; /** * Conducts instrumentation of methods and constructors. * * @author Alexander Wert * */ public final class BCInjector { private static final AIMLogger LOGGER = AIMLoggerFactory.getLogger(BCInjector.class); private static BCInjector instance; /** * Returns singleton instance. * * @return singleton */ public static synchronized BCInjector getInstance() { if (instance == null) { instance = new BCInjector(); } return instance; } private final Map<Class<?>, byte[]> originalByteCodes; private BCInjector() { originalByteCodes = new HashMap<>(); } /** * Injects bytecode according to the passed instrumentation description. * * @param instrumentationSet * aggregated instrumentation description * @return mapping from classes to new instrumented bytecodes * @param instrumentationRestriction * restriction for the instrumentation */ public synchronized Map<Class<?>, byte[]> injectInstrumentationProbes(InstrumentationSet instrumentationSet, Restriction instrumentationRestriction) { Map<Class<?>, byte[]> classesToRedefine = new HashMap<Class<?>, byte[]>(); for (Class<?> clazz : instrumentationSet.classesToInstrument()) { try { CtClass ctClass = JavassistWrapper.getInstance().getCtClass(clazz); if (ctClass == null) { LOGGER.warn("No CtClass found for class {}. Skipping this class for instrumentation.", clazz.getCanonicalName()); continue; } byte[] originalByteCode = ctClass.toBytecode(); if (ctClass.isFrozen()) { ctClass.defrost(); } for (Entry<String, Set<Long>> methodEntry : instrumentationSet.methodsToInstrument(clazz).entrySet()) { instrumentBehaviour(instrumentationSet.probesToInject(methodEntry.getKey()), ctClass, methodEntry.getKey(), methodEntry.getValue(), instrumentationRestriction); } ctClass.freeze(); if (!originalByteCodes.containsKey(clazz)) { originalByteCodes.put(clazz, originalByteCode); } classesToRedefine.put(clazz, ctClass.toBytecode()); } catch (Throwable e) { LOGGER.warn("Error ocured during instrumentation. Ignoring this error... {}", e); // throw new RuntimeException(e); // TODO: continue; ???? } } return classesToRedefine; } /** * Reverts instrumentation on the given classes. * * @param classes * classes to revert * @return mapping from classes to original bytecodes */ public synchronized Map<Class<?>, byte[]> partlyRevertInstrumentation(Collection<Class<?>> classes) { Map<Class<?>, byte[]> classesToRedefine = new HashMap<Class<?>, byte[]>(); for (Class<?> clazz : classes) { try { if (originalByteCodes.containsKey(clazz)) { CtClass ctClass = JavassistWrapper.getInstance().getCtClass(clazz); if (ctClass != null) { ctClass.detach(); } classesToRedefine.put(clazz, originalByteCodes.get(clazz)); originalByteCodes.remove(clazz); } } catch (Exception e) { LOGGER.error("Error: {}", e); } } return classesToRedefine; } /** * Completely reverts instrumentation. * * @return mapping from classes to original bytecodes */ public synchronized Map<Class<?>, byte[]> revertInstrumentation() { List<Class<?>> classes = new ArrayList<>(); classes.addAll(originalByteCodes.keySet()); return partlyRevertInstrumentation(classes); } /** * Instruments the behaviour. * * @param probeTypes * probe types to inject * @param ctClass * class of the behaviour * @param behaviourSignature * signature of the behaviour * @param scopeIds * ids of the scopes (required for incremental instrumentation) * @param instrumentationRestriction * restriction * @throws InstrumentationException * thrown if instrumentation fails */ protected void instrumentBehaviour(Set<Class<? extends AbstractEnclosingProbe>> probeTypes, CtClass ctClass, String behaviourSignature, Set<Long> scopeIds, Restriction instrumentationRestriction) throws InstrumentationException { try { ProbeBuilder pBuilder = new ProbeBuilder(behaviourSignature, instrumentationRestriction.getGranularity()); for (Class<? extends AbstractEnclosingProbe> probeType : probeTypes) { pBuilder.inject(probeType); } CtBehavior ctBehaviour = Utils.getCtBehaviour(ctClass, behaviourSignature); if (ctBehaviour != null) { Snippet snippet = pBuilder.build(); if (!snippet.getIncrementalPart().isEmpty() && !scopeIds.isEmpty()) { for (Long scopeId : scopeIds) { String tempSnippet = snippet.getIncrementalPart().replace( IncrementalInstrumentationProbe.CLAZZ, "$0"); tempSnippet = tempSnippet.replace(IncrementalInstrumentationProbe.INST_DESCRIPTION, String.valueOf(scopeId) + "L"); FullTraceMethodEditor ftmEditor = new FullTraceMethodEditor(ctBehaviour.getLongName(), tempSnippet, instrumentationRestriction); ctBehaviour.instrument(ftmEditor); } } Utils.insertMethodLocalVariables(ctBehaviour, snippet.getVariables()); ctBehaviour.insertBefore(snippet.getBeforePart()); ctBehaviour.insertAfter(snippet.getAfterPart()); } } catch (CannotCompileException e) { throw new InstrumentationException("Failed instrumenting behaviour: " + behaviourSignature, e); } } }