private void installParameters(boolean isStatic, String className) throws TypeException { Type type; // add a binding for the helper so we can call builtin static methods type = typeGroup.create(helperClass.getName()); Binding ruleBinding = bindings.lookup("$$"); if (ruleBinding != null) { ruleBinding.setType(type); } else { bindings.append(new Binding(this, "$$", type)); } if (!isStatic) { Binding recipientBinding = bindings.lookup("$0"); if (recipientBinding != null) { type = typeGroup.create(className); if (type.isUndefined()) { throw new TypeException( "Rule.installParameters : Rule " + name + " unable to load class " + className); } recipientBinding.setType(type); } } String returnTypeName = Type.parseMethodReturnType(triggerDescriptor); returnType = typeGroup.create(returnTypeName); Iterator<Binding> iterator = bindings.iterator(); while (iterator.hasNext()) { Binding binding = iterator.next(); // these bindings are typed via the descriptor installed during trigger injection // note that the return type has to be done this way because it may represent the // trigger method return type or the invoke method return type if (binding.isParam() || binding.isLocalVar() || binding.isReturn()) { String typeName = binding.getDescriptor(); String[] typeAndArrayBounds = typeName.split("\\["); Type baseType = typeGroup.create(typeAndArrayBounds[0]); Type fullType = baseType; if (baseType.isUndefined()) { throw new TypeException( "Rule.installParameters : Rule " + name + " unable to load class " + baseType); } for (int i = 1; i < typeAndArrayBounds.length; i++) { fullType = typeGroup.createArray(fullType); } binding.setType(fullType); } else if (binding.isThrowable()) { // TODO -- enable a more precise specification of the throwable type // we need to be able to obtain the type descriptor for the throw operation binding.setType(typeGroup.ensureType(Throwable.class)); } else if (binding.isParamCount()) { binding.setType(Type.I); } else if (binding.isParamArray() || binding.isInvokeParamArray()) { binding.setType(Type.OBJECT.arrayType()); } else if (binding.isTriggerClass() || binding.isTriggerMethod()) { binding.setType(Type.STRING); } } }
/** * find a method to resolve this method call expression. * * @param publicOnly true if only public methods should be considered * @throws TypeException */ private void findMethod(boolean publicOnly) throws TypeException { // check all declared methods of each class in the class hierarchy using the one with // the most specific recipient type if we can find it TypeGroup typeGroup = getTypeGroup(); Class<?> clazz = rootType.getTargetClass(); boolean isStatic = (recipient == null); int arity = arguments.size(); LinkedList<Class<?>> clazzes = new LinkedList<Class<?>>(); if (publicOnly) { // we can use getDeclaredMethods on just one class to list all possible candidates clazzes.add(clazz); } else { // we need to iterate over the class and interface hierarchy bottom up while (clazz != null) { clazzes.add(clazz); // collect all direct interfaces in order Class[] ifaces = clazz.getInterfaces(); LinkedList<Class<?>> toBeChecked = new LinkedList<Class<?>>(); for (int i = 0; i < ifaces.length; i++) { toBeChecked.addLast(ifaces[i]); } // process each interface in turn, also collecting its parent interfaces for consideration while (!toBeChecked.isEmpty()) { Class<?> iface = toBeChecked.pop(); // only need to process it if we have not already seen it if (!clazzes.contains(iface)) { clazzes.addLast(iface); Class[] ifaces2 = iface.getInterfaces(); // don't bother to check for repeats here as we check later anyway for (int j = 0; j < ifaces2.length; j++) { toBeChecked.addLast(ifaces2[j]); } } } // move on to the next class clazz = clazz.getSuperclass(); } } // now check for a matching method in each class or interface in order while (!clazzes.isEmpty()) { clazz = clazzes.pop(); List<Method> candidates = new ArrayList<Method>(); try { Method[] methods; if (publicOnly) { methods = clazz.getMethods(); } else { methods = clazz.getDeclaredMethods(); } argumentTypes = new ArrayList<Type>(); for (Method method : methods) { int modifiers = method.getModifiers(); // ensure we only look at static or non static methods as appropriate if (Modifier.isStatic(modifiers) == isStatic) { if (method.getName().equals(name) && method.getParameterTypes().length == arity) { candidates.add(method); } } } // check each argument in turn -- if all candidates have the same argument type then // use that as the type to check against for (int i = 0; i < arguments.size(); i++) { if (candidates.isEmpty()) { // no more possible matches break; } Class candidateClass = getCandidateArgClass(candidates, i); Type candidateType; if (candidateClass != null) { candidateType = typeGroup.ensureType(candidateClass); } else { candidateType = Type.UNDEFINED; } Type argType = arguments.get(i).typeCheck(candidateType); argumentTypes.add(argType); if (candidateType == Type.UNDEFINED) { // we had several methods to choose from candidates = pruneCandidates(candidates, i, argType.getTargetClass()); } } // see if we have a unique best fit Method method = bestMatchCandidate(candidates); if (method != null) { if (!Modifier.isPublic(method.getModifiers())) { // see if we can actually access this method try { method.setAccessible(true); } catch (SecurityException e) { // hmm, maybe try the next super continue; } // we need to remember that this is not public isPublicMethod = false; // save the method so we can use it from the compiled code methodIndex = rule.addAccessibleMethod(method); } else { isPublicMethod = true; } this.method = method; return; } else if (candidates.size() > 1) { // ambiguous method so throw up here throw new TypeException( "MethodExpression.typeCheck : ambiguous method signature " + name + " for target class " + rootType.getName() + getPos()); } } catch (SecurityException e) { // continue in case we can find an implementation } } // no more possible candidates so throw up here throw new TypeException( "MethodExpression.typeCheck : invalid method " + name + " for target class " + rootType.getName() + getPos()); }
public void compile(MethodVisitor mv, CompileContext compileContext) throws CompileException { // make sure we are at the right source line compileContext.notifySourceLine(line); int currentStack = compileContext.getStackCount(); int extraParams = 0; // space used by stacked args after conversion int expected = 0; // no need for type conversion as return type was derived from method if (type.getNBytes() > 4) { expected = 2; } else if (type != Type.VOID) { expected = 1; } else { expected = 0; } int argCount = arguments.size(); if (isPublicMethod) { // we can just do this as a direct call // stack the recipient if necessary then stack the args and then invoke the method if (recipient != null) { // compile code for recipient recipient.compile(mv, compileContext); extraParams += 1; } for (int i = 0; i < argCount; i++) { Expression argument = arguments.get(i); Type argType = argumentTypes.get(i); Type paramType = paramTypes.get(i); // compile code to stack argument and type convert if necessary argument.compile(mv, compileContext); compileTypeConversion(argType, paramType, mv, compileContext); // allow for stacked paramType value extraParams += (paramType.getNBytes() > 4 ? 2 : 1); } // enable triggering before we call the method // this adds an extra value to the stack so modify the compile context to ensure // we increase the maximum height if necessary mv.visitMethodInsn( Opcodes.INVOKESTATIC, "org/jboss/byteman/rule/Rule", "enableTriggersInternal", "()Z"); compileContext.addStackCount(1); mv.visitInsn(Opcodes.POP); compileContext.addStackCount(-1); // ok, now just call the method -- removes extraParams words String ownerName = Type.internalName(method.getDeclaringClass()); if (recipient == null) { mv.visitMethodInsn(Opcodes.INVOKESTATIC, ownerName, method.getName(), getDescriptor()); } else if (method.getDeclaringClass().isInterface()) { mv.visitMethodInsn(Opcodes.INVOKEINTERFACE, ownerName, method.getName(), getDescriptor()); } else { mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, ownerName, method.getName(), getDescriptor()); } // decrement the stack height to account for stacked param values (removed) and return value // (added) compileContext.addStackCount(expected - extraParams); // now disable triggering again // this temporarily adds an extra value to the stack -- n.b. we *must* increment and // then decrement the stack height even though we bumped the max before the call. in // some cases the stack may be larger after the method call than before e.g. calling // a static which returns a value or calling a non-static which returns a long/double mv.visitMethodInsn( Opcodes.INVOKESTATIC, "org/jboss/byteman/rule/Rule", "disableTriggersInternal", "()Z"); compileContext.addStackCount(1); mv.visitInsn(Opcodes.POP); compileContext.addStackCount(-1); } else { // if we are calling a method by reflection then we need to stack the current helper then // the recipient or null if there is none and then build an object array on the stack mv.visitVarInsn(Opcodes.ALOAD, 0); compileContext.addStackCount(1); if (recipient != null) { // compile code for recipient recipient.compile(mv, compileContext); } else { mv.visitInsn(Opcodes.ACONST_NULL); compileContext.addStackCount(1); } // stack arg count then create a new array mv.visitLdcInsn(argCount); compileContext.addStackCount(1); // this just swaps one word for another mv.visitTypeInsn(Opcodes.ANEWARRAY, "java/lang/Object"); // duplicate the array, stack the index, compile code to generate the arg and the do an array // put for (int i = 0; i < argCount; i++) { mv.visitInsn(Opcodes.DUP); mv.visitLdcInsn(i); // that was two extra words compileContext.addStackCount(2); Expression argument = arguments.get(i); Type argType = argumentTypes.get(i); Type paramType = paramTypes.get(i); // compile code to stack argument and type convert/box if necessary argument.compile(mv, compileContext); compileTypeConversion(argType, paramType, mv, compileContext); compileBox(paramType, mv, compileContext); // that's 3 extra words which now get removed mv.visitInsn(Opcodes.AASTORE); compileContext.addStackCount(-3); } // now stack the method object index mv.visitLdcInsn(methodIndex); compileContext.addStackCount(1); // enable triggering before we call the method // this adds an extra value to the stack so modify the compile context to ensure // we increase the maximum height if necessary mv.visitMethodInsn( Opcodes.INVOKESTATIC, "org/jboss/byteman/rule/Rule", "enableTriggersInternal", "()Z"); compileContext.addStackCount(1); mv.visitInsn(Opcodes.POP); compileContext.addStackCount(-1); // ok, we now have the recipient, args array and method index on the stack // so we can call the HelperAdapter method to do the actual reflective invocation mv.visitMethodInsn( Opcodes.INVOKEINTERFACE, Type.internalName(HelperAdapter.class), "invokeAccessibleMethod", "(Ljava/lang/Object;[Ljava/lang/Object;I)Ljava/lang/Object;"); // we popped 4 words and left one in its place compileContext.addStackCount(-3); if (type == Type.VOID) { mv.visitInsn(Opcodes.POP); compileContext.addStackCount(-1); } else { // do any necessary casting and/or unboxing compileTypeConversion(Type.OBJECT, type, mv, compileContext); } // now disable triggering again // this temporarily adds an extra value to the stack -- n.b. no need to increment and // then decrement the stack height here because the previous enable call will already have // bumped the max when we had 4 slots on the stack and any return value on the stack will // occupy at most 2 slots mv.visitMethodInsn( Opcodes.INVOKESTATIC, "org/jboss/byteman/rule/Rule", "disableTriggersInternal", "()Z"); compileContext.addStackCount(1); mv.visitInsn(Opcodes.POP); compileContext.addStackCount(-1); } // ensure we have only increased the stack by the return value size if (compileContext.getStackCount() != currentStack + expected) { throw new CompileException( "MethodExpression.compile : invalid stack height " + compileContext.getStackCount() + " expecting " + (currentStack + expected)); } // no need to update max stack since compiling the recipient or arguments will // have done so (and there will be no change if there was no such compile call) }
public Type typeCheck(Type expected) throws TypeException { // if we have no recipient then we use the rule's helper as a target via a binding // to $-1. this means we can type check the call against methods of class Helper // without having to do any special case processing. TypeGroup typeGroup = getTypeGroup(); if (recipient == null && pathList != null) { // treat the pathlist as a typename or a static field dereference possibly combined with // further field dereferences // factor off a typename from the path Type rootType = typeGroup.match(pathList); if (rootType == null) { throw new TypeException( "MethodExpression.typeCheck : invalid path " + getPath(pathList.length) + " to static method " + name + getPos()); } // find out how many of the path elements are included in the type name String rootTypeName = rootType.getName(); int idx = getPathCount(rootTypeName); if (idx < pathList.length) { // create a static field reference using the type name and the first field name and wrap it // with // enough field references to use up all the path String fieldName = pathList[idx++]; Expression recipient = new StaticExpression(rule, Type.UNDEFINED, token, fieldName, rootTypeName); while (idx < pathList.length) { recipient = new FieldExpression(rule, Type.UNDEFINED, token, pathList[idx++], recipient, null); } this.recipient = recipient; } else { // ok, this method reference is actually a static method call -- record the root type for // later this.recipient = null; this.rootType = rootType; } // get rid of the path list now this.pathList = null; // not strictly necessary? if (this.recipient != null) { this.recipient.bind(); } } // if we don't have a recipient and we didn't find a static class for the method then this is // a builtin boolean isBuiltIn = false; if (recipient == null) { if (rootType == null) { isBuiltIn = true; Type ruleType = typeGroup.create(rule.getHelperClass().getCanonicalName()); recipient = new DollarExpression(rule, ruleType, token, DollarExpression.HELPER_IDX); recipient.bind(); rootType = recipient.typeCheck(Type.UNDEFINED); } } else { rootType = recipient.typeCheck(Type.UNDEFINED); } // see if we can find a method for this call findMethod(isBuiltIn); // now go back and identify the parameter types this.paramTypes = new ArrayList<Type>(); Class<?>[] paramClasses = method.getParameterTypes(); for (int i = 0; i < arguments.size(); i++) { Class<?> paramClass = paramClasses[i]; paramTypes.add(typeGroup.ensureType(paramClass)); } type = typeGroup.ensureType(method.getReturnType()); if (Type.dereference(expected).isDefined() && !expected.isAssignableFrom(type)) { throw new TypeException( "MethodExpression.typeCheck : invalid expected type " + expected.getName() + getPos()); } return type; }