/*
* Copyright 2014 NAVER Corp.
*
* 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 com.navercorp.pinpoint.profiler.instrument;
import java.lang.reflect.Method;
import com.navercorp.pinpoint.bootstrap.instrument.*;
import com.navercorp.pinpoint.bootstrap.interceptor.annotation.Scope;
import com.navercorp.pinpoint.bootstrap.interceptor.scope.InterceptorScope;
import com.navercorp.pinpoint.profiler.instrument.interceptor.*;
import com.navercorp.pinpoint.profiler.metadata.ApiMetaDataService;
import com.navercorp.pinpoint.profiler.objectfactory.ObjectBinderFactory;
import javassist.CannotCompileException;
import javassist.CtBehavior;
import javassist.CtClass;
import javassist.CtConstructor;
import javassist.CtMethod;
import javassist.Modifier;
import javassist.NotFoundException;
import javassist.bytecode.BadBytecode;
import javassist.bytecode.Bytecode;
import javassist.bytecode.CodeAttribute;
import javassist.bytecode.CodeIterator;
import javassist.bytecode.ConstPool;
import javassist.bytecode.Descriptor;
import javassist.compiler.CompileError;
import javassist.compiler.Javac;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.navercorp.pinpoint.bootstrap.context.MethodDescriptor;
import com.navercorp.pinpoint.bootstrap.interceptor.Interceptor;
import com.navercorp.pinpoint.bootstrap.interceptor.scope.ExecutionPolicy;
import com.navercorp.pinpoint.bootstrap.interceptor.registry.InterceptorRegistry;
import com.navercorp.pinpoint.common.util.Asserts;
import com.navercorp.pinpoint.profiler.context.DefaultMethodDescriptor;
import com.navercorp.pinpoint.profiler.interceptor.factory.AnnotatedInterceptorFactory;
import com.navercorp.pinpoint.profiler.interceptor.registry.InterceptorRegistryBinder;
import com.navercorp.pinpoint.profiler.util.JavaAssistUtils;
public class JavassistMethod implements InstrumentMethod {
private final Logger logger = LoggerFactory.getLogger(this.getClass());
private final boolean isDebug = logger.isDebugEnabled();
private final ObjectBinderFactory objectBinderFactory;
private final InstrumentContext pluginContext;
private final InterceptorRegistryBinder interceptorRegistryBinder;
private final ApiMetaDataService apiMetaDataService;
private final CtBehavior behavior;
private final InstrumentClass declaringClass;
private final MethodDescriptor descriptor;
// TODO fix inject InterceptorDefinitionFactory
private static final InterceptorDefinitionFactory interceptorDefinitionFactory = new InterceptorDefinitionFactory();
public JavassistMethod(ObjectBinderFactory objectBinderFactory, InstrumentContext pluginContext, InterceptorRegistryBinder interceptorRegistryBinder, ApiMetaDataService apiMetaDataService, InstrumentClass declaringClass, CtBehavior behavior) {
if (objectBinderFactory == null) {
throw new NullPointerException("objectBinderFactory must not be null");
}
if (pluginContext == null) {
throw new NullPointerException("pluginContext must not be null");
}
this.objectBinderFactory = objectBinderFactory;
this.pluginContext = pluginContext;
this.interceptorRegistryBinder = interceptorRegistryBinder;
this.apiMetaDataService = apiMetaDataService;
this.behavior = behavior;
this.declaringClass = declaringClass;
String[] parameterVariableNames = JavaAssistUtils.getParameterVariableName(behavior);
int lineNumber = JavaAssistUtils.getLineNumber(behavior);
DefaultMethodDescriptor descriptor = new DefaultMethodDescriptor(behavior.getDeclaringClass().getName(), behavior.getName(), getParameterTypes(), parameterVariableNames);
descriptor.setLineNumber(lineNumber);
this.descriptor = descriptor;
}
@Override
public String getName() {
return behavior.getName();
}
@Override
public String[] getParameterTypes() {
return JavaAssistUtils.parseParameterSignature(behavior.getSignature());
}
@Override
public String getReturnType() {
if (behavior instanceof CtMethod) {
try {
return ((CtMethod) behavior).getReturnType().getName();
} catch (NotFoundException e) {
return null;
}
}
return null;
}
@Override
public int getModifiers() {
return behavior.getModifiers();
}
@Override
public boolean isConstructor() {
return behavior instanceof CtConstructor;
}
@Override
public MethodDescriptor getDescriptor() {
return descriptor;
}
@Override
public int addInterceptor(String interceptorClassName) throws InstrumentException {
Asserts.notNull(interceptorClassName, "interceptorClassName");
return addInterceptor0(interceptorClassName, null, null, null);
}
@Override
public int addInterceptor(String interceptorClassName, Object[] constructorArgs) throws InstrumentException {
Asserts.notNull(interceptorClassName, "interceptorClassName");
Asserts.notNull(constructorArgs, "constructorArgs");
return addInterceptor0(interceptorClassName, constructorArgs, null, null);
}
@Override
public int addScopedInterceptor(String interceptorClassName, String scopeName) throws InstrumentException {
Asserts.notNull(interceptorClassName, "interceptorClassName");
Asserts.notNull(scopeName, "scopeName");
final InterceptorScope interceptorScope = this.pluginContext.getInterceptorScope(scopeName);
return addInterceptor0(interceptorClassName, null, interceptorScope, null);
}
@Override
public int addScopedInterceptor(String interceptorClassName, InterceptorScope scope) throws InstrumentException {
Asserts.notNull(interceptorClassName, "interceptorClassName");
Asserts.notNull(scope, "scope");
return addInterceptor0(interceptorClassName, null, scope, null);
}
@Override
public int addScopedInterceptor(String interceptorClassName, String scopeName, ExecutionPolicy executionPolicy) throws InstrumentException {
Asserts.notNull(interceptorClassName, "interceptorClassName");
Asserts.notNull(scopeName, "scopeName");
Asserts.notNull(executionPolicy, "executionPolicy");
final InterceptorScope interceptorScope = this.pluginContext.getInterceptorScope(scopeName);
return addInterceptor0(interceptorClassName, null, interceptorScope, executionPolicy);
}
@Override
public int addScopedInterceptor(String interceptorClassName, InterceptorScope scope, ExecutionPolicy executionPolicy) throws InstrumentException {
Asserts.notNull(interceptorClassName, "interceptorClassName");
Asserts.notNull(scope, "scope");
Asserts.notNull(executionPolicy, "executionPolicy");
return addInterceptor0(interceptorClassName, null, scope, executionPolicy);
}
@Override
public int addScopedInterceptor(String interceptorClassName, Object[] constructorArgs, String scopeName) throws InstrumentException {
Asserts.notNull(interceptorClassName, "interceptorClassName");
Asserts.notNull(constructorArgs, "constructorArgs");
Asserts.notNull(scopeName, "scopeName");
final InterceptorScope interceptorScope = this.pluginContext.getInterceptorScope(scopeName);
return addInterceptor0(interceptorClassName, constructorArgs, interceptorScope, null);
}
@Override
public int addScopedInterceptor(String interceptorClassName, Object[] constructorArgs, InterceptorScope scope) throws InstrumentException {
Asserts.notNull(interceptorClassName, "interceptorClassName");
Asserts.notNull(constructorArgs, "constructorArgs");
Asserts.notNull(scope, "scope");
return addInterceptor0(interceptorClassName, constructorArgs, scope, null);
}
@Override
public int addScopedInterceptor(String interceptorClassName, Object[] constructorArgs, String scopeName, ExecutionPolicy executionPolicy) throws InstrumentException {
Asserts.notNull(interceptorClassName, "interceptorClassName");
Asserts.notNull(constructorArgs, "constructorArgs");
Asserts.notNull(scopeName, "scopeName");
Asserts.notNull(executionPolicy, "executionPolicy");
final InterceptorScope interceptorScope = this.pluginContext.getInterceptorScope(scopeName);
return addInterceptor0(interceptorClassName, constructorArgs, interceptorScope, executionPolicy);
}
@Override
public int addScopedInterceptor(String interceptorClassName, Object[] constructorArgs, InterceptorScope scope, ExecutionPolicy executionPolicy) throws InstrumentException {
Asserts.notNull(interceptorClassName, "interceptorClassName");
Asserts.notNull(constructorArgs, "constructorArgs");
Asserts.notNull(scope, "scope");
Asserts.notNull(executionPolicy, "executionPolicy");
return addInterceptor0(interceptorClassName, constructorArgs, scope, executionPolicy);
}
@Override
public void addInterceptor(int interceptorId) throws InstrumentException {
Interceptor interceptor = InterceptorRegistry.getInterceptor(interceptorId);
try {
addInterceptor0(interceptor, interceptorId);
} catch (Exception e) {
throw new InstrumentException("Failed to add interceptor " + interceptor.getClass().getName() + " to " + behavior.getLongName(), e);
}
}
private ScopeInfo resolveScopeInfo(String interceptorClassName, InterceptorScope scope, ExecutionPolicy policy) {
final Class<? extends Interceptor> interceptorType = pluginContext.injectClass(declaringClass.getClassLoader(), interceptorClassName);
if (scope == null) {
Scope interceptorScope = interceptorType.getAnnotation(Scope.class);
if (interceptorScope != null) {
String scopeName = interceptorScope.value();
scope = pluginContext.getInterceptorScope(scopeName);
policy = interceptorScope.executionPolicy();
}
}
if (scope == null) {
policy = null;
} else if (policy == null) {
policy = ExecutionPolicy.BOUNDARY;
}
return new ScopeInfo(scope, policy);
}
private static class ScopeInfo {
private final InterceptorScope scope;
private final ExecutionPolicy policy;
public ScopeInfo(InterceptorScope scope, ExecutionPolicy policy) {
this.scope = scope;
this.policy = policy;
}
public InterceptorScope getScope() {
return scope;
}
public ExecutionPolicy getPolicy() {
return policy;
}
}
// for internal api
int addInterceptorInternal(String interceptorClassName, Object[] constructorArgs, InterceptorScope scope, ExecutionPolicy executionPolicy) throws InstrumentException {
if (interceptorClassName == null) {
throw new NullPointerException("interceptorClassName must not be null");
}
return addInterceptor0(interceptorClassName, constructorArgs, scope, executionPolicy);
}
private int addInterceptor0(String interceptorClassName, Object[] constructorArgs, InterceptorScope scope, ExecutionPolicy executionPolicy) throws InstrumentException {
try {
ScopeInfo scopeInfo = resolveScopeInfo(interceptorClassName, scope, executionPolicy);
Interceptor interceptor = createInterceptor(interceptorClassName, scopeInfo, constructorArgs);
int interceptorId = interceptorRegistryBinder.getInterceptorRegistryAdaptor().addInterceptor(interceptor);
addInterceptor0(interceptor, interceptorId);
return interceptorId;
} catch (CannotCompileException ccex) {
throw new InstrumentException("Failed to add interceptor " + interceptorClassName + " to " + behavior.getLongName(), ccex);
} catch (NotFoundException nex) {
throw new InstrumentException("Failed to add interceptor " + interceptorClassName + " to " + behavior.getLongName(), nex);
}
}
private Interceptor createInterceptor(String interceptorClassName, ScopeInfo scopeInfo, Object[] constructorArgs) {
ClassLoader classLoader = declaringClass.getClassLoader();
AnnotatedInterceptorFactory factory = objectBinderFactory.newAnnotatedInterceptorFactory(pluginContext, false);
Interceptor interceptor = factory.getInterceptor(classLoader, interceptorClassName, constructorArgs, scopeInfo.getScope(), scopeInfo.getPolicy(), declaringClass, this);
return interceptor;
}
private void addInterceptor0(Interceptor interceptor, int interceptorId) throws CannotCompileException, NotFoundException {
if (interceptor == null) {
throw new NullPointerException("interceptor must not be null");
}
final InterceptorDefinition interceptorDefinition = interceptorDefinitionFactory.createInterceptorDefinition(interceptor.getClass());
final String localVariableName = initializeLocalVariable(interceptorId);
int originalCodeOffset = insertBefore(-1, localVariableName);
boolean localVarsInitialized = false;
final int offset = addBeforeInterceptor(interceptorDefinition, interceptorId, originalCodeOffset);
if (offset != -1) {
localVarsInitialized = true;
originalCodeOffset = offset;
}
addAfterInterceptor(interceptorDefinition, interceptorId, localVarsInitialized, originalCodeOffset);
}
private String initializeLocalVariable(int interceptorId) throws CannotCompileException, NotFoundException {
final String interceptorInstanceVar = InvokeCodeGenerator.getInterceptorVar(interceptorId);
addLocalVariable(interceptorInstanceVar, Interceptor.class);
final StringBuilder initVars = new StringBuilder();
initVars.append(interceptorInstanceVar);
initVars.append(" = null;");
return initVars.toString();
}
private void addAfterInterceptor(InterceptorDefinition interceptorDefinition, int interceptorId, boolean localVarsInitialized, int originalCodeOffset) throws NotFoundException, CannotCompileException {
final Class<?> interceptorClass = interceptorDefinition.getInterceptorClass();
final CaptureType captureType = interceptorDefinition.getCaptureType();
if (!isAfterInterceptor(captureType)) {
return;
}
final Method interceptorMethod = interceptorDefinition.getAfterMethod();
if (interceptorMethod == null) {
if (isDebug) {
logger.debug("Skip adding after interceptor because the interceptor doesn't have after method: {}", interceptorClass.getName());
}
return;
}
InvokeAfterCodeGenerator catchGenerator = new InvokeAfterCodeGenerator(interceptorId, interceptorDefinition, declaringClass, this, apiMetaDataService, localVarsInitialized, true);
String catchCode = catchGenerator.generate();
if (isDebug) {
logger.debug("addAfterInterceptor catch behavior:{} code:{}", behavior.getLongName(), catchCode);
}
CtClass throwable = behavior.getDeclaringClass().getClassPool().get("java.lang.Throwable");
insertCatch(originalCodeOffset, catchCode, throwable, "$e");
InvokeAfterCodeGenerator afterGenerator = new InvokeAfterCodeGenerator(interceptorId, interceptorDefinition, declaringClass, this, apiMetaDataService, localVarsInitialized, false);
final String afterCode = afterGenerator.generate();
if (isDebug) {
logger.debug("addAfterInterceptor after behavior:{} code:{}", behavior.getLongName(), afterCode);
}
behavior.insertAfter(afterCode);
}
private boolean isAfterInterceptor(CaptureType captureType) {
return CaptureType.AFTER == captureType || CaptureType.AROUND == captureType;
}
private int addBeforeInterceptor(InterceptorDefinition interceptorDefinition, int interceptorId, int pos) throws CannotCompileException, NotFoundException {
final Class<?> interceptorClass = interceptorDefinition.getInterceptorClass();
final CaptureType captureType = interceptorDefinition.getCaptureType();
if (!isBeforeInterceptor(captureType)) {
return -1;
}
final Method interceptorMethod = interceptorDefinition.getBeforeMethod();
if (interceptorMethod == null) {
if (isDebug) {
logger.debug("Skip adding before interceptorDefinition because the interceptorDefinition doesn't have before method: {}", interceptorClass.getName());
}
return -1;
}
final InvokeBeforeCodeGenerator generator = new InvokeBeforeCodeGenerator(interceptorId, interceptorDefinition, declaringClass, this, apiMetaDataService);
final String beforeCode = generator.generate();
if (isDebug) {
logger.debug("addBeforeInterceptor before behavior:{} code:{}", behavior.getLongName(), beforeCode);
}
return insertBefore(pos, beforeCode);
}
private boolean isBeforeInterceptor(CaptureType captureType) {
return CaptureType.BEFORE == captureType || CaptureType.AROUND == captureType;
}
private void addLocalVariable(String name, Class<?> type) throws CannotCompileException, NotFoundException {
final String interceptorClassName = type.getName();
final CtClass interceptorCtClass = behavior.getDeclaringClass().getClassPool().get(interceptorClassName);
behavior.addLocalVariable(name, interceptorCtClass);
}
private int insertBefore(int pos, String src) throws CannotCompileException {
if (isConstructor()) {
return insertBeforeConstructor(pos, src);
} else {
return insertBeforeMethod(pos, src);
}
}
private int insertBeforeMethod(int pos, String src) throws CannotCompileException {
CtClass cc = behavior.getDeclaringClass();
CodeAttribute ca = behavior.getMethodInfo().getCodeAttribute();
if (ca == null)
throw new CannotCompileException("no method body");
CodeIterator iterator = ca.iterator();
Javac jv = new Javac(cc);
try {
int nvars = jv.recordParams(behavior.getParameterTypes(), Modifier.isStatic(getModifiers()));
jv.recordParamNames(ca, nvars);
jv.recordLocalVariables(ca, 0);
jv.recordType(getReturnType0());
jv.compileStmnt(src);
Bytecode b = jv.getBytecode();
int stack = b.getMaxStack();
int locals = b.getMaxLocals();
if (stack > ca.getMaxStack())
ca.setMaxStack(stack);
if (locals > ca.getMaxLocals())
ca.setMaxLocals(locals);
if (pos != -1) {
iterator.insertEx(pos, b.get());
} else {
pos = iterator.insertEx(b.get());
}
iterator.insert(b.getExceptionTable(), pos);
behavior.getMethodInfo().rebuildStackMapIf6(cc.getClassPool(), cc.getClassFile2());
return pos + b.length();
} catch (NotFoundException e) {
throw new CannotCompileException(e);
} catch (CompileError e) {
throw new CannotCompileException(e);
} catch (BadBytecode e) {
throw new CannotCompileException(e);
}
}
private int insertBeforeConstructor(int pos, String src) throws CannotCompileException {
CtClass cc = behavior.getDeclaringClass();
CodeAttribute ca = behavior.getMethodInfo().getCodeAttribute();
CodeIterator iterator = ca.iterator();
Bytecode b = new Bytecode(behavior.getMethodInfo().getConstPool(),
ca.getMaxStack(), ca.getMaxLocals());
b.setStackDepth(ca.getMaxStack());
Javac jv = new Javac(b, cc);
try {
jv.recordParams(behavior.getParameterTypes(), false);
jv.recordLocalVariables(ca, 0);
jv.compileStmnt(src);
ca.setMaxStack(b.getMaxStack());
ca.setMaxLocals(b.getMaxLocals());
iterator.skipConstructor();
if (pos != -1) {
iterator.insertEx(pos, b.get());
} else {
pos = iterator.insertEx(b.get());
}
iterator.insert(b.getExceptionTable(), pos);
behavior.getMethodInfo().rebuildStackMapIf6(cc.getClassPool(), cc.getClassFile2());
return pos + b.length();
}
catch (NotFoundException e) {
throw new CannotCompileException(e);
}
catch (CompileError e) {
throw new CannotCompileException(e);
}
catch (BadBytecode e) {
throw new CannotCompileException(e);
}
}
private void insertCatch(int from, String src, CtClass exceptionType, String exceptionName) throws CannotCompileException {
CtClass cc = behavior.getDeclaringClass();
ConstPool cp = behavior.getMethodInfo().getConstPool();
CodeAttribute ca = behavior.getMethodInfo().getCodeAttribute();
CodeIterator iterator = ca.iterator();
Bytecode b = new Bytecode(cp, ca.getMaxStack(), ca.getMaxLocals());
b.setStackDepth(1);
Javac jv = new Javac(b, cc);
try {
jv.recordParams(behavior.getParameterTypes(), Modifier.isStatic(getModifiers()));
jv.recordLocalVariables(ca, from);
int var = jv.recordVariable(exceptionType, exceptionName);
b.addAstore(var);
jv.compileStmnt(src);
int stack = b.getMaxStack();
int locals = b.getMaxLocals();
if (stack > ca.getMaxStack())
ca.setMaxStack(stack);
if (locals > ca.getMaxLocals())
ca.setMaxLocals(locals);
int len = iterator.getCodeLength();
int pos = iterator.append(b.get());
ca.getExceptionTable().add(from, len, len, cp.addClassInfo(exceptionType));
iterator.append(b.getExceptionTable(), pos);
behavior.getMethodInfo().rebuildStackMapIf6(cc.getClassPool(), cc.getClassFile2());
} catch (NotFoundException e) {
throw new CannotCompileException(e);
} catch (CompileError e) {
throw new CannotCompileException(e);
} catch (BadBytecode e) {
throw new CannotCompileException(e);
}
}
private CtClass getReturnType0() throws NotFoundException {
return Descriptor.getReturnType(behavior.getMethodInfo().getDescriptor(),
behavior.getDeclaringClass().getClassPool());
}
}