use of com.kalessil.phpStorm.phpInspectionsEA.openApi.BasePhpElementVisitor in project phpinspectionsea by kalessil.
the class CallableMethodValidityInspector 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 && functionName.equals("is_callable")) {
final PsiElement[] arguments = reference.getParameters();
if (arguments.length == 1) {
final Set<PsiElement> values = PossibleValuesDiscoveryUtil.discover(arguments[0]);
final PsiElement callable = values.size() == 1 ? values.iterator().next() : null;
if (callable != null && this.isTarget(callable)) {
final PsiReference resolver = this.buildResolver(callable);
if (resolver != null) {
this.analyzeValidity(resolver.resolve(), arguments[0], callable);
}
}
values.clear();
}
}
}
@Nullable
private PsiReference buildResolver(@NotNull PsiElement callable) {
PsiReference result = null;
final PhpCallbackFunctionUtil.PhpCallbackInfoHolder callback = PhpCallbackFunctionUtil.createCallback(callable);
if (callback instanceof PhpCallbackFunctionUtil.PhpMemberCallbackInfoHolder) {
PsiElement classReference = ((PhpCallbackFunctionUtil.PhpMemberCallbackInfoHolder) callback).getClassElement();
result = PhpCallbackReferenceBase.createMemberReference(classReference, callback.getCallbackElement(), true);
}
return result;
}
private boolean isTarget(@NotNull PsiElement callback) {
boolean result = callback instanceof StringLiteralExpression;
if (!result && callback instanceof ArrayCreationExpression) {
result = callback.getChildren().length == 2;
}
return result;
}
private void analyzeValidity(@Nullable PsiElement element, @NotNull PsiElement target, @NotNull PsiElement callable) {
if (element instanceof Method) {
final Method method = (Method) element;
if (!method.getAccess().isPublic()) {
holder.registerProblem(target, MessagesPresentationUtil.prefixWithEa(patternNotPublic.replace("%m%", method.getName())));
}
boolean needStatic = false;
if (callable instanceof StringLiteralExpression && !method.isStatic()) {
needStatic = true;
}
if (callable instanceof ArrayCreationExpression && !method.isStatic()) {
final PsiElement classCandidate = callable.getChildren()[0].getFirstChild();
/* try resolving the expression */
if (classCandidate instanceof PhpTypedElement) {
final PhpTypedElement candidate = (PhpTypedElement) classCandidate;
final Project project = holder.getProject();
for (final String type : candidate.getType().global(project).filterUnknown().getTypes()) {
final String resolvedType = Types.getType(type);
if (resolvedType.equals(Types.strString) || resolvedType.equals(Types.strCallable)) {
needStatic = true;
break;
}
}
}
/* older PS compatibility: recognize ::class properly */
if (!needStatic && classCandidate instanceof ClassConstantReference) {
final String constantName = ((ClassConstantReference) classCandidate).getName();
needStatic = null != constantName && constantName.equals("class");
}
}
if (needStatic) {
holder.registerProblem(target, MessagesPresentationUtil.prefixWithEa(patternNotStatic.replace("%m%", method.getName())));
}
}
}
};
}
use of com.kalessil.phpStorm.phpInspectionsEA.openApi.BasePhpElementVisitor in project phpinspectionsea by kalessil.
the class UnserializeExploitsInspector 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 && functionName.equals("unserialize")) {
final boolean supportsOptions = PhpLanguageLevel.get(holder.getProject()).atLeast(PhpLanguageLevel.PHP700);
final PsiElement[] arguments = reference.getParameters();
if (arguments.length == 1 && !this.isTestContext(reference)) {
/* pattern: use 2nd argument since PHP7 */
if (supportsOptions) {
holder.registerProblem(reference, MessagesPresentationUtil.prefixWithEa(messageUseSecondArgument));
}
/* pattern: exploitable calls */
this.inspectExploits(holder, arguments[0]);
} else if (arguments.length == 2 && !this.isTestContext(reference)) {
if (arguments[1] instanceof ArrayCreationExpression) {
final boolean hasClassesListed = arguments[1].getChildren().length > 0;
if (!hasClassesListed) {
holder.registerProblem(reference, MessagesPresentationUtil.prefixWithEa(messageUseSecondArgument));
}
} else if (PhpLanguageUtil.isTrue(arguments[1])) {
holder.registerProblem(reference, MessagesPresentationUtil.prefixWithEa(messageUseSecondArgument));
}
}
}
}
private void inspectExploits(@NotNull ProblemsHolder holder, @NotNull PsiElement argument) {
final Set<PsiElement> values = PossibleValuesDiscoveryUtil.discover(argument);
if (!values.isEmpty()) {
final List<String> reporting = new ArrayList<>();
for (PsiElement value : values) {
if (OpenapiTypesUtil.isFunctionReference(value)) {
final FunctionReference call = (FunctionReference) value;
final String functionName = call.getName();
if (functionName != null && untrustedFunctions.contains(functionName)) {
reporting.add(functionName + "(...)");
}
continue;
}
/* extract array access variable */
if (value instanceof ArrayAccessExpression) {
PsiElement container = value;
while (container instanceof ArrayAccessExpression) {
container = ((ArrayAccessExpression) container).getValue();
}
if (container instanceof Variable) {
value = container;
}
}
if (value instanceof Variable && untrustedVars.contains(((Variable) value).getName())) {
reporting.add(value.getText());
// continue
}
/* other expressions are not supported currently */
}
/* got something for reporting */
if (!reporting.isEmpty()) {
/* sort reporting list to produce testable results */
Collections.sort(reporting);
holder.registerProblem(argument, MessagesPresentationUtil.prefixWithEa(messagePattern.replace("%e%", String.join(", ", reporting))), ProblemHighlightType.GENERIC_ERROR);
reporting.clear();
}
}
values.clear();
}
};
}
use of com.kalessil.phpStorm.phpInspectionsEA.openApi.BasePhpElementVisitor in project phpinspectionsea by kalessil.
the class OnlyWritesOnParameterInspector method buildVisitor.
@Override
@NotNull
public PsiElementVisitor buildVisitor(@NotNull final ProblemsHolder holder, boolean isOnTheFly) {
return new BasePhpElementVisitor() {
@Override
public void visitPhpMethod(@NotNull Method method) {
if (!method.isAbstract()) {
this.visitPhpFunction(method);
}
}
@Override
public void visitPhpFunction(@NotNull Function function) {
Arrays.stream(function.getParameters()).filter(parameter -> !parameter.getName().isEmpty() && !parameter.isPassByRef()).filter(parameter -> {
final PhpType declaredType = OpenapiResolveUtil.resolveDeclaredType(parameter).filterUnknown().filterNull();
final boolean isObject = !declaredType.isEmpty() && declaredType.getTypes().stream().anyMatch(t -> {
final String type = Types.getType(t);
return type.equals(Types.strObject) || type.startsWith("\\");
});
return !isObject;
}).forEach(parameter -> this.analyzeAndReturnUsagesCount(parameter.getName(), function));
final List<Variable> variables = ExpressionSemanticUtil.getUseListVariables(function);
if (variables != null) {
this.checkUseVariables(variables, function);
variables.clear();
}
}
@Override
public void visitPhpAssignmentExpression(@NotNull AssignmentExpression assignment) {
/* because this hook fired e.g. for `.=` assignments (a BC break by PhpStorm) */
if (OpenapiTypesUtil.isAssignment(assignment)) {
final PsiElement variable = assignment.getVariable();
if (variable instanceof Variable) {
/* false-positives: predefined and global variables */
final String variableName = ((Variable) variable).getName();
if (variableName.isEmpty() || ExpressionCostEstimateUtil.predefinedVars.contains(variableName)) {
return;
}
/* filter target contexts: we are supporting only certain of them */
final PsiElement parent = assignment.getParent();
final boolean isTargetContext = parent instanceof ParenthesizedExpression || parent instanceof ArrayIndex || (parent instanceof BinaryExpression && OpenapiTypesUtil.tsCOMPARE_EQUALITY_OPS.contains(((BinaryExpression) parent).getOperationType())) || OpenapiTypesUtil.isStatementImpl(parent) || OpenapiTypesUtil.isAssignment(parent);
if (isTargetContext) {
final Function scope = ExpressionSemanticUtil.getScope(assignment);
if (scope != null && Arrays.stream(scope.getParameters()).noneMatch(p -> p.getName().equals(variableName))) {
final List<Variable> uses = ExpressionSemanticUtil.getUseListVariables(scope);
final boolean isUseVariable = uses != null && uses.stream().anyMatch(u -> u.getName().equals(variableName));
if (!isUseVariable) {
this.analyzeAndReturnUsagesCount(variableName, scope);
}
}
}
}
}
}
private void checkUseVariables(@NotNull List<Variable> variables, @NotNull Function function) {
for (final Variable variable : variables) {
final String parameterName = variable.getName();
if (!parameterName.isEmpty()) {
PsiElement previous = variable.getPrevSibling();
if (previous instanceof PsiWhiteSpace) {
previous = previous.getPrevSibling();
}
if (OpenapiTypesUtil.is(previous, PhpTokenTypes.opBIT_AND)) {
if (OpenapiControlFlowUtil.getFollowingVariableAccessInstructions(function.getControlFlow().getEntryPoint(), parameterName).isEmpty()) {
holder.registerProblem(variable, MessagesPresentationUtil.prefixWithEa(messageUnused), ProblemHighlightType.LIKE_UNUSED_SYMBOL);
}
} else if (this.analyzeAndReturnUsagesCount(parameterName, function) == 0) {
holder.registerProblem(variable, MessagesPresentationUtil.prefixWithEa(messageUnused), ProblemHighlightType.LIKE_UNUSED_SYMBOL);
}
}
}
}
private int analyzeAndReturnUsagesCount(@NotNull String parameterName, @NotNull Function function) {
final List<PhpAccessVariableInstruction> usages = OpenapiControlFlowUtil.getFollowingVariableAccessInstructions(function.getControlFlow().getEntryPoint(), parameterName);
if (usages.isEmpty()) {
return 0;
}
final List<PsiElement> targetExpressions = new ArrayList<>();
boolean isReference = false;
int intCountReadAccesses = 0;
int intCountWriteAccesses = 0;
for (final PhpAccessVariableInstruction instruction : usages) {
final PsiElement variable = instruction.getAnchor();
final PsiElement parent = variable.getParent();
if (parent instanceof ArrayAccessExpression) {
/* find out which expression is holder */
PsiElement objLastSemanticExpression = variable;
PsiElement objTopSemanticExpression = objLastSemanticExpression.getParent();
/* TODO: iterator for array access expression */
while (objTopSemanticExpression instanceof ArrayAccessExpression) {
objLastSemanticExpression = objTopSemanticExpression;
objTopSemanticExpression = objTopSemanticExpression.getParent();
}
/* estimate operation type */
if (objTopSemanticExpression instanceof AssignmentExpression && ((AssignmentExpression) objTopSemanticExpression).getVariable() == objLastSemanticExpression) {
intCountWriteAccesses++;
if (isReference) {
/* when modifying the reference it's link READ and linked WRITE semantics */
intCountReadAccesses++;
} else {
/* when modifying non non-reference, register as write only access for reporting */
targetExpressions.add(objLastSemanticExpression);
}
continue;
}
if (objTopSemanticExpression instanceof UnaryExpression) {
final PsiElement operation = ((UnaryExpression) objTopSemanticExpression).getOperation();
if (OpenapiTypesUtil.is(operation, PhpTokenTypes.opINCREMENT) || OpenapiTypesUtil.is(operation, PhpTokenTypes.opDECREMENT)) {
targetExpressions.add(objLastSemanticExpression);
++intCountWriteAccesses;
continue;
}
}
intCountReadAccesses++;
continue;
}
/* ++/-- operations */
if (parent instanceof UnaryExpression) {
final PsiElement operation = ((UnaryExpression) parent).getOperation();
if (OpenapiTypesUtil.is(operation, PhpTokenTypes.opINCREMENT) || OpenapiTypesUtil.is(operation, PhpTokenTypes.opDECREMENT)) {
++intCountWriteAccesses;
if (isReference) {
/* when modifying the reference it's link READ and linked WRITE semantics */
++intCountReadAccesses;
} else {
/* when modifying non-reference, register as write only access for reporting */
targetExpressions.add(parent);
}
}
if (!OpenapiTypesUtil.isStatementImpl(parent.getParent())) {
++intCountReadAccesses;
}
continue;
}
if (parent instanceof SelfAssignmentExpression) {
final SelfAssignmentExpression selfAssignment = (SelfAssignmentExpression) parent;
final PsiElement sameVariableCandidate = selfAssignment.getVariable();
if (sameVariableCandidate instanceof Variable) {
final Variable candidate = (Variable) sameVariableCandidate;
if (candidate.getName().equals(parameterName)) {
++intCountWriteAccesses;
if (isReference) {
/* when modifying the reference it's link READ and linked WRITE semantics */
++intCountReadAccesses;
} else {
/* when modifying non-reference, register as write only access for reporting */
targetExpressions.add(variable);
}
if (!OpenapiTypesUtil.isStatementImpl(parent.getParent())) {
++intCountReadAccesses;
}
continue;
}
}
}
/* if variable assigned with reference, we need to preserve this information for correct checks */
if (parent instanceof AssignmentExpression) {
/* ensure variable with the same name being written */
final AssignmentExpression referenceAssignmentCandidate = (AssignmentExpression) parent;
/* check if the target used as a container */
final PsiElement assignmentVariableCandidate = referenceAssignmentCandidate.getVariable();
if (assignmentVariableCandidate instanceof Variable) {
final Variable candidate = (Variable) assignmentVariableCandidate;
if (candidate.getName().equals(parameterName)) {
++intCountWriteAccesses;
if (isReference) {
/* when modifying the reference it's link READ and linked WRITE semantics */
++intCountReadAccesses;
}
/* now ensure operation is assignment of reference */
if (OpenapiTypesUtil.isAssignmentByReference(referenceAssignmentCandidate)) {
isReference = true;
}
/* false-negative: inline assignment result has been used */
if (usages.size() == 2 && usages.get(0).getAnchor() == usages.get(1).getAnchor()) {
holder.registerProblem(assignmentVariableCandidate, MessagesPresentationUtil.prefixWithEa(messageUnused), ProblemHighlightType.LIKE_UNUSED_SYMBOL);
return 1;
}
continue;
}
}
/* check if the target used as a value */
final PsiElement assignmentValueCandidate = referenceAssignmentCandidate.getValue();
if (assignmentValueCandidate instanceof Variable) {
final Variable candidate = (Variable) assignmentValueCandidate;
if (candidate.getName().equals(parameterName)) {
++intCountReadAccesses;
continue;
}
}
}
/* local variables access wrongly reported write in some cases, so rely on custom checks */
if (parent instanceof ParameterList || parent instanceof PhpUseList || parent instanceof PhpUnset || parent instanceof PhpEmpty || parent instanceof PhpIsset || parent instanceof ForeachStatement) {
intCountReadAccesses++;
continue;
}
/* ok variable usage works well with openapi */
final PhpAccessInstruction.Access instructionAccess = instruction.getAccess();
if (instructionAccess.isWrite()) {
targetExpressions.add(variable);
++intCountWriteAccesses;
} else if (instructionAccess.isRead()) {
++intCountReadAccesses;
}
}
if (intCountReadAccesses == 0 && intCountWriteAccesses > 0 && !this.isAnySuppressed(targetExpressions)) {
final boolean report = IGNORE_INCLUDES || !this.hasIncludes(function);
if (report) {
for (final PsiElement targetExpression : new HashSet<>(targetExpressions)) {
holder.registerProblem(targetExpression, MessagesPresentationUtil.prefixWithEa(messageOnlyWrites), ProblemHighlightType.LIKE_UNUSED_SYMBOL);
}
}
}
targetExpressions.clear();
return usages.size();
}
private boolean isAnySuppressed(@NotNull List<PsiElement> expressions) {
for (final PsiElement one : expressions) {
final PsiElement parent = one.getParent();
if (parent instanceof AssignmentExpression) {
final PsiElement grandParent = parent.getParent();
if (OpenapiTypesUtil.isStatementImpl(grandParent)) {
final PsiElement previous = ((PhpPsiElement) grandParent).getPrevPsiSibling();
if (previous instanceof PhpDocComment) {
final String candidate = previous.getText();
if (candidate.contains("@noinspection") && candidate.contains(getShortName())) {
return true;
}
}
}
}
}
return false;
}
private boolean hasIncludes(@NotNull Function function) {
final GroupStatement body = ExpressionSemanticUtil.getGroupStatement(function);
if (body != null) {
return PsiTreeUtil.findChildOfType(body, Include.class) != null;
}
return false;
}
};
}
use of com.kalessil.phpStorm.phpInspectionsEA.openApi.BasePhpElementVisitor in project phpinspectionsea by kalessil.
the class ClassOverridesFieldOfSuperClassInspector method buildVisitor.
@Override
@NotNull
public PsiElementVisitor buildVisitor(@NotNull final ProblemsHolder holder, boolean isOnTheFly) {
return new BasePhpElementVisitor() {
@Override
public void visitPhpField(@NotNull Field ownField) {
/* skip un-explorable and test classes */
final PhpClass clazz = ownField.getContainingClass();
if (clazz == null || this.isTestContext(clazz)) {
return;
}
/* skip static, constants and un-processable fields */
final PsiElement ownFieldNameId = NamedElementUtil.getNameIdentifier(ownField);
if (null == ownFieldNameId || ownField.isConstant() || ownField.getModifier().isStatic()) {
return;
}
/* ensure field is not defined via annotation */
if (!(ExpressionSemanticUtil.getBlockScope(ownFieldNameId) instanceof PhpClass)) {
return;
}
/* ensure field doesn't have any user-land annotations */
final PhpDocTag[] tags = PsiTreeUtil.getChildrenOfType(ownField.getDocComment(), PhpDocTag.class);
final boolean annotated = tags != null && Arrays.stream(tags).anyMatch(t -> !t.getName().equals(t.getName().toLowerCase()));
if (annotated) {
return;
}
final PhpClass parent = OpenapiResolveUtil.resolveSuperClass(clazz);
final String ownFieldName = ownField.getName();
final Field parentField = parent == null ? null : OpenapiResolveUtil.resolveField(parent, ownFieldName);
if (parentField != null) {
final PhpClass parentFieldHolder = parentField.getContainingClass();
final PsiElement fieldNameNode = NamedElementUtil.getNameIdentifier(ownField);
if (fieldNameNode != null && parentFieldHolder != null) {
/* re-defining private fields with the same name is pretty suspicious itself */
if (parentField.getModifier().isPrivate()) {
if (REPORT_PRIVATE_REDEFINITION) {
holder.registerProblem(fieldNameNode, MessagesPresentationUtil.prefixWithEa(patternProtectedCandidate.replace("%c%", parentFieldHolder.getFQN())), ProblemHighlightType.WEAK_WARNING);
}
return;
}
/* report only cases when access level is not changed */
if (!ownField.getModifier().getAccess().isWeakerThan(parentField.getModifier().getAccess())) {
/* fire common warning */
holder.registerProblem(fieldNameNode, MessagesPresentationUtil.prefixWithEa(patternShadows.replace("%p%", ownFieldName).replace("%c%", parentFieldHolder.getFQN())), ProblemHighlightType.WEAK_WARNING);
}
}
}
}
};
}
use of com.kalessil.phpStorm.phpInspectionsEA.openApi.BasePhpElementVisitor in project phpinspectionsea by kalessil.
the class LongInheritanceChainInspector method buildVisitor.
@Override
@NotNull
public PsiElementVisitor buildVisitor(@NotNull final ProblemsHolder holder, boolean isOnTheFly) {
return new BasePhpElementVisitor() {
@Override
public void visitPhpClass(@NotNull PhpClass clazz) {
final PsiElement psiClassName = NamedElementUtil.getNameIdentifier(clazz);
final String className = clazz.getName();
/* skip un-reportable, exception and test classes */
if (psiClassName == null || className.endsWith("Exception") || this.isTestContext(clazz)) {
return;
}
PhpClass classToCheck = clazz;
PhpClass parent = OpenapiResolveUtil.resolveSuperClass(classToCheck);
/* false-positives: abstract class implementation */
if (null != parent && !classToCheck.isAbstract() && parent.isAbstract()) {
return;
}
int parentsCount = 0;
/* in source code class CAN extend itself, PS will report it but data structure is incorrect still */
while (null != parent && clazz != parent) {
classToCheck = parent;
parent = OpenapiResolveUtil.resolveSuperClass(classToCheck);
++parentsCount;
if (null != parent) {
/* show-stoppers: frameworks god classes */
if (showStoppers.contains(parent.getFQN())) {
++parentsCount;
break;
}
/* exceptions named according to DDD, check parents named with exception suffix */
if (parent.getName().endsWith("Exception")) {
return;
}
}
}
if (parentsCount >= COMPLAIN_THRESHOLD && !clazz.isDeprecated()) {
holder.registerProblem(psiClassName, MessagesPresentationUtil.prefixWithEa(messagePattern.replace("%c%", String.valueOf(parentsCount))), ProblemHighlightType.WEAK_WARNING);
}
}
};
}
Aggregations