com.kalessil.phpStorm.phpInspectionsEA.inspectors.apiUsage.PrintfScanfArgumentsInspector.java Source code

Java tutorial

Introduction

Here is the source code for com.kalessil.phpStorm.phpInspectionsEA.inspectors.apiUsage.PrintfScanfArgumentsInspector.java

Source

package com.kalessil.phpStorm.phpInspectionsEA.inspectors.apiUsage;

import com.intellij.codeInspection.ProblemHighlightType;
import com.intellij.codeInspection.ProblemsHolder;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiElementVisitor;
import com.jetbrains.php.lang.lexer.PhpTokenTypes;
import com.jetbrains.php.lang.psi.elements.AssignmentExpression;
import com.jetbrains.php.lang.psi.elements.FunctionReference;
import com.jetbrains.php.lang.psi.elements.ParameterList;
import com.jetbrains.php.lang.psi.elements.StringLiteralExpression;
import com.kalessil.phpStorm.phpInspectionsEA.openApi.BasePhpElementVisitor;
import com.kalessil.phpStorm.phpInspectionsEA.openApi.BasePhpInspection;
import com.kalessil.phpStorm.phpInspectionsEA.utils.ExpressionSemanticUtil;
import com.kalessil.phpStorm.phpInspectionsEA.utils.OpenapiTypesUtil;
import org.apache.commons.lang.StringUtils;
import org.jetbrains.annotations.NotNull;

import java.util.HashMap;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/*
 * This file is part of the Php Inspections (EA Extended) package.
 *
 * (c) Vladimir Reznichenko <kalessil@gmail.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

public class PrintfScanfArgumentsInspector extends BasePhpInspection {
    private static final String messagePattern = "Pattern seems to be not valid.";
    private static final String messageParameters = "Amount of expected parameters is %c%.";

    @NotNull
    public String getShortName() {
        return "PrintfScanfArgumentsInspection";
    }

    private static final Map<String, Integer> functions = new HashMap<>();
    static {
        /* pairs function name -> pattern position */
        functions.put("printf", 0);
        functions.put("sprintf", 0);
        functions.put("sscanf", 1);
        functions.put("fprintf", 1);
        functions.put("fscanf", 1);
    }

    final static private Pattern regexPlaceHolders;
    static {
        // raw regex: %((\d+)\$)?[+-]?(?:[ 0]|\\?'.)?-?\d*(?:\.\d+)?[\[sducoxXbgGeEfF]
        regexPlaceHolders = Pattern
                .compile("%((\\d+)\\$)?[+-]?(?:[ 0]|\\\\?'.)?-?\\d*(?:\\.\\d+)?[\\[sducoxXbgGeEfF]");
    }

    @Override
    @NotNull
    public PsiElementVisitor buildVisitor(@NotNull final ProblemsHolder holder, boolean isOnTheFly) {
        return new BasePhpElementVisitor() {
            @Override
            public void visitPhpFunctionCall(@NotNull FunctionReference reference) {
                final String functionName = reference.getName();
                if (functionName == null || !functions.containsKey(functionName)) {
                    return;
                }

                /* resolve needed parameter */
                final int neededPosition = functions.get(functionName);
                final int minimumArgumentsForAnalysis = neededPosition + 1;
                StringLiteralExpression pattern = null;
                final PsiElement[] params = reference.getParameters();
                if (params.length >= minimumArgumentsForAnalysis) {
                    pattern = ExpressionSemanticUtil.resolveAsStringLiteral(params[neededPosition]);
                }
                /* not available */
                if (null == pattern) {
                    return;
                }

                final String content = pattern.getContents().trim();
                if (!content.isEmpty()) {
                    /* find valid placeholders and extract positions specifiers as well */
                    int countWithoutPositionSpecifier = 0;
                    int maxPositionSpecifier = 0;
                    int countParsedAll = 0;

                    /* do normalization: %%, inline variables */
                    final String contentAdapted = content.replace("%%", "");
                    final String contentNoVars = contentAdapted.replaceAll("\\$\\{?\\$?[a-zA-Z0-9]+\\}?", "");
                    if (contentAdapted.length() != contentNoVars.length()) {
                        return;
                    }

                    final Matcher regexMatcher = regexPlaceHolders.matcher(contentAdapted);
                    while (regexMatcher.find()) {
                        ++countParsedAll;

                        if (null != regexMatcher.group(2)) {
                            maxPositionSpecifier = Math.max(maxPositionSpecifier,
                                    Integer.parseInt(regexMatcher.group(2)));
                            continue;
                        }

                        ++countWithoutPositionSpecifier;
                    }
                    final int expectedParametersCount = minimumArgumentsForAnalysis
                            + Math.max(countWithoutPositionSpecifier, maxPositionSpecifier);

                    /* check for pattern validity */
                    final int parametersInPattern = StringUtils.countMatches(content.replace("%%", ""), "%");
                    if (countParsedAll != parametersInPattern) {
                        holder.registerProblem(params[neededPosition], messagePattern,
                                ProblemHighlightType.GENERIC_ERROR);
                        return;
                    }

                    /* check for arguments matching */
                    if (expectedParametersCount != params.length) {
                        /* fscanf/sscanf will also return parsed values as an array if no values containers provided */
                        if (2 == params.length) {
                            final boolean returnsArray = functionName.equals("fscanf")
                                    || functionName.equals("sscanf");
                            final PsiElement parent = returnsArray ? reference.getParent() : null;
                            final PsiElement grandParent = parent == null ? null : parent.getParent();
                            if (returnsArray && parent != null) {
                                /* false-positive: dispatching/deconstructing into containers */
                                if (parent instanceof AssignmentExpression
                                        || grandParent instanceof AssignmentExpression) {
                                    return;
                                }
                                /* false-positive: dispatching into calls */
                                else if (parent instanceof ParameterList
                                        && grandParent instanceof FunctionReference) {
                                    return;
                                }
                            }
                        }

                        /* false-positives: variadic */
                        if (OpenapiTypesUtil.is(params[params.length - 1].getPrevSibling(),
                                PhpTokenTypes.opVARIADIC)) {
                            return;
                        }

                        final String message = messageParameters.replace("%c%",
                                String.valueOf(expectedParametersCount));
                        holder.registerProblem(reference, message, ProblemHighlightType.GENERIC_ERROR);
                    }
                }
            }
        };
    }
}