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);
}
}
}
}
}
};
}
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;
}
};
}
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));
}
}
}
}
};
}
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();
}
};
}
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);
}
};
}
Aggregations