use of com.kalessil.phpStorm.phpInspectionsEA.openApi.BasePhpElementVisitor in project phpinspectionsea by kalessil.
the class MissingArrayInitializationInspector method buildVisitor.
@Override
@NotNull
public PsiElementVisitor buildVisitor(@NotNull final ProblemsHolder problemsHolder, final boolean isOnTheFly) {
return new BasePhpElementVisitor() {
@Override
public void visitPhpArrayAccessExpression(@NotNull ArrayAccessExpression expression) {
final ArrayIndex index = expression.getIndex();
if (index != null && index.getValue() == null) {
final Function scope = ExpressionSemanticUtil.getScope(expression);
if (scope != null) {
/* identify nesting level */
int nestingLevel = 0;
PsiElement parent = expression.getParent();
while (parent != null && parent != scope) {
if (OpenapiTypesUtil.isLoop(parent) && ++nestingLevel >= 2) {
break;
}
parent = parent.getParent();
}
/* target 2+ nesting levels */
if (nestingLevel >= 2) {
PsiElement container = expression.getValue();
while (container instanceof ArrayAccessExpression) {
container = ((ArrayAccessExpression) container).getValue();
}
if (container instanceof Variable) {
final GroupStatement body = ExpressionSemanticUtil.getGroupStatement(scope);
if (body != null) {
final String variableName = ((Variable) container).getName();
/* false-positives: parameters */
if (Arrays.stream(scope.getParameters()).anyMatch(p -> p.getName().equals(variableName))) {
return;
}
/* false-positives: use-variables */
final List<Variable> uses = ExpressionSemanticUtil.getUseListVariables(scope);
if (uses != null && uses.stream().anyMatch(p -> p.getName().equals(variableName))) {
return;
}
for (final PsiElement candidate : PsiTreeUtil.findChildrenOfType(body, container.getClass())) {
final PsiElement context = candidate.getParent();
/* a value has been written */
if (context instanceof AssignmentExpression) {
final PsiElement value = ((AssignmentExpression) context).getValue();
if (value != container && OpenapiEquivalenceUtil.areEqual(candidate, container)) {
return;
}
}
/* container initialized by a foreach loop */
if (context instanceof ForeachStatement) {
final boolean areSame = OpenapiEquivalenceUtil.areEqual(candidate, container);
if (areSame) {
return;
}
}
}
problemsHolder.registerProblem(expression, MessagesPresentationUtil.prefixWithEa(message));
}
}
}
}
}
}
};
}
use of com.kalessil.phpStorm.phpInspectionsEA.openApi.BasePhpElementVisitor in project phpinspectionsea by kalessil.
the class SuspiciousBinaryOperationInspector method buildVisitor.
@Override
@NotNull
public PsiElementVisitor buildVisitor(@NotNull final ProblemsHolder holder, boolean isOnTheFly) {
return new BasePhpElementVisitor() {
@Override
public void visitPhpBinaryExpression(@NotNull BinaryExpression expression) {
final Collection<BooleanSupplier> callbacks = new ArrayList<>();
callbacks.add(() -> InstanceOfTraitStrategy.apply(expression, holder));
callbacks.add(() -> EqualsInAssignmentContextStrategy.apply(expression, holder));
callbacks.add(() -> GreaterOrEqualInHashElementStrategy.apply(expression, holder));
callbacks.add(() -> NullableArgumentComparisonStrategy.apply(expression, holder));
callbacks.add(() -> IdenticalOperandsStrategy.apply(expression, holder));
callbacks.add(() -> MisplacedOperatorStrategy.apply(expression, holder));
callbacks.add(() -> NullCoalescingOperatorCorrectnessStrategy.apply(expression, holder));
callbacks.add(() -> ConcatenationWithArrayStrategy.apply(expression, holder));
if (VERIFY_CONSTANTS_IN_CONDITIONS) {
callbacks.add(() -> HardcodedConstantValuesStrategy.apply(expression, holder));
}
if (VERIFY_UNCLEAR_OPERATIONS_PRIORITIES) {
callbacks.add(() -> UnclearOperationsPriorityStrategy.apply(expression, holder));
}
/* run through strategies until the first one fired something */
for (final BooleanSupplier strategy : callbacks) {
if (strategy.getAsBoolean()) {
break;
}
}
callbacks.clear();
}
};
}
use of com.kalessil.phpStorm.phpInspectionsEA.openApi.BasePhpElementVisitor in project phpinspectionsea by kalessil.
the class PassingByReferenceCorrectnessInspector 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.isEmpty() && !skippedFunctionsCache.containsKey(functionName)) {
final boolean skip = skippedFunctions.contains(functionName) && this.isFromRootNamespace(reference);
if (!skip && this.hasIncompatibleArguments(reference)) {
this.analyze(reference);
}
}
}
@Override
public void visitPhpMethodReference(@NotNull MethodReference reference) {
final String methodName = reference.getName();
if (methodName != null && !methodName.isEmpty() && this.hasIncompatibleArguments(reference)) {
this.analyze(reference);
}
}
private boolean hasIncompatibleArguments(@NotNull FunctionReference reference) {
final PsiElement[] arguments = reference.getParameters();
if (arguments.length > 0) {
final boolean supportsNew = PhpLanguageLevel.get(holder.getProject()).below(PhpLanguageLevel.PHP700);
return !Arrays.stream(arguments).allMatch(a -> a instanceof Variable || (supportsNew && a instanceof NewExpression));
}
return false;
}
private void analyze(@NotNull FunctionReference reference) {
final PsiElement resolved = OpenapiResolveUtil.resolveReference(reference);
if (resolved instanceof Function) {
final Function function = (Function) resolved;
final Parameter[] parameters = function.getParameters();
final PsiElement[] arguments = reference.getParameters();
/* search for anomalies */
for (int index = 0, max = Math.min(parameters.length, arguments.length); index < max; ++index) {
if (parameters[index].isPassByRef()) {
final PsiElement argument = arguments[index];
if (argument instanceof FunctionReference && !this.isByReference(argument)) {
final PsiElement inner = OpenapiResolveUtil.resolveReference((FunctionReference) argument);
if (inner instanceof Function) {
final PsiElement name = NamedElementUtil.getNameIdentifier((Function) inner);
if (!this.isByReference(name)) {
holder.registerProblem(argument, MessagesPresentationUtil.prefixWithEa(message));
}
}
} else if (argument instanceof NewExpression) {
holder.registerProblem(argument, MessagesPresentationUtil.prefixWithEa(message));
}
}
}
/* remember global functions without references */
if (parameters.length > 0 && OpenapiTypesUtil.isFunctionReference(reference)) {
final boolean hasReferences = Arrays.stream(parameters).anyMatch(Parameter::isPassByRef);
if (!hasReferences) {
final String functionName = function.getName();
final boolean isFromRootNamespace = function.getFQN().equals('\\' + functionName);
if (isFromRootNamespace) {
skippedFunctionsCache.putIfAbsent(functionName, functionName);
}
}
}
}
}
private boolean isByReference(@Nullable PsiElement element) {
boolean result = false;
if (element != null) {
PsiElement ampersandCandidate = element.getPrevSibling();
if (ampersandCandidate instanceof PsiWhiteSpace) {
ampersandCandidate = ampersandCandidate.getPrevSibling();
}
result = OpenapiTypesUtil.is(ampersandCandidate, PhpTokenTypes.opBIT_AND);
}
return result;
}
};
}
use of com.kalessil.phpStorm.phpInspectionsEA.openApi.BasePhpElementVisitor in project phpinspectionsea by kalessil.
the class ClassReImplementsParentInterfaceInspector method buildVisitor.
@Override
@NotNull
public PsiElementVisitor buildVisitor(@NotNull final ProblemsHolder holder, boolean isOnTheFly) {
return new BasePhpElementVisitor() {
@Override
public void visitPhpClass(@NotNull PhpClass clazz) {
final List<ClassReference> implemented = clazz.getImplementsList().getReferenceElements();
if (!implemented.isEmpty()) {
/* resolve own interfaces an maintain relation to original element */
final Map<PsiElement, PhpClass> ownInterfaces = new LinkedHashMap<>(implemented.size());
for (final ClassReference reference : implemented) {
final PsiElement resolved = OpenapiResolveUtil.resolveReference(reference);
if (resolved instanceof PhpClass) {
ownInterfaces.put(reference, (PhpClass) resolved);
}
}
implemented.clear();
if (!ownInterfaces.isEmpty()) {
/* Case: indirect declaration duplication (parent already implements) */
final PhpClass parent = OpenapiResolveUtil.resolveSuperClass(clazz);
if (parent != null) {
final Set<PhpClass> inherited = InterfacesExtractUtil.getCrawlInheritanceTree(parent, false);
if (!inherited.isEmpty()) {
final Set<PsiElement> processed = new HashSet<>();
for (final Map.Entry<PsiElement, PhpClass> entry : ownInterfaces.entrySet()) {
final PhpClass ownInterface = entry.getValue();
if (inherited.contains(ownInterface) && processed.add(entry.getKey())) {
holder.registerProblem(entry.getKey(), String.format(MessagesPresentationUtil.prefixWithEa(patternIndirectDuplication), ownInterface.getFQN(), parent.getFQN()), new TheLocalFix());
}
}
processed.clear();
inherited.clear();
}
}
ownInterfaces.clear();
}
}
}
};
}
use of com.kalessil.phpStorm.phpInspectionsEA.openApi.BasePhpElementVisitor in project phpinspectionsea by kalessil.
the class MkdirRaceConditionInspector 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("mkdir")) {
return;
}
final PsiElement[] arguments = reference.getParameters();
if (arguments.length == 0 || arguments.length > 3) {
return;
}
/* false-positives: test classes and functions not from root NS */
if (this.isTestContext(reference) || !this.isFromRootNamespace(reference)) {
return;
}
/* ind out expression where the call is contained - quite big set of variations */
final ExpressionLocateResult searchResult = new ExpressionLocateResult();
this.locateExpression(reference, searchResult);
final PsiElement target = searchResult.getReportingTarget();
if (target == null) {
return;
}
final PsiElement context = target.getParent();
// case 1: if ([!]mkdir(...) [===|!== true|false])
if (context instanceof If || OpenapiTypesUtil.isStatementImpl(context)) {
final List<String> fixerArguments = Arrays.stream(arguments).map(PsiElement::getText).collect(Collectors.toList());
final String binary = searchResult.isInverted ? patternFailAndCondition : patternFailOrCondition;
final String messagePattern = (context instanceof If ? binary : patternDirectCall);
holder.registerProblem(context instanceof If ? target : context, MessagesPresentationUtil.prefixWithEa(String.format(messagePattern, String.join(", ", fixerArguments))), context instanceof If ? new HardenConditionFix(arguments[0], fixerArguments, searchResult.isInverted) : new ThrowExceptionFix(arguments[0], fixerArguments));
} else // case 2: && and || expressions
if (context instanceof BinaryExpression) {
boolean isSecondExistenceCheckExists = false;
/* false-positive: `... or die` construct */
BinaryExpression binary = (BinaryExpression) context;
if (binary.getRightOperand() instanceof PhpExit) {
return;
}
/* deal with nested conditions */
final PsiElement parent = binary.getParent();
if (binary.getRightOperand() == target && parent instanceof BinaryExpression) {
binary = (BinaryExpression) parent;
}
/* check if following expression contains is_dir */
final PsiElement candidate = binary.getRightOperand();
final List<FunctionReference> calls = new ArrayList<>();
if (candidate instanceof FunctionReference) {
calls.add((FunctionReference) candidate);
}
calls.addAll(PsiTreeUtil.findChildrenOfType(candidate, FunctionReference.class));
for (final FunctionReference call : calls) {
final String name = call.getName();
if (name != null && name.equals("is_dir") && OpenapiTypesUtil.isFunctionReference(call)) {
/* TODO: argument needs match as well */
isSecondExistenceCheckExists = true;
break;
}
}
calls.clear();
/* report when needed */
if (!isSecondExistenceCheckExists) {
final List<String> fixerArguments = Arrays.stream(arguments).map(PsiElement::getText).collect(Collectors.toList());
final String messagePattern = (PhpTokenTypes.tsSHORT_CIRCUIT_AND_OPS.contains(binary.getOperationType()) ? patternFailAndCondition : patternFailOrCondition);
holder.registerProblem(target, MessagesPresentationUtil.prefixWithEa(String.format(messagePattern, String.join(", ", fixerArguments), arguments[0].getText())), new HardenConditionFix(arguments[0], fixerArguments, searchResult.isInverted));
}
}
}
private void locateExpression(@NotNull PsiElement expression, @NotNull ExpressionLocateResult status) {
final PsiElement parent = expression.getParent();
if (parent instanceof If || parent instanceof AssignmentExpression || OpenapiTypesUtil.isStatementImpl(parent)) {
status.setReportingTarget(expression);
} else if (parent instanceof ParenthesizedExpression) {
this.locateExpression(parent, status);
} else if (parent instanceof UnaryExpression) {
final PsiElement operation = ((UnaryExpression) parent).getOperation();
if (operation != null) {
if (OpenapiTypesUtil.is(operation, PhpTokenTypes.opNOT)) {
status.setInverted(!status.isInverted());
this.locateExpression(parent, status);
} else if (OpenapiTypesUtil.is(operation, PhpTokenTypes.opSILENCE)) {
this.locateExpression(parent, status);
}
}
} else if (parent instanceof BinaryExpression) {
final BinaryExpression binary = (BinaryExpression) parent;
final IElementType operation = binary.getOperationType();
if (PhpTokenTypes.tsSHORT_CIRCUIT_AND_OPS.contains(operation) || PhpTokenTypes.tsSHORT_CIRCUIT_OR_OPS.contains(operation)) {
status.setReportingTarget(expression);
} else {
if (operation == PhpTokenTypes.opIDENTICAL || operation == PhpTokenTypes.opNOT_IDENTICAL) {
final PsiElement second = OpenapiElementsUtil.getSecondOperand(binary, expression);
if (PhpLanguageUtil.isBoolean(second)) {
if (PhpLanguageUtil.isFalse(second)) {
status.setInverted(!status.isInverted());
}
if (operation == PhpTokenTypes.opNOT_IDENTICAL) {
status.setInverted(!status.isInverted());
}
}
}
this.locateExpression(parent, status);
}
}
}
};
}
Aggregations