Search in sources :

Example 6 with PhpAccessVariableInstruction

use of com.jetbrains.php.codeInsight.controlFlow.instructions.PhpAccessVariableInstruction in project phpinspectionsea by kalessil.

the class OnlyWritesOnParameterInspector method buildVisitor.

@Override
@NotNull
public PsiElementVisitor buildVisitor(@NotNull final ProblemsHolder holder, boolean isOnTheFly) {
    return new BasePhpElementVisitor() {

        @Override
        public void visitPhpMethod(@NotNull Method method) {
            if (!method.isAbstract()) {
                this.visitPhpFunction(method);
            }
        }

        @Override
        public void visitPhpFunction(@NotNull Function function) {
            Arrays.stream(function.getParameters()).filter(parameter -> !parameter.getName().isEmpty() && !parameter.isPassByRef()).filter(parameter -> {
                final PhpType declaredType = OpenapiResolveUtil.resolveDeclaredType(parameter).filterUnknown().filterNull();
                final boolean isObject = !declaredType.isEmpty() && declaredType.getTypes().stream().anyMatch(t -> {
                    final String type = Types.getType(t);
                    return type.equals(Types.strObject) || type.startsWith("\\");
                });
                return !isObject;
            }).forEach(parameter -> this.analyzeAndReturnUsagesCount(parameter.getName(), function));
            final List<Variable> variables = ExpressionSemanticUtil.getUseListVariables(function);
            if (variables != null) {
                this.checkUseVariables(variables, function);
                variables.clear();
            }
        }

        @Override
        public void visitPhpAssignmentExpression(@NotNull AssignmentExpression assignment) {
            /* because this hook fired e.g. for `.=` assignments (a BC break by PhpStorm) */
            if (OpenapiTypesUtil.isAssignment(assignment)) {
                final PsiElement variable = assignment.getVariable();
                if (variable instanceof Variable) {
                    /* false-positives: predefined and global variables */
                    final String variableName = ((Variable) variable).getName();
                    if (variableName.isEmpty() || ExpressionCostEstimateUtil.predefinedVars.contains(variableName)) {
                        return;
                    }
                    /* filter target contexts: we are supporting only certain of them */
                    final PsiElement parent = assignment.getParent();
                    final boolean isTargetContext = parent instanceof ParenthesizedExpression || parent instanceof ArrayIndex || (parent instanceof BinaryExpression && OpenapiTypesUtil.tsCOMPARE_EQUALITY_OPS.contains(((BinaryExpression) parent).getOperationType())) || OpenapiTypesUtil.isStatementImpl(parent) || OpenapiTypesUtil.isAssignment(parent);
                    if (isTargetContext) {
                        final Function scope = ExpressionSemanticUtil.getScope(assignment);
                        if (scope != null && Arrays.stream(scope.getParameters()).noneMatch(p -> p.getName().equals(variableName))) {
                            final List<Variable> uses = ExpressionSemanticUtil.getUseListVariables(scope);
                            final boolean isUseVariable = uses != null && uses.stream().anyMatch(u -> u.getName().equals(variableName));
                            if (!isUseVariable) {
                                this.analyzeAndReturnUsagesCount(variableName, scope);
                            }
                        }
                    }
                }
            }
        }

        private void checkUseVariables(@NotNull List<Variable> variables, @NotNull Function function) {
            for (final Variable variable : variables) {
                final String parameterName = variable.getName();
                if (!parameterName.isEmpty()) {
                    PsiElement previous = variable.getPrevSibling();
                    if (previous instanceof PsiWhiteSpace) {
                        previous = previous.getPrevSibling();
                    }
                    if (OpenapiTypesUtil.is(previous, PhpTokenTypes.opBIT_AND)) {
                        if (OpenapiControlFlowUtil.getFollowingVariableAccessInstructions(function.getControlFlow().getEntryPoint(), parameterName).isEmpty()) {
                            holder.registerProblem(variable, MessagesPresentationUtil.prefixWithEa(messageUnused), ProblemHighlightType.LIKE_UNUSED_SYMBOL);
                        }
                    } else if (this.analyzeAndReturnUsagesCount(parameterName, function) == 0) {
                        holder.registerProblem(variable, MessagesPresentationUtil.prefixWithEa(messageUnused), ProblemHighlightType.LIKE_UNUSED_SYMBOL);
                    }
                }
            }
        }

        private int analyzeAndReturnUsagesCount(@NotNull String parameterName, @NotNull Function function) {
            final List<PhpAccessVariableInstruction> usages = OpenapiControlFlowUtil.getFollowingVariableAccessInstructions(function.getControlFlow().getEntryPoint(), parameterName);
            if (usages.isEmpty()) {
                return 0;
            }
            final List<PsiElement> targetExpressions = new ArrayList<>();
            boolean isReference = false;
            int intCountReadAccesses = 0;
            int intCountWriteAccesses = 0;
            for (final PhpAccessVariableInstruction instruction : usages) {
                final PsiElement variable = instruction.getAnchor();
                final PsiElement parent = variable.getParent();
                if (parent instanceof ArrayAccessExpression) {
                    /* find out which expression is holder */
                    PsiElement objLastSemanticExpression = variable;
                    PsiElement objTopSemanticExpression = objLastSemanticExpression.getParent();
                    /* TODO: iterator for array access expression */
                    while (objTopSemanticExpression instanceof ArrayAccessExpression) {
                        objLastSemanticExpression = objTopSemanticExpression;
                        objTopSemanticExpression = objTopSemanticExpression.getParent();
                    }
                    /* estimate operation type */
                    if (objTopSemanticExpression instanceof AssignmentExpression && ((AssignmentExpression) objTopSemanticExpression).getVariable() == objLastSemanticExpression) {
                        intCountWriteAccesses++;
                        if (isReference) {
                            /* when modifying the reference it's link READ and linked WRITE semantics */
                            intCountReadAccesses++;
                        } else {
                            /* when modifying non non-reference, register as write only access for reporting */
                            targetExpressions.add(objLastSemanticExpression);
                        }
                        continue;
                    }
                    if (objTopSemanticExpression instanceof UnaryExpression) {
                        final PsiElement operation = ((UnaryExpression) objTopSemanticExpression).getOperation();
                        if (OpenapiTypesUtil.is(operation, PhpTokenTypes.opINCREMENT) || OpenapiTypesUtil.is(operation, PhpTokenTypes.opDECREMENT)) {
                            targetExpressions.add(objLastSemanticExpression);
                            ++intCountWriteAccesses;
                            continue;
                        }
                    }
                    intCountReadAccesses++;
                    continue;
                }
                /* ++/-- operations */
                if (parent instanceof UnaryExpression) {
                    final PsiElement operation = ((UnaryExpression) parent).getOperation();
                    if (OpenapiTypesUtil.is(operation, PhpTokenTypes.opINCREMENT) || OpenapiTypesUtil.is(operation, PhpTokenTypes.opDECREMENT)) {
                        ++intCountWriteAccesses;
                        if (isReference) {
                            /* when modifying the reference it's link READ and linked WRITE semantics */
                            ++intCountReadAccesses;
                        } else {
                            /* when modifying non-reference, register as write only access for reporting */
                            targetExpressions.add(parent);
                        }
                    }
                    if (!OpenapiTypesUtil.isStatementImpl(parent.getParent())) {
                        ++intCountReadAccesses;
                    }
                    continue;
                }
                if (parent instanceof SelfAssignmentExpression) {
                    final SelfAssignmentExpression selfAssignment = (SelfAssignmentExpression) parent;
                    final PsiElement sameVariableCandidate = selfAssignment.getVariable();
                    if (sameVariableCandidate instanceof Variable) {
                        final Variable candidate = (Variable) sameVariableCandidate;
                        if (candidate.getName().equals(parameterName)) {
                            ++intCountWriteAccesses;
                            if (isReference) {
                                /* when modifying the reference it's link READ and linked WRITE semantics */
                                ++intCountReadAccesses;
                            } else {
                                /* when modifying non-reference, register as write only access for reporting */
                                targetExpressions.add(variable);
                            }
                            if (!OpenapiTypesUtil.isStatementImpl(parent.getParent())) {
                                ++intCountReadAccesses;
                            }
                            continue;
                        }
                    }
                }
                /* if variable assigned with reference, we need to preserve this information for correct checks */
                if (parent instanceof AssignmentExpression) {
                    /* ensure variable with the same name being written */
                    final AssignmentExpression referenceAssignmentCandidate = (AssignmentExpression) parent;
                    /* check if the target used as a container */
                    final PsiElement assignmentVariableCandidate = referenceAssignmentCandidate.getVariable();
                    if (assignmentVariableCandidate instanceof Variable) {
                        final Variable candidate = (Variable) assignmentVariableCandidate;
                        if (candidate.getName().equals(parameterName)) {
                            ++intCountWriteAccesses;
                            if (isReference) {
                                /* when modifying the reference it's link READ and linked WRITE semantics */
                                ++intCountReadAccesses;
                            }
                            /* now ensure operation is assignment of reference */
                            if (OpenapiTypesUtil.isAssignmentByReference(referenceAssignmentCandidate)) {
                                isReference = true;
                            }
                            /* false-negative: inline assignment result has been used */
                            if (usages.size() == 2 && usages.get(0).getAnchor() == usages.get(1).getAnchor()) {
                                holder.registerProblem(assignmentVariableCandidate, MessagesPresentationUtil.prefixWithEa(messageUnused), ProblemHighlightType.LIKE_UNUSED_SYMBOL);
                                return 1;
                            }
                            continue;
                        }
                    }
                    /* check if the target used as a value */
                    final PsiElement assignmentValueCandidate = referenceAssignmentCandidate.getValue();
                    if (assignmentValueCandidate instanceof Variable) {
                        final Variable candidate = (Variable) assignmentValueCandidate;
                        if (candidate.getName().equals(parameterName)) {
                            ++intCountReadAccesses;
                            continue;
                        }
                    }
                }
                /* local variables access wrongly reported write in some cases, so rely on custom checks */
                if (parent instanceof ParameterList || parent instanceof PhpUseList || parent instanceof PhpUnset || parent instanceof PhpEmpty || parent instanceof PhpIsset || parent instanceof ForeachStatement) {
                    intCountReadAccesses++;
                    continue;
                }
                /* ok variable usage works well with openapi */
                final PhpAccessInstruction.Access instructionAccess = instruction.getAccess();
                if (instructionAccess.isWrite()) {
                    targetExpressions.add(variable);
                    ++intCountWriteAccesses;
                } else if (instructionAccess.isRead()) {
                    ++intCountReadAccesses;
                }
            }
            if (intCountReadAccesses == 0 && intCountWriteAccesses > 0 && !this.isAnySuppressed(targetExpressions)) {
                final boolean report = IGNORE_INCLUDES || !this.hasIncludes(function);
                if (report) {
                    for (final PsiElement targetExpression : new HashSet<>(targetExpressions)) {
                        holder.registerProblem(targetExpression, MessagesPresentationUtil.prefixWithEa(messageOnlyWrites), ProblemHighlightType.LIKE_UNUSED_SYMBOL);
                    }
                }
            }
            targetExpressions.clear();
            return usages.size();
        }

        private boolean isAnySuppressed(@NotNull List<PsiElement> expressions) {
            for (final PsiElement one : expressions) {
                final PsiElement parent = one.getParent();
                if (parent instanceof AssignmentExpression) {
                    final PsiElement grandParent = parent.getParent();
                    if (OpenapiTypesUtil.isStatementImpl(grandParent)) {
                        final PsiElement previous = ((PhpPsiElement) grandParent).getPrevPsiSibling();
                        if (previous instanceof PhpDocComment) {
                            final String candidate = previous.getText();
                            if (candidate.contains("@noinspection") && candidate.contains(getShortName())) {
                                return true;
                            }
                        }
                    }
                }
            }
            return false;
        }

        private boolean hasIncludes(@NotNull Function function) {
            final GroupStatement body = ExpressionSemanticUtil.getGroupStatement(function);
            if (body != null) {
                return PsiTreeUtil.findChildOfType(body, Include.class) != null;
            }
            return false;
        }
    };
}
Also used : PhpAccessInstruction(com.jetbrains.php.codeInsight.controlFlow.instructions.PhpAccessInstruction) PhpDocComment(com.jetbrains.php.lang.documentation.phpdoc.psi.PhpDocComment) Arrays(java.util.Arrays) BasePhpInspection(com.kalessil.phpStorm.phpInspectionsEA.openApi.BasePhpInspection) com.jetbrains.php.lang.psi.elements(com.jetbrains.php.lang.psi.elements) PhpTokenTypes(com.jetbrains.php.lang.lexer.PhpTokenTypes) ExpressionCostEstimateUtil(com.kalessil.phpStorm.phpInspectionsEA.inspectors.ifs.utils.ExpressionCostEstimateUtil) OptionsComponent(com.kalessil.phpStorm.phpInspectionsEA.options.OptionsComponent) ArrayList(java.util.ArrayList) HashSet(java.util.HashSet) PsiTreeUtil(com.intellij.psi.util.PsiTreeUtil) BasePhpElementVisitor(com.kalessil.phpStorm.phpInspectionsEA.openApi.BasePhpElementVisitor) com.kalessil.phpStorm.phpInspectionsEA.utils(com.kalessil.phpStorm.phpInspectionsEA.utils) List(java.util.List) PsiElement(com.intellij.psi.PsiElement) PsiWhiteSpace(com.intellij.psi.PsiWhiteSpace) ProblemHighlightType(com.intellij.codeInspection.ProblemHighlightType) PhpType(com.jetbrains.php.lang.psi.resolve.types.PhpType) NotNull(org.jetbrains.annotations.NotNull) PsiElementVisitor(com.intellij.psi.PsiElementVisitor) ProblemsHolder(com.intellij.codeInspection.ProblemsHolder) PhpAccessVariableInstruction(com.jetbrains.php.codeInsight.controlFlow.instructions.PhpAccessVariableInstruction) javax.swing(javax.swing) ArrayList(java.util.ArrayList) NotNull(org.jetbrains.annotations.NotNull) PhpType(com.jetbrains.php.lang.psi.resolve.types.PhpType) BasePhpElementVisitor(com.kalessil.phpStorm.phpInspectionsEA.openApi.BasePhpElementVisitor) ArrayList(java.util.ArrayList) List(java.util.List) PhpAccessInstruction(com.jetbrains.php.codeInsight.controlFlow.instructions.PhpAccessInstruction) PsiElement(com.intellij.psi.PsiElement) HashSet(java.util.HashSet) PhpAccessVariableInstruction(com.jetbrains.php.codeInsight.controlFlow.instructions.PhpAccessVariableInstruction) PhpDocComment(com.jetbrains.php.lang.documentation.phpdoc.psi.PhpDocComment) PsiWhiteSpace(com.intellij.psi.PsiWhiteSpace) NotNull(org.jetbrains.annotations.NotNull)

Example 7 with PhpAccessVariableInstruction

use of com.jetbrains.php.codeInsight.controlFlow.instructions.PhpAccessVariableInstruction in project phpinspectionsea by kalessil.

the class UselessUnsetInspector method buildVisitor.

@Override
@NotNull
public PsiElementVisitor buildVisitor(@NotNull final ProblemsHolder holder, final boolean isOnTheFly) {
    /* foreach is also a case, but there is no way to get flow entry point in actual JB platform API */
    return new BasePhpElementVisitor() {

        @Override
        public void visitPhpMethod(@NotNull Method method) {
            this.inspectUsages(method.getParameters(), method);
        }

        @Override
        public void visitPhpFunction(@NotNull Function function) {
            this.inspectUsages(function.getParameters(), function);
        }

        private void inspectUsages(@NotNull Parameter[] parameters, @NotNull PhpScopeHolder scope) {
            if (parameters.length > 0) {
                final PhpEntryPointInstruction entry = scope.getControlFlow().getEntryPoint();
                for (final Parameter parameter : parameters) {
                    final String parameterName = parameter.getName();
                    if (!parameterName.isEmpty()) {
                        for (final PhpAccessVariableInstruction usage : OpenapiControlFlowUtil.getFollowingVariableAccessInstructions(entry, parameterName)) {
                            final PsiElement expression = usage.getAnchor();
                            final PsiElement parent = expression.getParent();
                            if (parent instanceof PhpUnset) {
                                int unsetParametersCount = ((PhpUnset) parent).getArguments().length;
                                final PsiElement target = (unsetParametersCount == 1 ? parent : expression);
                                holder.registerProblem(target, MessagesPresentationUtil.prefixWithEa(message), ProblemHighlightType.LIKE_UNUSED_SYMBOL);
                            }
                        }
                    }
                }
            }
        }
    };
}
Also used : BasePhpElementVisitor(com.kalessil.phpStorm.phpInspectionsEA.openApi.BasePhpElementVisitor) Function(com.jetbrains.php.lang.psi.elements.Function) PhpAccessVariableInstruction(com.jetbrains.php.codeInsight.controlFlow.instructions.PhpAccessVariableInstruction) PhpEntryPointInstruction(com.jetbrains.php.codeInsight.controlFlow.instructions.PhpEntryPointInstruction) PhpScopeHolder(com.jetbrains.php.codeInsight.PhpScopeHolder) PhpUnset(com.jetbrains.php.lang.psi.elements.PhpUnset) Parameter(com.jetbrains.php.lang.psi.elements.Parameter) Method(com.jetbrains.php.lang.psi.elements.Method) NotNull(org.jetbrains.annotations.NotNull) PsiElement(com.intellij.psi.PsiElement) NotNull(org.jetbrains.annotations.NotNull)

Example 8 with PhpAccessVariableInstruction

use of com.jetbrains.php.codeInsight.controlFlow.instructions.PhpAccessVariableInstruction in project phpinspectionsea by kalessil.

the class ReferenceMismatchInspector method buildVisitor.

@Override
@NotNull
public PsiElementVisitor buildVisitor(@NotNull final ProblemsHolder holder, boolean isOnTheFly) {
    return new BasePhpElementVisitor() {

        /**
         * TODO: checkReferenceReturnedByCallable - ternary operator, argument usages ?
         */
        /* parameters by reference */
        @Override
        public void visitPhpMethod(@NotNull Method method) {
            /* PHP7 seems to be ref mismatch free */
            final PhpLanguageLevel php = PhpProjectConfigurationFacade.getInstance(holder.getProject()).getLanguageLevel();
            if (!php.hasFeature(PhpLanguageFeature.SCALAR_TYPE_HINTS)) {
                this.checkParameters(method.getParameters(), method);
            }
        }

        @Override
        public void visitPhpFunction(@NotNull Function function) {
            /* PHP7 seems to be ref mismatch free */
            final PhpLanguageLevel phpVersion = PhpProjectConfigurationFacade.getInstance(holder.getProject()).getLanguageLevel();
            if (phpVersion.hasFeature(PhpLanguageFeature.SCALAR_TYPE_HINTS)) {
                // PHP7 and newer
                return;
            }
            /* older versions are still affected */
            this.checkParameters(function.getParameters(), function);
        }

        private void checkParameters(Parameter[] arrParameters, Function objScopeHolder) {
            PhpEntryPointInstruction objEntryPoint = objScopeHolder.getControlFlow().getEntryPoint();
            HashSet<PsiElement> emptyReportedItemsRegistry = ReferenceMismatchInspector.getFunctionReportingRegistry(objScopeHolder);
            for (Parameter parameter : arrParameters) {
                /* skip un-discoverable and non-reference parameters */
                String strParameterName = parameter.getName();
                if (!parameter.isPassByRef() || StringUtils.isEmpty(strParameterName)) {
                    continue;
                }
                inspectScopeForReferenceMissUsages(objEntryPoint, strParameterName, emptyReportedItemsRegistry);
            }
            emptyReportedItemsRegistry.clear();
        }

        /* = & variable/property patterns */
        @Override
        public void visitPhpAssignmentExpression(@NotNull AssignmentExpression assignmentExpression) {
            /* PHP7 seems to be ref mismatch free */
            final PhpLanguageLevel phpVersion = PhpProjectConfigurationFacade.getInstance(holder.getProject()).getLanguageLevel();
            if (phpVersion.hasFeature(PhpLanguageFeature.SCALAR_TYPE_HINTS)) {
                // PHP7 and newer
                return;
            }
            /* older versions are still affected */
            PsiElement value = assignmentExpression.getValue();
            PsiElement variable = assignmentExpression.getVariable();
            if (variable instanceof Variable && (value instanceof Variable || value instanceof FieldReference || value instanceof ArrayAccessExpression || value instanceof FunctionReference)) {
                String strVariable = ((Variable) variable).getName();
                PsiElement operation = value.getPrevSibling();
                if (operation instanceof PsiWhiteSpace) {
                    operation = operation.getPrevSibling();
                }
                if (!StringUtils.isEmpty(strVariable) && null != operation && operation.getText().replaceAll("\\s+", "").equals("=&")) {
                    /* the case, scan for miss-usages assuming variable is unique */
                    Function scope = ExpressionSemanticUtil.getScope(assignmentExpression);
                    if (null != scope) {
                        // report items, but ensure no duplicated messages
                        HashSet<PsiElement> reportedItemsRegistry = ReferenceMismatchInspector.getFunctionReportingRegistry(scope);
                        inspectScopeForReferenceMissUsages(scope.getControlFlow().getEntryPoint(), strVariable, reportedItemsRegistry);
                    }
                }
            }
        }

        /* assign reference from function */
        @Override
        public void visitPhpMethodReference(@NotNull MethodReference reference) {
            /* PHP7 seems to be ref mismatch free */
            final PhpLanguageLevel phpVersion = PhpProjectConfigurationFacade.getInstance(holder.getProject()).getLanguageLevel();
            if (phpVersion.hasFeature(PhpLanguageFeature.SCALAR_TYPE_HINTS)) {
                // PHP7 and newer
                return;
            }
            /* older versions are still affected */
            this.checkReferenceReturnedByCallable(reference);
        }

        @Override
        public void visitPhpFunctionCall(@NotNull FunctionReference reference) {
            /* PHP7 seems to be ref mismatch free */
            final PhpLanguageLevel php = PhpProjectConfigurationFacade.getInstance(holder.getProject()).getLanguageLevel();
            if (!php.hasFeature(PhpLanguageFeature.SCALAR_TYPE_HINTS)) {
                this.checkReferenceReturnedByCallable(reference);
            }
        }

        /* aggressive foreach optimization when value is reference */
        @Override
        public void visitPhpForeach(@NotNull ForeachStatement foreach) {
            /* PHP7 seems to be ref mismatch free */
            final PhpLanguageLevel phpVersion = PhpProjectConfigurationFacade.getInstance(holder.getProject()).getLanguageLevel();
            if (phpVersion.hasFeature(PhpLanguageFeature.SCALAR_TYPE_HINTS)) {
                // PHP7 and newer
                return;
            }
            /* older versions are still affected */
            /* lookup for reference preceding value */
            Variable objForeachValue = foreach.getValue();
            if (null != objForeachValue) {
                String strVariable = objForeachValue.getName();
                PsiElement prevElement = objForeachValue.getPrevSibling();
                if (prevElement instanceof PsiWhiteSpace) {
                    prevElement = prevElement.getPrevSibling();
                }
                if (!StringUtils.isEmpty(strVariable) && OpenapiTypesUtil.is(prevElement, PhpTokenTypes.opBIT_AND)) {
                    /* the case, scan for miss-usages assuming value is unique */
                    Function scope = ExpressionSemanticUtil.getScope(foreach);
                    if (null != scope) {
                        // report items, but ensure no duplicated messages
                        HashSet<PsiElement> reportedItemsRegistry = ReferenceMismatchInspector.getFunctionReportingRegistry(scope);
                        reportedItemsRegistry.add(objForeachValue);
                        inspectScopeForReferenceMissUsages(scope.getControlFlow().getEntryPoint(), strVariable, reportedItemsRegistry);
                    }
                }
            }
        }

        private void inspectScopeForReferenceMissUsages(@NotNull PhpEntryPointInstruction objEntryPoint, @NotNull String strParameterName, @NotNull Set<PsiElement> reportedItemsRegistry) {
            PsiElement previous;
            PsiElement objExpression = null;
            /* find usage inside scope */
            PhpAccessVariableInstruction[] arrUsages = PhpControlFlowUtil.getFollowingVariableAccessInstructions(objEntryPoint, strParameterName, false);
            for (final PhpAccessVariableInstruction objInstruction : arrUsages) {
                previous = objExpression;
                objExpression = objInstruction.getAnchor().getParent();
                /* collided with foreach index/value => bug */
                if (objExpression instanceof ForeachStatement) {
                    final ForeachStatement foreach = (ForeachStatement) objExpression;
                    if (previous instanceof PhpUnset) {
                        break;
                    }
                    final Variable foreachValue = foreach.getValue();
                    if (null != foreachValue && !StringUtils.isEmpty(foreachValue.getName()) && foreachValue.getName().equals(strParameterName)) {
                        if (!reportedItemsRegistry.contains(foreachValue)) {
                            reportedItemsRegistry.add(foreachValue);
                            holder.registerProblem(foreachValue, strErrorForeachIntoReference, ProblemHighlightType.ERROR);
                        }
                        continue;
                    }
                    final Variable foreachKey = foreach.getKey();
                    if (null != foreachKey && !StringUtils.isEmpty(foreachKey.getName()) && foreachKey.getName().equals(strParameterName)) {
                        if (!reportedItemsRegistry.contains(foreachKey)) {
                            reportedItemsRegistry.add(foreachKey);
                            holder.registerProblem(foreachKey, strErrorForeachIntoReference, ProblemHighlightType.ERROR);
                        }
                        continue;
                    }
                }
                /* test if provided as non-reference argument (copy dispatched) */
                if (objExpression instanceof ParameterList && objExpression.getParent() instanceof FunctionReference) {
                    FunctionReference reference = (FunctionReference) objExpression.getParent();
                    /* not resolved or known re-unsafe function */
                    final PsiElement callable = OpenapiResolveUtil.resolveReference(reference);
                    if (!(callable instanceof Function)) {
                        continue;
                    }
                    final String strCallableName = ((Function) callable).getName();
                    if (!StringUtils.isEmpty(strCallableName) && legalizedMismatchingFunctions.contains(strCallableName)) {
                        continue;
                    }
                    /* check if call arguments contains our parameter */
                    int indexInArguments = -1;
                    boolean providedAsArgument = false;
                    for (PsiElement callArgument : reference.getParameters()) {
                        ++indexInArguments;
                        if (callArgument instanceof Variable) {
                            Variable argument = (Variable) callArgument;
                            String argumentName = argument.getName();
                            if (!StringUtils.isEmpty(argumentName) && argumentName.equals(strParameterName)) {
                                providedAsArgument = true;
                                break;
                            }
                        }
                    }
                    /* if not found, keep processing usages */
                    if (!providedAsArgument) {
                        continue;
                    }
                    /* now check what is declared in resolved callable */
                    final Parameter[] usageCallableParameters = ((Function) callable).getParameters();
                    if (usageCallableParameters.length >= indexInArguments + 1) {
                        final Parameter parameter = usageCallableParameters[indexInArguments];
                        if (!parameter.isPassByRef()) {
                            /* additionally try filtering types for reducing false-positives on scalars */
                            final PhpType type = OpenapiResolveUtil.resolveType(parameter, holder.getProject());
                            if (type != null && !PhpType.isSubType(type, legalizedTypesForMismatchingSet)) {
                                final PsiElement target = reference.getParameters()[indexInArguments];
                                if (!reportedItemsRegistry.contains(target)) {
                                    holder.registerProblem(target, "Reference mismatch, copy will be dispatched into function", ProblemHighlightType.WEAK_WARNING);
                                    reportedItemsRegistry.add(target);
                                }
                                continue;
                            }
                        }
                    }
                }
                /* test is assigned to a variable without stating its reference (copy stored) */
                if (objExpression instanceof AssignmentExpression) {
                    /* assignment structure verify */
                    AssignmentExpression assignment = (AssignmentExpression) objExpression;
                    if (assignment.getValue() instanceof Variable) {
                        Variable variable = (Variable) assignment.getValue();
                        String strVariable = variable.getName();
                        /* references parameter */
                        if (!StringUtils.isEmpty(strVariable) && strVariable.equals(strParameterName)) {
                            /* check if assignments states reference usage */
                            PsiElement operation = variable.getPrevSibling();
                            if (operation instanceof PsiWhiteSpace) {
                                operation = operation.getPrevSibling();
                            }
                            /* report if not */
                            if (null != operation && !operation.getText().replaceAll("\\s+", "").equals("=&")) {
                                if (!reportedItemsRegistry.contains(objExpression)) {
                                    holder.registerProblem(objExpression, "Reference mismatch, copy will be stored (for non-objects)", ProblemHighlightType.WEAK_WARNING);
                                    reportedItemsRegistry.add(objExpression);
                                }
                            }
                        }
                    }
                }
            }
        }

        private void checkReferenceReturnedByCallable(@NotNull FunctionReference reference) {
            /* check context before resolving anything  */
            final PsiElement parent = reference.getParent();
            if (parent instanceof AssignmentExpression) {
                /* assignment structure verify */
                final AssignmentExpression assignment = (AssignmentExpression) parent;
                if (assignment.getValue() == reference) {
                    /* try resolving now */
                    final PsiElement callable = OpenapiResolveUtil.resolveReference(reference);
                    if (callable instanceof Function) {
                        /* ensure name discoverable */
                        final Function function = (Function) callable;
                        final PsiElement nameNode = NamedElementUtil.getNameIdentifier(function);
                        if (null != nameNode) {
                            /* is defined like returning reference */
                            PsiElement prevElement = nameNode.getPrevSibling();
                            if (prevElement instanceof PsiWhiteSpace) {
                                prevElement = prevElement.getPrevSibling();
                            }
                            if (OpenapiTypesUtil.is(prevElement, PhpTokenTypes.opBIT_AND)) {
                                /* check if assignments states reference usage */
                                PsiElement operation = reference.getPrevSibling();
                                if (operation instanceof PsiWhiteSpace) {
                                    operation = operation.getPrevSibling();
                                }
                                /* report if not */
                                if (null != operation && !operation.getText().replaceAll("\\s+", "").equals("=&")) {
                                    holder.registerProblem(parent, "Reference mismatch, copy will be stored (for non-objects)", ProblemHighlightType.WEAK_WARNING);
                                }
                            }
                        }
                    }
                }
            }
        }
    };
}
Also used : HashSet(java.util.HashSet) Set(java.util.Set) PhpEntryPointInstruction(com.jetbrains.php.codeInsight.controlFlow.instructions.PhpEntryPointInstruction) NotNull(org.jetbrains.annotations.NotNull) PhpType(com.jetbrains.php.lang.psi.resolve.types.PhpType) BasePhpElementVisitor(com.kalessil.phpStorm.phpInspectionsEA.openApi.BasePhpElementVisitor) PsiElement(com.intellij.psi.PsiElement) PhpAccessVariableInstruction(com.jetbrains.php.codeInsight.controlFlow.instructions.PhpAccessVariableInstruction) PhpLanguageLevel(com.jetbrains.php.config.PhpLanguageLevel) PsiWhiteSpace(com.intellij.psi.PsiWhiteSpace) NotNull(org.jetbrains.annotations.NotNull)

Example 9 with PhpAccessVariableInstruction

use of com.jetbrains.php.codeInsight.controlFlow.instructions.PhpAccessVariableInstruction in project phpinspectionsea by kalessil.

the class ForeachSourceInspector method buildVisitor.

@Override
@NotNull
public PsiElementVisitor buildVisitor(@NotNull final ProblemsHolder holder, final boolean isOnTheFly) {
    return new BasePhpElementVisitor() {

        @Override
        public void visitPhpForeach(@NotNull ForeachStatement foreach) {
            final PsiElement source = ExpressionSemanticUtil.getExpressionTroughParenthesis(foreach.getArray());
            if (source instanceof PhpTypedElement && !isEnsuredByPyParentIf(foreach, source)) {
                this.analyseContainer(source);
            }
        }

        /* should cover is_array/is_iterable in direct parent if of the loop, while PS types resolving gets improved */
        private boolean isEnsuredByPyParentIf(@NotNull ForeachStatement foreach, @NotNull PsiElement source) {
            boolean result = false;
            if (foreach.getPrevPsiSibling() == null) {
                final PsiElement ifCandidate = foreach.getParent() instanceof GroupStatement ? foreach.getParent().getParent() : null;
                final PsiElement conditions;
                if (ifCandidate instanceof If) {
                    conditions = ((If) ifCandidate).getCondition();
                } else if (ifCandidate instanceof ElseIf) {
                    conditions = ((ElseIf) ifCandidate).getCondition();
                } else {
                    conditions = null;
                }
                if (conditions != null) {
                    for (final PsiElement candidate : PsiTreeUtil.findChildrenOfType(conditions, source.getClass())) {
                        if (OpeanapiEquivalenceUtil.areEqual(candidate, source)) {
                            final PsiElement call = candidate.getParent() instanceof ParameterList ? candidate.getParent().getParent() : null;
                            if (OpenapiTypesUtil.isFunctionReference(call)) {
                                final String functionName = ((FunctionReference) call).getName();
                                if (functionName != null && (functionName.equals("is_array") || functionName.equals("is_iterable"))) {
                                    result = true;
                                    break;
                                }
                            }
                        }
                    }
                }
            }
            return result;
        }

        private void analyseContainer(@NotNull PsiElement container) {
            final PhpType resolvedType = OpenapiResolveUtil.resolveType((PhpTypedElement) container, container.getProject());
            if (resolvedType == null) {
                return;
            }
            final Set<String> types = new HashSet<>();
            resolvedType.filterUnknown().getTypes().forEach(t -> types.add(Types.getType(t)));
            if (types.isEmpty()) {
                /* false-positives: pre-defined variables */
                if (container instanceof Variable) {
                    final String variableName = ((Variable) container).getName();
                    if (ExpressionCostEstimateUtil.predefinedVars.contains(variableName)) {
                        return;
                    }
                }
                if (REPORT_UNRECOGNIZED_TYPES) {
                    holder.registerProblem(container, patternNotRecognized, ProblemHighlightType.WEAK_WARNING);
                }
                return;
            }
            /* false-positives: multiple return types checked only in function/method; no global context */
            final PsiElement scope = ExpressionSemanticUtil.getBlockScope(container);
            if (types.size() > 1 && !(scope instanceof Function)) {
                types.clear();
                return;
            }
            /* false-positives: mixed parameter type, parameter overridden before foreach */
            if (types.size() > 1 && scope instanceof Function && container instanceof Variable) {
                final String parameter = ((Variable) container).getName();
                final PhpEntryPointInstruction start = ((Function) scope).getControlFlow().getEntryPoint();
                final PhpAccessVariableInstruction[] uses = PhpControlFlowUtil.getFollowingVariableAccessInstructions(start, parameter, false);
                for (final PhpAccessVariableInstruction instruction : uses) {
                    final PhpPsiElement expression = instruction.getAnchor();
                    /* when matched itself, stop processing */
                    if (expression == container) {
                        break;
                    }
                    final PsiElement parent = expression.getParent();
                    if (parent instanceof AssignmentExpression) {
                        final PsiElement matchCandidate = ((AssignmentExpression) parent).getVariable();
                        if (matchCandidate != null && OpeanapiEquivalenceUtil.areEqual(matchCandidate, container)) {
                            types.clear();
                            return;
                        }
                    }
                }
            }
            /* false-positives: array type parameter declaration adds mixed */
            if (types.size() > 1 && scope instanceof Function && container instanceof ArrayAccessExpression) {
                final PsiElement candidate = ((ArrayAccessExpression) container).getValue();
                if (candidate instanceof Variable && types.contains(Types.strMixed) && types.contains(Types.strArray)) {
                    types.remove(Types.strMixed);
                }
            }
            /* gracefully request to specify exact types which can appear (mixed, object) */
            if (types.contains(Types.strMixed)) {
                /* false-positive: mixed definitions from stub functions */
                boolean isStubFunction = false;
                if (OpenapiTypesUtil.isFunctionReference(container)) {
                    final PsiElement function = OpenapiResolveUtil.resolveReference((FunctionReference) container);
                    final String filePath = function == null ? null : function.getContainingFile().getVirtualFile().getCanonicalPath();
                    isStubFunction = filePath != null && filePath.contains(".jar!") && filePath.contains("/stubs/");
                }
                /* false-positive: mixed definition from array type */
                if (!isStubFunction && !types.contains(Types.strArray) && REPORT_MIXED_TYPES) {
                    final String message = String.format(patternMixedTypes, Types.strMixed);
                    holder.registerProblem(container, message, ProblemHighlightType.WEAK_WARNING);
                }
                types.remove(Types.strMixed);
            }
            if (types.contains(Types.strObject)) {
                if (REPORT_MIXED_TYPES) {
                    final String message = String.format(patternMixedTypes, Types.strObject);
                    holder.registerProblem(container, message, ProblemHighlightType.WEAK_WARNING);
                }
                types.remove(Types.strObject);
            }
            /* respect patter when returned array and bool|null for indicating failures*/
            if (types.size() == 2 && types.contains(Types.strArray)) {
                types.remove(Types.strBoolean);
                types.remove(Types.strNull);
            }
            /* do not process foreach-compatible types */
            types.remove(Types.strArray);
            types.remove(Types.strIterable);
            types.remove("\\Traversable");
            types.remove("\\Iterator");
            types.remove("\\IteratorAggregate");
            /* don't process mysterious empty set type */
            types.remove(Types.strEmptySet);
            /* iterate rest of types */
            if (!types.isEmpty()) {
                final PhpIndex index = PhpIndex.getInstance(holder.getProject());
                for (final String type : types) {
                    /* report if scalar type is met */
                    if (!type.startsWith("\\")) {
                        holder.registerProblem(container, String.format(patternScalar, type), ProblemHighlightType.GENERIC_ERROR);
                        continue;
                    }
                    /* check classes for the Traversable interface in the inheritance chain */
                    final List<PhpClass> classes = OpenapiResolveUtil.resolveClassesAndInterfacesByFQN(type, index);
                    if (!classes.isEmpty()) {
                        boolean hasTraversable = false;
                        for (final PhpClass clazz : classes) {
                            final Set<PhpClass> interfaces = InterfacesExtractUtil.getCrawlInheritanceTree(clazz, false);
                            if (!interfaces.isEmpty()) {
                                hasTraversable = interfaces.stream().anyMatch(i -> i.getFQN().equals("\\Traversable"));
                                interfaces.clear();
                                if (hasTraversable) {
                                    break;
                                }
                            }
                        }
                        classes.clear();
                        if (!hasTraversable) {
                            holder.registerProblem(container, String.format(patternObject, type));
                        }
                    }
                }
                types.clear();
            }
        }
    };
}
Also used : PhpEntryPointInstruction(com.jetbrains.php.codeInsight.controlFlow.instructions.PhpEntryPointInstruction) BasePhpInspection(com.kalessil.phpStorm.phpInspectionsEA.openApi.BasePhpInspection) com.jetbrains.php.lang.psi.elements(com.jetbrains.php.lang.psi.elements) InterfacesExtractUtil(com.kalessil.phpStorm.phpInspectionsEA.utils.hierarhy.InterfacesExtractUtil) Set(java.util.Set) ExpressionCostEstimateUtil(com.kalessil.phpStorm.phpInspectionsEA.inspectors.ifs.utils.ExpressionCostEstimateUtil) PhpIndex(com.jetbrains.php.PhpIndex) OptionsComponent(com.kalessil.phpStorm.phpInspectionsEA.options.OptionsComponent) PhpControlFlowUtil(com.jetbrains.php.codeInsight.controlFlow.PhpControlFlowUtil) HashSet(java.util.HashSet) PsiTreeUtil(com.intellij.psi.util.PsiTreeUtil) BasePhpElementVisitor(com.kalessil.phpStorm.phpInspectionsEA.openApi.BasePhpElementVisitor) com.kalessil.phpStorm.phpInspectionsEA.utils(com.kalessil.phpStorm.phpInspectionsEA.utils) List(java.util.List) PsiElement(com.intellij.psi.PsiElement) ProblemHighlightType(com.intellij.codeInspection.ProblemHighlightType) PhpType(com.jetbrains.php.lang.psi.resolve.types.PhpType) NotNull(org.jetbrains.annotations.NotNull) PsiElementVisitor(com.intellij.psi.PsiElementVisitor) ProblemsHolder(com.intellij.codeInspection.ProblemsHolder) PhpAccessVariableInstruction(com.jetbrains.php.codeInsight.controlFlow.instructions.PhpAccessVariableInstruction) javax.swing(javax.swing) PhpEntryPointInstruction(com.jetbrains.php.codeInsight.controlFlow.instructions.PhpEntryPointInstruction) NotNull(org.jetbrains.annotations.NotNull) PhpType(com.jetbrains.php.lang.psi.resolve.types.PhpType) BasePhpElementVisitor(com.kalessil.phpStorm.phpInspectionsEA.openApi.BasePhpElementVisitor) PsiElement(com.intellij.psi.PsiElement) HashSet(java.util.HashSet) PhpAccessVariableInstruction(com.jetbrains.php.codeInsight.controlFlow.instructions.PhpAccessVariableInstruction) PhpIndex(com.jetbrains.php.PhpIndex) NotNull(org.jetbrains.annotations.NotNull)

Aggregations

PhpAccessVariableInstruction (com.jetbrains.php.codeInsight.controlFlow.instructions.PhpAccessVariableInstruction)9 PsiElement (com.intellij.psi.PsiElement)8 NotNull (org.jetbrains.annotations.NotNull)8 BasePhpElementVisitor (com.kalessil.phpStorm.phpInspectionsEA.openApi.BasePhpElementVisitor)7 PhpEntryPointInstruction (com.jetbrains.php.codeInsight.controlFlow.instructions.PhpEntryPointInstruction)5 PhpScopeHolder (com.jetbrains.php.codeInsight.PhpScopeHolder)4 PhpType (com.jetbrains.php.lang.psi.resolve.types.PhpType)4 HashSet (java.util.HashSet)4 ProblemsHolder (com.intellij.codeInspection.ProblemsHolder)3 PsiElementVisitor (com.intellij.psi.PsiElementVisitor)3 com.jetbrains.php.lang.psi.elements (com.jetbrains.php.lang.psi.elements)3 BasePhpInspection (com.kalessil.phpStorm.phpInspectionsEA.openApi.BasePhpInspection)3 com.kalessil.phpStorm.phpInspectionsEA.utils (com.kalessil.phpStorm.phpInspectionsEA.utils)3 Set (java.util.Set)3 ProblemHighlightType (com.intellij.codeInspection.ProblemHighlightType)2 PsiWhiteSpace (com.intellij.psi.PsiWhiteSpace)2 PsiTreeUtil (com.intellij.psi.util.PsiTreeUtil)2 PhpIndex (com.jetbrains.php.PhpIndex)2 PhpAccessInstruction (com.jetbrains.php.codeInsight.controlFlow.instructions.PhpAccessInstruction)2 PhpDocComment (com.jetbrains.php.lang.documentation.phpdoc.psi.PhpDocComment)2