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

org.sonar.java.resolve.FirstPass Maven / Gradle / Ivy

The newest version!
/*
 * SonarQube Java
 * Copyright (C) 2012 SonarSource
 * [email protected]
 *
 * 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  02
 */
package org.sonar.java.resolve;

import com.google.common.base.Preconditions;
import com.google.common.collect.Lists;
import org.sonar.java.ast.api.JavaPunctuator;
import org.sonar.java.model.declaration.ClassTreeImpl;
import org.sonar.java.model.declaration.MethodTreeImpl;
import org.sonar.java.model.declaration.VariableTreeImpl;
import org.sonar.java.resolve.Scope.OrderedScope;
import org.sonar.plugins.java.api.tree.AnnotationTree;
import org.sonar.plugins.java.api.tree.BaseTreeVisitor;
import org.sonar.plugins.java.api.tree.BlockTree;
import org.sonar.plugins.java.api.tree.CatchTree;
import org.sonar.plugins.java.api.tree.ClassTree;
import org.sonar.plugins.java.api.tree.CompilationUnitTree;
import org.sonar.plugins.java.api.tree.EnumConstantTree;
import org.sonar.plugins.java.api.tree.ExpressionTree;
import org.sonar.plugins.java.api.tree.ForEachStatement;
import org.sonar.plugins.java.api.tree.ForStatementTree;
import org.sonar.plugins.java.api.tree.IdentifierTree;
import org.sonar.plugins.java.api.tree.ImportTree;
import org.sonar.plugins.java.api.tree.LambdaExpressionTree;
import org.sonar.plugins.java.api.tree.MethodTree;
import org.sonar.plugins.java.api.tree.Modifier;
import org.sonar.plugins.java.api.tree.ModifiersTree;
import org.sonar.plugins.java.api.tree.Tree;
import org.sonar.plugins.java.api.tree.TypeParameterTree;
import org.sonar.plugins.java.api.tree.VariableTree;

import java.util.Collections;
import java.util.List;

/**
 * Defines scopes and symbols.
 */
public class FirstPass extends BaseTreeVisitor {

  private final SemanticModel semanticModel;

  private final List uncompleted = Lists.newArrayList();
  private final SecondPass completer;
  private final Symbols symbols;
  private Resolve resolve;

  /**
   * Environment.
   * {@code env.scope.symbol} - enclosing symbol.
   */
  private Resolve.Env env;

  public FirstPass(SemanticModel semanticModel, Symbols symbols, Resolve resolve, ParametrizedTypeCache parametrizedTypeCache, TypeAndReferenceSolver typeAndReferenceSolver) {
    this.semanticModel = semanticModel;
    this.resolve = resolve;
    this.completer = new SecondPass(semanticModel, symbols, parametrizedTypeCache, typeAndReferenceSolver);
    this.symbols = symbols;
  }

  private void restoreEnvironment(Tree tree) {
    if (env.next == null) {
      // Invariant: env.next == null for CompilationUnit
      Preconditions.checkState(tree.is(Tree.Kind.COMPILATION_UNIT));
    } else {
      env = env.next;
    }
  }

  public void completeSymbols() {
    for (JavaSymbol symbol : uncompleted) {
      symbol.complete();
    }
    uncompleted.clear();
  }


  @Override
  public void visitCompilationUnit(CompilationUnitTree tree) {
    JavaSymbol.PackageJavaSymbol compilationUnitPackage = symbols.defaultPackage;

    ExpressionTree packageName = tree.packageName();
    if (packageName != null) {
      PackageResolverVisitor packageResolver = new PackageResolverVisitor();
      packageName.accept(packageResolver);
      compilationUnitPackage = (JavaSymbol.PackageJavaSymbol) resolve.findIdentInPackage(compilationUnitPackage, packageResolver.packageName, JavaSymbol.PCK);
      semanticModel.associateSymbol(packageName, compilationUnitPackage);
    }
    compilationUnitPackage.members = new Scope(compilationUnitPackage);

    env = new Resolve.Env();
    env.packge = compilationUnitPackage;
    env.scope = compilationUnitPackage.members;
    env.namedImports = new Scope(compilationUnitPackage);
    env.starImports = resolve.createStarImportScope(compilationUnitPackage);
    env.staticStarImports = resolve.createStaticStarImportScope(compilationUnitPackage);
    semanticModel.associateEnv(tree, env);

    super.visitCompilationUnit(tree);
    restoreEnvironment(tree);
    resolveImports(tree.imports());
    completeSymbols();
  }

  private class PackageResolverVisitor extends BaseTreeVisitor {
    private String packageName;

    public PackageResolverVisitor() {
      packageName = "";
    }

    @Override
    public void visitIdentifier(IdentifierTree tree) {
      if (!packageName.isEmpty()) {
        packageName += ".";
      }
      packageName += tree.name();
    }
  }

  private void resolveImports(List imports) {
    ImportResolverVisitor importResolverVisitor = new ImportResolverVisitor();
    for (ImportTree importTree : imports) {
      importTree.accept(importResolverVisitor);
    }
  }

  private class ImportResolverVisitor extends BaseTreeVisitor {
    private JavaSymbol currentSymbol;
    private List resolved;
    private boolean isStatic;

    @Override
    public void visitImport(ImportTree tree) {
      //reset currentSymbol to default package
      currentSymbol = symbols.defaultPackage;
      isStatic = tree.isStatic();
      super.visitImport(tree);
      //Associate symbol only if found.
      if (currentSymbol.kind < JavaSymbol.ERRONEOUS) {
        enterSymbol(currentSymbol, tree);
      } else {
        if (isStatic) {
          for (JavaSymbol symbol : resolved) {
            //add only static fields
            //TODO accessibility should be checked : package/public
            if ((symbol.flags & Flags.STATIC) != 0) {
              //TODO only the first symbol found will be associated with the tree.
              enterSymbol(symbol, tree);
            }
          }
        }
      }
    }

    private void enterSymbol(JavaSymbol symbol, ImportTree tree) {
      env.namedImports.enter(symbol);
      //FIXME We add all symbols to named Imports for static methods, but only the first one will be resolved as we don't handle arguments.
      //FIXME That is why we only add the first symbol so we resolve references at best for now.
      //add to semantic model only the first symbol.
      //twice the same import : ignore the duplication JLS8 7.5.1.
      if (semanticModel.getSymbol(tree) == null && semanticModel.getTree(symbol) == null) {
        semanticModel.associateSymbol(tree, symbol);
      }
    }

    @Override
    public void visitIdentifier(IdentifierTree tree) {
      if (JavaPunctuator.STAR.getValue().equals(tree.name())) {
        //star import : we save the current symbol
        if (isStatic) {
          env.staticStarImports().enter(currentSymbol);
        } else {
          env.starImports().enter(currentSymbol);
        }
        //we set current symbol to not found to do not put it in named import scope.
        currentSymbol = new Resolve.JavaSymbolNotFound();
      } else {
        if (currentSymbol.kind == JavaSymbol.PCK) {
          currentSymbol = resolve.findIdentInPackage(currentSymbol, tree.name(), JavaSymbol.PCK | JavaSymbol.TYP);
          resolved = Collections.emptyList();
        } else if (currentSymbol.kind == JavaSymbol.TYP) {
          resolved = ((JavaSymbol.TypeJavaSymbol) currentSymbol).members().lookup(tree.name());
          currentSymbol = resolve.findIdentInType(env, (JavaSymbol.TypeJavaSymbol) currentSymbol, tree.name(), JavaSymbol.TYP | JavaSymbol.VAR);
        } else {
          //Site symbol is not found so we won't be able to resolve the import.
          currentSymbol = new Resolve.JavaSymbolNotFound();
          resolved = Collections.emptyList();
        }
      }
    }

  }

  @Override
  public void visitClass(ClassTree tree) {
    int flag = 0;
    boolean anonymousClass = tree.simpleName() == null;
    String name = "";
    if (!anonymousClass) {
      name = tree.simpleName().name();
      flag = computeClassFlags(tree);
    }
    JavaSymbol.TypeJavaSymbol symbol = new JavaSymbol.TypeJavaSymbol(flag, name, env.scope.owner);
    symbol.declaration = tree;
    ((ClassTreeImpl) tree).setSymbol(symbol);
    //Only register classes that can be accessible, so classes owned by a method are not registered.
    //TODO : register also based on flags ?
    if (!anonymousClass) {
      if (env.scope.owner.kind == JavaSymbol.TYP || env.scope.owner.kind == JavaSymbol.PCK) {
        resolve.registerClass(symbol);
      }
      enterSymbol(tree, symbol);
    }
    symbol.members = new Scope(symbol);
    symbol.completer = completer;
    uncompleted.add(symbol);

    //Define type parameters:
    createNewEnvironment(tree.typeParameters());
    // Save current environment to be able to complete class later
    semanticModel.saveEnv(symbol, env);
    for (TypeParameterTree typeParameterTree : tree.typeParameters()) {
      JavaSymbol.TypeVariableJavaSymbol typeVariableSymbol = new JavaSymbol.TypeVariableJavaSymbol(typeParameterTree.identifier().name(), symbol);
      symbol.addTypeParameter((JavaType.TypeVariableJavaType) typeVariableSymbol.type);
      enterSymbol(typeParameterTree, typeVariableSymbol);
    }
    symbol.typeParameters = env.scope;
    Resolve.Env classEnv = env.dup();
    classEnv.outer = env;
    classEnv.enclosingClass = symbol;
    classEnv.scope = symbol.members;
    env = classEnv;
    semanticModel.associateEnv(tree, env);
    scan(tree.modifiers());
    //skip type parameters
    scan(tree.superClass());
    scan(tree.superInterfaces());
    scan(tree.members());
    restoreEnvironment(tree);
    restoreEnvironment(tree);
  }

  private int computeClassFlags(ClassTree tree) {
    int flags = computeFlags(tree.modifiers());
    if (tree.is(Tree.Kind.INTERFACE)) {
      flags |= Flags.INTERFACE;
    }else if (tree.is(Tree.Kind.ENUM)) {
      flags |= Flags.ENUM;
    }else if (tree.is(Tree.Kind.ANNOTATION_TYPE)) {
      flags |= Flags.INTERFACE | Flags.ANNOTATION;
    }
    if (env.scope.owner instanceof JavaSymbol.TypeJavaSymbol && ((env.enclosingClass.flags() & Flags.INTERFACE) != 0)) {
      // JLS7 6.6.1: All members of interfaces are implicitly public.
      flags |= Flags.PUBLIC;
    }
    return flags;
  }

  @Override
  public void visitMethod(MethodTree tree) {
    String name = tree.returnType() == null ? "" : tree.simpleName().name();
    JavaSymbol.MethodJavaSymbol symbol = new JavaSymbol.MethodJavaSymbol(computeFlags(tree.modifiers()), name, env.scope.owner);
    symbol.declaration = tree;
    if((env.scope.owner.flags & Flags.ENUM) !=0 && tree.returnType()==null ) {
      //enum constructors are private.
      symbol.flags |= Flags.PRIVATE;
    }
    enterSymbol(tree, symbol);
    symbol.parameters = new OrderedScope(symbol);
    symbol.completer = completer;
    uncompleted.add(symbol);

    ((MethodTreeImpl) tree).setSymbol(symbol);
    createNewEnvironment(tree.typeParameters());
    for (TypeParameterTree typeParameterTree : tree.typeParameters()) {
      JavaSymbol.TypeVariableJavaSymbol typeVariableSymbol = new JavaSymbol.TypeVariableJavaSymbol(typeParameterTree.identifier().name(), symbol);
      symbol.addTypeParameter((JavaType.TypeVariableJavaType) typeVariableSymbol.type);
      enterSymbol(typeParameterTree, typeVariableSymbol);
    }
    // Save current environment to be able to complete method later
    semanticModel.saveEnv(symbol, env);

    symbol.typeParameters = env.scope;
    // Create new environment - this is required, because new scope is created
    Resolve.Env methodEnv = env.dup();
    methodEnv.scope = symbol.parameters;
    methodEnv.outer = env;
    env = methodEnv;
    scan(tree.modifiers());
    //skip type parameters.
    scan(tree.returnType());
    scan(tree.parameters());
    scan(tree.defaultValue());
    scan(tree.throwsClauses());
    scan(tree.block());
    restoreEnvironment(tree);
    restoreEnvironment(tree);
  }

  @Override
  public void visitEnumConstant(EnumConstantTree tree) {
    declareVariable(Flags.PUBLIC | Flags.ENUM, tree.simpleName(), (VariableTreeImpl) tree);
    super.visitEnumConstant(tree);
  }

  @Override
  public void visitVariable(VariableTree tree) {
    declareVariable(computeFlags(tree.modifiers()), tree.simpleName(), (VariableTreeImpl) tree);
    super.visitVariable(tree);
  }

  private int computeFlags(ModifiersTree modifiers) {
    int result = 0;
    //JLS7 6.6.1: All members of interfaces are implicitly public.
    if ((env.scope.owner.flags & Flags.INTERFACE) != 0) {
      result = Flags.PUBLIC;
    }
    for (Modifier modifier : modifiers.modifiers()) {
      result |= Flags.flagForModifier(modifier);
    }
    if(hasDeprecatedAnnotation(modifiers.annotations())) {
      result |= Flags.DEPRECATED;
    }
    return result;
  }

  private boolean hasDeprecatedAnnotation(Iterable annotations) {
    for (AnnotationTree annotationTree : annotations) {
      if (isDeprecated(annotationTree)) {
        return true;
      }
    }
    return false;
  }

  private boolean isDeprecated(AnnotationTree tree) {
    return tree.annotationType().is(Tree.Kind.IDENTIFIER) &&
        "Deprecated".equals(((IdentifierTree) tree.annotationType()).name());
  }

  private void declareVariable(int flags, IdentifierTree identifierTree, VariableTreeImpl tree) {
    JavaSymbol.VariableJavaSymbol symbol = new JavaSymbol.VariableJavaSymbol(flags, identifierTree.name(), env.scope.owner);
    symbol.declaration = tree;
    enterSymbol(tree, symbol);
    symbol.completer = completer;
    uncompleted.add(symbol);

    tree.setSymbol(symbol);

    // Save current environment to be able to complete variable declaration later
    semanticModel.saveEnv(symbol, env);
  }

  @Override
  public void visitBlock(BlockTree tree) {
    // Create new environment - this is required, because block can declare types
    createNewEnvironment(tree);
    super.visitBlock(tree);
    restoreEnvironment(tree);
  }

  @Override
  public void visitForStatement(ForStatementTree tree) {
    // Create new environment - this is required, because new scope is created
    createNewEnvironment(tree);
    super.visitForStatement(tree);
    restoreEnvironment(tree);
  }

  @Override
  public void visitForEachStatement(ForEachStatement tree) {
    // Create new environment - this is required, because new scope is created
    createNewEnvironment(tree);
    super.visitForEachStatement(tree);
    restoreEnvironment(tree);
  }

  @Override
  public void visitCatch(CatchTree tree) {
    // Create new environment - this is required, because new scope is created
    createNewEnvironment(tree);
    super.visitCatch(tree);
    restoreEnvironment(tree);
  }

  @Override
  public void visitLambdaExpression(LambdaExpressionTree tree) {
    // Create new environment - this is required, because new scope is created
    createNewEnvironment(tree);
    super.visitLambdaExpression(tree);
    restoreEnvironment(tree);
  }

  private void createNewEnvironment(Tree tree) {
    Scope scope = new Scope(env.scope);
    Resolve.Env newEnv = env.dup();
    newEnv.scope = scope;
    env = newEnv;
    semanticModel.associateEnv(tree, env);
  }

  private void enterSymbol(Tree tree, JavaSymbol symbol) {
    env.scope.enter(symbol);
    semanticModel.associateSymbol(tree, symbol);
  }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy