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

org.neo4j.codegen.bytecode.ByteCodeVerifier Maven / Gradle / Ivy

There is a newer version: 5.26.0
Show newest version
/*
 * Copyright (c) "Neo4j"
 * Neo4j Sweden AB [http://neo4j.com]
 *
 * This file is part of Neo4j.
 *
 * Neo4j is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program 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 for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see .
 */
package org.neo4j.codegen.bytecode;

import org.objectweb.asm.ClassReader;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;
import org.objectweb.asm.tree.AbstractInsnNode;
import org.objectweb.asm.tree.ClassNode;
import org.objectweb.asm.tree.MethodNode;
import org.objectweb.asm.tree.analysis.Analyzer;
import org.objectweb.asm.tree.analysis.AnalyzerException;
import org.objectweb.asm.tree.analysis.BasicValue;
import org.objectweb.asm.tree.analysis.Frame;
import org.objectweb.asm.tree.analysis.SimpleVerifier;
import org.objectweb.asm.tree.analysis.Value;
import org.objectweb.asm.util.CheckClassAdapter;
import org.objectweb.asm.util.Textifier;
import org.objectweb.asm.util.TraceMethodVisitor;

import java.io.PrintWriter;
import java.io.StringWriter;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import java.util.function.IntFunction;
import javax.tools.Diagnostic;

import org.neo4j.codegen.ByteCodes;
import org.neo4j.codegen.CodeGeneratorOption;
import org.neo4j.codegen.CompilationFailureException;

import static org.objectweb.asm.ClassReader.SKIP_DEBUG;

@SuppressWarnings( "unused"/*used through reflection, since it depends on optional code*/ )
class ByteCodeVerifier implements ByteCodeChecker, CodeGeneratorOption
{
    /**
     * Invoked by {@link ByteCode#load(String)} to load this class.
     *
     * @return an instance of this class, if all dependencies are available.
     */
    static CodeGeneratorOption loadVerifier()
    {
        if ( Analyzer.class.getName().isEmpty() || CheckClassAdapter.class.getName().isEmpty() )
        {
            throw new AssertionError( "This code is here to ensure the optional ASM classes are on the classpath" );
        }
        return new ByteCodeVerifier();
    }

    /**
     * Add this verification step to the configuration, if applicable.
     *
     * @param target
     *         the configuration to add this verification step to.
     */
    @Override
    public void applyTo( Object target )
    {
        if ( target instanceof Configuration )
        {
            ((Configuration) target).withBytecodeChecker( this );
        }
    }

    /**
     * Check the bytecode from one round of bytecode generation.
     *
     * @param classpathLoader
     *         the ClassLoader to use for loading classes from the classpath.
     * @param byteCodes
     *         the bytecodes generated in this round.
     * @throws CompilationFailureException
     *         if any issue is discovered in the verification.
     */
    @Override
    public void check( ClassLoader classpathLoader, Collection byteCodes ) throws CompilationFailureException
    {
        List classes = new ArrayList<>( byteCodes.size() );
        List failures = new ArrayList<>();
        // load (and verify) the structure of the generated classes
        for ( ByteCodes byteCode : byteCodes )
        {
            try
            {
                classes.add( classNode( byteCode.bytes() ) );
            }
            catch ( Exception e )
            {
                failures.add( new Failure( e, e.toString() ) );
            }
        }
        // if there are problems with the structure of the generated classes,
        // we are not going to be able to verify their methods
        if ( !failures.isEmpty() )
        {
            throw compilationFailure( failures );
        }
        // continue with verifying the methods of the classes
        AssignmentChecker check = new AssignmentChecker( classpathLoader, classes );
        for ( ClassNode clazz : classes )
        {
            verify( check, clazz, failures );
        }
        if ( !failures.isEmpty() )
        {
            throw compilationFailure( failures );
        }
    }

    /**
     * Verify the methods of a single class.
     *
     * @param check
     *         a helper for verifying assignments.
     * @param clazz
     *         the class to check the methods of.
     * @param failures
     *         where any detected errors are added.
     */
    private static void verify( AssignmentChecker check, ClassNode clazz, List failures )
    {
        Verifier verifier = new Verifier( clazz, check );
        for ( MethodNode method : clazz.methods )
        {
            Analyzer analyzer = new Analyzer<>( verifier );
            try
            {
                analyzer.analyze( clazz.name, method );
            }
            catch ( Exception cause )
            {
                failures.add( new Failure( cause, detailedMessage(
                        cause.getMessage(),
                        method,
                        analyzer.getFrames(),
                        cause instanceof AnalyzerException ? ((AnalyzerException) cause).node : null ) ) );
            }
        }
    }

    private static ClassNode classNode( ByteBuffer bytecode )
    {
        byte[] bytes;
        if ( bytecode.hasArray() )
        {
            bytes = bytecode.array();
        }
        else
        {
            bytes = new byte[bytecode.limit()];
            bytecode.get( bytes );
        }
        ClassNode classNode = new ClassNode();
        new ClassReader( bytes ).accept( new CheckClassAdapter( classNode, false ), SKIP_DEBUG );
        return classNode;
    }

    private static CompilationFailureException compilationFailure( List failures )
    {
        List> diagnostics = new ArrayList<>( failures.size() );
        for ( Failure failure : failures )
        {
            diagnostics.add( new BytecodeDiagnostic( failure.message ) );
        }
        CompilationFailureException exception = new CompilationFailureException( diagnostics );
        for ( Failure failure : failures )
        {
            exception.addSuppressed( failure.cause );
        }
        return exception;
    }

    private static class Failure
    {
        final Throwable cause;
        final String message;

        Failure( Throwable cause, String message )
        {
            this.cause = cause;
            this.message = message;
        }
    }

    private static String detailedMessage(
            String errorMessage,
            MethodNode method,
            Frame[] frames,
            AbstractInsnNode errorLocation )
    {
        StringWriter message = new StringWriter();
        try ( PrintWriter out = new PrintWriter( message ) )
        {
            List localLengths = new ArrayList<>();
            List stackLengths = new ArrayList<>();
            for ( Frame frame : frames )
            {
                if ( frame != null )
                {
                    for ( int i = 0; i < frame.getLocals(); i++ )
                    {
                        insert( i, frame.getLocal( i ), localLengths );
                    }
                    for ( int i = 0; i < frame.getStackSize(); i++ )
                    {
                        insert( i, frame.getStack( i ), stackLengths );
                    }
                }
            }
            Textifier formatted = new Textifier();
            TraceMethodVisitor mv = new TraceMethodVisitor( formatted );

            out.println( errorMessage );
            out.append( "\t\tin " ).append( method.name ).append( method.desc ).println();
            for ( int i = 0; i < method.instructions.size(); i++ )
            {
                AbstractInsnNode insn = method.instructions.get( i );
                insn.accept( mv );
                Frame frame = frames[i];
                out.append( "\t\t" );
                out.append( insn == errorLocation ? ">>> " : "    " );
                out.format( "%05d [", i );
                if ( frame == null )
                {
                    padding( out, localLengths.listIterator(), '?' );
                    out.append( " : " );
                    padding( out, stackLengths.listIterator(), '?' );
                }
                else
                {
                    emit( out, localLengths, frame::getLocal, frame.getLocals() );
                    padding( out, localLengths.listIterator( frame.getLocals() ), '-' );
                    out.append( " : " );
                    emit( out, stackLengths, frame::getStack, frame.getStackSize() );
                    padding( out, stackLengths.listIterator( frame.getStackSize() ), ' ' );
                }
                out.print( "] : " );
                out.print( formatted.text.get( formatted.text.size() - 1 ) );
            }
            for ( int j = 0; j < method.tryCatchBlocks.size(); j++ )
            {
                method.tryCatchBlocks.get( j ).accept( mv );
                out.print( " " + formatted.text.get( formatted.text.size() - 1 ) );
            }
        }
        return message.toString();
    }

    private static void emit( PrintWriter out, List lengths, IntFunction valueLookup, int values )
    {
        for ( int i = 0; i < values; i++ )
        {
            if ( i > 0 )
            {
                out.append( ' ' );
            }
            String name = shortName( valueLookup.apply( i ).toString() );
            for ( int pad = lengths.get( i ) - name.length(); pad-- > 0; )
            {
                out.append( ' ' );
            }
            out.append( name );
        }
    }

    private static void padding( PrintWriter out, ListIterator lengths, char var )
    {
        while ( lengths.hasNext() )
        {
            if ( lengths.nextIndex() > 0 )
            {
                out.append( ' ' );
            }
            for ( int length = lengths.next(); length-- > 1; )
            {
                out.append( ' ' );
            }
            out.append( var );
        }
    }

    private static void insert( int i, Value value, List values )
    {
        int length = shortName( value.toString() ).length();
        while ( i >= values.size() )
        {
            values.add( 1 );
        }
        if ( length > values.get( i ) )
        {
            values.set( i, length );
        }
    }

    private static String shortName( String name )
    {
        int start = name.lastIndexOf( '/' );
        int end = name.length();
        if ( name.charAt( end - 1 ) == ';' )
        {
            end--;
        }
        return start == -1 ? name : name.substring( start + 1, end );
    }

    // This class might look failed in the IDE, but javac will accept it
    // The reason is because the base class has been re-written to work on old JVMs - generics have been dropped.
    private static class Verifier extends SimpleVerifier
    {
        private final AssignmentChecker check;

        Verifier( ClassNode clazz, AssignmentChecker check )
        {
            super( ASM6, Type.getObjectType( clazz.name ), superClass( clazz ), interfaces( clazz ),
                    isInterfaceNode( clazz ) );
            this.check = check;
        }

        @Override
        protected boolean isAssignableFrom( Type target, Type value )
        {
            return check.isAssignableFrom( target, value );
        }

        @Override
        protected boolean isSubTypeOf( BasicValue value, BasicValue expected )
        {
            return super.isSubTypeOf( value, expected ) || check
                    .invocableInterface( expected.getType(), value.getType() );
        }

        private static Type superClass( ClassNode clazz )
        {
            return clazz.superName == null ? null : Type.getObjectType( clazz.superName );
        }

        @SuppressWarnings( "unchecked" )
        private static List interfaces( ClassNode clazz )
        {
            List interfaces = new ArrayList<>( clazz.interfaces.size() );
            for ( String iFace : clazz.interfaces )
            {
                interfaces.add( Type.getObjectType( iFace ) );
            }
            return interfaces;
        }
    }

    private static class AssignmentChecker
    {
        private final ClassLoader classpathLoader;
        private final Map classes = new HashMap<>();

        AssignmentChecker( ClassLoader classpathLoader, List classes )
        {
            this.classpathLoader = classpathLoader;
            for ( ClassNode node : classes )
            {
                this.classes.put( Type.getObjectType( node.name ), node );
            }
        }

        boolean invocableInterface( Type target, Type value )
        {
            // this method allows a bit too much through,
            // it really ought to only be used for the target type of INVOKEINTERFACE,
            // since any object type is allowed as target for INVOKEINTERFACE,
            // this allows fewer CHECKCAST instructions when using generics.
            ClassNode targetNode = classes.get( target );
            if ( targetNode != null )
            {
                if ( isInterfaceNode( targetNode ) )
                {
                    return value.getSort() == Type.OBJECT || value.getSort() == Type.ARRAY;
                }
                return false;
            }
            Class targetClass = getClass( target );
            if ( targetClass.isInterface() )
            {
                return value.getSort() == Type.OBJECT || value.getSort() == Type.ARRAY;
            }
            return false;
        }

        boolean isAssignableFrom( Type target, Type value )
        {
            if ( target.equals( value ) )
            {
                return true;
            }
            ClassNode targetNode = classes.get( target );
            ClassNode valueNode = classes.get( value );
            if ( targetNode != null && valueNode == null )
            {
                // if the target is among the types we have generated and the value isn't, then
                // the value type either doesn't exist, or it is defined in the class loader, and thus cannot
                // be a subtype of the target type
                return false;
            }
            else if ( valueNode != null )
            {
                return isAssignableFrom( target, valueNode );
            }
            else
            {
                return getClass( target ).isAssignableFrom( getClass( value ) );
            }
        }

        private boolean isAssignableFrom( Type target, ClassNode value )
        {
            if ( value.superName != null && isAssignableFrom( target, Type.getObjectType( value.superName ) ) )
            {
                return true;
            }
            for ( String iFace : value.interfaces )
            {
                if ( isAssignableFrom( target, Type.getObjectType( iFace ) ) )
                {
                    return true;
                }
            }
            return false;
        }

        private Class getClass( Type type )
        {
            try
            {
                if ( type.getSort() == Type.ARRAY )
                {
                    return Class.forName( type.getDescriptor().replace( '/', '.' ), false, classpathLoader );
                }
                return Class.forName( type.getClassName(), false, classpathLoader );
            }
            catch ( ClassNotFoundException e )
            {
                throw new RuntimeException( e.toString() );
            }
        }
    }

    private static boolean isInterfaceNode( ClassNode clazz )
    {
        return (clazz.access & Opcodes.ACC_INTERFACE) != 0;
    }
}





© 2015 - 2025 Weber Informatics LLC | Privacy Policy