org.sonar.php.tree.symbols.SymbolVisitor.java Source code

Java tutorial

Introduction

Here is the source code for org.sonar.php.tree.symbols.SymbolVisitor.java

Source

/*
 * SonarQube PHP Plugin
 * Copyright (C) 2010-2016 SonarSource SA
 * mailto:contact 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.php.tree.symbols;

import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Lists;
import java.util.ArrayDeque;
import java.util.Deque;
import java.util.Locale;
import java.util.Set;
import org.sonar.php.api.PHPKeyword;
import org.sonar.php.tree.impl.PHPTree;
import org.sonar.php.utils.SourceBuilder;
import org.sonar.plugins.php.api.symbols.Symbol;
import org.sonar.plugins.php.api.tree.CompilationUnitTree;
import org.sonar.plugins.php.api.tree.SeparatedList;
import org.sonar.plugins.php.api.tree.Tree;
import org.sonar.plugins.php.api.tree.Tree.Kind;
import org.sonar.plugins.php.api.tree.declaration.ClassDeclarationTree;
import org.sonar.plugins.php.api.tree.declaration.ClassMemberTree;
import org.sonar.plugins.php.api.tree.declaration.ClassPropertyDeclarationTree;
import org.sonar.plugins.php.api.tree.declaration.ConstantDeclarationTree;
import org.sonar.plugins.php.api.tree.declaration.FunctionDeclarationTree;
import org.sonar.plugins.php.api.tree.declaration.MethodDeclarationTree;
import org.sonar.plugins.php.api.tree.declaration.NamespaceNameTree;
import org.sonar.plugins.php.api.tree.declaration.ParameterTree;
import org.sonar.plugins.php.api.tree.declaration.VariableDeclarationTree;
import org.sonar.plugins.php.api.tree.expression.CompoundVariableTree;
import org.sonar.plugins.php.api.tree.expression.ComputedVariableTree;
import org.sonar.plugins.php.api.tree.expression.ExpressionTree;
import org.sonar.plugins.php.api.tree.expression.FunctionCallTree;
import org.sonar.plugins.php.api.tree.expression.FunctionExpressionTree;
import org.sonar.plugins.php.api.tree.expression.IdentifierTree;
import org.sonar.plugins.php.api.tree.expression.LexicalVariablesTree;
import org.sonar.plugins.php.api.tree.expression.LiteralTree;
import org.sonar.plugins.php.api.tree.expression.MemberAccessTree;
import org.sonar.plugins.php.api.tree.expression.NameIdentifierTree;
import org.sonar.plugins.php.api.tree.expression.VariableIdentifierTree;
import org.sonar.plugins.php.api.tree.expression.VariableTree;
import org.sonar.plugins.php.api.tree.lexical.SyntaxToken;
import org.sonar.plugins.php.api.tree.statement.GlobalStatementTree;
import org.sonar.plugins.php.api.visitors.PHPVisitorCheck;

public class SymbolVisitor extends PHPVisitorCheck {

    private static final Set<String> BUILT_IN_VARIABLES = ImmutableSet.of("$THIS", "$GLOBALS", "$_SERVER", "$_GET",
            "$_POST", "$_FILES", "$_SESSION", "$_ENV", "$PHP_ERRORMSG", "$HTTP_RAW_POST_DATA",
            "$HTTP_RESPONSE_HEADER", "$ARGC", "$ARGV", "$_COOKIE", "$_REQUEST");

    private Scope classScope = null;
    private Deque<Boolean> insideCallee = new ArrayDeque<>();

    static class ClassMemberUsageState {
        boolean isStatic = false;

        boolean isField = false;

        boolean isSelfMember = false;

        boolean isConst = false;
    }

    private SymbolTableImpl symbolTable;
    private Scope currentScope;
    private Scope globalScope;
    private ClassMemberUsageState classMemberUsageState = null;

    public SymbolVisitor(SymbolTableImpl symbolTable) {
        this.symbolTable = symbolTable;
        this.currentScope = null;
        this.globalScope = null;
    }

    @Override
    public void visitCompilationUnit(CompilationUnitTree tree) {
        enterScope(tree);
        globalScope = currentScope;
        super.visitCompilationUnit(tree);
    }

    @Override
    public void visitFunctionDeclaration(FunctionDeclarationTree tree) {
        createSymbol(tree.name(), Symbol.Kind.FUNCTION);
        enterScope(tree);
        super.visitFunctionDeclaration(tree);
        leaveScope();
    }

    @Override
    public void visitFunctionExpression(FunctionExpressionTree tree) {
        enterScope(tree);
        super.visitFunctionExpression(tree);
        leaveScope();
    }

    @Override
    public void visitMethodDeclaration(MethodDeclarationTree tree) {
        enterScope(tree);
        super.visitMethodDeclaration(tree);
        leaveScope();
    }

    @Override
    public void visitClassPropertyDeclaration(ClassPropertyDeclarationTree tree) {
        // do nothing as this symbols already saved during visiting class tree
    }

    @Override
    public void visitClassDeclaration(ClassDeclarationTree tree) {
        createSymbol(tree.name(), Symbol.Kind.CLASS);
        enterScope(tree);
        classScope = currentScope;
        createMemberSymbols(tree);
        super.visitClassDeclaration(tree);
        classScope = null;
        leaveScope();
    }

    private void createMemberSymbols(ClassDeclarationTree tree) {
        for (ClassMemberTree member : tree.members()) {
            if (member.is(Kind.METHOD_DECLARATION)) {
                createSymbol(((MethodDeclarationTree) member).name(), Symbol.Kind.FUNCTION)
                        .addModifiers(((MethodDeclarationTree) member).modifiers());

            } else if (member.is(Kind.CLASS_CONSTANT_PROPERTY_DECLARATION, Kind.CLASS_PROPERTY_DECLARATION)) {
                ClassPropertyDeclarationTree classPropertyDeclaration = (ClassPropertyDeclarationTree) member;
                for (VariableDeclarationTree field : classPropertyDeclaration.declarations()) {
                    createSymbol(field.identifier(), Symbol.Kind.FIELD)
                            .addModifiers(classPropertyDeclaration.modifierTokens());
                }
            }

        }
    }

    @Override
    public void visitConstDeclaration(ConstantDeclarationTree tree) {
        for (VariableDeclarationTree constant : tree.declarations()) {
            createSymbol(constant.identifier(), Symbol.Kind.VARIABLE)
                    .addModifiers(Lists.newArrayList(tree.constToken()));
        }
    }

    @Override
    public void visitVariableIdentifier(VariableIdentifierTree tree) {
        if (!isBuiltInVariable(tree)) {
            if (classMemberUsageState == null) {
                createOrUseVariableIdentifierSymbol(tree);

            } else {

                if (classMemberUsageState.isSelfMember && classScope != null && classMemberUsageState.isStatic) {
                    Symbol symbol = classScope.getSymbol(tree.text(), Symbol.Kind.FIELD);
                    if (symbol != null) {
                        symbol.addUsage(tree);
                    }
                }

                // see test_property_name_in_variable and case $this->$key ($key stores the name of field)
                Symbol symbol = currentScope.getSymbol(tree.text(), Symbol.Kind.VARIABLE, Symbol.Kind.PARAMETER);
                if (symbol != null) {
                    symbol.addUsage(tree);
                }

                classMemberUsageState = null;
            }
        }
    }

    @Override
    public void visitToken(SyntaxToken token) {
        if (classMemberUsageState != null && classMemberUsageState.isStatic
                && token.text().equals(PHPKeyword.CLASS.getValue())) {
            classMemberUsageState = null;
        }

        super.visitToken(token);
    }

    @Override
    public void visitNameIdentifier(NameIdentifierTree tree) {
        if (classMemberUsageState != null && classScope != null) {

            if (classMemberUsageState.isField) {
                String dollar = classMemberUsageState.isConst ? "" : "$";
                Symbol symbol = classScope.getSymbol(dollar + tree.text(), Symbol.Kind.FIELD);
                if (symbol != null) {
                    symbol.addUsage(tree);
                }

            } else {
                Symbol symbol = classScope.getSymbol(tree.text(), Symbol.Kind.FUNCTION);
                if (symbol != null) {
                    symbol.addUsage(tree);
                }
            }

        } else {
            // fixme (Lena): class names are visible in function scopes, so may be globalScope.getSymbol(tree.text(), Symbol.Kind.CLASS) will work here
            Symbol symbol = currentScope.getSymbol(tree.text(), Symbol.Kind.CLASS);
            if (symbol != null) {
                symbol.addUsage(tree);
            }
        }

        classMemberUsageState = null;
    }

    private static boolean isBuiltInVariable(VariableIdentifierTree tree) {
        return BUILT_IN_VARIABLES.contains(tree.text().toUpperCase(Locale.ENGLISH));
    }

    @Override
    public void visitCompoundVariable(CompoundVariableTree tree) {
        SyntaxToken firstExpressionToken = ((PHPTree) tree.variableExpression()).getFirstToken();
        if (firstExpressionToken.text().charAt(0) != '$') {
            Symbol symbol = currentScope.getSymbol("$" + firstExpressionToken.text());
            if (symbol != null) {
                symbol.addUsage(firstExpressionToken);
            }
        }

        super.visitCompoundVariable(tree);
    }

    @Override
    public void visitParameter(ParameterTree tree) {
        createSymbol(tree.variableIdentifier(), Symbol.Kind.PARAMETER);
        // do not scan the children to not pass through variableIdentifier
    }

    @Override
    public void visitGlobalStatement(GlobalStatementTree tree) {
        for (VariableTree variable : tree.variables()) {
            // Other cases are not supported
            if (variable.is(Tree.Kind.VARIABLE_IDENTIFIER)) {
                IdentifierTree identifier = (IdentifierTree) variable.variableExpression();
                Symbol symbol = globalScope.getSymbol(identifier.text(), Symbol.Kind.VARIABLE);
                if (symbol != null) {
                    // actually this identifier in global statement is not usage, but we do this for the symbol highlighting
                    symbol.addUsage(identifier);
                    currentScope.addSymbol(symbol);
                } else {
                    createSymbol(identifier, Symbol.Kind.VARIABLE);
                }

            }
        }
    }

    @Override
    public void visitLexicalVariables(LexicalVariablesTree tree) {
        for (VariableTree variable : tree.variables()) {
            IdentifierTree identifier = null;
            if (variable.is(Tree.Kind.VARIABLE_IDENTIFIER)) {
                identifier = (IdentifierTree) variable.variableExpression();
            } else if (variable.is(Tree.Kind.REFERENCE_VARIABLE)
                    && variable.variableExpression().is(Tree.Kind.VARIABLE_IDENTIFIER)) {
                identifier = ((VariableIdentifierTree) variable.variableExpression()).variableExpression();
            }

            if (identifier != null) {
                Symbol symbol = currentScope.outer().getSymbol(identifier.text());
                if (symbol != null) {
                    symbol.addUsage(identifier);
                } else if (variable.is(Kind.REFERENCE_VARIABLE)) {
                    symbolTable.declareSymbol(identifier, Symbol.Kind.VARIABLE, currentScope.outer());
                }
                createSymbol(identifier, Symbol.Kind.VARIABLE);
            }
        }

    }

    @Override
    public void visitFunctionCall(FunctionCallTree tree) {
        if (tree.callee().is(Tree.Kind.NAMESPACE_NAME)) {
            NamespaceNameTree namespaceNameCallee = (NamespaceNameTree) tree.callee();
            usageForNamespaceName(namespaceNameCallee, Symbol.Kind.FUNCTION);

        }

        this.insideCallee.push(true);
        tree.callee().accept(this);
        this.insideCallee.pop();

        String callee = SourceBuilder.build(tree.callee()).trim();
        if ("compact".equals(callee)) {
            visitCompactFunctionCall(tree.arguments());
        }

        scan(tree.arguments());
    }

    /**
     * See <a href="http://php.net/manual/en/function.compact.php">docs</a> of "compact" function
     * @param arguments of call of "compact" function
     */
    private void visitCompactFunctionCall(SeparatedList<ExpressionTree> arguments) {
        for (ExpressionTree argument : arguments) {

            if (argument.is(Kind.REGULAR_STRING_LITERAL)) {
                String value = ((LiteralTree) argument).value();
                String variableName = "$" + value.substring(1, value.length() - 1);

                Symbol symbol = currentScope.getSymbol(variableName, Symbol.Kind.VARIABLE, Symbol.Kind.PARAMETER);

                if (symbol != null) {
                    symbol.addUsage(((LiteralTree) argument).token());
                }
            }
        }
    }

    private void usageForNamespaceName(NamespaceNameTree namespaceName, Symbol.Kind kind) {
        if (namespaceName.name().is(Tree.Kind.NAME_IDENTIFIER) && namespaceName.namespaces().isEmpty()) {
            NameIdentifierTree usageIdentifier = (NameIdentifierTree) namespaceName.name();
            Symbol symbol = currentScope.getSymbol(usageIdentifier.text(), kind);
            if (symbol != null) {
                symbol.addUsage(usageIdentifier);
            }

        }
    }

    @Override
    public void visitMemberAccess(MemberAccessTree tree) {
        tree.object().accept(this);

        final ImmutableSet<String> selfObjects = ImmutableSet.of("$this", "self", "static");
        String strObject = SourceBuilder.build(tree.object()).trim();

        classMemberUsageState = new ClassMemberUsageState();
        classMemberUsageState.isStatic = tree.isStatic();
        classMemberUsageState.isSelfMember = selfObjects.contains(strObject.toLowerCase(Locale.ENGLISH));
        classMemberUsageState.isField = insideCallee.isEmpty();
        classMemberUsageState.isConst = classMemberUsageState.isField && tree.isStatic();

        tree.member().accept(this);
    }

    @Override
    public void visitComputedVariable(ComputedVariableTree tree) {
        classMemberUsageState = null;
        super.visitComputedVariable(tree);
    }

    private void leaveScope() {
        Preconditions.checkState(currentScope != null,
                "Current scope should never be null when calling method \"leaveScope\"");
        currentScope = currentScope.outer();
    }

    private void enterScope(Tree tree) {
        currentScope = new Scope(currentScope, tree);
        symbolTable.addScope(currentScope);
    }

    private Symbol createOrUseVariableIdentifierSymbol(IdentifierTree identifier) {
        Symbol symbol = currentScope.getSymbol(identifier.text(), Symbol.Kind.VARIABLE, Symbol.Kind.PARAMETER);

        if (symbol == null) {
            symbol = symbolTable.declareSymbol(identifier, Symbol.Kind.VARIABLE, currentScope);

        } else if (!symbol.is(Symbol.Kind.FIELD)) {
            symbol.addUsage(identifier);
        }

        return symbol;
    }

    private Symbol createSymbol(IdentifierTree identifier, Symbol.Kind kind) {
        Symbol symbol = currentScope.getSymbol(identifier.text(), kind);

        if (symbol == null) {
            symbol = symbolTable.declareSymbol(identifier, kind, currentScope);

        } else {
            symbol = currentScope.getSymbol(identifier.text());
            if (symbol != null && !symbol.is(Symbol.Kind.FIELD)) {
                symbol.addUsage(identifier);
            }
        }

        return symbol;
    }

}