Search in sources :

Example 66 with BasePhpElementVisitor

use of com.kalessil.phpStorm.phpInspectionsEA.openApi.BasePhpElementVisitor in project phpinspectionsea by kalessil.

the class UnsupportedStringOffsetOperationsInspector method buildVisitor.

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

        @Override
        public void visitPhpArrayAccessExpression(@NotNull ArrayAccessExpression expression) {
            final Project project = holder.getProject();
            if (PhpLanguageLevel.get(project).atLeast(PhpLanguageLevel.PHP710)) {
                PsiElement target = null;
                String message = null;
                boolean isTargetContext = false;
                /* context identification phase */
                final PsiElement candidate = expression.getValue();
                if (candidate instanceof Variable || candidate instanceof FieldReference || candidate instanceof ArrayAccessExpression) {
                    /* false-positives: pushing to pre-defined globals */
                    PsiElement possiblyValue = candidate;
                    while (possiblyValue instanceof ArrayAccessExpression) {
                        possiblyValue = ((ArrayAccessExpression) possiblyValue).getValue();
                    }
                    if (possiblyValue instanceof Variable) {
                        final String variableName = ((Variable) possiblyValue).getName();
                        if (ExpressionCostEstimateUtil.predefinedVars.contains(variableName)) {
                            return;
                        }
                    }
                    final PsiElement parent = expression.getParent();
                    /* case 1: unsupported casting to array */
                    if (parent instanceof ArrayAccessExpression) {
                        message = messageOffset;
                        target = parent;
                        while (target.getParent() instanceof ArrayAccessExpression) {
                            target = target.getParent();
                        }
                        PsiElement context = target.getParent();
                        if (context instanceof AssignmentExpression) {
                            isTargetContext = ((AssignmentExpression) context).getValue() != target;
                        } else if (OpenapiTypesUtil.is(context, PhpElementTypes.ARRAY_VALUE)) {
                            final PsiElement array = context.getParent();
                            if ((context = array.getParent()) instanceof AssignmentExpression) {
                                isTargetContext = ((AssignmentExpression) context).getValue() != array;
                            }
                        }
                    } else /* case 2: array push operator is not supported by strings */
                    {
                        final ArrayIndex index = expression.getIndex();
                        if (index == null || index.getValue() == null) {
                            final PsiElement context = expression.getParent();
                            if (context instanceof AssignmentExpression) {
                                message = messagePush;
                                target = expression;
                                isTargetContext = ((AssignmentExpression) context).getValue() != expression;
                            }
                        }
                    }
                }
                /* type verification and reporting phase */
                if (isTargetContext && ExpressionSemanticUtil.getScope(target) != null) {
                    final PhpType resolved = OpenapiResolveUtil.resolveType((PhpTypedElement) candidate, project);
                    if (resolved != null) {
                        final boolean isTarget = resolved.filterUnknown().getTypes().stream().anyMatch(type -> Types.getType(type).equals(Types.strString));
                        if (isTarget) {
                            holder.registerProblem(target, MessagesPresentationUtil.prefixWithEa(message), ProblemHighlightType.GENERIC_ERROR);
                        }
                    }
                }
            }
        }
    };
}
Also used : BasePhpElementVisitor(com.kalessil.phpStorm.phpInspectionsEA.openApi.BasePhpElementVisitor) Project(com.intellij.openapi.project.Project) NotNull(org.jetbrains.annotations.NotNull) PsiElement(com.intellij.psi.PsiElement) PhpType(com.jetbrains.php.lang.psi.resolve.types.PhpType) NotNull(org.jetbrains.annotations.NotNull)

Example 67 with BasePhpElementVisitor

use of com.kalessil.phpStorm.phpInspectionsEA.openApi.BasePhpElementVisitor in project phpinspectionsea by kalessil.

the class CurlSslServerSpoofingInspector method buildVisitor.

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

        @Override
        public void visitPhpConstantReference(@NotNull ConstantReference reference) {
            final String constantName = reference.getName();
            if (constantName != null && constantName.startsWith("CURLOPT_")) {
                final PsiElement parent = reference.getParent();
                final PsiElement context = parent == null ? null : parent.getParent();
                if (context != null) {
                    final boolean isTarget = constantName.equals("CURLOPT_SSL_VERIFYHOST") || constantName.equals("CURLOPT_SSL_VERIFYPEER");
                    if (isTarget) {
                        this.analyze(context, constantName, reference);
                    }
                }
            }
        }

        private void analyze(@NotNull PsiElement parent, @NotNull String constantName, @NotNull ConstantReference constant) {
            if (parent instanceof FunctionReference) {
                final FunctionReference call = (FunctionReference) parent;
                final String functionName = call.getName();
                if (functionName != null && functionName.equals("curl_setopt")) {
                    final PsiElement[] arguments = call.getParameters();
                    if (arguments.length == 3) {
                        final PsiElement value = arguments[2];
                        if (value != null) {
                            if (constantName.equals("CURLOPT_SSL_VERIFYHOST")) {
                                if (this.isHostVerifyDisabled(value)) {
                                    holder.registerProblem(parent, MessagesPresentationUtil.prefixWithEa(messageVerifyHost), ProblemHighlightType.GENERIC_ERROR);
                                }
                            } else if (constantName.equals("CURLOPT_SSL_VERIFYPEER")) {
                                if (this.isPeerVerifyDisabled(value)) {
                                    holder.registerProblem(parent, MessagesPresentationUtil.prefixWithEa(messageVerifyPeer), ProblemHighlightType.GENERIC_ERROR);
                                }
                            }
                        }
                    }
                }
            } else if (parent instanceof ArrayHashElement && constant == ((ArrayHashElement) parent).getKey()) {
                final PsiElement value = ((ArrayHashElement) parent).getValue();
                if (value != null) {
                    if (constantName.equals("CURLOPT_SSL_VERIFYHOST")) {
                        if (this.isHostVerifyDisabled(value)) {
                            holder.registerProblem(parent, MessagesPresentationUtil.prefixWithEa(messageVerifyHost), ProblemHighlightType.GENERIC_ERROR);
                        }
                    } else if (constantName.equals("CURLOPT_SSL_VERIFYPEER")) {
                        if (this.isPeerVerifyDisabled(value)) {
                            holder.registerProblem(parent, MessagesPresentationUtil.prefixWithEa(messageVerifyPeer), ProblemHighlightType.GENERIC_ERROR);
                        }
                    }
                }
            } else if (parent instanceof ArrayAccessExpression) {
                PsiElement context = parent;
                while (context instanceof ArrayAccessExpression) {
                    context = context.getParent();
                }
                if (OpenapiTypesUtil.isAssignment(context)) {
                    final PsiElement value = ((AssignmentExpression) context).getValue();
                    if (value != null) {
                        if (constantName.equals("CURLOPT_SSL_VERIFYHOST")) {
                            if (this.isHostVerifyDisabled(value)) {
                                holder.registerProblem(context, MessagesPresentationUtil.prefixWithEa(messageVerifyHost), ProblemHighlightType.GENERIC_ERROR);
                            }
                        } else if (constantName.equals("CURLOPT_SSL_VERIFYPEER")) {
                            if (this.isPeerVerifyDisabled(value)) {
                                holder.registerProblem(context, MessagesPresentationUtil.prefixWithEa(messageVerifyPeer), ProblemHighlightType.GENERIC_ERROR);
                            }
                        }
                    }
                }
            }
        }

        private boolean isHostVerifyDisabled(@NotNull PsiElement value) {
            boolean result = false;
            final Set<PsiElement> discovered = PossibleValuesDiscoveryUtil.discover(value);
            if (!discovered.isEmpty()) {
                int countDisables = 0;
                int countEnables = 0;
                for (final PsiElement possibleValue : discovered) {
                    if (possibleValue instanceof StringLiteralExpression) {
                        boolean disabled = !((StringLiteralExpression) possibleValue).getContents().equals("2");
                        if (disabled) {
                            ++countDisables;
                        } else {
                            ++countEnables;
                        }
                    } else if (possibleValue instanceof ConstantReference) {
                        ++countDisables;
                    } else if (possibleValue.getTextLength() == 1) {
                        boolean disabled = !possibleValue.getText().equals("2");
                        if (disabled) {
                            ++countDisables;
                        } else {
                            ++countEnables;
                        }
                    }
                }
                result = countDisables > 0 && countEnables == 0;
            }
            discovered.clear();
            return result;
        }

        private boolean isPeerVerifyDisabled(@NotNull PsiElement value) {
            boolean result = false;
            final Set<PsiElement> discovered = PossibleValuesDiscoveryUtil.discover(value);
            if (!discovered.isEmpty()) {
                int countDisables = 0;
                int countEnables = 0;
                for (final PsiElement possibleValue : discovered) {
                    if (possibleValue instanceof StringLiteralExpression) {
                        boolean disabled = !((StringLiteralExpression) possibleValue).getContents().equals("1");
                        if (disabled) {
                            ++countDisables;
                        } else {
                            ++countEnables;
                        }
                    } else if (possibleValue instanceof ConstantReference) {
                        boolean disabled = !PhpLanguageUtil.isTrue(possibleValue);
                        if (disabled) {
                            ++countDisables;
                        } else {
                            ++countEnables;
                        }
                    } else if (possibleValue.getTextLength() == 1) {
                        boolean disabled = !possibleValue.getText().equals("1");
                        if (disabled) {
                            ++countDisables;
                        } else {
                            ++countEnables;
                        }
                    }
                /* other expressions are not supported currently */
                }
                result = countDisables > 0 && countEnables == 0;
            }
            discovered.clear();
            return result;
        }
    };
}
Also used : NotNull(org.jetbrains.annotations.NotNull) BasePhpElementVisitor(com.kalessil.phpStorm.phpInspectionsEA.openApi.BasePhpElementVisitor) PsiElement(com.intellij.psi.PsiElement) NotNull(org.jetbrains.annotations.NotNull)

Example 68 with BasePhpElementVisitor

use of com.kalessil.phpStorm.phpInspectionsEA.openApi.BasePhpElementVisitor in project phpinspectionsea by kalessil.

the class ElvisOperatorCanBeUsedInspector method buildVisitor.

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

        @Override
        public void visitPhpTernaryExpression(@NotNull TernaryExpression ternary) {
            if (!ternary.isShort()) {
                final PsiElement condition = ExpressionSemanticUtil.getExpressionTroughParenthesis(ternary.getCondition());
                final PsiElement trueVariant = ExpressionSemanticUtil.getExpressionTroughParenthesis(ternary.getTrueVariant());
                if (condition != null && trueVariant != null) {
                    final PsiElement falseVariant = ExpressionSemanticUtil.getExpressionTroughParenthesis(ternary.getFalseVariant());
                    if (falseVariant != null && OpenapiEquivalenceUtil.areEqual(condition, trueVariant)) {
                        final String replacement = String.format("%s ?: %s", ternary.getCondition().getText(), ternary.getFalseVariant().getText());
                        holder.registerProblem(ternary, MessagesPresentationUtil.prefixWithEa(String.format(messagePattern, replacement)), new UseElvisOperatorFix(replacement));
                    }
                }
            }
        }
    };
}
Also used : BasePhpElementVisitor(com.kalessil.phpStorm.phpInspectionsEA.openApi.BasePhpElementVisitor) TernaryExpression(com.jetbrains.php.lang.psi.elements.TernaryExpression) NotNull(org.jetbrains.annotations.NotNull) PsiElement(com.intellij.psi.PsiElement) NotNull(org.jetbrains.annotations.NotNull)

Example 69 with BasePhpElementVisitor

use of com.kalessil.phpStorm.phpInspectionsEA.openApi.BasePhpElementVisitor in project phpinspectionsea by kalessil.

the class PotentialMalwareInspector method buildVisitor.

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

        @Override
        public void visitPhpFunctionCall(@NotNull FunctionReference reference) {
            final String functionName = reference.getName();
            if (functionName != null) {
                if (functionName.equals("touch")) {
                    final PsiElement[] arguments = reference.getParameters();
                    if (arguments.length == 3) {
                        holder.registerProblem(reference, MessagesPresentationUtil.prefixWithEa(messageFileHide));
                    }
                } else if (functionName.equals("get_defined_functions")) {
                    holder.registerProblem(reference, MessagesPresentationUtil.prefixWithEa(messageCall));
                }
            }
        }

        @Override
        public void visitPhpEval(@NotNull PhpEval eval) {
            /* get through "(...)" and "@..." constructs */
            PsiElement argument = eval.getFirstPsiChild();
            /* TODO: merge into getExpressionThruParenthesises */
            while (argument instanceof UnaryExpression || argument instanceof ParenthesizedExpression) {
                if (argument instanceof ParenthesizedExpression) {
                    argument = ((ParenthesizedExpression) argument).getArgument();
                    continue;
                }
                final UnaryExpression unaryArgument = (UnaryExpression) argument;
                if (OpenapiTypesUtil.is(unaryArgument.getOperation(), PhpTokenTypes.opSILENCE)) {
                    argument = unaryArgument.getValue();
                    continue;
                }
                break;
            }
            /* if argument is not a function call or not a target function, terminate inspection */
            if (!(argument instanceof FunctionReference)) {
                return;
            }
            final String name = ((FunctionReference) argument).getName();
            if (name == null || !evalSuspects.contains(name.toLowerCase())) {
                return;
            }
            /* eval(file_get_contents('php://input')) is widely used as an interactive console */
            if (name.equalsIgnoreCase("file_get_contents")) {
                final PsiElement[] params = ((FunctionReference) argument).getParameters();
                if (params.length > 0 && params[0] instanceof StringLiteralExpression) {
                    return;
                }
            }
            /* fire a warning message */
            holder.registerProblem(eval, MessagesPresentationUtil.prefixWithEa(messageEval));
        }

        @Override
        public void visitPhpFunction(@NotNull Function function) {
            /* we expect return to be the last statement */
            final GroupStatement body = ExpressionSemanticUtil.getGroupStatement(function);
            final PsiElement lastStatement = null == body ? null : ExpressionSemanticUtil.getLastStatement(body);
            final PsiElement nameNode = NamedElementUtil.getNameIdentifier(function);
            if (null == nameNode || !(lastStatement instanceof PhpReturn)) {
                return;
            }
            /* we need 2+ expressions in the body */
            if (ExpressionSemanticUtil.countExpressionsInGroup(body) < 2) {
                return;
            }
            /* decode function is hidden */
            PsiElement returnArgument = ExpressionSemanticUtil.getReturnValue((PhpReturn) lastStatement);
            /* TODO: merge into getExpressionThruParenthesises */
            while (returnArgument instanceof UnaryExpression || returnArgument instanceof ParenthesizedExpression) {
                if (returnArgument instanceof ParenthesizedExpression) {
                    returnArgument = ((ParenthesizedExpression) returnArgument).getArgument();
                    continue;
                }
                final UnaryExpression unaryArgument = (UnaryExpression) returnArgument;
                final PsiElement operator = unaryArgument.getOperation();
                if (null != operator && PhpTokenTypes.opSILENCE == operator.getNode().getElementType()) {
                    returnArgument = unaryArgument.getValue();
                    continue;
                }
                break;
            }
            /* we expect a function reference returned */
            if (!(returnArgument instanceof FunctionReference)) {
                return;
            }
            final FunctionReference call = (FunctionReference) returnArgument;
            /* Case 1: analyze implicit function call */
            final String callName = call.getName();
            if (callName != null && decodeSuspects.contains(callName.toLowerCase())) {
                holder.registerProblem(nameNode, MessagesPresentationUtil.prefixWithEa(messageDecode));
                return;
            }
            /* Case 2: analyze variable function call */
            if (!(call.getFirstPsiChild() instanceof Variable)) {
                return;
            }
            final Set<PsiElement> values = PossibleValuesDiscoveryUtil.discover(call.getFirstPsiChild());
            if (!values.isEmpty()) {
                for (final PsiElement value : values) {
                    if (value instanceof StringLiteralExpression) {
                        final String name = ((StringLiteralExpression) value).getContents().toLowerCase();
                        if (decodeSuspects.contains(name)) {
                            holder.registerProblem(nameNode, MessagesPresentationUtil.prefixWithEa(messageDecode));
                            break;
                        }
                    }
                }
            }
            values.clear();
        }
    };
}
Also used : NotNull(org.jetbrains.annotations.NotNull) BasePhpElementVisitor(com.kalessil.phpStorm.phpInspectionsEA.openApi.BasePhpElementVisitor) PsiElement(com.intellij.psi.PsiElement) NotNull(org.jetbrains.annotations.NotNull)

Example 70 with BasePhpElementVisitor

use of com.kalessil.phpStorm.phpInspectionsEA.openApi.BasePhpElementVisitor in project phpinspectionsea by kalessil.

the class CallableParameterUseCaseInTypeContextInspection method buildVisitor.

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

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

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

        private void inspectUsages(@NotNull Parameter[] parameters, @NotNull PhpScopeHolder scopeHolder) {
            final Project project = holder.getProject();
            final PhpIndex index = PhpIndex.getInstance(project);
            final PhpEntryPointInstruction entryPoint = scopeHolder.getControlFlow().getEntryPoint();
            for (final Parameter parameter : parameters) {
                /* normalize parameter types, skip analysis when mixed or object appears */
                final Set<String> paramTypes = new HashSet<>();
                final PhpType parameterType = OpenapiResolveUtil.resolveType(parameter, project);
                if (parameterType != null) {
                    label: for (final String type : parameterType.filterUnknown().getTypes()) {
                        final String typeNormalized = Types.getType(type);
                        switch(typeNormalized) {
                            case Types.strMixed:
                            case Types.strObject:
                                paramTypes.clear();
                                break label;
                            case Types.strCallable:
                                paramTypes.add(Types.strArray);
                                paramTypes.add(Types.strString);
                                paramTypes.add("\\Closure");
                                break;
                            case Types.strIterable:
                                paramTypes.add(Types.strArray);
                                paramTypes.add("\\Traversable");
                                break;
                        }
                        paramTypes.add(typeNormalized);
                    }
                }
                if (paramTypes.isEmpty()) {
                    continue;
                } else {
                    /* in some case PhpStorm is not recognizing default value as parameter type */
                    final PsiElement defaultValue = parameter.getDefaultValue();
                    if (defaultValue instanceof PhpTypedElement) {
                        final PhpType defaultType = OpenapiResolveUtil.resolveType((PhpTypedElement) defaultValue, project);
                        if (defaultType != null) {
                            defaultType.filterUnknown().getTypes().forEach(t -> paramTypes.add(Types.getType(t)));
                        }
                    }
                }
                /* false-positive: type is not resolved correctly, default null is taken */
                if (paramTypes.size() == 1 && paramTypes.contains(Types.strNull)) {
                    final PsiElement defaultValue = parameter.getDefaultValue();
                    if (PhpLanguageUtil.isNull(defaultValue)) {
                        continue;
                    }
                }
                /* now find instructions operating on the parameter and perform analysis */
                final String parameterName = parameter.getName();
                for (final PhpAccessVariableInstruction instruction : OpenapiControlFlowUtil.getFollowingVariableAccessInstructions(entryPoint, parameterName)) {
                    final PsiElement parent = instruction.getAnchor().getParent();
                    final PsiElement callCandidate = null == parent ? null : parent.getParent();
                    /* Case 1: check if is_* functions being used according to definitions */
                    if (OpenapiTypesUtil.isFunctionReference(callCandidate)) {
                        final FunctionReference functionCall = (FunctionReference) callCandidate;
                        final String functionName = functionCall.getName();
                        if (functionName == null) {
                            continue;
                        }
                        /* we expect that aliases usage has been fixed already */
                        final boolean isTypeAnnounced;
                        switch(functionName) {
                            case "is_array":
                                isTypeAnnounced = paramTypes.contains(Types.strArray) || paramTypes.contains(Types.strIterable);
                                break;
                            case "is_string":
                                isTypeAnnounced = paramTypes.contains(Types.strString);
                                break;
                            case "is_bool":
                                isTypeAnnounced = paramTypes.contains(Types.strBoolean);
                                break;
                            case "is_int":
                                isTypeAnnounced = paramTypes.contains(Types.strInteger) || paramTypes.contains(Types.strNumber);
                                break;
                            case "is_float":
                                isTypeAnnounced = paramTypes.contains(Types.strFloat) || paramTypes.contains(Types.strNumber);
                                break;
                            case "is_resource":
                                isTypeAnnounced = paramTypes.contains(Types.strResource);
                                break;
                            case "is_numeric":
                                if (paramTypes.contains(Types.strString)) {
                                    continue;
                                }
                                isTypeAnnounced = paramTypes.contains(Types.strNumber) || paramTypes.contains(Types.strFloat) || paramTypes.contains(Types.strInteger);
                                break;
                            case "is_callable":
                                isTypeAnnounced = paramTypes.contains(Types.strCallable) || paramTypes.contains(Types.strArray) || paramTypes.contains(Types.strString) || paramTypes.contains("\\Closure");
                                break;
                            case "is_object":
                                isTypeAnnounced = paramTypes.contains(Types.strObject) || paramTypes.contains(Types.strCallable) || paramTypes.stream().anyMatch(t -> classReferences.contains(t) || (t.startsWith("\\") && !t.equals("\\Closure")));
                                break;
                            case "is_a":
                                isTypeAnnounced = paramTypes.contains(Types.strObject) || paramTypes.contains(Types.strString) || paramTypes.stream().anyMatch(t -> (t.startsWith("\\") && !t.equals("\\Closure")) || classReferences.contains(t));
                                break;
                            default:
                                continue;
                        }
                        /* cases: call makes no sense, violation of defined types set */
                        if (!isTypeAnnounced) {
                            final PsiElement callParent = functionCall.getParent();
                            boolean isReversedCheck = false;
                            if (callParent instanceof UnaryExpression) {
                                final PsiElement operation = ((UnaryExpression) callParent).getOperation();
                                isReversedCheck = OpenapiTypesUtil.is(operation, PhpTokenTypes.opNOT);
                            }
                            holder.registerProblem(functionCall, MessagesPresentationUtil.prefixWithEa(isReversedCheck ? messageNoSense : messageViolationInCheck));
                        }
                        continue;
                    }
                    /* Case 2: assignments violating parameter definition */
                    if (OpenapiTypesUtil.isAssignment(parent)) {
                        final AssignmentExpression assignment = (AssignmentExpression) parent;
                        final PhpPsiElement variable = assignment.getVariable();
                        final PhpPsiElement value = assignment.getValue();
                        if (variable instanceof Variable && value instanceof PhpTypedElement) {
                            final String variableName = variable.getName();
                            if (variableName != null && variableName.equals(parameterName)) {
                                final PhpType resolvedType = OpenapiResolveUtil.resolveType((PhpTypedElement) value, project);
                                final Set<String> resolved = new HashSet<>();
                                if (resolvedType != null) {
                                    resolvedType.filterUnknown().getTypes().forEach(t -> resolved.add(Types.getType(t)));
                                }
                                if (resolved.size() >= 2) {
                                    /* false-positives: core functions returning string|array & false|null */
                                    if (resolved.contains(Types.strString) || resolved.contains(Types.strArray)) {
                                        if (resolved.contains(Types.strBoolean)) {
                                            final boolean isFunctionCall = OpenapiTypesUtil.isFunctionReference(value);
                                            if (isFunctionCall) {
                                                resolved.remove(Types.strBoolean);
                                            }
                                        } else if (resolved.contains(Types.strNull)) {
                                            final boolean isFunctionCall = OpenapiTypesUtil.isFunctionReference(value);
                                            if (isFunctionCall) {
                                                resolved.remove(Types.strNull);
                                            }
                                        }
                                    } else /* false-positives: nullable objects */
                                    if (resolved.contains(Types.strNull)) {
                                        final boolean isNullableObject = paramTypes.stream().anyMatch(t -> classReferences.contains(t) || t.startsWith("\\") && !t.equals("\\Closure"));
                                        if (isNullableObject) {
                                            resolved.remove(Types.strNull);
                                        }
                                    }
                                }
                                resolved.remove(Types.strMixed);
                                for (String type : resolved) {
                                    /* translate static/self into FQNs */
                                    if (classReferences.contains(type)) {
                                        PsiElement valueExtract = value;
                                        /* ` = <whatever> ?? <method call>` support */
                                        if (valueExtract instanceof BinaryExpression) {
                                            final BinaryExpression binary = (BinaryExpression) valueExtract;
                                            if (binary.getOperationType() == PhpTokenTypes.opCOALESCE) {
                                                final PsiElement left = binary.getLeftOperand();
                                                if (left != null && OpenapiEquivalenceUtil.areEqual(variable, left)) {
                                                    final PsiElement right = binary.getRightOperand();
                                                    if (right != null) {
                                                        valueExtract = right;
                                                    }
                                                }
                                            }
                                        }
                                        /* method call lookup */
                                        if (valueExtract instanceof MethodReference) {
                                            final PsiElement base = valueExtract.getFirstChild();
                                            if (base instanceof ClassReference) {
                                                final PsiElement resolvedClass = OpenapiResolveUtil.resolveReference((ClassReference) base);
                                                if (resolvedClass instanceof PhpClass) {
                                                    type = ((PhpClass) resolvedClass).getFQN();
                                                }
                                            } else if (base instanceof PhpTypedElement) {
                                                final PhpType clazzTypes = OpenapiResolveUtil.resolveType((PhpTypedElement) base, project);
                                                if (clazzTypes != null) {
                                                    final Set<String> filteredTypes = clazzTypes.filterUnknown().getTypes().stream().map(Types::getType).filter(t -> t.startsWith("\\")).collect(Collectors.toSet());
                                                    final int filteredTypesCount = filteredTypes.size();
                                                    /* clear resolved class or interface + class */
                                                    if (filteredTypesCount == 1 || filteredTypesCount == 2) {
                                                        type = filteredTypes.iterator().next();
                                                    }
                                                    filteredTypes.clear();
                                                }
                                            }
                                        }
                                        /* translate static/self into FQNs didn't work, skip */
                                        if (classReferences.contains(type)) {
                                            continue;
                                        }
                                    }
                                    final boolean isViolation = !this.isTypeCompatibleWith(type, paramTypes, index);
                                    if (isViolation) {
                                        holder.registerProblem(value, String.format(MessagesPresentationUtil.prefixWithEa(patternViolationInAssignment), type));
                                        break;
                                    }
                                }
                                resolved.clear();
                            }
                        }
                    }
                }
                paramTypes.clear();
            }
        }

        private boolean isTypeCompatibleWith(@NotNull String type, @NotNull Collection<String> allowedTypes, @NotNull PhpIndex index) {
            /* first case: implicit match */
            if (allowedTypes.contains(type)) {
                return true;
            }
            /* second case: inherited classes/interfaces */
            final Set<String> possibleTypes = new HashSet<>();
            if (type.startsWith("\\")) {
                index.getAnyByFQN(type).forEach(clazz -> InterfacesExtractUtil.getCrawlInheritanceTree(clazz, true).forEach(c -> possibleTypes.add(c.getFQN())));
            }
            return !possibleTypes.isEmpty() && allowedTypes.stream().anyMatch(possibleTypes::contains);
        }
    };
}
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) PhpTokenTypes(com.jetbrains.php.lang.lexer.PhpTokenTypes) Collection(java.util.Collection) Set(java.util.Set) PhpIndex(com.jetbrains.php.PhpIndex) Collectors(java.util.stream.Collectors) HashSet(java.util.HashSet) BasePhpElementVisitor(com.kalessil.phpStorm.phpInspectionsEA.openApi.BasePhpElementVisitor) com.kalessil.phpStorm.phpInspectionsEA.utils(com.kalessil.phpStorm.phpInspectionsEA.utils) PsiElement(com.intellij.psi.PsiElement) Project(com.intellij.openapi.project.Project) PhpType(com.jetbrains.php.lang.psi.resolve.types.PhpType) NotNull(org.jetbrains.annotations.NotNull) PsiElementVisitor(com.intellij.psi.PsiElementVisitor) PhpScopeHolder(com.jetbrains.php.codeInsight.PhpScopeHolder) ProblemsHolder(com.intellij.codeInspection.ProblemsHolder) PhpAccessVariableInstruction(com.jetbrains.php.codeInsight.controlFlow.instructions.PhpAccessVariableInstruction) Set(java.util.Set) HashSet(java.util.HashSet) 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) PhpScopeHolder(com.jetbrains.php.codeInsight.PhpScopeHolder) PsiElement(com.intellij.psi.PsiElement) HashSet(java.util.HashSet) PhpAccessVariableInstruction(com.jetbrains.php.codeInsight.controlFlow.instructions.PhpAccessVariableInstruction) PhpIndex(com.jetbrains.php.PhpIndex) Project(com.intellij.openapi.project.Project) Collection(java.util.Collection) NotNull(org.jetbrains.annotations.NotNull)

Aggregations

BasePhpElementVisitor (com.kalessil.phpStorm.phpInspectionsEA.openApi.BasePhpElementVisitor)169 NotNull (org.jetbrains.annotations.NotNull)169 PsiElement (com.intellij.psi.PsiElement)157 FunctionReference (com.jetbrains.php.lang.psi.elements.FunctionReference)43 ProblemsHolder (com.intellij.codeInspection.ProblemsHolder)40 BasePhpInspection (com.kalessil.phpStorm.phpInspectionsEA.openApi.BasePhpInspection)39 PsiElementVisitor (com.intellij.psi.PsiElementVisitor)37 PhpType (com.jetbrains.php.lang.psi.resolve.types.PhpType)33 IElementType (com.intellij.psi.tree.IElementType)32 com.jetbrains.php.lang.psi.elements (com.jetbrains.php.lang.psi.elements)29 Project (com.intellij.openapi.project.Project)25 BinaryExpression (com.jetbrains.php.lang.psi.elements.BinaryExpression)25 HashSet (java.util.HashSet)24 PsiTreeUtil (com.intellij.psi.util.PsiTreeUtil)22 MessagesPresentationUtil (com.kalessil.phpStorm.phpInspectionsEA.utils.MessagesPresentationUtil)19 StringLiteralExpression (com.jetbrains.php.lang.psi.elements.StringLiteralExpression)18 ArrayList (java.util.ArrayList)18 Set (java.util.Set)18 Nullable (org.jetbrains.annotations.Nullable)18 PhpTokenTypes (com.jetbrains.php.lang.lexer.PhpTokenTypes)17