org.sonar.java.resolve.BytecodeVisitor.java Source code

Java tutorial

Introduction

Here is the source code for org.sonar.java.resolve.BytecodeVisitor.java

Source

/*
 * SonarQube Java
 * Copyright (C) 2012-2017 SonarSource SA
 * mailto:info AT sonarsource DOT com
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 */
package org.sonar.java.resolve;

import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists;
import org.objectweb.asm.AnnotationVisitor;
import org.objectweb.asm.Attribute;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.FieldVisitor;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.TypePath;
import org.objectweb.asm.signature.SignatureReader;
import org.objectweb.asm.signature.SignatureVisitor;
import org.sonar.api.utils.log.Logger;
import org.sonar.api.utils.log.Loggers;

import javax.annotation.Nullable;

import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

public class BytecodeVisitor extends ClassVisitor {

    private static final Logger LOG = Loggers.get(BytecodeVisitor.class);
    private final Symbols symbols;
    private final JavaSymbol.TypeJavaSymbol classSymbol;
    private final ParametrizedTypeCache parametrizedTypeCache;
    private BytecodeCompleter bytecodeCompleter;
    /**
     * Name of current class in a format as it appears in bytecode, i.e. "org/example/MyClass$InnerClass".
     */
    private String className;

    BytecodeVisitor(BytecodeCompleter bytecodeCompleter, Symbols symbols, JavaSymbol.TypeJavaSymbol classSymbol,
            ParametrizedTypeCache parametrizedTypeCache) {
        super(Opcodes.ASM5);
        this.bytecodeCompleter = bytecodeCompleter;
        this.symbols = symbols;
        this.classSymbol = classSymbol;
        this.parametrizedTypeCache = parametrizedTypeCache;
    }

    private JavaSymbol.TypeJavaSymbol getClassSymbol(String bytecodeName) {
        return bytecodeCompleter.getClassSymbol(Convert.flatName(bytecodeName));
    }

    private JavaSymbol.TypeJavaSymbol getClassSymbol(JavaSymbol.TypeJavaSymbol classSymbol, String bytecodeName,
            int flags) {
        return bytecodeCompleter.getClassSymbol(classSymbol, Convert.flatName(bytecodeName), flags);
    }

    private JavaSymbol.TypeJavaSymbol getClassSymbol(String bytecodeName, int flags) {
        return bytecodeCompleter.getClassSymbol(Convert.flatName(bytecodeName), flags);
    }

    @Override
    public void visit(int version, int flags, String name, @Nullable String signature, @Nullable String superName,
            @Nullable String[] interfaces) {
        Preconditions.checkState(name.endsWith(classSymbol.name), "Name : '%s' should ends with %s", name,
                classSymbol.name);
        Preconditions.checkState(name.endsWith("package-info") || !BytecodeCompleter.isSynthetic(flags),
                "%s is synthetic", name);
        className = name;
        if (signature != null) {
            SignatureReader signatureReader = new SignatureReader(signature);
            signatureReader.accept(new TypeParameterDeclaration(classSymbol));
            ReadGenericSignature readGenericSignature = new ReadGenericSignature();
            signatureReader.accept(readGenericSignature);
            ((ClassJavaType) classSymbol.type).interfaces = readGenericSignature.interfaces();
        } else {
            if (superName == null) {
                Preconditions.checkState("java/lang/Object".equals(className),
                        "superName must be null only for java/lang/Object, but not for %s", className);
                // TODO(Godin): what about interfaces and annotations
            } else {
                ((ClassJavaType) classSymbol.type).supertype = getClassSymbol(superName).type;
            }
            ((ClassJavaType) classSymbol.type).interfaces = getCompletedClassSymbolsType(interfaces);

        }
        //if class has already access flags set (inner class) then do not reset those.
        //The important access flags are the one defined in the outer class.
        if ((classSymbol.flags & Flags.ACCESS_FLAGS) != 0) {
            classSymbol.flags |= bytecodeCompleter.filterBytecodeFlags(flags & ~Flags.ACCESS_FLAGS);
        } else {
            classSymbol.flags |= bytecodeCompleter.filterBytecodeFlags(flags);
        }
        classSymbol.members = new Scope(classSymbol);

    }

    @Override
    public void visitSource(@Nullable String source, @Nullable String debug) {
        throw new IllegalStateException();
    }

    /**
     * {@inheritDoc}
     * <p/>
     * In other words must be called only for anonymous classes or named classes declared within methods,
     * which must not be processed by {@link org.sonar.java.resolve.BytecodeCompleter}, therefore this method always throws {@link IllegalStateException}.
     *
     * @throws IllegalStateException always
     */
    @Override
    public void visitOuterClass(String owner, String name, String desc) {
        throw new IllegalStateException();
    }

    @Override
    public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
        JavaType annotationType = convertAsmType(org.objectweb.asm.Type.getType(desc));
        AnnotationInstanceResolve annotationInstance = new AnnotationInstanceResolve(annotationType.getSymbol());
        classSymbol.metadata().addAnnotation(annotationInstance);
        return new BytecodeAnnotationVisitor(annotationInstance, this);
    }

    @Override
    public AnnotationVisitor visitTypeAnnotation(int typeRef, TypePath typePath, String desc, boolean visible) {
        // (Godin): can return AnnotationVisitor to read annotations
        return null;
    }

    @Override
    public void visitAttribute(Attribute attr) {
        // skip non standard attributes
    }

    @Override
    public void visitInnerClass(String name, @Nullable String outerName, @Nullable String innerName, int flags) {
        if (!BytecodeCompleter.isSynthetic(flags)) {
            // TODO what about flags?
            if (innerName == null) {
                // anonymous class
            } else if (outerName == null) {
                // named class declared within method
            } else if (className.equals(outerName)) {
                defineInnerClass(name, flags);
            } else if (className.equals(name)) {
                defineOuterClass(outerName, innerName, flags);
            } else {
                // FIXME(Godin): for example if loading started from "C1.C2.C3" in case of
                // class C1 { class C2 { class C3 { } } }
                // then name="C1$C2", outerName="C1" and innerName="C3"
            }
        }
    }

    /**
     * Invoked when current class classified as outer class of some inner class.
     * Adds inner class as member.
     */
    private void defineInnerClass(String bytecodeName, int flags) {
        JavaSymbol.TypeJavaSymbol innerClass = getClassSymbol(classSymbol, bytecodeName, flags);
        innerClass.flags |= bytecodeCompleter.filterBytecodeFlags(flags);
        Preconditions.checkState(innerClass.owner == classSymbol, "Innerclass: " + innerClass.owner.getName()
                + " and classSymbol: " + classSymbol.getName() + " are not the same.");
        classSymbol.members.enter(innerClass);
    }

    /**
     * Invoked when current class classified as inner class.
     * Owner of inner classes - is some outer class,
     * which is either already completed, and thus already has this inner class as member,
     * either will be completed by {@link org.sonar.java.resolve.BytecodeCompleter}, and thus will have this inner class as member (see {@link #defineInnerClass(String, int)}).
     */
    private void defineOuterClass(String outerName, String innerName, int flags) {
        JavaSymbol.TypeJavaSymbol outerClassSymbol = getClassSymbol(outerName, flags);
        Preconditions.checkState(
                outerClassSymbol.completer == null || outerClassSymbol.completer instanceof BytecodeCompleter);
        classSymbol.name = innerName;
        classSymbol.flags = flags | bytecodeCompleter.filterBytecodeFlags(classSymbol.flags & ~Flags.ACCESS_FLAGS);
        classSymbol.owner = outerClassSymbol;
    }

    @Override
    public FieldVisitor visitField(int flags, String name, String desc, @Nullable String signature,
            @Nullable Object value) {
        Preconditions.checkNotNull(name);
        Preconditions.checkNotNull(desc);
        if (!BytecodeCompleter.isSynthetic(flags)) {
            //Flags from asm lib are defined in Opcodes class and map to flags defined in Flags class
            final JavaSymbol.VariableJavaSymbol symbol = new JavaSymbol.VariableJavaSymbol(
                    bytecodeCompleter.filterBytecodeFlags(flags), name,
                    convertAsmType(org.objectweb.asm.Type.getType(desc)), classSymbol);
            classSymbol.members.enter(symbol);
            if (signature != null) {
                ReadType typeReader = new ReadType();
                new SignatureReader(signature).accept(typeReader);
                symbol.type = typeReader.typeRead;
            }
            // checks for annotations on the field
            return new BytecodeFieldVisitor(symbol, this);
        }
        return null;
    }

    @Override
    public MethodVisitor visitMethod(int flags, String name, String desc, @Nullable String signature,
            @Nullable String[] exceptions) {
        Preconditions.checkNotNull(name);
        Preconditions.checkNotNull(desc);
        if (!BytecodeCompleter.isSynthetic(flags)) {
            if ((flags & Opcodes.ACC_BRIDGE) != 0) {
                LOG.warn("bridge method {} not marked as synthetic in class {}", name, className);
                return null;
            }
            // TODO(Godin): according to JVMS 4.7.24 - parameter can be marked as synthetic
            MethodJavaType type = new MethodJavaType(convertAsmTypes(org.objectweb.asm.Type.getArgumentTypes(desc)),
                    convertAsmType(org.objectweb.asm.Type.getReturnType(desc)),
                    getCompletedClassSymbolsType(exceptions), classSymbol);
            final JavaSymbol.MethodJavaSymbol methodSymbol = new JavaSymbol.MethodJavaSymbol(
                    bytecodeCompleter.filterBytecodeFlags(flags), name, type, classSymbol);
            classSymbol.members.enter(methodSymbol);
            if (signature != null) {
                SignatureReader signatureReader = new SignatureReader(signature);
                signatureReader.accept(new TypeParameterDeclaration(methodSymbol));
                signatureReader.accept(new ReadMethodSignature(methodSymbol));
            }
            methodSymbol.parameters = new Scope(methodSymbol);
            for (int i = 0; i < type.argTypes.size(); i += 1) {
                methodSymbol.parameters.enter(new JavaSymbol.VariableJavaSymbol(0, "arg" + i, methodSymbol));
            }
            // checks for annotations on the method and its parameters
            return new BytecodeMethodVisitor(methodSymbol, this);
        }
        return null;
    }

    private List<JavaType> convertAsmTypes(org.objectweb.asm.Type[] asmTypes) {
        ImmutableList.Builder<JavaType> result = ImmutableList.builder();
        for (org.objectweb.asm.Type asmType : asmTypes) {
            result.add(convertAsmType(asmType));
        }
        return result.build();
    }

    public JavaType convertAsmType(org.objectweb.asm.Type asmType) {
        JavaType result;
        switch (asmType.getSort()) {
        case org.objectweb.asm.Type.OBJECT:
            result = getClassSymbol(asmType.getInternalName()).type;
            break;
        case org.objectweb.asm.Type.BYTE:
            result = symbols.byteType;
            break;
        case org.objectweb.asm.Type.CHAR:
            result = symbols.charType;
            break;
        case org.objectweb.asm.Type.SHORT:
            result = symbols.shortType;
            break;
        case org.objectweb.asm.Type.INT:
            result = symbols.intType;
            break;
        case org.objectweb.asm.Type.LONG:
            result = symbols.longType;
            break;
        case org.objectweb.asm.Type.FLOAT:
            result = symbols.floatType;
            break;
        case org.objectweb.asm.Type.DOUBLE:
            result = symbols.doubleType;
            break;
        case org.objectweb.asm.Type.BOOLEAN:
            result = symbols.booleanType;
            break;
        case org.objectweb.asm.Type.ARRAY:
            result = buildArrayType(convertAsmType(asmType.getElementType()), asmType.getDimensions());
            break;
        case org.objectweb.asm.Type.VOID:
            result = symbols.voidType;
            break;
        default:
            throw new IllegalArgumentException(asmType.toString());
        }
        return result;
    }

    private JavaType buildArrayType(JavaType elementType, int dimensions) {
        ArrayJavaType type = new ArrayJavaType(elementType, symbols.arrayClass);
        for (int i = 1; i < dimensions; i++) {
            type = new ArrayJavaType(type, symbols.arrayClass);
        }
        return type;
    }

    /**
     * If at this point there is no owner of current class, then this is a top-level class,
     * because outer classes always will be completed before inner classes - see {@link #defineOuterClass(String, String, int)}.
     * Owner of top-level classes - is a package.
     */
    @Override
    public void visitEnd() {
        if (classSymbol.owner == null) {
            String flatName = className.replace('/', '.');
            classSymbol.name = flatName.substring(flatName.lastIndexOf('.') + 1);
            classSymbol.owner = bytecodeCompleter.enterPackage(flatName);
            JavaSymbol.PackageJavaSymbol owner = (JavaSymbol.PackageJavaSymbol) classSymbol.owner;
            if (owner.members == null) {
                // package was without classes so far
                owner.members = new Scope(owner);
            }
            owner.members.enter(classSymbol);
        }
    }

    private List<JavaType> getCompletedClassSymbolsType(@Nullable String[] bytecodeNames) {
        if (bytecodeNames == null) {
            return ImmutableList.of();
        }
        return Arrays.stream(bytecodeNames).map(bName -> getClassSymbol(bName).type).collect(Collectors.toList());
    }

    private class ReadGenericSignature extends SignatureVisitor {

        JavaSymbol.TypeVariableJavaSymbol typeVariableSymbol;
        List<JavaType> bounds;
        ImmutableList.Builder<JavaType> interfaces;

        public ReadGenericSignature() {
            super(Opcodes.ASM5);
            interfaces = ImmutableList.builder();
        }

        @Override
        public void visitFormalTypeParameter(String name) {
            List<JavaSymbol> lookup = classSymbol.typeParameters.lookup(name);
            int lookupSize = lookup.size();
            Preconditions.checkState(lookupSize == 1, "found %s instead of 1", lookupSize);
            typeVariableSymbol = (JavaSymbol.TypeVariableJavaSymbol) lookup.iterator().next();
            bounds = ((TypeVariableJavaType) typeVariableSymbol.type).bounds;
        }

        @Override
        public SignatureVisitor visitSuperclass() {
            return new ReadType() {
                @Override
                public void visitEnd() {
                    super.visitEnd();
                    ((ClassJavaType) classSymbol.type).supertype = typeRead;
                }
            };
        }

        @Override
        public SignatureVisitor visitInterface() {
            return new ReadType() {
                @Override
                public void visitEnd() {
                    super.visitEnd();
                    interfaces.add(typeRead);
                }
            };
        }

        @Override
        public SignatureVisitor visitInterfaceBound() {
            return boundVisitor();
        }

        @Override
        public SignatureVisitor visitClassBound() {
            return boundVisitor();
        }

        private ReadType boundVisitor() {
            return new ReadType() {
                @Override
                public void visitEnd() {
                    super.visitEnd();
                    if (bounds != null) {
                        bounds.add(typeRead);
                    }
                }
            };
        }

        @Override
        public void visitClassType(String name) {
            if (bounds != null) {
                bounds.add(getClassSymbol(name).type);
            }
        }

        @Override
        public void visitEnd() {
            if (typeVariableSymbol != null) {
                if (bounds.isEmpty()) {
                    bounds.add(symbols.objectType);
                }
                typeVariableSymbol = null;
                bounds = null;
            }
        }

        public List<JavaType> interfaces() {
            return interfaces.build();
        }
    }

    private static class TypeParameterDeclaration extends SignatureVisitor {

        private final JavaSymbol symbol;

        public TypeParameterDeclaration(JavaSymbol symbol) {
            super(Opcodes.ASM5);
            this.symbol = symbol;
            if (symbol.isTypeSymbol()) {
                ((JavaSymbol.TypeJavaSymbol) symbol).typeParameters = new Scope(symbol);
            } else if (symbol.isMethodSymbol()) {
                ((JavaSymbol.MethodJavaSymbol) symbol).typeParameters = new Scope(symbol);
            }
        }

        @Override
        public void visitFormalTypeParameter(String name) {
            JavaSymbol.TypeVariableJavaSymbol typeVariableSymbol = new JavaSymbol.TypeVariableJavaSymbol(name,
                    symbol);
            ((TypeVariableJavaType) typeVariableSymbol.type).bounds = Lists.newArrayList();
            if (symbol.isTypeSymbol()) {
                JavaSymbol.TypeJavaSymbol typeJavaSymbol = (JavaSymbol.TypeJavaSymbol) symbol;
                typeJavaSymbol.typeParameters.enter(typeVariableSymbol);
                typeJavaSymbol.addTypeParameter((TypeVariableJavaType) typeVariableSymbol.type);
            } else if (symbol.isMethodSymbol()) {
                JavaSymbol.MethodJavaSymbol methodSymbol = (JavaSymbol.MethodJavaSymbol) symbol;
                methodSymbol.typeParameters.enter(typeVariableSymbol);
                methodSymbol.addTypeParameter((TypeVariableJavaType) typeVariableSymbol.type);
            }
        }
    }

    private class ReadMethodSignature extends SignatureVisitor {

        private final JavaSymbol.MethodJavaSymbol methodSymbol;
        private int exceptionRead = 0;

        JavaSymbol.TypeVariableJavaSymbol typeVariableSymbol;
        List<JavaType> bounds;

        public ReadMethodSignature(JavaSymbol.MethodJavaSymbol methodSymbol) {
            super(Opcodes.ASM5);
            this.methodSymbol = methodSymbol;
            ((MethodJavaType) methodSymbol.type).argTypes = Lists.newArrayList();
        }

        @Override
        public void visitFormalTypeParameter(String name) {
            List<JavaSymbol> lookup = methodSymbol.typeParameters.lookup(name);
            int lookupSize = lookup.size();
            Preconditions.checkState(lookupSize == 1, "found %s instead of 1", lookupSize);
            typeVariableSymbol = (JavaSymbol.TypeVariableJavaSymbol) lookup.iterator().next();
            bounds = ((TypeVariableJavaType) typeVariableSymbol.type).bounds;
        }

        @Override
        public SignatureVisitor visitClassBound() {
            return new ReadType(methodSymbol) {
                @Override
                public void visitEnd() {
                    super.visitEnd();
                    bounds.add(typeRead);
                }
            };
        }

        @Override
        public SignatureVisitor visitInterfaceBound() {
            return new ReadType(methodSymbol) {
                @Override
                public void visitEnd() {
                    super.visitEnd();
                    bounds.add(typeRead);
                }
            };
        }

        @Override
        public SignatureVisitor visitParameterType() {
            return new ReadType(methodSymbol) {
                @Override
                public void visitEnd() {
                    super.visitEnd();
                    ((MethodJavaType) methodSymbol.type).argTypes.add(typeRead);
                }
            };
        }

        @Override
        public SignatureVisitor visitReturnType() {
            return new ReadType(methodSymbol) {
                @Override
                public void visitEnd() {
                    super.visitEnd();
                    ((MethodJavaType) methodSymbol.type).resultType = typeRead;
                    methodSymbol.returnType = typeRead.symbol;
                }
            };
        }

        @Override
        public SignatureVisitor visitExceptionType() {
            return new ReadType(methodSymbol) {
                @Override
                public void visitEnd() {
                    super.visitEnd();
                    ((MethodJavaType) methodSymbol.type).thrown.remove(exceptionRead);
                    ((MethodJavaType) methodSymbol.type).thrown.add(exceptionRead, typeRead);
                    exceptionRead++;

                }
            };
        }

        @Override
        public void visitEnd() {
            if (typeVariableSymbol != null) {
                if (bounds.isEmpty()) {
                    bounds.add(symbols.objectType);
                }
                typeVariableSymbol = null;
                bounds = null;
            }
        }

    }

    private class ReadType extends SignatureVisitor {
        @Nullable
        private final JavaSymbol.MethodJavaSymbol methodSymbol;
        JavaType typeRead;
        List<JavaType> typeArguments = Lists.newArrayList();

        public ReadType() {
            super(Opcodes.ASM5);
            this.methodSymbol = null;
        }

        public ReadType(@Nullable JavaSymbol.MethodJavaSymbol methodSymbol) {
            super(Opcodes.ASM5);
            this.methodSymbol = methodSymbol;
        }

        @Override
        public void visitClassType(String name) {
            typeRead = getClassSymbol(name).type;
        }

        @Override
        public void visitTypeArgument() {
            // is called only when using unbounded wildcard '<?>'
            this.typeArguments.add(symbols.unboundedWildcard);
        }

        @Override
        public SignatureVisitor visitTypeArgument(final char wildcard) {
            return new ReadType(methodSymbol) {
                @Override
                public void visitEnd() {
                    super.visitEnd();
                    JavaType typeArgument;
                    switch (wildcard) {
                    case EXTENDS:
                        typeArgument = parametrizedTypeCache.getWildcardType(this.typeRead,
                                WildCardType.BoundType.EXTENDS);
                        break;
                    case SUPER:
                        typeArgument = parametrizedTypeCache.getWildcardType(this.typeRead,
                                WildCardType.BoundType.SUPER);
                        break;
                    case INSTANCEOF:
                    default:
                        typeArgument = this.typeRead;
                        break;
                    }
                    ReadType.this.typeArguments.add(typeArgument);
                }
            };
        }

        @Override
        public SignatureVisitor visitArrayType() {
            return new ReadType(methodSymbol) {
                @Override
                public void visitEnd() {
                    super.visitEnd();
                    ReadType.this.typeRead = new ArrayJavaType(typeRead, symbols.arrayClass);
                    ReadType.this.visitEnd();
                }
            };
        }

        @Override
        public void visitBaseType(char descriptor) {
            typeRead = symbols.getPrimitiveFromDescriptor(descriptor);
            visitEnd();
        }

        @Override
        public void visitTypeVariable(String name) {
            List<JavaSymbol> lookup = Lists.newArrayList();
            JavaSymbol currentSymbol = classSymbol;
            if (methodSymbol != null) {
                currentSymbol = methodSymbol;
            }
            while ((currentSymbol.isKind(JavaSymbol.TYP) || currentSymbol.isKind(JavaSymbol.MTH))
                    && lookup.isEmpty()) {
                if (currentSymbol.isKind(JavaSymbol.MTH)) {
                    lookup = ((JavaSymbol.MethodJavaSymbol) currentSymbol).typeParameters().lookup(name);
                } else if (currentSymbol.isKind(JavaSymbol.TYP)) {
                    lookup = ((JavaSymbol.TypeJavaSymbol) currentSymbol).typeParameters().lookup(name);
                }
                currentSymbol = currentSymbol.owner();
            }

            Preconditions.checkState(!lookup.isEmpty(), "Could not resolve type parameter: %s in class %s", name,
                    classSymbol.getName());
            Preconditions.checkState(lookup.size() == 1, "More than one type parameter with the same name");
            typeRead = lookup.get(0).type;
            visitEnd();
        }

        @Override
        public void visitEnd() {
            if (!typeArguments.isEmpty()) {
                JavaSymbol.TypeJavaSymbol readSymbol = typeRead.symbol;
                readSymbol.complete();
                //Mismatch between type variable and type arguments means we are lacking some pieces of bytecode to resolve substitution properly.
                if (typeArguments.size() == readSymbol.typeVariableTypes.size()) {
                    TypeSubstitution substitution = new TypeSubstitution();
                    int i = 0;
                    for (JavaType typeArgument : typeArguments) {
                        substitution.add(readSymbol.typeVariableTypes.get(i), typeArgument);
                        i++;
                    }
                    typeRead = parametrizedTypeCache.getParametrizedTypeType(readSymbol, substitution);
                }
            }
        }
    }

}