use of com.jetbrains.php.codeInsight.controlFlow.instructions.PhpAccessVariableInstruction in project phpinspectionsea by kalessil.
the class ExplodeMissUseInspector method buildVisitor.
@Override
@NotNull
public PsiElementVisitor buildVisitor(@NotNull final ProblemsHolder holder, boolean isOnTheFly) {
return new BasePhpElementVisitor() {
@Override
public void visitPhpFunctionCall(@NotNull FunctionReference reference) {
/* general structure expectations */
final String functionName = reference.getName();
if (functionName == null || !semanticMapping.containsKey(functionName)) {
return;
}
final PsiElement[] arguments = reference.getParameters();
if (arguments.length != 1) {
return;
}
/* discover possible values */
final Set<PsiElement> values = PossibleValuesDiscoveryUtil.discover(arguments[0]);
/* do not analyze invariants */
if (1 == values.size()) {
final PsiElement value = values.iterator().next();
values.clear();
if (OpenapiTypesUtil.isFunctionReference(value)) {
/* inner call must be explode() */
final FunctionReference innerCall = (FunctionReference) value;
final String innerFunctionName = innerCall.getName();
if (innerFunctionName == null || !innerFunctionName.equals("explode")) {
return;
}
final PsiElement[] innerArguments = innerCall.getParameters();
if (innerArguments.length != 2) {
return;
}
/* if the parameter is a variable, ensure it used only 2 times (write, read) */
if (arguments[0] instanceof Variable) {
final PhpScopeHolder parentScope = ExpressionSemanticUtil.getScope(reference);
if (null != parentScope) {
final PhpAccessVariableInstruction[] usages = PhpControlFlowUtil.getFollowingVariableAccessInstructions(parentScope.getControlFlow().getEntryPoint(), ((Variable) arguments[0]).getName(), false);
if (2 != usages.length) {
return;
}
}
}
final String replacement = semanticMapping.get(functionName).replace("%f%", innerArguments[0].getText()).replace("%s%", innerArguments[1].getText());
final String message = messagePattern.replace("%e%", replacement);
if (arguments[0] == value) {
holder.registerProblem(reference, message, new UseAlternativeFix(replacement));
} else {
holder.registerProblem(reference, message);
}
}
}
values.clear();
}
};
}
use of com.jetbrains.php.codeInsight.controlFlow.instructions.PhpAccessVariableInstruction 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);
}
};
}
use of com.jetbrains.php.codeInsight.controlFlow.instructions.PhpAccessVariableInstruction in project phpinspectionsea by kalessil.
the class OneTimeUseVariablesInspector method buildVisitor.
@Override
@NotNull
public PsiElementVisitor buildVisitor(@NotNull final ProblemsHolder holder, boolean isOnTheFly) {
return new BasePhpElementVisitor() {
void checkOneTimeUse(@NotNull PhpPsiElement construct, @NotNull Variable argument) {
final String variableName = argument.getName();
final PsiElement previous = construct.getPrevPsiSibling();
/* verify preceding expression (assignment needed) */
if (null != previous && OpenapiTypesUtil.isAssignment(previous.getFirstChild())) {
final AssignmentExpression assign = (AssignmentExpression) previous.getFirstChild();
/* ensure variables are the same */
final PhpPsiElement assignVariable = assign.getVariable();
final PsiElement assignValue = ExpressionSemanticUtil.getExpressionTroughParenthesis(assign.getValue());
if (null != assignValue && assignVariable instanceof Variable) {
final String assignVariableName = assignVariable.getName();
if (assignVariableName == null || !assignVariableName.equals(variableName)) {
return;
}
/* check if variable as a function/use(...) parameter by reference */
final Function function = ExpressionSemanticUtil.getScope(construct);
if (null != function) {
for (final Parameter param : function.getParameters()) {
if (param.isPassByRef() && param.getName().equals(variableName)) {
return;
}
}
final List<Variable> useList = ExpressionSemanticUtil.getUseListVariables(function);
if (useList != null && !useList.isEmpty()) {
for (final Variable param : useList) {
if (param.getName().equals(variableName)) {
/* detect parameters by reference in use clause */
PsiElement ampersandCandidate = param.getPrevSibling();
if (ampersandCandidate instanceof PsiWhiteSpace) {
ampersandCandidate = ampersandCandidate.getPrevSibling();
}
if (OpenapiTypesUtil.is(ampersandCandidate, PhpTokenTypes.opBIT_AND)) {
return;
}
}
}
useList.clear();
}
}
/* too long return/throw statements can be decoupled as a variable */
if (!ALLOW_LONG_STATEMENTS && assign.getText().length() > 80) {
return;
}
/* heavy part, find usage inside function/method to analyze multiple writes */
final PhpScopeHolder parentScope = ExpressionSemanticUtil.getScope(assign);
if (null != parentScope) {
int countWrites = 0;
int countReads = 0;
for (final PhpAccessVariableInstruction oneCase : OpenapiControlFlowUtil.getFollowingVariableAccessInstructions(parentScope.getControlFlow().getEntryPoint(), variableName)) {
final boolean isWrite = oneCase.getAccess().isWrite();
if (isWrite) {
/* false-positives: type specification */
final PsiElement context = oneCase.getAnchor().getParent();
if (OpenapiTypesUtil.isAssignment(context)) {
final boolean typeAnnotated = this.isTypeAnnotated((PhpPsiElement) context.getParent(), variableName);
if (typeAnnotated) {
return;
}
}
}
countWrites += isWrite ? 1 : 0;
countReads += isWrite ? 0 : 1;
if (countWrites > 1 || countReads > 1) {
return;
}
}
}
if (!(assignValue instanceof NewExpression) || PhpLanguageLevel.get(holder.getProject()).atLeast(PhpLanguageLevel.PHP540)) {
holder.registerProblem(assignVariable, MessagesPresentationUtil.prefixWithEa(messagePattern.replace("%v%", variableName)), new TheLocalFix(holder.getProject(), assign.getParent(), argument, assignValue));
}
}
}
}
@Override
public void visitPhpReturn(@NotNull PhpReturn returnStatement) {
if (ANALYZE_RETURN_STATEMENTS) {
/* if function returning reference, do not inspect returns */
final Function callable = ExpressionSemanticUtil.getScope(returnStatement);
final PsiElement nameNode = NamedElementUtil.getNameIdentifier(callable);
if (null != callable && null != nameNode) {
/* is defined like returning reference */
PsiElement referenceCandidate = nameNode.getPrevSibling();
if (referenceCandidate instanceof PsiWhiteSpace) {
referenceCandidate = referenceCandidate.getPrevSibling();
}
if (OpenapiTypesUtil.is(referenceCandidate, PhpTokenTypes.opBIT_AND)) {
return;
}
}
/* regular function, check one-time use variables */
final PsiElement argument = ExpressionSemanticUtil.getExpressionTroughParenthesis(returnStatement.getArgument());
if (argument instanceof PhpPsiElement) {
final Variable variable = this.getVariable((PhpPsiElement) argument);
if (null != variable) {
checkOneTimeUse(returnStatement, variable);
}
}
}
}
@Override
public void visitPhpMultiassignmentExpression(@NotNull MultiassignmentExpression multiassignmentExpression) {
if (ANALYZE_ARRAY_DESTRUCTURING) {
final PsiElement firstChild = multiassignmentExpression.getFirstChild();
final IElementType nodeType = null == firstChild ? null : firstChild.getNode().getElementType();
if (null != nodeType && (PhpTokenTypes.kwLIST == nodeType || PhpTokenTypes.chLBRACKET == nodeType)) {
final Variable variable = this.getVariable(multiassignmentExpression.getValue());
final PsiElement parent = multiassignmentExpression.getParent();
if (null != variable && OpenapiTypesUtil.isStatementImpl(parent)) {
checkOneTimeUse((PhpPsiElement) parent, variable);
}
}
}
}
/* TODO: once got bored, add foreach source pattern here =) I'm naive but nevertheless ^_^ */
@Override
public void visitPhpThrowExpression(@NotNull PhpThrowExpression expression) {
if (ANALYZE_THROW_STATEMENTS) {
final PsiElement argument = ExpressionSemanticUtil.getExpressionTroughParenthesis(expression.getArgument());
if (argument instanceof PhpPsiElement) {
final Variable variable = this.getVariable((PhpPsiElement) argument);
if (null != variable) {
checkOneTimeUse(expression.getExpression(), variable);
}
}
}
}
@Nullable
private Variable getVariable(@Nullable PhpPsiElement expression) {
if (expression != null) {
if (expression instanceof Variable) {
return (Variable) expression;
} else if (expression instanceof MemberReference) {
final MemberReference reference = (MemberReference) expression;
final PsiElement candidate = reference.getFirstChild();
if (candidate instanceof Variable) {
final PsiElement operator = OpenapiPsiSearchUtil.findResolutionOperator(reference);
if (OpenapiTypesUtil.is(operator, PhpTokenTypes.ARROW)) {
return (Variable) candidate;
}
}
} else if (OpenapiTypesUtil.isPhpExpressionImpl(expression)) {
/* instanceof passes child classes as well, what isn't correct */
return getVariable(expression.getFirstPsiChild());
}
}
return null;
}
private boolean isTypeAnnotated(@NotNull PhpPsiElement current, @NotNull String variableName) {
boolean result = false;
final PsiElement phpdocCandidate = current.getPrevPsiSibling();
if (phpdocCandidate instanceof PhpDocComment) {
final PhpDocTag[] hints = ((PhpDocComment) phpdocCandidate).getTagElementsByName("@var");
if (hints.length == 1) {
final PhpDocVariable specifiedVariable = PsiTreeUtil.findChildOfType(hints[0], PhpDocVariable.class);
result = specifiedVariable != null && specifiedVariable.getName().equals(variableName);
}
}
return result;
}
};
}
use of com.jetbrains.php.codeInsight.controlFlow.instructions.PhpAccessVariableInstruction in project phpinspectionsea by kalessil.
the class ParameterImmediateOverrideStrategy method apply.
public static void apply(@NotNull Function function, @NotNull ProblemsHolder holder) {
/* general requirements for a function */
final Parameter[] params = function.getParameters();
final GroupStatement body = ExpressionSemanticUtil.getGroupStatement(function);
if (body == null || params.length == 0 || ExpressionSemanticUtil.countExpressionsInGroup(body) == 0) {
return;
}
final PhpEntryPointInstruction start = function.getControlFlow().getEntryPoint();
for (final Parameter param : params) {
/* overriding params by reference is totally fine */
if (param.isPassByRef()) {
continue;
}
final String parameterName = param.getName();
List<PhpAccessVariableInstruction> uses = OpenapiControlFlowUtil.getFollowingVariableAccessInstructions(start, parameterName);
/* at least 2 uses expected: override and any other operation */
if (uses.size() < 2) {
continue;
}
/* first use should be a write directly in function body */
final PhpPsiElement expression = uses.get(0).getAnchor();
final PsiElement parent = expression.getParent();
if (OpenapiTypesUtil.isAssignment(parent) && expression == ((AssignmentExpression) parent).getVariable()) {
/* the assignment must be directly in the body, no conditional/in-loop overrides are checked */
final PsiElement grandParent = parent.getParent();
if (grandParent != null && body != grandParent.getParent()) {
continue;
}
/* count name hits, to identify if original value was considered */
int nameHits = 0;
for (final Variable variable : PsiTreeUtil.findChildrenOfType(parent, Variable.class)) {
if (parameterName.equals(variable.getName()) && ++nameHits > 1) {
break;
}
}
if (nameHits == 1) {
holder.registerProblem(expression, MessagesPresentationUtil.prefixWithEa(message));
}
}
}
}
use of com.jetbrains.php.codeInsight.controlFlow.instructions.PhpAccessVariableInstruction in project phpinspectionsea by kalessil.
the class ReferenceMismatchInspector method buildVisitor.
@Override
@NotNull
public PsiElementVisitor buildVisitor(@NotNull final ProblemsHolder holder, boolean isOnTheFly) {
return new BasePhpElementVisitor() {
/**
* TODO: checkReferenceReturnedByCallable - ternary operator, argument usages ?
*/
/* parameters by reference */
@Override
public void visitPhpMethod(@NotNull Method method) {
/* PHP7 seems to be ref mismatch free */
final PhpLanguageLevel php = PhpProjectConfigurationFacade.getInstance(holder.getProject()).getLanguageLevel();
if (!php.hasFeature(PhpLanguageFeature.SCALAR_TYPE_HINTS)) {
this.checkParameters(method.getParameters(), method);
}
}
@Override
public void visitPhpFunction(@NotNull Function function) {
/* PHP7 seems to be ref mismatch free */
final PhpLanguageLevel phpVersion = PhpProjectConfigurationFacade.getInstance(holder.getProject()).getLanguageLevel();
if (phpVersion.hasFeature(PhpLanguageFeature.SCALAR_TYPE_HINTS)) {
// PHP7 and newer
return;
}
/* older versions are still affected */
this.checkParameters(function.getParameters(), function);
}
private void checkParameters(Parameter[] arrParameters, Function objScopeHolder) {
PhpEntryPointInstruction objEntryPoint = objScopeHolder.getControlFlow().getEntryPoint();
HashSet<PsiElement> emptyReportedItemsRegistry = ReferenceMismatchInspector.getFunctionReportingRegistry(objScopeHolder);
for (Parameter parameter : arrParameters) {
/* skip un-discoverable and non-reference parameters */
String strParameterName = parameter.getName();
if (!parameter.isPassByRef() || StringUtils.isEmpty(strParameterName)) {
continue;
}
inspectScopeForReferenceMissUsages(objEntryPoint, strParameterName, emptyReportedItemsRegistry);
}
emptyReportedItemsRegistry.clear();
}
/* = & variable/property patterns */
@Override
public void visitPhpAssignmentExpression(@NotNull AssignmentExpression assignmentExpression) {
/* PHP7 seems to be ref mismatch free */
final PhpLanguageLevel phpVersion = PhpProjectConfigurationFacade.getInstance(holder.getProject()).getLanguageLevel();
if (phpVersion.hasFeature(PhpLanguageFeature.SCALAR_TYPE_HINTS)) {
// PHP7 and newer
return;
}
/* older versions are still affected */
PsiElement value = assignmentExpression.getValue();
PsiElement variable = assignmentExpression.getVariable();
if (variable instanceof Variable && (value instanceof Variable || value instanceof FieldReference || value instanceof ArrayAccessExpression || value instanceof FunctionReference)) {
String strVariable = ((Variable) variable).getName();
PsiElement operation = value.getPrevSibling();
if (operation instanceof PsiWhiteSpace) {
operation = operation.getPrevSibling();
}
if (!StringUtils.isEmpty(strVariable) && null != operation && operation.getText().replaceAll("\\s+", "").equals("=&")) {
/* the case, scan for miss-usages assuming variable is unique */
Function scope = ExpressionSemanticUtil.getScope(assignmentExpression);
if (null != scope) {
// report items, but ensure no duplicated messages
HashSet<PsiElement> reportedItemsRegistry = ReferenceMismatchInspector.getFunctionReportingRegistry(scope);
inspectScopeForReferenceMissUsages(scope.getControlFlow().getEntryPoint(), strVariable, reportedItemsRegistry);
}
}
}
}
/* assign reference from function */
@Override
public void visitPhpMethodReference(@NotNull MethodReference reference) {
/* PHP7 seems to be ref mismatch free */
final PhpLanguageLevel phpVersion = PhpProjectConfigurationFacade.getInstance(holder.getProject()).getLanguageLevel();
if (phpVersion.hasFeature(PhpLanguageFeature.SCALAR_TYPE_HINTS)) {
// PHP7 and newer
return;
}
/* older versions are still affected */
this.checkReferenceReturnedByCallable(reference);
}
@Override
public void visitPhpFunctionCall(@NotNull FunctionReference reference) {
/* PHP7 seems to be ref mismatch free */
final PhpLanguageLevel php = PhpProjectConfigurationFacade.getInstance(holder.getProject()).getLanguageLevel();
if (!php.hasFeature(PhpLanguageFeature.SCALAR_TYPE_HINTS)) {
this.checkReferenceReturnedByCallable(reference);
}
}
/* aggressive foreach optimization when value is reference */
@Override
public void visitPhpForeach(@NotNull ForeachStatement foreach) {
/* PHP7 seems to be ref mismatch free */
final PhpLanguageLevel phpVersion = PhpProjectConfigurationFacade.getInstance(holder.getProject()).getLanguageLevel();
if (phpVersion.hasFeature(PhpLanguageFeature.SCALAR_TYPE_HINTS)) {
// PHP7 and newer
return;
}
/* older versions are still affected */
/* lookup for reference preceding value */
Variable objForeachValue = foreach.getValue();
if (null != objForeachValue) {
String strVariable = objForeachValue.getName();
PsiElement prevElement = objForeachValue.getPrevSibling();
if (prevElement instanceof PsiWhiteSpace) {
prevElement = prevElement.getPrevSibling();
}
if (!StringUtils.isEmpty(strVariable) && OpenapiTypesUtil.is(prevElement, PhpTokenTypes.opBIT_AND)) {
/* the case, scan for miss-usages assuming value is unique */
Function scope = ExpressionSemanticUtil.getScope(foreach);
if (null != scope) {
// report items, but ensure no duplicated messages
HashSet<PsiElement> reportedItemsRegistry = ReferenceMismatchInspector.getFunctionReportingRegistry(scope);
reportedItemsRegistry.add(objForeachValue);
inspectScopeForReferenceMissUsages(scope.getControlFlow().getEntryPoint(), strVariable, reportedItemsRegistry);
}
}
}
}
private void inspectScopeForReferenceMissUsages(@NotNull PhpEntryPointInstruction objEntryPoint, @NotNull String strParameterName, @NotNull Set<PsiElement> reportedItemsRegistry) {
PsiElement previous;
PsiElement objExpression = null;
/* find usage inside scope */
PhpAccessVariableInstruction[] arrUsages = PhpControlFlowUtil.getFollowingVariableAccessInstructions(objEntryPoint, strParameterName, false);
for (final PhpAccessVariableInstruction objInstruction : arrUsages) {
previous = objExpression;
objExpression = objInstruction.getAnchor().getParent();
/* collided with foreach index/value => bug */
if (objExpression instanceof ForeachStatement) {
final ForeachStatement foreach = (ForeachStatement) objExpression;
if (previous instanceof PhpUnset) {
break;
}
final Variable foreachValue = foreach.getValue();
if (null != foreachValue && !StringUtils.isEmpty(foreachValue.getName()) && foreachValue.getName().equals(strParameterName)) {
if (!reportedItemsRegistry.contains(foreachValue)) {
reportedItemsRegistry.add(foreachValue);
holder.registerProblem(foreachValue, strErrorForeachIntoReference, ProblemHighlightType.ERROR);
}
continue;
}
final Variable foreachKey = foreach.getKey();
if (null != foreachKey && !StringUtils.isEmpty(foreachKey.getName()) && foreachKey.getName().equals(strParameterName)) {
if (!reportedItemsRegistry.contains(foreachKey)) {
reportedItemsRegistry.add(foreachKey);
holder.registerProblem(foreachKey, strErrorForeachIntoReference, ProblemHighlightType.ERROR);
}
continue;
}
}
/* test if provided as non-reference argument (copy dispatched) */
if (objExpression instanceof ParameterList && objExpression.getParent() instanceof FunctionReference) {
FunctionReference reference = (FunctionReference) objExpression.getParent();
/* not resolved or known re-unsafe function */
final PsiElement callable = OpenapiResolveUtil.resolveReference(reference);
if (!(callable instanceof Function)) {
continue;
}
final String strCallableName = ((Function) callable).getName();
if (!StringUtils.isEmpty(strCallableName) && legalizedMismatchingFunctions.contains(strCallableName)) {
continue;
}
/* check if call arguments contains our parameter */
int indexInArguments = -1;
boolean providedAsArgument = false;
for (PsiElement callArgument : reference.getParameters()) {
++indexInArguments;
if (callArgument instanceof Variable) {
Variable argument = (Variable) callArgument;
String argumentName = argument.getName();
if (!StringUtils.isEmpty(argumentName) && argumentName.equals(strParameterName)) {
providedAsArgument = true;
break;
}
}
}
/* if not found, keep processing usages */
if (!providedAsArgument) {
continue;
}
/* now check what is declared in resolved callable */
final Parameter[] usageCallableParameters = ((Function) callable).getParameters();
if (usageCallableParameters.length >= indexInArguments + 1) {
final Parameter parameter = usageCallableParameters[indexInArguments];
if (!parameter.isPassByRef()) {
/* additionally try filtering types for reducing false-positives on scalars */
final PhpType type = OpenapiResolveUtil.resolveType(parameter, holder.getProject());
if (type != null && !PhpType.isSubType(type, legalizedTypesForMismatchingSet)) {
final PsiElement target = reference.getParameters()[indexInArguments];
if (!reportedItemsRegistry.contains(target)) {
holder.registerProblem(target, "Reference mismatch, copy will be dispatched into function", ProblemHighlightType.WEAK_WARNING);
reportedItemsRegistry.add(target);
}
continue;
}
}
}
}
/* test is assigned to a variable without stating its reference (copy stored) */
if (objExpression instanceof AssignmentExpression) {
/* assignment structure verify */
AssignmentExpression assignment = (AssignmentExpression) objExpression;
if (assignment.getValue() instanceof Variable) {
Variable variable = (Variable) assignment.getValue();
String strVariable = variable.getName();
/* references parameter */
if (!StringUtils.isEmpty(strVariable) && strVariable.equals(strParameterName)) {
/* check if assignments states reference usage */
PsiElement operation = variable.getPrevSibling();
if (operation instanceof PsiWhiteSpace) {
operation = operation.getPrevSibling();
}
/* report if not */
if (null != operation && !operation.getText().replaceAll("\\s+", "").equals("=&")) {
if (!reportedItemsRegistry.contains(objExpression)) {
holder.registerProblem(objExpression, "Reference mismatch, copy will be stored (for non-objects)", ProblemHighlightType.WEAK_WARNING);
reportedItemsRegistry.add(objExpression);
}
}
}
}
}
}
}
private void checkReferenceReturnedByCallable(@NotNull FunctionReference reference) {
/* check context before resolving anything */
final PsiElement parent = reference.getParent();
if (parent instanceof AssignmentExpression) {
/* assignment structure verify */
final AssignmentExpression assignment = (AssignmentExpression) parent;
if (assignment.getValue() == reference) {
/* try resolving now */
final PsiElement callable = OpenapiResolveUtil.resolveReference(reference);
if (callable instanceof Function) {
/* ensure name discoverable */
final Function function = (Function) callable;
final PsiElement nameNode = NamedElementUtil.getNameIdentifier(function);
if (null != nameNode) {
/* is defined like returning reference */
PsiElement prevElement = nameNode.getPrevSibling();
if (prevElement instanceof PsiWhiteSpace) {
prevElement = prevElement.getPrevSibling();
}
if (OpenapiTypesUtil.is(prevElement, PhpTokenTypes.opBIT_AND)) {
/* check if assignments states reference usage */
PsiElement operation = reference.getPrevSibling();
if (operation instanceof PsiWhiteSpace) {
operation = operation.getPrevSibling();
}
/* report if not */
if (null != operation && !operation.getText().replaceAll("\\s+", "").equals("=&")) {
holder.registerProblem(parent, "Reference mismatch, copy will be stored (for non-objects)", ProblemHighlightType.WEAK_WARNING);
}
}
}
}
}
}
}
};
}
Aggregations