/*
* 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;
import com.sun.btrace.annotations.Sampled;
import java.util.Map;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Set;
import com.sun.btrace.org.objectweb.asm.Label;
import com.sun.btrace.org.objectweb.asm.Opcodes;
import com.sun.btrace.org.objectweb.asm.Type;
import static com.sun.btrace.org.objectweb.asm.Opcodes.*;
import static com.sun.btrace.runtime.Constants.*;
import com.sun.btrace.services.api.Service;
import java.util.List;
/**
* This class verifies that the BTrace "action" method is
* safe - boundedness and read-only rules are checked
* such as no backward jumps (loops), no throw/new/invoke etc.
*
* @author A. Sundararajan
*/
final class MethodVerifier extends StackTrackingMethodVisitor {
final private static Set<String> primitiveWrapperTypes;
final private static Set<String> unboxMethods;
static {
primitiveWrapperTypes = new HashSet<>();
unboxMethods = new HashSet<>();
primitiveWrapperTypes.add("java/lang/Boolean");
primitiveWrapperTypes.add("java/lang/Byte");
primitiveWrapperTypes.add("java/lang/Character");
primitiveWrapperTypes.add("java/lang/Short");
primitiveWrapperTypes.add("java/lang/Integer");
primitiveWrapperTypes.add("java/lang/Long");
primitiveWrapperTypes.add("java/lang/Float");
primitiveWrapperTypes.add("java/lang/Double");
unboxMethods.add("booleanValue");
unboxMethods.add("byteValue");
unboxMethods.add("charValue");
unboxMethods.add("shortValue");
unboxMethods.add("intValue");
unboxMethods.add("longValue");
unboxMethods.add("floatValue");
unboxMethods.add("doubleValue");
}
protected Location loc;
private final String className;
private final String methodName;
private final String methodDesc;
private final int access;
private final Map<Label, Label> labels;
private Object delayedClzLoad = null;
public MethodVerifier(BTraceMethodNode parent, int access, String className, String methodName, String desc) {
super(parent, className, desc, ((access & Opcodes.ACC_STATIC) == Opcodes.ACC_STATIC));
this.className = className;
this.methodName = methodName;
this.methodDesc = desc;
this.access = access;
labels = new HashMap<>();
}
private BTraceMethodNode getParent() {
return (BTraceMethodNode)mv;
}
@Override
public void visitEnd() {
super.visitEnd();
if (getParent().isBTraceHandler()) { // only btrace handlers are enforced to be public
if ((access & ACC_PUBLIC) == 0 && !methodName.equals(CLASS_INITIALIZER)) {
Verifier.reportError("method.should.be.public", methodName + methodDesc);
}
if (!Type.getReturnType(methodDesc).equals(Type.VOID_TYPE)) {
Verifier.reportError("return.type.should.be.void", methodName + methodDesc);
}
}
validateSamplerLocation();
labels.clear();
}
@Override
public void visitFieldInsn(int opcode, String owner, String name, String desc) {
if (opcode == PUTFIELD) {
Verifier.reportError("no.assignment");
}
if (opcode == PUTSTATIC) {
if (!owner.equals(className)) {
Verifier.reportError("no.assignment");
}
}
super.visitFieldInsn(opcode, owner, name, desc);
}
@Override
public void visitInsn(int opcode) {
switch (opcode) {
case IASTORE:
case LASTORE:
case FASTORE:
case DASTORE:
case AASTORE:
case BASTORE:
case CASTORE:
case SASTORE:
Verifier.reportError("no.assignment");
break;
case ATHROW:
Verifier.reportError("no.throw");
break;
case MONITORENTER:
case MONITOREXIT:
Verifier.reportError("no.synchronized.blocks");
break;
}
super.visitInsn(opcode);
}
@Override
public void visitIntInsn(int opcode, int operand) {
if (opcode == NEWARRAY) {
Verifier.reportError("no.array.creation");
}
super.visitIntInsn(opcode, operand);
}
@Override
public void visitJumpInsn(int opcode, Label label) {
if (labels.get(label) != null) {
Verifier.reportError("no.loops");
}
super.visitJumpInsn(opcode, label);
}
@Override
public void visitLabel(Label label) {
labels.put(label, label);
super.visitLabel(label);
}
@Override
public void visitLdcInsn(Object cst) {
if (cst instanceof Type) {
delayedClzLoad = cst;
}
super.visitLdcInsn(cst);
}
@Override
public void visitMethodInsn(int opcode, String owner,
String name, String desc, boolean itf) {
switch (opcode) {
case INVOKEVIRTUAL:
if (isPrimitiveWrapper(owner) && unboxMethods.contains(name)) {
// allow primitive type unbox methods.
// These calls are generated by javac for auto-unboxing
// and can't be caught by source AST analyzer as well.
} else if (owner.equals(Type.getInternalName(StringBuilder.class))) {
// allow string concatenation via StringBuilder
} else {
List<StackItem> args = getMethodParams(desc, false);
if (!isServiceTarget(args.get(0))) {
Verifier.reportError("no.method.calls", owner + "." + name + desc);
}
}
break;
case INVOKEINTERFACE:
Verifier.reportError("no.method.calls", owner + "." + name + desc);
break;
case INVOKESPECIAL:
if (owner.equals(OBJECT_INTERNAL) && name.equals(CONSTRUCTOR)) {
// allow object initializer
} else if (owner.equals(Type.getInternalName(StringBuilder.class))) {
// allow string concatenation via StringBuilder
} else {
Verifier.reportError("no.method.calls", owner + "." + name + desc);
}
break;
case INVOKESTATIC:
if (owner.equals(SERVICE)) {
delayedClzLoad = null;
} else if (!owner.equals(BTRACE_UTILS) &&
!owner.startsWith(BTRACE_UTILS + "$") &&
!owner.equals(className)) {
if ("valueOf".equals(name) && isPrimitiveWrapper(owner)) {
// allow primitive wrapper boxing methods.
// These calls are generated by javac for autoboxing
// and can't be caught sourc AST analyzer as well.
} else {
Verifier.reportError("no.method.calls", owner + "." + name + desc);
}
}
break;
}
if (delayedClzLoad != null) {
Verifier.reportError("no.class.literals", delayedClzLoad.toString());
}
super.visitMethodInsn(opcode, owner, name, desc, itf);
}
@Override
public void visitMultiANewArrayInsn(String desc, int dims) {
Verifier.reportError("no.array.creation");
}
@Override
public void visitTryCatchBlock(Label start, Label end,
Label handler, String type) {
Verifier.reportError("no.catch");
}
@Override
public void visitTypeInsn(int opcode, String desc) {
if (opcode == ANEWARRAY) {
Verifier.reportError("no.array.creation", desc);
}
if (opcode == NEW) {
// allow StringBuilder creation for string concatenation
if (!desc.equals(Type.getInternalName(StringBuilder.class))) {
Verifier.reportError("no.new.object", desc);
}
}
super.visitTypeInsn(opcode, desc);
}
@Override
public void visitVarInsn(int opcode, int var) {
if (opcode == RET) {
Verifier.reportError("no.try");
}
super.visitVarInsn(opcode, var);
}
private static boolean isPrimitiveWrapper(String type) {
return primitiveWrapperTypes.contains(type);
}
private boolean isServiceTarget(StackItem si) {
if (si instanceof ResultItem) {
ResultItem ri = (ResultItem)si;
if (ri.getOwner().equals(Type.getInternalName(Service.class))) {
return true;
} else if (ri.getOwner().equals(className) &&
getParent().isFieldInjected(ri.getName())) {
return true;
}
}
for(StackItem p : si.getParents()) {
if (isServiceTarget(p)) {
return true;
}
}
return false;
}
private void validateSamplerLocation() {
BTraceMethodNode mn = getParent();
// if not sampled just return
if (!mn.isSampled()) return;
OnMethod om = mn.getOnMethod();
if (om == null && mn.isSampled()) {
Verifier.reportError("sampler.invalid.location", methodName + methodDesc);
return;
}
if (om != null && om.getSamplerKind() != Sampled.Sampler.None) {
switch (om.getLocation().getValue()) {
case ENTRY:
case RETURN:
case ERROR:
case CALL: {
// ok
break;
}
default: {
Verifier.reportError("sampler.invalid.location", methodName + methodDesc);
}
}
}
}
}