org.glowroot.agent.weaving.WeavingMethodVisitor Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of glowroot-agent-it-harness Show documentation
Show all versions of glowroot-agent-it-harness Show documentation
Glowroot Agent Integration Test Harness
/*
* Copyright 2012-2018 the original author or authors.
*
* 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.glowroot.agent.weaving;
import java.lang.annotation.Annotation;
import java.lang.reflect.Modifier;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.atomic.AtomicInteger;
import org.glowroot.agent.shaded.org.glowroot.agent.it.harness.shaded.com.google.common.collect.ImmutableList;
import org.glowroot.agent.shaded.org.glowroot.agent.it.harness.shaded.com.google.common.collect.Lists;
import org.glowroot.agent.shaded.org.glowroot.agent.it.harness.shaded.com.google.common.collect.Maps;
import org.glowroot.agent.shaded.org.checkerframework.checker.nullness.qual.MonotonicNonNull;
import org.glowroot.agent.shaded.org.checkerframework.checker.nullness.qual.Nullable;
import org.glowroot.agent.shaded.org.checkerframework.checker.nullness.qual.RequiresNonNull;
import org.immutables.value.Value;
import org.glowroot.agent.shaded.org.objectweb.asm.AnnotationVisitor;
import org.glowroot.agent.shaded.org.objectweb.asm.Label;
import org.glowroot.agent.shaded.org.objectweb.asm.MethodVisitor;
import org.glowroot.agent.shaded.org.objectweb.asm.Type;
import org.glowroot.agent.shaded.org.objectweb.asm.commons.Method;
import org.glowroot.agent.shaded.org.glowroot.agent.shaded.org.slf4j.Logger;
import org.glowroot.agent.shaded.org.glowroot.agent.shaded.org.slf4j.LoggerFactory;
import org.glowroot.agent.bytecode.api.Bytecode;
import org.glowroot.agent.bytecode.api.ThreadContextPlus;
import org.glowroot.agent.bytecode.api.ThreadContextThreadLocal;
import org.glowroot.agent.plugin.api.weaving.BindParameter;
import org.glowroot.agent.plugin.api.weaving.BindTraveler;
import org.glowroot.agent.plugin.api.weaving.IsEnabled;
import org.glowroot.agent.plugin.api.weaving.OnAfter;
import org.glowroot.agent.plugin.api.weaving.OnBefore;
import org.glowroot.agent.plugin.api.weaving.OnReturn;
import org.glowroot.agent.plugin.api.weaving.OnThrow;
import org.glowroot.agent.weaving.Advice.AdviceParameter;
import org.glowroot.agent.weaving.Advice.ParameterKind;
import org.glowroot.agent.shaded.org.glowroot.common.util.Styles;
import static org.glowroot.agent.shaded.org.glowroot.agent.it.harness.shaded.com.google.common.base.Preconditions.checkNotNull;
import static org.glowroot.agent.shaded.org.glowroot.agent.it.harness.shaded.com.google.common.base.Preconditions.checkState;
class WeavingMethodVisitor extends AdviceAdapter {
private static final Logger logger = LoggerFactory.getLogger(WeavingMethodVisitor.class);
private static final Type objectType = Type.getType(Object.class);
private static final Type fastThreadContextThreadLocalHolderType =
Type.getType(ThreadContextThreadLocal.Holder.class);
private static final Type threadContextPlusType = Type.getType(ThreadContextPlus.class);
private static final Type bytecodeType = Type.getType(Bytecode.class);
// starts at 1 since 0 is used for "no nesting group"
private static final AtomicInteger nestingGroupIdCounter = new AtomicInteger(1);
// starts at 1 since 0 is used for "no suppression key"
private static final AtomicInteger suppressionKeyIdCounter = new AtomicInteger(1);
private static final ConcurrentMap nestingGroupIds =
new ConcurrentHashMap();
private static final ConcurrentMap suppressionKeyIds =
new ConcurrentHashMap();
private final boolean frames;
private final int access;
private final String name;
private final Type owner;
private final ImmutableList advisors;
private final Type[] argumentTypes;
private final Type returnType;
private final @Nullable String metaHolderInternalName;
private final @Nullable Integer methodMetaGroupUniqueNum;
private final boolean bootstrapClassLoader;
private final boolean needsOnReturn;
private final boolean needsOnThrow;
private final @Nullable MethodVisitor outerMethodVisitor;
private final Object[] implicitFrameLocals;
private final Map enabledLocals = Maps.newHashMap();
private final Map travelerLocals = Maps.newHashMap();
private final Map prevNestingGroupIdLocals = Maps.newHashMap();
private final Map prevSuppressionKeyIdLocals = Maps.newHashMap();
// don't need map of thread context locals since all advice can share the same
// threadContextLocal
private @MonotonicNonNull Integer threadContextLocal;
private @MonotonicNonNull Integer threadContextHolderLocal;
private final List catchHandlers = Lists.newArrayList();
private @MonotonicNonNull Integer returnOpcode;
private @MonotonicNonNull Label methodStartLabel;
private @MonotonicNonNull Label onReturnLabel;
private @MonotonicNonNull Label catchStartLabel;
private boolean visitedLocalVariableThis;
private int[] savedArgLocals = new int[0];
WeavingMethodVisitor(MethodVisitor mv, boolean frames, int access, String name, String desc,
Type owner, Iterable advisors, @Nullable String metaHolderInternalName,
@Nullable Integer methodMetaGroupUniqueNum, boolean bootstrapClassLoader,
@Nullable MethodVisitor outerMethodVisitor) {
super(ASM6, new FrameDeduppingMethodVisitor(mv), access, name, desc);
this.frames = frames;
this.access = access;
this.name = name;
this.owner = owner;
this.advisors = ImmutableList.copyOf(advisors);
argumentTypes = Type.getArgumentTypes(desc);
returnType = Type.getReturnType(desc);
this.metaHolderInternalName = metaHolderInternalName;
this.methodMetaGroupUniqueNum = methodMetaGroupUniqueNum;
this.bootstrapClassLoader = bootstrapClassLoader;
boolean needsOnReturn = false;
boolean needsOnThrow = false;
for (Advice advice : advisors) {
if (!advice.pointcut().nestingGroup().isEmpty()
|| !advice.pointcut().suppressionKey().isEmpty()
|| advice.onAfterAdvice() != null) {
needsOnReturn = true;
needsOnThrow = true;
break;
}
if (advice.onReturnAdvice() != null) {
needsOnReturn = true;
}
if (advice.onThrowAdvice() != null) {
needsOnThrow = true;
}
}
this.needsOnReturn = needsOnReturn;
this.needsOnThrow = needsOnThrow;
this.outerMethodVisitor = outerMethodVisitor;
int nImplicitFrameLocals = argumentTypes.length;
boolean needsReceiver = !Modifier.isStatic(access);
if (needsReceiver) {
nImplicitFrameLocals++;
}
Object[] implicitFrameLocals = new Object[nImplicitFrameLocals];
int i = 0;
if (needsReceiver) {
if (name.equals("")) {
implicitFrameLocals[i++] = UNINITIALIZED_THIS;
} else {
implicitFrameLocals[i++] = owner.getInternalName();
}
}
for (int j = 0; j < argumentTypes.length; j++) {
implicitFrameLocals[i++] = convert(argumentTypes[j]);
}
this.implicitFrameLocals = implicitFrameLocals;
}
@Override
public @Nullable AnnotationVisitor visitAnnotation(String desc, boolean visible) {
if (outerMethodVisitor != null) {
return outerMethodVisitor.visitAnnotation(desc, visible);
}
return super.visitAnnotation(desc, visible);
}
@Override
protected void onMethodPreEnter() {
stackFrameTracking = false;
try {
onMethodPreEnterInternal();
if (name.equals("")) {
implicitFrameLocals[0] = owner.getInternalName();
}
} finally {
stackFrameTracking = true;
}
}
@Override
protected void onMethodEnter() {
stackFrameTracking = false;
try {
onMethodEnterInternal();
} finally {
stackFrameTracking = true;
}
}
@Override
public void visitInsn(int opcode) {
if (needsOnReturn && isReturnOpcode(opcode)) {
// ATHROW not included, instructions to catch throws will be written (if necessary) in
// visitMaxs
checkNotNull(onReturnLabel, "Call to onMethodEnter() is required");
returnOpcode = opcode;
stackFrameTracking = false;
try {
cleanUpStackIfNeeded(opcode);
} finally {
stackFrameTracking = true;
}
visitJumpInsn(GOTO, onReturnLabel);
} else {
super.visitInsn(opcode);
}
}
@Override
public void visitLocalVariable(String name, String desc, @Nullable String signature,
Label start, Label end, int index) {
// the JSRInlinerAdapter writes the local variable "this" across different label ranges
// so visitedLocalVariableThis is checked and updated to ensure this block is only executed
// once per method
//
if (!name.equals("this") || visitedLocalVariableThis) {
super.visitLocalVariable(name, desc, signature, start, end, index);
return;
}
visitedLocalVariableThis = true;
checkNotNull(methodStartLabel, "Call to onMethodEnter() is required");
// this is only so that eclipse debugger will not display
// inside code when inside of code before the previous method start label
// (the debugger asks for "this", which is not otherwise available in the new code
// inserted at the beginning of the method)
//
// ClassReader always visits local variables at the very end (just prior to visitMaxs)
// so this is a good place to put the outer end label for the local variable 'this'
Label outerEndLabel = new Label();
visitLabel(outerEndLabel);
super.visitLocalVariable(name, desc, signature, methodStartLabel, outerEndLabel, index);
// at the same time, may as well define local vars for enabled and traveler as
// applicable
for (int i = 0; i < advisors.size(); i++) {
Advice advice = advisors.get(i);
Integer enabledLocalIndex = enabledLocals.get(advice);
if (enabledLocalIndex != null) {
super.visitLocalVariable("glowroot$enabled$" + i, Type.BOOLEAN_TYPE.getDescriptor(),
null, methodStartLabel, outerEndLabel, enabledLocalIndex);
}
Integer travelerLocalIndex = travelerLocals.get(advice);
if (travelerLocalIndex != null) {
Type travelerType = advice.travelerType();
if (travelerType == null) {
logger.error("visitLocalVariable(): traveler local index is not null,"
+ " but traveler type is null");
} else {
super.visitLocalVariable("glowroot$traveler$" + i, travelerType.getDescriptor(),
null, methodStartLabel, outerEndLabel, travelerLocalIndex);
}
}
}
}
@Override
public void visitMaxs(int maxStack, int maxLocals) {
stackFrameTracking = false;
// catch end should not precede @OnReturn and @OnAfter
Label catchEndLabel = new Label();
if (needsOnThrow) {
visitLabel(catchEndLabel);
}
// returnOpCode can be null if only ATHROW in method in which case method doesn't need
// onReturn advice
if (needsOnReturn && returnOpcode != null) {
checkNotNull(onReturnLabel, "Call to onMethodEnter() is required");
visitLabel(onReturnLabel);
if (returnType.getSort() == Type.VOID) {
visitImplicitFrame();
} else {
visitImplicitFrame(convert(returnType));
}
for (Advice advice : Lists.reverse(advisors)) {
visitOnReturnAdvice(advice, returnOpcode);
visitOnAfterAdvice(advice, false);
}
resetCurrentNestingGroupIfNeeded(false);
// need to call super.visitInsn() in order to avoid infinite loop
// could call mv.visitInsn(), but that would bypass special constructor handling in
// AdviceAdapter.visitInsn()
super.visitInsn(returnOpcode);
}
if (needsOnThrow) {
visitCatchHandlers(catchEndLabel);
}
super.visitMaxs(maxStack, maxLocals);
}
private void onMethodPreEnterInternal() {
methodStartLabel = new Label();
visitLabel(methodStartLabel);
// enabled and traveler locals must be defined outside of the try block so they will be
// accessible in the catch block
for (Advice advice : advisors) {
defineEnabledLocalVar(advice);
defineTravelerLocalVar(advice);
}
saveArgsForMethodExit();
}
private void onMethodEnterInternal() {
for (int i = 0; i < advisors.size(); i++) {
Advice advice = advisors.get(i);
evaluateEnabledLocalVar(advice);
invokeOnBefore(advice, travelerLocals.get(advice));
if (advice.onAfterAdvice() != null || advice.onThrowAdvice() != null) {
Label catchStartLabel = new Label();
visitLabel(catchStartLabel);
catchHandlers
.add(ImmutableCatchHandler.of(catchStartLabel, advisors.subList(0, i + 1)));
}
}
if (needsOnReturn) {
onReturnLabel = new Label();
}
if (needsOnThrow && catchHandlers.isEmpty()) {
// need catch for resetting thread locals
catchStartLabel = new Label();
visitLabel(catchStartLabel);
}
}
private void visitCatchHandlers(Label catchEndLabel) {
if (catchHandlers.isEmpty()) {
checkNotNull(catchStartLabel, "Call to onMethodEnter() is required");
Label catchHandlerLabel = new Label();
visitTryCatchBlock(catchStartLabel, catchEndLabel, catchHandlerLabel,
"java/lang/Throwable");
visitLabel(catchHandlerLabel);
visitImplicitFrame("java/lang/Throwable");
resetCurrentNestingGroupIfNeeded(true);
visitInsn(ATHROW);
} else {
for (CatchHandler catchHandler : Lists.reverse(catchHandlers)) {
Label catchHandlerLabel = new Label();
visitTryCatchBlock(catchHandler.catchStartLabel(), catchEndLabel, catchHandlerLabel,
"java/lang/Throwable");
visitLabel(catchHandlerLabel);
visitImplicitFrame("java/lang/Throwable");
for (Advice advice : Lists.reverse(catchHandler.advisors())) {
visitOnThrowAdvice(advice);
}
for (Advice advice : Lists.reverse(catchHandler.advisors())) {
visitOnAfterAdvice(advice, true);
}
resetCurrentNestingGroupIfNeeded(true);
visitInsn(ATHROW);
}
}
}
private void defineEnabledLocalVar(Advice advice) {
Integer enabledLocal = null;
Method isEnabledAdvice = advice.isEnabledAdvice();
if (isEnabledAdvice != null) {
enabledLocal = newLocal(Type.BOOLEAN_TYPE);
enabledLocals.put(advice, enabledLocal);
// temporary initial value needed for Java 7 stack map frames
visitInsn(ICONST_0);
storeLocal(enabledLocal);
}
String nestingGroup = advice.pointcut().nestingGroup();
String suppressionKey = advice.pointcut().suppressionKey();
String suppressibleUsingKey = advice.pointcut().suppressibleUsingKey();
if ((!nestingGroup.isEmpty() || !suppressionKey.isEmpty()
|| advice.hasBindThreadContext() || advice.hasBindOptionalThreadContext())
&& threadContextHolderLocal == null) {
// need to define thread context local var outside of any branches,
// but also don't want to load ThreadContext if enabledLocal exists and is false
threadContextHolderLocal = newLocal(fastThreadContextThreadLocalHolderType);
visitInsn(ACONST_NULL);
storeLocal(threadContextHolderLocal);
threadContextLocal = newLocal(threadContextPlusType);
visitInsn(ACONST_NULL);
storeLocal(threadContextLocal);
}
Integer prevNestingGroupIdLocal = null;
if (!nestingGroup.isEmpty()) {
// need to define thread context local var outside of any branches
// but also don't want to load ThreadContext if enabledLocal exists and is false
prevNestingGroupIdLocal = newLocal(Type.INT_TYPE);
prevNestingGroupIdLocals.put(advice, prevNestingGroupIdLocal);
visitIntInsn(BIPUSH, -1);
storeLocal(prevNestingGroupIdLocal);
}
Integer prevSuppressionKeyIdLocal = null;
if (!suppressionKey.isEmpty()) {
// need to define thread context local var outside of any branches
// but also don't want to load ThreadContext if enabledLocal exists and is false
prevSuppressionKeyIdLocal = newLocal(Type.INT_TYPE);
prevSuppressionKeyIdLocals.put(advice, prevSuppressionKeyIdLocal);
visitIntInsn(BIPUSH, -1);
storeLocal(prevSuppressionKeyIdLocal);
}
if (!nestingGroup.isEmpty() || !suppressibleUsingKey.isEmpty() || !suppressionKey.isEmpty()
|| (advice.hasBindThreadContext() && !advice.hasBindOptionalThreadContext())) {
if (enabledLocal == null) {
enabledLocal = newLocal(Type.BOOLEAN_TYPE);
enabledLocals.put(advice, enabledLocal);
// temporary initial value needed for Java 7 stack map frames
visitInsn(ICONST_0);
storeLocal(enabledLocal);
}
}
}
private void evaluateEnabledLocalVar(Advice advice) {
Method isEnabledAdvice = advice.isEnabledAdvice();
if (isEnabledAdvice != null) {
loadMethodParameters(advice.isEnabledParameters(), 0, null, advice.adviceType(),
IsEnabled.class, false);
visitMethodInsn(INVOKESTATIC, advice.adviceType().getInternalName(),
isEnabledAdvice.getName(), isEnabledAdvice.getDescriptor(), false);
// guaranteed to be non-null via defineEnabledLocalVar() above
int enabledLocal = checkNotNull(enabledLocals.get(advice));
storeLocal(enabledLocal);
}
String nestingGroup = advice.pointcut().nestingGroup();
String suppressionKey = advice.pointcut().suppressionKey();
String suppressibleUsingKey = advice.pointcut().suppressibleUsingKey();
Integer prevNestingGroupIdLocal = prevNestingGroupIdLocals.get(advice);
Integer prevSuppressionKeyIdLocal = prevSuppressionKeyIdLocals.get(advice);
// need to load ThreadContext
if (!nestingGroup.isEmpty() || !suppressibleUsingKey.isEmpty() || !suppressionKey.isEmpty()
|| (advice.hasBindThreadContext() && !advice.hasBindOptionalThreadContext())) {
Label disabledLabel = null;
// guaranteed to be non-null via defineEnabledLocalVar() above
int enabledLocal = checkNotNull(enabledLocals.get(advice));
if (isEnabledAdvice != null) {
loadLocal(enabledLocal);
disabledLabel = new Label();
visitJumpInsn(IFEQ, disabledLabel);
}
loadThreadContextHolder();
dup();
checkNotNull(threadContextHolderLocal);
storeLocal(threadContextHolderLocal);
visitMethodInsn(INVOKEVIRTUAL, fastThreadContextThreadLocalHolderType.getInternalName(),
"get", "()" + threadContextPlusType.getDescriptor(), false);
dup();
checkNotNull(threadContextLocal);
storeLocal(threadContextLocal);
if (advice.hasBindThreadContext() && !advice.hasBindOptionalThreadContext()) {
if (disabledLabel == null) {
disabledLabel = new Label();
}
visitJumpInsn(IFNULL, disabledLabel);
if (!suppressibleUsingKey.isEmpty()) {
checkSuppressibleUsingKey(suppressibleUsingKey, disabledLabel);
}
if (!nestingGroup.isEmpty()) {
checkNotNull(prevNestingGroupIdLocal);
checkAndUpdateNestingGroupId(prevNestingGroupIdLocal, nestingGroup,
disabledLabel);
}
if (!suppressionKey.isEmpty()) {
checkNotNull(prevSuppressionKeyIdLocal);
updateSuppressionKeyId(prevSuppressionKeyIdLocal, suppressionKey);
}
} else {
if (!suppressibleUsingKey.isEmpty()) {
if (disabledLabel == null) {
disabledLabel = new Label();
}
Label enabledLabel = new Label();
// if thread context == null, then not suppressible
visitJumpInsn(IFNULL, enabledLabel);
checkSuppressibleUsingKey(suppressibleUsingKey, disabledLabel);
visitLabel(enabledLabel);
visitImplicitFrame();
}
if (!nestingGroup.isEmpty()) {
if (disabledLabel == null) {
disabledLabel = new Label();
}
Label enabledLabel = new Label();
// if thread context == null, then not in nesting group
visitJumpInsn(IFNULL, enabledLabel);
checkNotNull(prevNestingGroupIdLocal);
checkAndUpdateNestingGroupId(prevNestingGroupIdLocal, nestingGroup,
disabledLabel);
visitLabel(enabledLabel);
visitImplicitFrame();
}
if (!suppressionKey.isEmpty()) {
Label enabledLabel = new Label();
// if thread context == null, then not in nesting group
visitJumpInsn(IFNULL, enabledLabel);
checkNotNull(prevSuppressionKeyIdLocal);
updateSuppressionKeyId(prevSuppressionKeyIdLocal, suppressionKey);
visitLabel(enabledLabel);
visitImplicitFrame();
}
}
visitInsn(ICONST_1);
if (disabledLabel != null) {
Label endLabel = new Label();
goTo(endLabel);
visitLabel(disabledLabel);
visitImplicitFrame();
visitInsn(ICONST_0);
visitLabel(endLabel);
visitImplicitFrame(INTEGER);
}
storeLocal(enabledLocal);
}
}
private void loadThreadContextHolder() {
// TODO optimize, don't need to look up ThreadContext thread local each time
visitMethodInsn(INVOKESTATIC, bytecodeType.getInternalName(),
"getCurrentThreadContextHolder",
"()" + fastThreadContextThreadLocalHolderType.getDescriptor(), false);
}
@RequiresNonNull("threadContextLocal")
private void checkAndUpdateNestingGroupId(int prevNestingGroupIdLocal, String nestingGroup,
Label disabledLabel) {
loadLocal(threadContextLocal);
visitMethodInsn(INVOKEINTERFACE, threadContextPlusType.getInternalName(),
"getCurrentNestingGroupId", "()I", true);
dup();
storeLocal(prevNestingGroupIdLocal);
int nestingGroupId = getNestingGroupId(nestingGroup);
mv.visitLdcInsn(nestingGroupId);
visitJumpInsn(IF_ICMPEQ, disabledLabel);
loadLocal(threadContextLocal);
mv.visitLdcInsn(nestingGroupId);
visitMethodInsn(INVOKEINTERFACE, threadContextPlusType.getInternalName(),
"setCurrentNestingGroupId", "(I)V", true);
}
@RequiresNonNull("threadContextLocal")
private void checkSuppressibleUsingKey(String suppressibleUsingKey, Label disabledLabel) {
loadLocal(threadContextLocal);
visitMethodInsn(INVOKEINTERFACE, threadContextPlusType.getInternalName(),
"getCurrentSuppressionKeyId", "()I", true);
int suppressionKeyId = getSuppressionKeyId(suppressibleUsingKey);
mv.visitLdcInsn(suppressionKeyId);
visitJumpInsn(IF_ICMPEQ, disabledLabel);
}
@RequiresNonNull("threadContextLocal")
private void updateSuppressionKeyId(int prevSuppressionKeyIdLocal, String suppressionKey) {
loadLocal(threadContextLocal);
visitMethodInsn(INVOKEINTERFACE, threadContextPlusType.getInternalName(),
"getCurrentSuppressionKeyId", "()I", true);
storeLocal(prevSuppressionKeyIdLocal);
int suppressionKeyId = getSuppressionKeyId(suppressionKey);
loadLocal(threadContextLocal);
mv.visitLdcInsn(suppressionKeyId);
visitMethodInsn(INVOKEINTERFACE, threadContextPlusType.getInternalName(),
"setCurrentSuppressionKeyId", "(I)V", true);
}
private void defineTravelerLocalVar(Advice advice) {
Method onBeforeAdvice = advice.onBeforeAdvice();
if (onBeforeAdvice == null) {
return;
}
Type travelerType = advice.travelerType();
if (travelerType == null) {
return;
}
// have to initialize it with a value, otherwise it won't be defined in the outer scope
int travelerLocal = newLocal(travelerType);
pushDefault(travelerType);
storeLocal(travelerLocal);
travelerLocals.put(advice, travelerLocal);
}
private void invokeOnBefore(Advice advice, @Nullable Integer travelerLocal) {
Method onBeforeAdvice = advice.onBeforeAdvice();
if (onBeforeAdvice == null) {
return;
}
Integer enabledLocal = enabledLocals.get(advice);
Label onBeforeBlockEnd = null;
if (enabledLocal != null) {
if (name.equals("")) {
loadLocal(enabledLocal);
} else {
onBeforeBlockEnd = new Label();
loadLocal(enabledLocal);
visitJumpInsn(IFEQ, onBeforeBlockEnd);
}
}
loadMethodParameters(advice.onBeforeParameters(), 0, null, advice.adviceType(),
OnBefore.class, false);
if (enabledLocal != null && name.equals("")) {
String desc = "(Z" + onBeforeAdvice.getDescriptor().substring(1);
visitMethodInsn(INVOKESTATIC, advice.adviceType().getInternalName(),
onBeforeAdvice.getName(), desc, false);
} else {
visitMethodInsn(INVOKESTATIC, advice.adviceType().getInternalName(),
onBeforeAdvice.getName(), onBeforeAdvice.getDescriptor(), false);
}
if (travelerLocal != null) {
storeLocal(travelerLocal);
}
String nestingGroup = advice.pointcut().nestingGroup();
String suppressionKey = advice.pointcut().suppressionKey();
boolean firstBlock = advice.hasBindOptionalThreadContext() && !nestingGroup.isEmpty();
boolean secondBlock = advice.hasBindOptionalThreadContext() && !suppressionKey.isEmpty();
if (firstBlock) {
// need to check if transaction was just started in @OnBefore and update its
// currentNestingGroupId
Integer prevNestingGroupIdLocal = prevNestingGroupIdLocals.get(advice);
checkNotNull(prevNestingGroupIdLocal);
loadLocal(prevNestingGroupIdLocal);
visitIntInsn(BIPUSH, -1);
Label label = null;
if (onBeforeBlockEnd == null || secondBlock) {
label = new Label();
visitJumpInsn(IF_ICMPNE, label);
} else {
// reuse onBeforeBlockEnd label
visitJumpInsn(IF_ICMPNE, onBeforeBlockEnd);
}
// the only reason prevNestingGroupId is -1 here is because no thread context at the
// start of the method
checkNotNull(threadContextLocal);
loadLocal(threadContextLocal);
visitMethodInsn(INVOKEINTERFACE, threadContextPlusType.getInternalName(),
"getCurrentNestingGroupId", "()I", true);
storeLocal(prevNestingGroupIdLocal);
loadLocal(threadContextLocal);
int nestingGroupId = getNestingGroupId(nestingGroup);
mv.visitLdcInsn(nestingGroupId);
visitMethodInsn(INVOKEINTERFACE, threadContextPlusType.getInternalName(),
"setCurrentNestingGroupId", "(I)V", true);
if (label != null) {
visitLabel(label);
visitImplicitFrame();
}
}
if (secondBlock) {
// need to check if transaction was just started in @OnBefore and update its
// currentSuppressionKeyId
Integer prevSuppressionKeyIdLocal = prevSuppressionKeyIdLocals.get(advice);
checkNotNull(prevSuppressionKeyIdLocal);
loadLocal(prevSuppressionKeyIdLocal);
visitIntInsn(BIPUSH, -1);
Label label = null;
if (onBeforeBlockEnd == null) {
label = new Label();
visitJumpInsn(IF_ICMPNE, label);
} else {
// reuse onBeforeBlockEnd label
visitJumpInsn(IF_ICMPNE, onBeforeBlockEnd);
}
// the only reason prevSuppressionKeyId is -1 here is because no thread context at the
// start of the method
checkNotNull(threadContextLocal);
loadLocal(threadContextLocal);
visitMethodInsn(INVOKEINTERFACE, threadContextPlusType.getInternalName(),
"getCurrentSuppressionKeyId", "()I", true);
storeLocal(prevSuppressionKeyIdLocal);
loadLocal(threadContextLocal);
int suppressionKeyId = getSuppressionKeyId(suppressionKey);
mv.visitLdcInsn(suppressionKeyId);
visitMethodInsn(INVOKEINTERFACE, threadContextPlusType.getInternalName(),
"setCurrentSuppressionKeyId", "(I)V", true);
if (label != null) {
visitLabel(label);
visitImplicitFrame();
}
}
if (onBeforeBlockEnd != null) {
visitLabel(onBeforeBlockEnd);
visitImplicitFrame();
}
}
private void saveArgsForMethodExit() {
int numSavedArgs = getNumSavedArgsNeeded();
if (numSavedArgs == 0) {
return;
}
savedArgLocals = new int[numSavedArgs];
for (int i = 0; i < numSavedArgs; i++) {
savedArgLocals[i] = newLocal(argumentTypes[i]);
loadArg(i);
storeLocal(savedArgLocals[i]);
}
}
private int getNumSavedArgsNeeded() {
int numSaveArgsNeeded = 0;
for (Advice advice : advisors) {
numSaveArgsNeeded = Math.max(numSaveArgsNeeded, getNum(advice.onReturnParameters()));
numSaveArgsNeeded = Math.max(numSaveArgsNeeded, getNum(advice.onAfterParameters()));
numSaveArgsNeeded = Math.max(numSaveArgsNeeded, getNum(advice.onThrowParameters()));
}
return numSaveArgsNeeded;
}
private int getNum(List adviceParameters) {
int numSaveArgsNeeded = 0;
for (AdviceParameter parameter : adviceParameters) {
if (parameter.kind() == ParameterKind.METHOD_ARG_ARRAY) {
return argumentTypes.length;
} else if (parameter.kind() == ParameterKind.METHOD_ARG) {
numSaveArgsNeeded++;
}
}
return numSaveArgsNeeded;
}
private void visitOnReturnAdvice(Advice advice, int opcode) {
Method onReturnAdvice = advice.onReturnAdvice();
if (onReturnAdvice == null) {
return;
}
Integer enabledLocal = enabledLocals.get(advice);
Label onReturnBlockEnd = null;
if (enabledLocal != null) {
onReturnBlockEnd = new Label();
loadLocal(enabledLocal);
visitJumpInsn(IFEQ, onReturnBlockEnd);
}
weaveOnReturnAdvice(opcode, advice, onReturnAdvice);
if (onReturnBlockEnd != null) {
visitLabel(onReturnBlockEnd);
if (returnType.getSort() == Type.VOID) {
visitImplicitFrame();
} else {
visitImplicitFrame(convert(returnType));
}
}
}
private void weaveOnReturnAdvice(int opcode, Advice advice, Method onReturnAdvice) {
if (onReturnAdvice.getArgumentTypes().length > 0) {
// @BindReturn must be the first argument to @OnReturn (if present)
int startIndex;
Object[] stack;
AdviceParameter parameter = advice.onReturnParameters().get(0);
boolean leaveReturnValueOnStack = onReturnAdvice.getReturnType().getSort() == Type.VOID;
switch (parameter.kind()) {
case RETURN:
loadNonOptionalReturnValue(opcode, parameter, leaveReturnValueOnStack);
startIndex = 1;
if (leaveReturnValueOnStack && opcode != RETURN) {
stack = new Object[] {convert(returnType), convert(parameter.type())};
} else {
stack = new Object[] {convert(parameter.type())};
}
break;
case OPTIONAL_RETURN:
loadOptionalReturnValue(opcode, leaveReturnValueOnStack);
startIndex = 1;
if (leaveReturnValueOnStack && opcode != RETURN) {
stack = new Object[] {convert(returnType), convert(parameter.type())};
} else {
stack = new Object[] {convert(parameter.type())};
}
break;
default:
// first argument is not @BindReturn (which means there is no @BindReturn)
startIndex = 0;
if (opcode == RETURN) {
stack = new Object[] {};
} else {
if (onReturnAdvice.getReturnType().getSort() == Type.VOID) {
stack = new Object[] {convert(returnType)};
} else {
pop();
stack = new Object[] {};
}
}
break;
}
loadMethodParameters(advice.onReturnParameters(), startIndex,
travelerLocals.get(advice), advice.adviceType(), OnReturn.class, true, stack);
} else if (onReturnAdvice.getReturnType().getSort() != Type.VOID && opcode != RETURN) {
pop();
}
visitMethodInsn(INVOKESTATIC, advice.adviceType().getInternalName(),
onReturnAdvice.getName(), onReturnAdvice.getDescriptor(), false);
if (onReturnAdvice.getReturnType().getSort() != Type.VOID && opcode == RETURN) {
pop();
}
}
private void loadNonOptionalReturnValue(int opcode, AdviceParameter parameter, boolean dup) {
if (opcode == RETURN) {
logger.warn("cannot use @BindReturn on a @Pointcut returning void");
pushDefault(parameter.type());
} else {
boolean primitive = parameter.type().getSort() < Type.ARRAY;
loadReturnValue(opcode, dup, !primitive);
}
}
private void loadOptionalReturnValue(int opcode, boolean dup) {
if (opcode == RETURN) {
// void
visitMethodInsn(INVOKESTATIC, "org/glowroot/agent/bytecode/api/VoidReturn",
"getInstance", "()Lorg/glowroot/agent/plugin/api/weaving/OptionalReturn;",
false);
} else {
loadReturnValue(opcode, dup, true);
visitMethodInsn(INVOKESTATIC, "org/glowroot/agent/bytecode/api/NonVoidReturn", "create",
"(Ljava/lang/Object;)Lorg/glowroot/agent/plugin/api/weaving/OptionalReturn;",
false);
}
}
private void loadReturnValue(int opcode, boolean dup, boolean autobox) {
if (dup) {
if (opcode == LRETURN || opcode == DRETURN) {
visitInsn(DUP2);
} else {
visitInsn(DUP);
}
}
if (autobox && opcode != ARETURN && opcode != ATHROW) {
box(returnType);
}
}
private void visitOnThrowAdvice(Advice advice) {
Method onThrowAdvice = advice.onThrowAdvice();
if (onThrowAdvice == null) {
return;
}
Integer enabledLocal = enabledLocals.get(advice);
Label onThrowBlockEnd = null;
if (enabledLocal != null) {
onThrowBlockEnd = new Label();
loadLocal(enabledLocal);
visitJumpInsn(IFEQ, onThrowBlockEnd);
}
if (onThrowAdvice.getArgumentTypes().length > 0) {
int startIndex;
Object[] stack;
if (advice.onThrowParameters().get(0).kind() == ParameterKind.THROWABLE) {
// @BindThrowable must be the first argument to @OnThrow (if present)
visitInsn(DUP);
startIndex = 1;
stack = new Object[] {"java/lang/Throwable", "java/lang/Throwable"};
} else {
startIndex = 0;
stack = new Object[] {"java/lang/Throwable"};
}
loadMethodParameters(advice.onThrowParameters(), startIndex, travelerLocals.get(advice),
advice.adviceType(), OnThrow.class, true, stack);
}
visitMethodInsn(INVOKESTATIC, advice.adviceType().getInternalName(),
onThrowAdvice.getName(), onThrowAdvice.getDescriptor(), false);
if (onThrowBlockEnd != null) {
visitLabel(onThrowBlockEnd);
visitImplicitFrame("java/lang/Throwable");
}
}
private void visitOnAfterAdvice(Advice advice, boolean insideCatchHandler) {
Method onAfterAdvice = advice.onAfterAdvice();
if (onAfterAdvice == null) {
return;
}
Integer enabledLocal = enabledLocals.get(advice);
Label onAfterBlockEnd = null;
if (enabledLocal != null) {
onAfterBlockEnd = new Label();
loadLocal(enabledLocal);
visitJumpInsn(IFEQ, onAfterBlockEnd);
}
loadMethodParameters(advice.onAfterParameters(), 0, travelerLocals.get(advice),
advice.adviceType(), OnAfter.class, true);
visitMethodInsn(INVOKESTATIC, advice.adviceType().getInternalName(),
onAfterAdvice.getName(), onAfterAdvice.getDescriptor(), false);
if (onAfterBlockEnd != null) {
visitLabel(onAfterBlockEnd);
// either inside catch handler or inside on return block
if (insideCatchHandler) {
visitImplicitFrame("java/lang/Throwable");
} else if (returnType.getSort() == Type.VOID) {
visitImplicitFrame();
} else {
visitImplicitFrame(convert(returnType));
}
}
}
// only called from inside catch handler or inside on return block
private void resetCurrentNestingGroupIfNeeded(boolean insideCatchHandler) {
ListIterator i = advisors.listIterator(advisors.size());
while (i.hasPrevious()) {
Advice advice = i.previous();
Integer prevNestingGroupIdLocal = prevNestingGroupIdLocals.get(advice);
if (prevNestingGroupIdLocal != null) {
loadLocal(prevNestingGroupIdLocal);
visitIntInsn(BIPUSH, -1);
Label label = new Label();
visitJumpInsn(IF_ICMPEQ, label);
checkNotNull(threadContextLocal);
loadLocal(threadContextLocal);
loadLocal(prevNestingGroupIdLocal);
visitMethodInsn(INVOKEINTERFACE, threadContextPlusType.getInternalName(),
"setCurrentNestingGroupId", "(I)V", true);
visitLabel(label);
// either inside catch handler or inside on return block
if (insideCatchHandler) {
visitImplicitFrame("java/lang/Throwable");
} else if (returnType.getSort() == Type.VOID) {
visitImplicitFrame();
} else {
visitImplicitFrame(convert(returnType));
}
}
Integer prevSuppressionKeyIdLocal = prevSuppressionKeyIdLocals.get(advice);
if (prevSuppressionKeyIdLocal != null) {
loadLocal(prevSuppressionKeyIdLocal);
visitIntInsn(BIPUSH, -1);
Label label = new Label();
visitJumpInsn(IF_ICMPEQ, label);
checkNotNull(threadContextLocal);
loadLocal(threadContextLocal);
loadLocal(prevSuppressionKeyIdLocal);
visitMethodInsn(INVOKEINTERFACE, threadContextPlusType.getInternalName(),
"setCurrentSuppressionKeyId", "(I)V", true);
visitLabel(label);
// either inside catch handler or inside on return block
if (insideCatchHandler) {
visitImplicitFrame("java/lang/Throwable");
} else if (returnType.getSort() == Type.VOID) {
visitImplicitFrame();
} else {
visitImplicitFrame(convert(returnType));
}
}
}
}
private void loadMethodParameters(List parameters, int startIndex,
@Nullable Integer travelerLocal, Type adviceType,
Class extends Annotation> annotationType, boolean useSavedArgs, Object... stack) {
int argIndex = 0;
for (int i = startIndex; i < parameters.size(); i++) {
AdviceParameter parameter = parameters.get(i);
switch (parameter.kind()) {
case RECEIVER:
loadTarget();
break;
case METHOD_ARG:
loadMethodParameter(adviceType, annotationType, argIndex++, parameter,
useSavedArgs);
break;
case METHOD_ARG_ARRAY:
loadArgArray(useSavedArgs);
break;
case METHOD_NAME:
loadMethodName();
break;
case TRAVELER:
loadTraveler(travelerLocal, adviceType, annotationType, parameter);
break;
case CLASS_META:
checkNotNull(metaHolderInternalName);
loadClassMeta(parameter);
break;
case METHOD_META:
checkNotNull(metaHolderInternalName);
checkNotNull(methodMetaGroupUniqueNum);
loadMethodMeta(parameter);
break;
case THREAD_CONTEXT:
checkNotNull(threadContextLocal);
loadLocal(threadContextLocal);
break;
case OPTIONAL_THREAD_CONTEXT:
checkNotNull(threadContextHolderLocal);
checkNotNull(threadContextLocal);
loadOptionalThreadContext(stack);
break;
default:
// this should have been caught during Advice construction, but just in case:
logger.warn(
"the @{} method in {} has an unexpected parameter kind {} at index {}",
annotationType.getSimpleName(), adviceType.getClassName(),
parameter.kind(), i);
pushDefault(parameter.type());
break;
}
}
}
private void loadTarget() {
if (!Modifier.isStatic(access)) {
visitVarInsn(ALOAD, 0);
} else {
// cannot use push(Type) since .class constants are not supported in classes
// that were compiled to jdk 1.4
visitLdcInsn(owner.getClassName());
visitMethodInsn(INVOKESTATIC, "java/lang/Class", "forName",
"(Ljava/lang/String;)Ljava/lang/Class;", false);
}
}
private void loadMethodParameter(Type adviceType, Class extends Annotation> annotationType,
int argIndex, AdviceParameter parameter, boolean useSavedArg) {
if (argIndex >= argumentTypes.length) {
logger.warn(
"the @{} method in {} has more @{} arguments than the number of args in"
+ " the target method",
annotationType.getSimpleName(), adviceType.getClassName(),
BindParameter.class.getSimpleName());
pushDefault(parameter.type());
return;
}
if (useSavedArg) {
loadLocal(savedArgLocals[argIndex]);
} else {
loadArg(argIndex);
}
boolean primitive = parameter.type().getSort() < Type.ARRAY;
if (!primitive) {
// autobox
box(argumentTypes[argIndex]);
}
}
private void loadArgArray(boolean useSavedArgs) {
push(argumentTypes.length);
newArray(objectType);
for (int i = 0; i < argumentTypes.length; i++) {
dup();
push(i);
if (useSavedArgs) {
loadLocal(savedArgLocals[i]);
} else {
loadArg(i);
}
box(argumentTypes[i]);
arrayStore(objectType);
}
}
private void loadMethodName() {
visitLdcInsn(name);
}
private void loadTraveler(@Nullable Integer travelerLocal, Type adviceType,
Class extends Annotation> annotationType, AdviceParameter parameter) {
if (travelerLocal == null) {
logger.warn("the @{} method in {} requested @{} but @{} returns void",
annotationType.getSimpleName(), adviceType.getClassName(),
BindTraveler.class.getSimpleName(), OnBefore.class.getSimpleName());
pushDefault(parameter.type());
} else {
loadLocal(travelerLocal);
}
}
@RequiresNonNull("metaHolderInternalName")
private void loadClassMeta(AdviceParameter parameter) {
Type classMetaFieldType = parameter.type();
String classMetaFieldName =
"glowroot$class$meta$" + classMetaFieldType.getInternalName().replace('/', '$');
if (bootstrapClassLoader) {
int index = BootstrapMetaHolders.reserveClassMetaHolderIndex(metaHolderInternalName,
classMetaFieldName);
push(index);
visitMethodInsn(INVOKESTATIC, bytecodeType.getInternalName(), "getClassMeta",
"(I)Ljava/lang/Object;", false);
mv.visitTypeInsn(CHECKCAST, classMetaFieldType.getInternalName());
} else {
visitFieldInsn(GETSTATIC, metaHolderInternalName, classMetaFieldName,
classMetaFieldType.getDescriptor());
}
}
@RequiresNonNull({"metaHolderInternalName", "methodMetaGroupUniqueNum"})
private void loadMethodMeta(AdviceParameter parameter) {
Type methodMetaFieldType = parameter.type();
String methodMetaFieldName = "glowroot$method$meta$" + methodMetaGroupUniqueNum + '$'
+ methodMetaFieldType.getInternalName().replace('/', '$');
if (bootstrapClassLoader) {
int index = BootstrapMetaHolders.reserveMethodMetaHolderIndex(metaHolderInternalName,
methodMetaFieldName);
push(index);
visitMethodInsn(INVOKESTATIC, bytecodeType.getInternalName(), "getMethodMeta",
"(I)Ljava/lang/Object;", false);
mv.visitTypeInsn(CHECKCAST, methodMetaFieldType.getInternalName());
} else {
visitFieldInsn(GETSTATIC, metaHolderInternalName, methodMetaFieldName,
methodMetaFieldType.getDescriptor());
}
}
@RequiresNonNull({"threadContextHolderLocal", "threadContextLocal"})
private void loadOptionalThreadContext(Object... stack) {
loadLocal(threadContextHolderLocal);
Label label = new Label();
visitJumpInsn(IFNONNULL, label);
loadThreadContextHolder();
storeLocal(threadContextHolderLocal);
visitLabel(label);
visitImplicitFrame(stack);
loadLocal(threadContextHolderLocal);
visitMethodInsn(INVOKEVIRTUAL, fastThreadContextThreadLocalHolderType.getInternalName(),
"get", "()" + threadContextPlusType.getDescriptor(), false);
dup();
storeLocal(threadContextLocal);
Label label2 = new Label();
visitJumpInsn(IFNONNULL, label2);
loadLocal(threadContextHolderLocal);
visitMethodInsn(INVOKESTATIC, bytecodeType.getInternalName(), "createOptionalThreadContext",
"(" + fastThreadContextThreadLocalHolderType.getDescriptor() + ")"
+ threadContextPlusType.getDescriptor(),
false);
storeLocal(threadContextLocal);
visitLabel(label2);
visitImplicitFrame(stack);
loadLocal(threadContextLocal);
}
private void visitImplicitFrame(Object... stack) {
if (frames) {
super.visitFrame(F_NEW, implicitFrameLocals.length, implicitFrameLocals, stack.length,
stack);
}
}
@Override
public void visitFrame(int type, int nLocal, Object /*@Nullable*/ [] local, int nStack,
Object /*@Nullable*/ [] stack) {
checkState(type == F_NEW, "Unexpected frame type: " + type);
if (nLocal < implicitFrameLocals.length) {
super.visitFrame(type, implicitFrameLocals.length, implicitFrameLocals, nStack, stack);
} else {
super.visitFrame(type, nLocal, local, nStack, stack);
}
}
private void pushDefault(Type type) {
pushDefault(this, type);
}
static void pushDefault(MethodVisitor mv, Type type) {
switch (type.getSort()) {
case Type.BOOLEAN:
mv.visitInsn(ICONST_0);
break;
case Type.CHAR:
case Type.BYTE:
case Type.SHORT:
case Type.INT:
mv.visitInsn(ICONST_0);
break;
case Type.FLOAT:
mv.visitInsn(FCONST_0);
break;
case Type.LONG:
mv.visitInsn(LCONST_0);
break;
case Type.DOUBLE:
mv.visitInsn(DCONST_0);
break;
default:
mv.visitInsn(ACONST_NULL);
break;
}
}
// need to drain stack if any
// normal javac bytecode leaves clean stack, but this is not a requirement of valid
// bytecode, e.g. see:
// https://github.com/jbossas/jboss-invocation/blob/09ac89f4c77f59be12a96a1946273e4fd40a9f78/src/main/java/org/jboss/invocation/proxy/ProxyFactory.java#L166
// ideally this would have else statement and pop() the prior method result if the
// bytecode generated method returns void
private void cleanUpStackIfNeeded(int opcode) {
int expectedStackFrameSize = getExpectedStackFrameSize(opcode);
if (stackFrame.size() == expectedStackFrameSize) {
return;
}
if (stackFrame.size() < expectedStackFrameSize) {
// this shouldn't happen
return;
}
if (expectedStackFrameSize == 0) {
cleanExcessFramesLeavingNothing();
return;
}
if (expectedStackFrameSize == 1) {
cleanExcessFramesLeavingOneWord();
return;
}
cleanExcessFramesLeavingDoubleWord();
}
private void cleanExcessFramesLeavingNothing() {
int excessFrames = stackFrame.size();
for (int i = excessFrames - 1; i >= 0; i--) {
if (stackFrame.get(i) == SECOND_WORD) {
pop2();
i--;
} else {
pop();
}
}
}
private void cleanExcessFramesLeavingOneWord() {
int excessFrames = stackFrame.size() - 1;
for (int i = excessFrames - 1; i >= 0; i--) {
if (stackFrame.get(i) == SECOND_WORD) {
// duplicate top word and insert beneath the third word
super.visitInsn(DUP_X2);
pop();
pop2();
i--;
} else {
swap();
pop();
}
}
}
private void cleanExcessFramesLeavingDoubleWord() {
int excessFrames = stackFrame.size() - 2;
for (int i = excessFrames - 1; i >= 0; i--) {
if (stackFrame.get(i) == SECOND_WORD) {
// duplicate two word and insert beneath the fourth word
super.visitInsn(DUP2_X2);
pop2();
pop2();
i--;
} else {
// duplicate two words and insert beneath third word
super.visitInsn(DUP2_X1);
pop2();
pop();
}
}
}
private static Object convert(Type type) {
switch (type.getSort()) {
case Type.BOOLEAN:
case Type.CHAR:
case Type.BYTE:
case Type.SHORT:
case Type.INT:
return INTEGER;
case Type.FLOAT:
return FLOAT;
case Type.LONG:
return LONG;
case Type.DOUBLE:
return DOUBLE;
case Type.ARRAY:
return type.getDescriptor();
case Type.OBJECT:
return type.getInternalName();
case Type.METHOD:
return type.getDescriptor();
default:
throw new IllegalStateException("Unexpected type: " + type.getDescriptor());
}
}
private static int getNestingGroupId(String nestingGroup) {
Integer nullableNestingGroupId = nestingGroupIds.get(nestingGroup);
if (nullableNestingGroupId != null) {
return nullableNestingGroupId;
}
int nestingGroupId = nestingGroupIdCounter.getAndIncrement();
Integer previousValue = nestingGroupIds.putIfAbsent(nestingGroup, nestingGroupId);
if (previousValue == null) {
return nestingGroupId;
} else {
// handling race condition
return previousValue;
}
}
private static int getSuppressionKeyId(String suppressionKey) {
Integer nullableSuppressionKeyId = suppressionKeyIds.get(suppressionKey);
if (nullableSuppressionKeyId != null) {
return nullableSuppressionKeyId;
}
int suppressionKeyId = suppressionKeyIdCounter.getAndIncrement();
Integer previousValue = suppressionKeyIds.putIfAbsent(suppressionKey, suppressionKeyId);
if (previousValue == null) {
return suppressionKeyId;
} else {
// handling race condition
return previousValue;
}
}
private static boolean isReturnOpcode(int opcode) {
return opcode >= IRETURN && opcode <= RETURN;
}
private static int getExpectedStackFrameSize(int opcode) {
if (opcode == IRETURN || opcode == FRETURN || opcode == ARETURN) {
return 1;
} else if (opcode == LRETURN || opcode == DRETURN) {
return 2;
} else {
return 0;
}
}
@Value.Immutable
@Styles.AllParameters
interface CatchHandler {
Label catchStartLabel();
// advisors that have successfully executed @OnBefore
List advisors();
}
}