Search in sources :

Example 41 with PhpType

use of com.jetbrains.php.lang.psi.resolve.types.PhpType in project phpinspectionsea by kalessil.

the class UnnecessaryCastingInspector method buildVisitor.

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

        @Override
        public void visitPhpUnaryExpression(@NotNull UnaryExpression expression) {
            final PsiElement argument = ExpressionSemanticUtil.getExpressionTroughParenthesis(expression.getValue());
            final PsiElement operation = expression.getOperation();
            final IElementType operator = operation == null ? null : operation.getNode().getElementType();
            if (operator != null && argument instanceof PhpTypedElement) {
                final PsiElement parent = expression.getParent();
                if (operator == PhpTokenTypes.opSTRING_CAST) {
                    /* case 1: concatenation `... . (string)...` */
                    if (parent instanceof BinaryExpression) {
                        final BinaryExpression binary = (BinaryExpression) parent;
                        if (binary.getOperationType() == PhpTokenTypes.opCONCAT) {
                            holder.registerProblem(operation, MessagesPresentationUtil.prefixWithEa(messageConcatenate), ProblemHighlightType.LIKE_UNUSED_SYMBOL, new ReplaceWithArgumentFix());
                            return;
                        }
                    } else /* case 2: self assign with `... .=  (string)...` */
                    if (parent instanceof SelfAssignmentExpression) {
                        final SelfAssignmentExpression assignment = (SelfAssignmentExpression) parent;
                        if (assignment.getOperationType() == PhpTokenTypes.opCONCAT_ASGN) {
                            holder.registerProblem(operation, MessagesPresentationUtil.prefixWithEa(messageConcatenate), ProblemHighlightType.LIKE_UNUSED_SYMBOL, new ReplaceWithArgumentFix());
                            return;
                        }
                    }
                }
                /* case 3: un-needed due to types */
                if (typesMapping.containsKey(operator)) {
                    final Set<String> types = this.resolveStrictly((PhpTypedElement) argument).getTypes();
                    if (types.size() == 1 && typesMapping.get(operator).equals(Types.getType(types.iterator().next()))) {
                        if (!(argument instanceof Variable) || !this.isWeakTypedParameter((Variable) argument)) {
                            final boolean isTarget = !this.isNullCoalescingOnly(argument);
                            if (isTarget) {
                                holder.registerProblem(operation, MessagesPresentationUtil.prefixWithEa(messageGeneric), ProblemHighlightType.LIKE_UNUSED_SYMBOL, new ReplaceWithArgumentFix());
                            }
                        }
                    }
                }
            }
        }

        private boolean isNullCoalescingOnly(@NotNull PsiElement argument) {
            boolean result = false;
            final Set<PsiElement> variants = PossibleValuesDiscoveryUtil.discover(argument);
            if (variants.size() == 1) {
                final PsiElement candidate = variants.iterator().next().getParent();
                if (candidate instanceof BinaryExpression) {
                    result = ((BinaryExpression) candidate).getOperationType() == PhpTokenTypes.opCOALESCE;
                }
            }
            variants.clear();
            return result;
        }

        @NotNull
        private PhpType resolveStrictly(@NotNull PhpTypedElement expression) {
            final Project project = holder.getProject();
            PhpType result = null;
            if (expression instanceof FieldReference) {
                /* fields has no type hints, hence private only */
                final PsiElement resolved = OpenapiResolveUtil.resolveReference((FieldReference) expression);
                if (resolved instanceof Field) {
                    final Field referencedField = (Field) resolved;
                    if (referencedField.getModifier().isPrivate()) {
                        result = OpenapiResolveUtil.resolveType(referencedField, project);
                    }
                }
            } else if (expression instanceof FunctionReference) {
                /* requires implicit return type declaration */
                final PsiElement resolved = OpenapiResolveUtil.resolveReference((FunctionReference) expression);
                if (resolved instanceof Function) {
                    final Function referencedFunction = (Function) resolved;
                    final PsiElement returnedType = OpenapiElementsUtil.getReturnType(referencedFunction);
                    if (returnedType != null) {
                        result = OpenapiResolveUtil.resolveType(referencedFunction, project);
                    }
                }
            } else {
                result = OpenapiResolveUtil.resolveType(expression, project);
            }
            /* Normalize the resolved types */
            if (result != null && !result.isEmpty()) {
                result = result.filterUnknown();
                /* When `?->` operator is used, add null */
                if (expression instanceof MemberReference) {
                    final MemberReference reference = (MemberReference) expression;
                    final PsiElement operator = OpenapiPsiSearchUtil.findResolutionOperator(reference);
                    if (OpenapiTypesUtil.is(operator, PhpTokenTypes.ARROW) && OpenapiTypesUtil.isNullSafeMemberReferenceOperator(operator)) {
                        result = new PhpType().add(result.filterNull()).add(PhpType.NULL);
                    }
                }
                return result;
            }
            return PhpType.EMPTY;
        }

        private boolean isWeakTypedParameter(@NotNull Variable variable) {
            boolean result = false;
            final Function scope = ExpressionSemanticUtil.getScope(variable);
            if (scope != null) {
                final String variableName = variable.getName();
                for (final Parameter parameter : scope.getParameters()) {
                    if (parameter.getName().equals(variableName)) {
                        result = OpenapiResolveUtil.resolveDeclaredType(parameter).isEmpty();
                        break;
                    }
                }
            }
            return result;
        }
    };
}
Also used : NotNull(org.jetbrains.annotations.NotNull) PhpType(com.jetbrains.php.lang.psi.resolve.types.PhpType) IElementType(com.intellij.psi.tree.IElementType) BasePhpElementVisitor(com.kalessil.phpStorm.phpInspectionsEA.openApi.BasePhpElementVisitor) Project(com.intellij.openapi.project.Project) PsiElement(com.intellij.psi.PsiElement) NotNull(org.jetbrains.annotations.NotNull)

Example 42 with PhpType

use of com.jetbrains.php.lang.psi.resolve.types.PhpType in project phpinspectionsea by kalessil.

the class OpAssignShortSyntaxInspector method buildVisitor.

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

        @Override
        public void visitPhpAssignmentExpression(@NotNull AssignmentExpression assignment) {
            final PsiElement value = ExpressionSemanticUtil.getExpressionTroughParenthesis(assignment.getValue());
            if (value instanceof BinaryExpression) {
                final BinaryExpression binary = (BinaryExpression) value;
                final PsiElement operator = binary.getOperation();
                if (operator != null) {
                    final PsiElement left = binary.getLeftOperand();
                    final PsiElement right = binary.getRightOperand();
                    final PsiElement variable = assignment.getVariable();
                    if (variable != null && left != null && right != null) {
                        final IElementType operation = operator.getNode().getElementType();
                        if (mapping.containsKey(operation)) {
                            final LinkedList<PsiElement> fragments = new LinkedList<>();
                            fragments.addLast(right);
                            PsiElement candidate = left;
                            while (candidate instanceof BinaryExpression) {
                                final BinaryExpression current = (BinaryExpression) candidate;
                                final PsiElement rightPart = current.getRightOperand();
                                if (rightPart != null) {
                                    fragments.addLast(rightPart);
                                }
                                if (current.getOperationType() != operation) {
                                    break;
                                }
                                candidate = current.getLeftOperand();
                            }
                            if (candidate != null && OpenapiEquivalenceUtil.areEqual(variable, candidate)) {
                                final boolean canShorten = (fragments.size() == 1 || chainingSafeOperators.contains(operation)) && fragments.stream().noneMatch(f -> f instanceof BinaryExpression);
                                if (canShorten) {
                                    /* false-positives: string elements manipulation, causes a fatal error */
                                    boolean isStringManipulation = false;
                                    if (variable instanceof ArrayAccessExpression) {
                                        final PsiElement stringCandidate = ((ArrayAccessExpression) variable).getValue();
                                        if (stringCandidate instanceof PhpTypedElement) {
                                            final PhpType resolved = OpenapiResolveUtil.resolveType((PhpTypedElement) stringCandidate, holder.getProject());
                                            if (resolved != null && !resolved.hasUnknown()) {
                                                isStringManipulation = resolved.getTypes().stream().anyMatch(t -> Types.getType(t).equals(Types.strString));
                                            }
                                        }
                                    }
                                    if (!isStringManipulation) {
                                        Collections.reverse(fragments);
                                        final String replacement = String.format("%s %s= %s", candidate.getText(), operator.getText(), fragments.stream().map(PsiElement::getText).collect(Collectors.joining(" " + operator.getText() + " ")));
                                        holder.registerProblem(assignment, String.format(MessagesPresentationUtil.prefixWithEa(messagePattern), replacement), new UseShorthandOperatorFix(replacement));
                                    }
                                }
                            }
                            fragments.clear();
                        }
                    }
                }
            }
        }
    };
}
Also used : java.util(java.util) BasePhpInspection(com.kalessil.phpStorm.phpInspectionsEA.openApi.BasePhpInspection) IElementType(com.intellij.psi.tree.IElementType) ArrayAccessExpression(com.jetbrains.php.lang.psi.elements.ArrayAccessExpression) AssignmentExpression(com.jetbrains.php.lang.psi.elements.AssignmentExpression) PhpTokenTypes(com.jetbrains.php.lang.lexer.PhpTokenTypes) UseSuggestedReplacementFixer(com.kalessil.phpStorm.phpInspectionsEA.fixers.UseSuggestedReplacementFixer) PhpTypedElement(com.jetbrains.php.lang.psi.elements.PhpTypedElement) Collectors(java.util.stream.Collectors) BasePhpElementVisitor(com.kalessil.phpStorm.phpInspectionsEA.openApi.BasePhpElementVisitor) com.kalessil.phpStorm.phpInspectionsEA.utils(com.kalessil.phpStorm.phpInspectionsEA.utils) BinaryExpression(com.jetbrains.php.lang.psi.elements.BinaryExpression) PsiElement(com.intellij.psi.PsiElement) PhpType(com.jetbrains.php.lang.psi.resolve.types.PhpType) NotNull(org.jetbrains.annotations.NotNull) PsiElementVisitor(com.intellij.psi.PsiElementVisitor) ProblemsHolder(com.intellij.codeInspection.ProblemsHolder) NotNull(org.jetbrains.annotations.NotNull) PhpType(com.jetbrains.php.lang.psi.resolve.types.PhpType) IElementType(com.intellij.psi.tree.IElementType) BasePhpElementVisitor(com.kalessil.phpStorm.phpInspectionsEA.openApi.BasePhpElementVisitor) AssignmentExpression(com.jetbrains.php.lang.psi.elements.AssignmentExpression) BinaryExpression(com.jetbrains.php.lang.psi.elements.BinaryExpression) PhpTypedElement(com.jetbrains.php.lang.psi.elements.PhpTypedElement) ArrayAccessExpression(com.jetbrains.php.lang.psi.elements.ArrayAccessExpression) PsiElement(com.intellij.psi.PsiElement) NotNull(org.jetbrains.annotations.NotNull)

Example 43 with PhpType

use of com.jetbrains.php.lang.psi.resolve.types.PhpType in project phpinspectionsea by kalessil.

the class TypeUnsafeComparisonInspector method buildVisitor.

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

        @Override
        public void visitPhpBinaryExpression(@NotNull BinaryExpression expression) {
            final IElementType operator = expression.getOperationType();
            if (operator == PhpTokenTypes.opEQUAL || operator == PhpTokenTypes.opNOT_EQUAL) {
                this.analyze(expression, operator);
            }
        }

        private void analyze(@NotNull final BinaryExpression subject, @NotNull final IElementType operator) {
            final String targetOperator = PhpTokenTypes.opEQUAL == operator ? "===" : "!==";
            final PsiElement left = subject.getLeftOperand();
            final PsiElement right = subject.getRightOperand();
            if (right instanceof StringLiteralExpression || left instanceof StringLiteralExpression) {
                final PsiElement nonStringOperand;
                final String literalValue;
                if (right instanceof StringLiteralExpression) {
                    literalValue = ((StringLiteralExpression) right).getContents();
                    nonStringOperand = ExpressionSemanticUtil.getExpressionTroughParenthesis(left);
                } else {
                    literalValue = ((StringLiteralExpression) left).getContents();
                    nonStringOperand = ExpressionSemanticUtil.getExpressionTroughParenthesis(right);
                }
                /* resolve 2nd operand type, if class ensure __toString is implemented */
                if (ClassInStringContextStrategy.apply(nonStringOperand, holder, subject, messageToStringMethodMissing)) {
                    /* TODO: weak warning regarding under-the-hood string casting */
                    return;
                }
                /* string literal is numeric or empty, no strict compare possible */
                if (!literalValue.isEmpty() && !literalValue.matches("^[+-]?[0-9]*\\.?[0-9]+$")) {
                    holder.registerProblem(subject, String.format(MessagesPresentationUtil.prefixWithEa(patternCompareStrict), targetOperator), new CompareStrictFix(targetOperator));
                    return;
                }
            }
            /* some objects supporting direct comparison: search for .compare_objects in PHP sources */
            if (left != null && right != null) {
                final boolean isComparableObject = this.isComparableObject(left) || this.isComparableObject(right);
                if (!isComparableObject) {
                    holder.registerProblem(subject, String.format(MessagesPresentationUtil.prefixWithEa(patternHarden), targetOperator), ProblemHighlightType.WEAK_WARNING);
                }
            }
        }

        private boolean isComparableObject(@NotNull PsiElement operand) {
            if (operand instanceof PhpTypedElement) {
                final Project project = holder.getProject();
                final PhpType resolved = OpenapiResolveUtil.resolveType((PhpTypedElement) operand, project);
                if (resolved != null) {
                    final PhpIndex index = PhpIndex.getInstance(project);
                    final Set<PhpClass> classes = new HashSet<>();
                    resolved.filterUnknown().getTypes().stream().filter(t -> t.charAt(0) == '\\').forEach(t -> classes.addAll(OpenapiResolveUtil.resolveClassesAndInterfacesByFQN(Types.getType(t), index)));
                    for (final PhpClass clazz : classes) {
                        final boolean hasAny = comparable.contains(clazz.getFQN()) || InterfacesExtractUtil.getCrawlInheritanceTree(clazz, true).stream().anyMatch(c -> comparable.contains(c.getFQN()));
                        if (hasAny) {
                            classes.clear();
                            return true;
                        }
                    }
                    classes.clear();
                }
            }
            return false;
        }
    };
}
Also used : IElementType(com.intellij.psi.tree.IElementType) PhpTokenTypes(com.jetbrains.php.lang.lexer.PhpTokenTypes) PhpIndex(com.jetbrains.php.PhpIndex) PhpTypedElement(com.jetbrains.php.lang.psi.elements.PhpTypedElement) HashSet(java.util.HashSet) ClassInStringContextStrategy(com.kalessil.phpStorm.phpInspectionsEA.utils.strategy.ClassInStringContextStrategy) BinaryExpression(com.jetbrains.php.lang.psi.elements.BinaryExpression) ProblemDescriptor(com.intellij.codeInspection.ProblemDescriptor) MessagesPresentationUtil(com.kalessil.phpStorm.phpInspectionsEA.utils.MessagesPresentationUtil) PsiElement(com.intellij.psi.PsiElement) Project(com.intellij.openapi.project.Project) PhpType(com.jetbrains.php.lang.psi.resolve.types.PhpType) LocalQuickFix(com.intellij.codeInspection.LocalQuickFix) PsiElementVisitor(com.intellij.psi.PsiElementVisitor) ProblemsHolder(com.intellij.codeInspection.ProblemsHolder) PhpPsiElementFactory(com.jetbrains.php.lang.psi.PhpPsiElementFactory) PhpClass(com.jetbrains.php.lang.psi.elements.PhpClass) BasePhpInspection(com.kalessil.phpStorm.phpInspectionsEA.openApi.BasePhpInspection) ExpressionSemanticUtil(com.kalessil.phpStorm.phpInspectionsEA.utils.ExpressionSemanticUtil) Types(com.kalessil.phpStorm.phpInspectionsEA.utils.Types) InterfacesExtractUtil(com.kalessil.phpStorm.phpInspectionsEA.utils.hierarhy.InterfacesExtractUtil) LeafPsiElement(com.intellij.psi.impl.source.tree.LeafPsiElement) Set(java.util.Set) BasePhpElementVisitor(com.kalessil.phpStorm.phpInspectionsEA.openApi.BasePhpElementVisitor) OpenapiResolveUtil(com.kalessil.phpStorm.phpInspectionsEA.utils.OpenapiResolveUtil) ProblemHighlightType(com.intellij.codeInspection.ProblemHighlightType) StringLiteralExpression(com.jetbrains.php.lang.psi.elements.StringLiteralExpression) NotNull(org.jetbrains.annotations.NotNull) StringLiteralExpression(com.jetbrains.php.lang.psi.elements.StringLiteralExpression) PhpIndex(com.jetbrains.php.PhpIndex) PhpClass(com.jetbrains.php.lang.psi.elements.PhpClass) NotNull(org.jetbrains.annotations.NotNull) PhpType(com.jetbrains.php.lang.psi.resolve.types.PhpType) IElementType(com.intellij.psi.tree.IElementType) BasePhpElementVisitor(com.kalessil.phpStorm.phpInspectionsEA.openApi.BasePhpElementVisitor) Project(com.intellij.openapi.project.Project) BinaryExpression(com.jetbrains.php.lang.psi.elements.BinaryExpression) PhpTypedElement(com.jetbrains.php.lang.psi.elements.PhpTypedElement) PsiElement(com.intellij.psi.PsiElement) LeafPsiElement(com.intellij.psi.impl.source.tree.LeafPsiElement) HashSet(java.util.HashSet) NotNull(org.jetbrains.annotations.NotNull)

Example 44 with PhpType

use of com.jetbrains.php.lang.psi.resolve.types.PhpType in project phpinspectionsea by kalessil.

the class InstanceofCanBeUsedInspector method buildVisitor.

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

        @Override
        public void visitPhpFunctionCall(@NotNull FunctionReference reference) {
            final String functionName = reference.getName();
            if (functionName != null) {
                switch(functionName) {
                    case "get_class":
                    case "get_parent_class":
                        {
                            final PsiElement[] arguments = reference.getParameters();
                            if (arguments.length == 1 && this.isTargetBinaryContext(reference) && this.isNotString(arguments[0])) {
                                final BinaryExpression binary = (BinaryExpression) reference.getParent();
                                final PsiElement candidate = OpenapiElementsUtil.getSecondOperand(binary, reference);
                                if (candidate != null) {
                                    final String fqn = this.extractClassFqn(candidate);
                                    if (fqn != null) {
                                        this.analyze(binary, arguments[0], fqn, !functionName.equals("get_class"));
                                    }
                                }
                            }
                            break;
                        }
                    case "is_a":
                    case "is_subclass_of":
                        {
                            final PsiElement[] arguments = reference.getParameters();
                            final boolean isTarget = arguments.length == 2 || (arguments.length == 3 && PhpLanguageUtil.isFalse(arguments[2]));
                            if (isTarget && this.isNotString(arguments[0])) {
                                final String fqn = this.extractClassFqn(arguments[1]);
                                if (fqn != null) {
                                    this.analyze(reference, arguments[0], fqn, true);
                                }
                            }
                            break;
                        }
                    case "in_array":
                        {
                            final PsiElement[] arguments = reference.getParameters();
                            if (arguments.length >= 2 && OpenapiTypesUtil.isFunctionReference(arguments[1])) {
                                final FunctionReference innerCall = (FunctionReference) arguments[1];
                                final String innerName = innerCall.getName();
                                if (innerName != null && (innerName.equals("class_implements") || innerName.equals("class_parents"))) {
                                    final PsiElement[] innerArguments = innerCall.getParameters();
                                    if (innerArguments.length > 0 && this.isNotString(innerArguments[0])) {
                                        final String fqn = this.extractClassFqn(arguments[0]);
                                        if (fqn != null) {
                                            this.analyze(reference, innerArguments[0], fqn, true);
                                        }
                                    }
                                }
                            }
                            break;
                        }
                }
            }
        }

        private void analyze(@NotNull PsiElement context, @NotNull PsiElement subject, @NotNull String fqn, boolean allowChildClasses) {
            final PhpIndex index = PhpIndex.getInstance(holder.getProject());
            final Collection<PhpClass> classes = OpenapiResolveUtil.resolveClassesByFQN(fqn, index);
            if (!classes.isEmpty() && (allowChildClasses || index.getDirectSubclasses(fqn).isEmpty())) {
                boolean isInverted = false;
                /* the calls can be inverted, less work for us */
                if (context instanceof BinaryExpression) {
                    final IElementType operator = ((BinaryExpression) context).getOperationType();
                    isInverted = operator == PhpTokenTypes.opNOT_IDENTICAL || operator == PhpTokenTypes.opNOT_EQUAL;
                }
                final String replacement = String.format(isInverted ? "! %s instanceof %s" : "%s instanceof %s", subject.getText(), fqn);
                holder.registerProblem(context, String.format(MessagesPresentationUtil.prefixWithEa(messagePattern), replacement), new UseInstanceofFix(replacement));
            }
        }

        @Nullable
        private String extractClassFqn(@NotNull PsiElement candidate) {
            if (candidate instanceof StringLiteralExpression) {
                final StringLiteralExpression string = (StringLiteralExpression) candidate;
                final String clazz = string.getContents();
                if (clazz.length() > 3 && !clazz.equals("__PHP_Incomplete_Class") && string.getFirstPsiChild() == null) {
                    return '\\' + clazz.replaceAll("\\\\\\\\", "\\\\");
                }
            }
            return null;
        }

        private boolean isTargetBinaryContext(@NotNull FunctionReference reference) {
            final PsiElement parent = reference.getParent();
            if (parent instanceof BinaryExpression) {
                return OpenapiTypesUtil.tsCOMPARE_EQUALITY_OPS.contains(((BinaryExpression) parent).getOperationType());
            }
            return false;
        }

        private boolean isNotString(@NotNull PsiElement subject) {
            if (subject instanceof PhpTypedElement && !(subject instanceof StringLiteralExpression)) {
                final PhpType resolved = OpenapiResolveUtil.resolveType((PhpTypedElement) subject, holder.getProject());
                if (resolved != null && !resolved.hasUnknown()) {
                    return resolved.getTypes().stream().noneMatch(type -> Types.getType(type).equals(Types.strString));
                }
            }
            return false;
        }
    };
}
Also used : PhpIndex(com.jetbrains.php.PhpIndex) NotNull(org.jetbrains.annotations.NotNull) PhpType(com.jetbrains.php.lang.psi.resolve.types.PhpType) IElementType(com.intellij.psi.tree.IElementType) BasePhpElementVisitor(com.kalessil.phpStorm.phpInspectionsEA.openApi.BasePhpElementVisitor) PsiElement(com.intellij.psi.PsiElement) NotNull(org.jetbrains.annotations.NotNull)

Example 45 with PhpType

use of com.jetbrains.php.lang.psi.resolve.types.PhpType 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)

Aggregations

PhpType (com.jetbrains.php.lang.psi.resolve.types.PhpType)56 PsiElement (com.intellij.psi.PsiElement)46 NotNull (org.jetbrains.annotations.NotNull)41 BasePhpElementVisitor (com.kalessil.phpStorm.phpInspectionsEA.openApi.BasePhpElementVisitor)33 ProblemsHolder (com.intellij.codeInspection.ProblemsHolder)20 Project (com.intellij.openapi.project.Project)20 HashSet (java.util.HashSet)20 BasePhpInspection (com.kalessil.phpStorm.phpInspectionsEA.openApi.BasePhpInspection)16 Nullable (org.jetbrains.annotations.Nullable)16 com.jetbrains.php.lang.psi.elements (com.jetbrains.php.lang.psi.elements)15 PsiElementVisitor (com.intellij.psi.PsiElementVisitor)14 PhpIndex (com.jetbrains.php.PhpIndex)14 com.kalessil.phpStorm.phpInspectionsEA.utils (com.kalessil.phpStorm.phpInspectionsEA.utils)13 IElementType (com.intellij.psi.tree.IElementType)12 Set (java.util.Set)12 PhpTokenTypes (com.jetbrains.php.lang.lexer.PhpTokenTypes)10 PsiTreeUtil (com.intellij.psi.util.PsiTreeUtil)9 PhpTypedElement (com.jetbrains.php.lang.psi.elements.PhpTypedElement)9 InterfacesExtractUtil (com.kalessil.phpStorm.phpInspectionsEA.utils.hierarhy.InterfacesExtractUtil)8 Collectors (java.util.stream.Collectors)8