/*
* Copyright (c) 2008, 2016, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code 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
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package com.sun.btrace.runtime.instr;
import com.sun.btrace.annotations.Where;
import com.sun.btrace.org.objectweb.asm.Label;
import com.sun.btrace.org.objectweb.asm.MethodVisitor;
import com.sun.btrace.org.objectweb.asm.Opcodes;
import com.sun.btrace.org.objectweb.asm.Type;
import com.sun.btrace.runtime.Assembler;
import com.sun.btrace.runtime.Level;
import com.sun.btrace.runtime.OnMethod;
import com.sun.btrace.runtime.TypeUtils;
import java.util.Arrays;
import java.util.Comparator;
import static com.sun.btrace.org.objectweb.asm.Opcodes.*;
import static com.sun.btrace.runtime.Constants.*;
import com.sun.btrace.util.Interval;
import com.sun.btrace.util.LocalVariableHelper;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* Base class for all out method instrumenting classes.
*
* @author A. Sundararajan
*/
public class MethodInstrumentor extends MethodVisitor implements LocalVariableHelper {
protected int levelCheckVar = Integer.MIN_VALUE;
protected void visitMethodPrologue() {
}
final protected static class ValidationResult {
final static private int[] EMPTY_ARRAY = new int[0];
final private boolean isValid;
final private int[] argsIndex;
public ValidationResult(boolean valid, int[] argsIndex) {
this.isValid = valid;
this.argsIndex = argsIndex;
}
public ValidationResult(boolean valid) {
this(valid, EMPTY_ARRAY);
}
public int getArgIdx(int ptr) {
return ptr > -1 && ptr < argsIndex.length ? argsIndex[ptr] : -1;
}
public int getArgCnt() {
return argsIndex.length;
}
public boolean isAny() {
return isValid && argsIndex.length == 0;
}
public boolean isValid() {
return isValid;
}
@Override
public boolean equals(Object obj) {
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
final ValidationResult other = (ValidationResult) obj;
if (this.isValid != other.isValid) {
return false;
}
return Arrays.equals(this.argsIndex, other.argsIndex);
}
@Override
public int hashCode() {
int hash = 5;
hash = 59 * hash + (this.isValid ? 1 : 0);
hash = 59 * hash + Arrays.hashCode(this.argsIndex);
return hash;
}
final protected static ValidationResult INVALID = new ValidationResult(false);
final protected static ValidationResult ANY = new ValidationResult(true);
}
public static abstract class ArgumentProvider {
private final int index;
protected final Assembler asm;
static final Comparator<ArgumentProvider> COMPARATOR = new Comparator<ArgumentProvider>() {
@Override
public final int compare(ArgumentProvider o1, ArgumentProvider o2) {
if (o1 == null && o2 == null) {
return 0;
}
if (o1 != null && o2 == null) return -1;
if (o2 != null && o1 == null) return 1;
if (o1.index == o2.index) {
return 0;
}
if (o1.index < o2.index) {
return -1;
}
return 1;
}
};
public ArgumentProvider(Assembler asm, int index) {
this.index = index;
this.asm = asm;
}
public int getIndex() {
return index;
}
final public void provide() {
if (index > -1) {
doProvide();
}
}
abstract protected void doProvide();
}
private static class LocalVarArgProvider extends ArgumentProvider {
private final Type type;
private final int ptr;
private final boolean boxValue;
public LocalVarArgProvider(Assembler asm, int index, Type type, int ptr) {
this(asm, index, type, ptr, false);
}
public LocalVarArgProvider(Assembler asm, int index, Type type, int ptr, boolean boxValue) {
super(asm, index);
this.type = type;
this.ptr = ptr;
this.boxValue = boxValue;
}
@Override
public void doProvide() {
asm.loadLocal(type, ptr);
if (boxValue) {
asm.box(type);
}
}
@Override
public String toString() {
return "LocalVar #" + ptr + " of type " + type + " (@" + getIndex() + ")";
}
}
private static class ConstantArgProvider extends ArgumentProvider {
private Object constant;
public ConstantArgProvider(Assembler asm, int index, Object constant) {
super(asm, index);
this.constant = constant;
}
@Override
public void doProvide() {
asm.ldc(constant);
}
@Override
public String toString() {
return "Constant " + constant + " (@" + getIndex() + ")";
}
}
protected class AnyTypeArgProvider extends ArgumentProvider {
private int argPtr;
private Type[] myArgTypes;
public AnyTypeArgProvider(Assembler asm, int index, int basePtr) {
this(asm, index, basePtr, argumentTypes);
}
public AnyTypeArgProvider(Assembler asm, int index, int basePtr, Type[] argTypes) {
super(asm, index);
this.argPtr = basePtr;
this.myArgTypes = argTypes;
}
@Override
public void doProvide() {
asm.push(myArgTypes.length);
asm.newArray(OBJECT_TYPE);
for (int j = 0; j < myArgTypes.length; j++) {
Type argType = myArgTypes[j];
asm.dup()
.push(j)
.loadLocal(argType, argPtr)
.box(argType)
.arrayStore(OBJECT_TYPE);
argPtr += argType.getSize();
}
}
}
private final int access;
private final String parentClz;
private final String superClz;
private final String name;
private final String desc;
private Type returnType;
private Type[] argumentTypes;
private Map<Integer, Type> extraTypes;
private Label skipLabel;
private boolean prologueVisited = false;
private final LocalVariableHelper lvt;
protected final Assembler asm;
protected MethodInstrumentor parent = null;
public MethodInstrumentor(
LocalVariableHelper lvt, String parentClz,
String superClz, int access, String name, String desc
) {
super(ASM5, (MethodVisitor)lvt);
this.parentClz = parentClz;
this.superClz = superClz;
this.access = access;
this.name = name;
this.desc = desc;
this.returnType = Type.getReturnType(desc);
this.argumentTypes = Type.getArgumentTypes(desc);
extraTypes = new HashMap<>();
this.lvt = lvt;
if (lvt instanceof MethodInstrumentor) {
((MethodInstrumentor)lvt).parent = this;
}
this.asm = new Assembler(this);
}
@Override
final public void visitCode() {
if (!isConstructor()) {
prologueVisited = true;
visitMethodPrologue();
}
super.visitCode();
}
@Override
public void visitMethodInsn(int opcode,
String owner,
String name,
String desc,
boolean iface) {
super.visitMethodInsn(opcode, owner, name, desc, iface);
if (isConstructor() && !prologueVisited) {
if (name.equals(CONSTRUCTOR) && (owner.equals(getParentClz()) || (getSuperClz() != null && owner.equals(getSuperClz())))) {
// super or this class constructor call.
// do method entry after that!
prologueVisited = true;
visitMethodPrologue();
}
}
}
@Override
public void visitInsn(int opcode) {
switch (opcode) {
case IRETURN:
case ARETURN:
case FRETURN:
case LRETURN:
case DRETURN:
case RETURN:
if (!prologueVisited) {
prologueVisited = true;
visitMethodPrologue();
}
break;
default:
break;
}
super.visitInsn(opcode);
}
public int getAccess() {
return access;
}
public final String getName() {
return getName(false);
}
public final String getName(boolean fqn) {
StringBuilder sb = new StringBuilder();
if (fqn) {
sb.append(Modifier.toString(access)).append(' ')
.append(TypeUtils.descriptorToSimplified(desc, parentClz, name));
} else {
sb.append(name);
}
return sb.toString();
}
@Override
public int storeNewLocal(Type type) {
return lvt.storeNewLocal(type);
}
public final Label getSkipLabel() {
return skipLabel;
}
public final void setSkipLabel(Label skipLabel) {
this.skipLabel = skipLabel;
}
public final String getDescriptor() {
return desc;
}
public final Type getReturnType() {
return returnType;
}
protected void addExtraTypeInfo(int index, Type type) {
if (index != -1) {
extraTypes.put(index, type);
}
}
protected void loadArguments(ArgumentProvider ... argumentProviders) {
Arrays.sort(argumentProviders, ArgumentProvider.COMPARATOR);
for(ArgumentProvider provider : argumentProviders) {
if (provider != null) provider.provide();
}
}
protected void loadArguments(ValidationResult vr, Type[] actionArgTypes, boolean isStatic, ArgumentProvider ... argumentProviders) {
int ptr = isStatic ? 0 : 1;
List<ArgumentProvider> argProvidersList = new ArrayList<>(argumentProviders.length + vr.getArgCnt());
argProvidersList.addAll(Arrays.asList(argumentProviders));
for(int i=0;i<vr.getArgCnt();i++) {
int index = vr.getArgIdx(i);
Type t = actionArgTypes[index];
if (TypeUtils.isAnyTypeArray(t)) {
argProvidersList.add(anytypeArg(index, ptr));
ptr++;
} else {
argProvidersList.add(localVarArg(index, t, ptr));
ptr += actionArgTypes[index].getSize();
}
}
loadArguments(argProvidersList);
}
private void loadArguments(List<ArgumentProvider> argumentProviders) {
Collections.sort(argumentProviders, ArgumentProvider.COMPARATOR);
for (ArgumentProvider ap : argumentProviders) {
if (ap != null) {
ap.provide();
}
}
}
public void loadThis() {
if ((access & ACC_STATIC) != 0) {
throw new IllegalStateException("no 'this' inside static method");
}
super.visitVarInsn(ALOAD, 0);
}
public int[] backupStack(Type[] methodArgTypes, boolean isStatic) {
int[] backupArgsIndexes = new int[methodArgTypes.length + 1];
int upper = methodArgTypes.length - 1;
for (int i = 0; i < methodArgTypes.length; i++) {
int index = lvt.storeNewLocal(methodArgTypes[upper - i]);
backupArgsIndexes[upper - i + 1] = index;
}
if (!isStatic) {
int index = lvt.storeNewLocal(OBJECT_TYPE); // store *callee*
backupArgsIndexes[0] = index;
}
return backupArgsIndexes;
}
public void restoreStack(int[] backupArgsIndexes, boolean isStatic) {
restoreStack(backupArgsIndexes, argumentTypes, isStatic);
}
public void restoreStack(int[] backupArgsIndexes, Type[] methodArgTypes, boolean isStatic) {
int upper = methodArgTypes.length - 1;
if (!isStatic) {
asm.loadLocal(OBJECT_TYPE, backupArgsIndexes[0]);
}
for (int i = methodArgTypes.length - 1; i > -1; i--) {
asm.loadLocal(methodArgTypes[upper - i], backupArgsIndexes[upper - i + 1]);
}
}
protected final ArgumentProvider localVarArg(int index, Type type, int ptr) {
return new LocalVarArgProvider(asm, index, type, ptr);
}
protected final ArgumentProvider localVarArg(int index, Type type, int ptr, boolean boxValue) {
return new LocalVarArgProvider(asm, index, type, ptr, boxValue);
}
protected final ArgumentProvider constArg(int index, Object val) {
return new ConstantArgProvider(asm, index, val);
}
protected final ArgumentProvider selfArg(int index, Type type) {
return isStatic() ? constArg(index, null) : localVarArg(index, type, 0);
}
protected final ArgumentProvider anytypeArg(int index, int basePtr) {
return new AnyTypeArgProvider(asm, index, basePtr);
}
protected final ArgumentProvider anytypeArg(int index, int basePtr, Type ... argTypes) {
return new AnyTypeArgProvider(asm, index, basePtr, argTypes);
}
protected final boolean isStatic() {
return (getAccess() & ACC_STATIC) != 0;
}
protected final boolean isConstructor() {
return CONSTRUCTOR.equals(name);
}
public void returnValue() {
super.visitInsn(returnType.getOpcode(IRETURN));
}
protected String getParentClz() {
return parentClz;
}
protected String getSuperClz() {
return superClz;
}
protected ValidationResult validateArguments(OnMethod om, Type[] actionArgTypes, Type[] methodArgTypes) {
int specialArgsCount = 0;
if (om.getSelfParameter() != -1) {
Type selfType = extraTypes.get(om.getSelfParameter());
if (selfType == null) {
if (!TypeUtils.isObject(actionArgTypes[om.getSelfParameter()])) {
report("Invalid @Self parameter. @Self parameter is not java.lang.Object. Expected " + OBJECT_TYPE + ", Received " + actionArgTypes[om.getSelfParameter()]);
return ValidationResult.INVALID;
}
} else {
if (!TypeUtils.isCompatible(actionArgTypes[om.getSelfParameter()], selfType)) {
report("Invalid @Self parameter. @Self parameter is not compatible. Expected " + selfType + ", Received " + actionArgTypes[om.getSelfParameter()]);
return ValidationResult.INVALID;
}
}
specialArgsCount++;
}
if (om.getReturnParameter() != -1) {
Type type = extraTypes.get(om.getReturnParameter());
if (type == null) {
type = returnType;
}
if (type == null) {
if (!TypeUtils.isObject(actionArgTypes[om.getReturnParameter()])) {
report("Invalid @Return parameter. @Return parameter is not java.lang.Object. Expected " + OBJECT_TYPE + ", Received " + actionArgTypes[om.getReturnParameter()]);
return ValidationResult.INVALID;
}
} else {
if (!TypeUtils.isCompatible(actionArgTypes[om.getReturnParameter()], type)) {
report("Invalid @Return parameter. Expected '" + returnType + ", received " + actionArgTypes[om.getReturnParameter()]);
return ValidationResult.INVALID;
}
}
specialArgsCount++;
}
if (om.getTargetMethodOrFieldParameter() != -1) {
if (!(TypeUtils.isCompatible(actionArgTypes[om.getTargetMethodOrFieldParameter()], STRING_TYPE))) {
report("Invalid @TargetMethodOrField parameter. Expected " + STRING_TYPE + ", received " + actionArgTypes[om.getTargetMethodOrFieldParameter()]);
return ValidationResult.INVALID;
}
specialArgsCount++;
}
if (om.getTargetInstanceParameter() != -1) {
Type calledType = extraTypes.get(om.getTargetInstanceParameter());
if (calledType == null) {
if (!TypeUtils.isObject(actionArgTypes[om.getTargetInstanceParameter()])) {
report("Invalid @TargetInstance parameter. @TargetInstance parameter is not java.lang.Object. Expected " + OBJECT_TYPE + ", Received " + actionArgTypes[om.getTargetInstanceParameter()]);
return ValidationResult.INVALID;
}
} else {
if (!TypeUtils.isCompatible(actionArgTypes[om.getTargetInstanceParameter()], calledType)) {
report("Invalid @TargetInstance parameter. Expected " + OBJECT_TYPE + ", received " + actionArgTypes[om.getTargetInstanceParameter()]);
return ValidationResult.INVALID;
}
}
specialArgsCount++;
}
if (om.getDurationParameter() != -1) {
if (!actionArgTypes[om.getDurationParameter()].equals(Type.LONG_TYPE)) {
return ValidationResult.INVALID;
}
specialArgsCount++;
}
if (om.getClassNameParameter() != -1) {
if (!(TypeUtils.isCompatible(actionArgTypes[om.getClassNameParameter()], STRING_TYPE))) {
return ValidationResult.INVALID;
}
specialArgsCount++;
}
if (om.getMethodParameter() != -1) {
if (!(TypeUtils.isCompatible(actionArgTypes[om.getMethodParameter()], STRING_TYPE))) {
return ValidationResult.INVALID;
}
specialArgsCount++;
}
Type[] cleansedArgArray = new Type[actionArgTypes.length - specialArgsCount];
int[] cleansedArgIndex = new int[cleansedArgArray.length];
int counter = 0;
for (int argIndex = 0; argIndex < actionArgTypes.length; argIndex++) {
if (argIndex != om.getSelfParameter() &&
argIndex != om.getClassNameParameter() &&
argIndex != om.getMethodParameter() &&
argIndex != om.getReturnParameter() &&
argIndex != om.getTargetInstanceParameter() &&
argIndex != om.getTargetMethodOrFieldParameter() &&
argIndex != om.getDurationParameter()) {
cleansedArgArray[counter] = actionArgTypes[argIndex];
cleansedArgIndex[counter] = argIndex;
counter++;
}
}
if (cleansedArgArray.length == 0) {
return ValidationResult.ANY;
} else {
if (cleansedArgArray.length > 0) {
if (!TypeUtils.isAnyTypeArray(cleansedArgArray[0]) &&
!TypeUtils.isCompatible(cleansedArgArray, methodArgTypes)) {
return ValidationResult.INVALID;
}
}
}
return new ValidationResult(true, cleansedArgIndex);
}
private Label levelCheck(OnMethod om, String className, boolean saveResult) {
Label l = null;
Level level = om.getLevel();
if (isLevelCheck(level)) {
l = new Label();
if (saveResult) {
// must store the level in a local var to be consistent
asm.compareLevel(className, level).dup();
levelCheckVar = storeNewLocal(Type.INT_TYPE);
asm.jump(Opcodes.IFLT, l);
} else {
asm.addLevelCheck(className, level, l);
}
}
return l;
}
protected Label levelCheck(OnMethod om, String className) {
return levelCheck(om, className, false);
}
protected Label levelCheckBefore(OnMethod om, String className) {
return levelCheck(om, className, om.getLocation().getWhere() == Where.AFTER);
}
protected Label levelCheckAfter(OnMethod om, String className) {
Label l = null;
if (levelCheckVar != Integer.MIN_VALUE) {
Level level = om.getLevel();
if (isLevelCheck(level)) {
l = new Label();
asm.loadLocal(Type.INT_TYPE, levelCheckVar)
.jump(Opcodes.IFLT, l);
}
} else {
l = levelCheck(om, className);
}
return l;
}
private static boolean isLevelCheck(Level level) {
return level != null && !level.getValue().equals(Interval.ge(0));
}
private void report(String msg) {
String out = "[" + getName(true) + "] " + msg;
System.err.println(out);
}
}