All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.nerdvision.agent.inst.asm.PluginVisitor Maven / Gradle / Ivy

package com.nerdvision.agent.inst.asm;

import com.nerdvision.agent.api.plugins.IMatcher;
import com.nerdvision.agent.plugins.MethodHook;
import java.com.nerdvision.agent.snapshot.ProxyMethodHook;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Type;
import org.objectweb.asm.commons.JSRInlinerAdapter;
import org.objectweb.asm.tree.InsnList;
import org.objectweb.asm.tree.InsnNode;
import org.objectweb.asm.tree.LdcInsnNode;
import org.objectweb.asm.tree.MethodInsnNode;
import org.objectweb.asm.tree.MethodNode;
import org.objectweb.asm.tree.TypeInsnNode;
import org.objectweb.asm.tree.VarInsnNode;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import static com.nerdvision.agent.inst.asm.Visitor.loadVariable;
import static java.lang.reflect.Modifier.isStatic;
import static org.objectweb.asm.Opcodes.ACONST_NULL;
import static org.objectweb.asm.Opcodes.ALOAD;
import static org.objectweb.asm.Opcodes.ASM7;
import static org.objectweb.asm.Opcodes.ASM8;
import static org.objectweb.asm.Opcodes.DUP;
import static org.objectweb.asm.Opcodes.INVOKESPECIAL;
import static org.objectweb.asm.Opcodes.INVOKESTATIC;
import static org.objectweb.asm.Opcodes.INVOKEVIRTUAL;
import static org.objectweb.asm.Opcodes.NEW;
import static org.objectweb.asm.Opcodes.POP;

/**
 * This visitor will process plugin calls to inject hooks into methods
 */
public class PluginVisitor extends ClassVisitor
{
    private static final Logger LOGGER = LoggerFactory.getLogger( PluginVisitor.class );

    public static final Class CALLBACK_CLASS;

    static
    {
        // this is here to make the tests easier.
        // we cannot use java. classes in the tests without screwing with the class loaders
        // so in the tests we use the 'nv.hook.class' which is the MethodHook.class
        // at runtime we use the ProxyMethodHook.class so we can bypass the osgi classloading restrictions
        final String property = System.getProperty( "nv.hook.class" );
        if( property == null )
        {
            CALLBACK_CLASS = ProxyMethodHook.class;
        }
        else
        {
            Class callbackClass;
            try
            {
                callbackClass = Class.forName( property );
            }
            catch( ClassNotFoundException e )
            {
                callbackClass = ProxyMethodHook.class;
            }
            CALLBACK_CLASS = callbackClass;
        }
    }

    private final String className;
    private final IMatcher plugin;
    private boolean changed = false;


    public PluginVisitor( final ClassVisitor v, final String className, final IMatcher plugin )
    {
        super( ASM8, v );
        this.className = className;
        this.plugin = plugin;
    }


    public MethodVisitor visitMethod( int access, String name, String desc, String signature, String[] exceptions )
    {
        // we need to ignore abstract methods
        if( TransformerUtils.isAbstract( access ) )
        {
            return super.visitMethod( access, name, desc, signature, exceptions );
        }

        // is this a method we want?
        final IMatcher.IMethodHook iMethodHook = this.plugin.matchesMethod( name, desc );
        if( iMethodHook == null )
        {
            return super.visitMethod( access, name, desc, signature, exceptions );
        }
        else
        {
            // register the extension and inject call
            final String uuid = UUID.randomUUID().toString();
            MethodHook.register( uuid, iMethodHook );
            final MethodVisitor methodVisitor = super.visitMethod( access, name, desc, signature, exceptions );
            final JSRInlinerAdapter jsrInlinerAdapter = new JSRInlinerAdapter( methodVisitor, access, name, desc, signature,
                    exceptions );
            return new MethodNode( ASM7, access, name, desc, signature, exceptions )
            {
                @Override
                public void visitEnd()
                {
                    final InsnList hook = new InsnList();

                    // add id
                    hook.add( new LdcInsnNode( uuid ) );

                    // add this
                    final boolean isStatic = isStatic( access );
                    if( !isStatic )
                    {
                        // var = this
                        hook.add( new VarInsnNode( ALOAD, 0 ) );
                    }
                    else
                    {
                        // var = null
                        hook.add( new InsnNode( ACONST_NULL ) );
                    }

                    // list = new ArrayList();
                    hook.add( new TypeInsnNode( NEW, Type.getInternalName( ArrayList.class ) ) );
                    hook.add( new InsnNode( DUP ) );
                    hook.add( new MethodInsnNode( INVOKESPECIAL, Type.getInternalName( ArrayList.class ), "",
                            "()V", false ) );

                    final Type[] argumentTypes = Type.getArgumentTypes( desc );
                    for( int i = 0; i < argumentTypes.length; i++ )
                    {
                        // list.add(param);
                        final Type argumentType = argumentTypes[i];
                        LOGGER.debug( "visitMethod capture param {} {}", className, name );
                        hook.add( new InsnNode( DUP ) ); // we need a ptr to our list
                        hook.add( loadVariable( argumentType, i + 1 ) ); // value
                        // call list.add(value)
                        hook.add( new MethodInsnNode( INVOKEVIRTUAL, Type.getInternalName( ArrayList.class ), "add",
                                "(Ljava/lang/Object;)Z" ) );
                        hook.add( new InsnNode( POP ) ); // dont care about return
                    }

                    // MethodHook.callHook(id, this, list)
                    hook.add( new MethodInsnNode( INVOKESTATIC, Type.getInternalName( CALLBACK_CLASS ),
                            "callHook",
                            Type.getMethodDescriptor( Type.VOID_TYPE, Type.getType( String.class ), Type.getType( Object.class ),
                                    Type.getType( List.class ) ),
                            false ) );

                    changed = true;
                    instructions.insert( hook );

                    this.accept( jsrInlinerAdapter );

                    super.visitEnd();
                }
            };
        }
    }


    public boolean wasChanged()
    {
        return changed;
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy