/** * return a checker object which can be used to retrieve the super and interfaces of a class from * its name and classloader, identifying it from the Class instance if it the class is already * loaded otherwise loading the corresponding bytecode and parsing it to obtain the relevant * details. * * @param name the name of the superclass being checked * @param baseLoader the class loader of the subclass's bytecode * @return the requisite checker or null if the class does not need to be checked or cannot be * loaded */ public org.jboss.byteman.agent.check.ClassChecker getClassChecker( String name, ClassLoader baseLoader) { // we would like to just do this // Class superClazz = baseLoader.loadClass(name) // and then access the details using methods of Class // however, this fails because we are in the middle of transforming the subclass and the // classloader // may not have loaded the super. if we force a load now then transforms will not be performed // on // the super class. this may cause us to miss the chance to apply rule injection into the super ClassLoader loader = baseLoader; Class clazz = loadCache.lookupClass(name, loader); if (clazz != null) { return new org.jboss.byteman.agent.check.LoadedClassChecker(clazz); } // ok, instead try loading the bytecode as a resource - user-defined loaders may not support // this but // at least the JVM system and boot loaders should String resourceName = name.replace('.', '/') + ".class"; try { InputStream is; if (baseLoader != null) { is = baseLoader.getResourceAsStream(resourceName); } else { is = ClassLoader.getSystemClassLoader().getResourceAsStream(resourceName); } if (is != null) { int length = is.available(); int count = 0; byte[] bytecode = new byte[length]; while (count < length) { int read = is.read(bytecode, count, length - count); if (read < 0) { throw new IOException("unexpected end of file"); } count += read; } return new org.jboss.byteman.agent.check.BytecodeChecker(bytecode); } else { // throw new IOException("unable to load bytecode for for class " + name); if (isVerbose()) { System.out.println( "Transformer.getClassChecker : unable to load bytecode for for class " + name); } return null; } } catch (IOException e) { // log the exception and return null e.printStackTrace(); return null; } }
/** * The implementation of this method may transform the supplied class file and return a new * replacement class file. * * <p>Once a transformer has been registered with {@link * java.lang.instrument.Instrumentation#addTransformer Instrumentation.addTransformer}, the * transformer will be called for every new class definition and every class redefinition. The * request for a new class definition is made with {@link ClassLoader#defineClass * ClassLoader.defineClass}. The request for a class redefinition is made with {@link * java.lang.instrument.Instrumentation#redefineClasses Instrumentation.redefineClasses} or its * native equivalents. The transformer is called during the processing of the request, before the * class file bytes have been verified or applied. * * <p>If the implementing method determines that no transformations are needed, it should return * <code>null</code>. Otherwise, it should create a new <code>byte[]</code> array, copy the input * <code>classfileBuffer</code> into it, along with all desired transformations, and return the * new array. The input <code>classfileBuffer</code> must not be modified. * * <p>In the redefine case, the transformer must support the redefinition semantics. If a class * that the transformer changed during initial definition is later redefined, the transformer must * insure that the second class output class file is a legal redefinition of the first output * class file. * * <p>If the transformer believes the <code>classFileBuffer</code> does not represent a validly * formatted class file, it should throw an <code>IllegalClassFormatException</code>. Subsequent * transformers will still be called and the load or redefine will still be attempted. Throwing an * <code>IllegalClassFormatException</code> thus has the same effect as returning null but * facilitates the logging or debugging of format corruptions. * * @param originalLoader the defining loader of the class to be transformed, may be <code>null * </code> if the bootstrap loader * @param className the name of the class in the internal form of fully qualified class and * interface names as defined in <i>The Java Virtual Machine Specification</i>. For example, * <code>"java/util/List"</code>. * @param classBeingRedefined if this is a redefine, the class being redefined, otherwise <code> * null</code> * @param protectionDomain the protection domain of the class being defined or redefined * @param classfileBuffer the input byte buffer in class file format - must not be modified * @return a well-formed class file buffer (the result of the transform), or <code>null</code> if * no transform is performed. * @throws java.lang.instrument.IllegalClassFormatException if the input does not represent a * well-formed class file * @see java.lang.instrument.Instrumentation#redefineClasses */ public byte[] transform( ClassLoader originalLoader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException { boolean enabled = true; ClassLoader loader = originalLoader; try { enabled = Rule.disableTriggersInternal(); byte[] newBuffer = classfileBuffer; // we only transform certain classes -- we do allow bootstrap classes whose loader is null // but we exclude byteman classes and java.lang classes String internalName = TypeHelper.internalizeClass(className); if (isBytemanClass(internalName) || !isTransformable(internalName)) { return null; } // we will need the super class name any outer class name and the name of the interfaces the // class implements ClassChecker checker = getClassChecker(newBuffer); // new ClassChecker(newBuffer); if (checker == null || checker.isInterface()) { return null; } /* if (checker.hasOuterClass()) { // we don't transform inner classes for now // TODO -- see if we can match and transform inner classes via the outer class return null; } */ // TODO-- reconsider this as it is a bit dodgy as far as security is concerned if (loader == null) { loader = ClassLoader.getSystemClassLoader(); } // if we need to traverse the interfaces then we have a DAG to deal with so // we had better find a way to avoid doing things twice LinkedList<String> toVisit = null; HashSet<String> visited = null; // ok, we need to check whether there are any class scripts associated with this class and if // so // we will consider transforming the byte code // TODO -- there are almost certainly concurrency issues to deal with here if rules are being // loaded/unloaded newBuffer = tryTransform(newBuffer, internalName, loader, internalName, false); int dotIdx = internalName.lastIndexOf('.'); if (dotIdx > 0) { newBuffer = tryTransform( newBuffer, internalName, loader, internalName.substring(dotIdx + 1), false); } if (scriptRepository.checkInterfaces()) { // now we need to do the same for any interface scripts // n.b. resist the temptation to call classBeingRedefined.getInterfaces() as this will // cause the class to be resolved, losing any changes we install // we need to check the transitive closure of the binary links // Class implements Interface and Interface extends Interface for this class // which in general is a DAG. toVisit = new LinkedList<String>(); visited = new HashSet<String>(); // we start with the original list of implemented interfaces int interfaceCount = checker.getInterfaceCount(); for (int i = 0; i < interfaceCount; i++) { String interfaceName = checker.getInterface(i); toVisit.add(interfaceName); } // ok now check each interface in turn while pushing its super interfaces // until we no longer have any new interfaces to check while (!toVisit.isEmpty()) { String interfaceName = toVisit.pop(); String internalInterfaceName = TypeHelper.internalizeClass(interfaceName); if (!visited.contains(interfaceName)) { // avoid visiting this interface again visited.add(interfaceName); // now see if we have any rules for this interface newBuffer = tryTransform(newBuffer, internalName, loader, internalInterfaceName, true); dotIdx = internalInterfaceName.lastIndexOf('.'); if (dotIdx >= 0) { newBuffer = tryTransform( newBuffer, internalName, loader, internalInterfaceName.substring(dotIdx + 1), true); } // check the extends list of this interface for new interfaces to consider ClassChecker newChecker = getClassChecker(interfaceName, originalLoader); if (newChecker != null) { interfaceCount = newChecker.getInterfaceCount(); for (int i = 0; i < interfaceCount; i++) { interfaceName = newChecker.getInterface(i); toVisit.add(interfaceName); } } } } } // checking supers is expensive so we obey the switch which disables it if (!skipOverrideRules()) { // ok, now check the superclass for this class and so on String superName = checker.getSuper(); while (superName != null) { // we need to check the super class structure // n.b. we use the original loader here because we don't want to search the system loader // when we have a class in the bootstrap loader checker = getClassChecker(superName, originalLoader); if (checker == null || checker.hasOuterClass()) { // we don't transform inner classes for now // TODO -- see if we can match and transform inner classes via the outer class break; } newBuffer = tryTransform(newBuffer, internalName, loader, superName, false, true); dotIdx = superName.lastIndexOf('.'); if (dotIdx > 0) { newBuffer = tryTransform( newBuffer, internalName, loader, superName.substring(dotIdx + 1), false, true); } if (scriptRepository.checkInterfaces()) { // we need to do another DAG visit but only for interfaces not already considered int interfaceCount = checker.getInterfaceCount(); for (int i = 0; i < interfaceCount; i++) { String interfaceName = checker.getInterface(i); toVisit.add(interfaceName); } // ok now check each interface in turn while pushing its super interfaces // until we no longer have any new interfaces to check while (!toVisit.isEmpty()) { String interfaceName = toVisit.pop(); String internalInterfaceName = TypeHelper.internalizeClass(interfaceName); if (!visited.contains(interfaceName)) { // avoid visiting this interface again visited.add(interfaceName); // now see if we have any rules for this interface newBuffer = tryTransform( newBuffer, internalName, loader, internalInterfaceName, true, true); dotIdx = interfaceName.lastIndexOf('.'); if (dotIdx >= 0) { newBuffer = tryTransform( newBuffer, internalName, loader, internalInterfaceName.substring(dotIdx + 1), true, true); } // check the extends list of this interface for new interfaces to consider ClassChecker newChecker = getClassChecker(interfaceName, originalLoader); if (newChecker != null) { interfaceCount = newChecker.getInterfaceCount(); for (int i = 0; i < interfaceCount; i++) { interfaceName = newChecker.getInterface(i); toVisit.add(interfaceName); } } } } } // move on to the next super superName = checker.getSuper(); } } if (newBuffer != classfileBuffer) { // see if we need to dump the transformed bytecode for checking maybeDumpClass(internalName, newBuffer); newBuffer = maybeVerifyTransformedBytes(originalLoader, internalName, protectionDomain, newBuffer); return newBuffer; } else { return null; } } finally { if (enabled) { Rule.enableTriggersInternal(); } } }