use of com.jetbrains.php.config.PhpLanguageLevel in project phpinspectionsea by kalessil.
the class NonSecureCryptUsageInspector 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("crypt")) {
return;
}
final PsiElement[] arguments = reference.getParameters();
if ((arguments.length != 1 && arguments.length != 2) || !this.isFromRootNamespace(reference)) {
return;
}
/* Case 1: suggest providing blowfish as the 2nd parameter*/
if (arguments.length == 1) {
holder.registerProblem(reference, messageWeakSalt);
return;
}
/* try resolving 2nd parameter, skip if failed, it contains injections or length is not as expected */
final String saltValue = this.resolveSalt(arguments[1]);
if (null == saltValue || saltValue.length() < 4) {
return;
}
/* Case 2: using $2a$; use $2y$ instead - http://php.net/security/crypt_blowfish.php*/
if (saltValue.startsWith("$2a$")) {
holder.registerProblem(reference, messageInsecureSalt, ProblemHighlightType.GENERIC_ERROR);
return;
}
/* Case 3: -> password_hash(PASSWORD_BCRYPT) in PHP 5.5+ */
final boolean isBlowfish = saltValue.startsWith("$2y$") || saltValue.startsWith("$2x$");
if (isBlowfish) {
PhpLanguageLevel php = PhpProjectConfigurationFacade.getInstance(holder.getProject()).getLanguageLevel();
if (php.compareTo(PhpLanguageLevel.PHP550) >= 0) {
holder.registerProblem(reference, messagePasswordHash, ProblemHighlightType.WEAK_WARNING);
}
}
}
@Nullable
private String resolveSalt(@NotNull PsiElement expression) {
/* collect possible value for further analysis */
final Set<PsiElement> discovered = PossibleValuesDiscoveryUtil.discover(expression);
if (discovered.size() != 1) {
discovered.clear();
return null;
}
/* simplify workflow by handling one expression */
final PsiElement saltExpression = discovered.iterator().next();
final StringBuilder resolvedSaltValue = new StringBuilder();
discovered.clear();
/* resolve string literals and concatenations */
PsiElement current = saltExpression;
while (current instanceof ConcatenationExpression) {
final ConcatenationExpression concat = (ConcatenationExpression) current;
final PsiElement right = ExpressionSemanticUtil.getExpressionTroughParenthesis(concat.getRightOperand());
final StringLiteralExpression part = ExpressionSemanticUtil.resolveAsStringLiteral(right);
resolvedSaltValue.insert(0, part == null ? "<?>" : part.getContents());
current = ExpressionSemanticUtil.getExpressionTroughParenthesis(concat.getLeftOperand());
}
/* don't forget to add the last element */
if (null != current) {
final StringLiteralExpression lastPart = ExpressionSemanticUtil.resolveAsStringLiteral(current);
resolvedSaltValue.insert(0, null == lastPart ? "<?>" : lastPart.getContents());
}
return resolvedSaltValue.toString();
}
};
}
use of com.jetbrains.php.config.PhpLanguageLevel in project phpinspectionsea by kalessil.
the class ExceptionsAnnotatingAndHandlingInspector method buildVisitor.
@Override
@NotNull
public PsiElementVisitor buildVisitor(@NotNull final ProblemsHolder holder, final boolean isOnTheFly) {
return new BasePhpElementVisitor() {
@Override
public void visitPhpFinally(@NotNull Finally element) {
PhpLanguageLevel phpVersion = PhpProjectConfigurationFacade.getInstance(holder.getProject()).getLanguageLevel();
if (!phpVersion.hasFeature(PhpLanguageFeature.FINALLY)) {
return;
}
final HashSet<PsiElement> processedRegistry = new HashSet<>();
final HashMap<PhpClass, HashSet<PsiElement>> exceptions = CollectPossibleThrowsUtil.collectNestedAndWorkflowExceptions(element, processedRegistry, holder);
/* report individual statements */
if (exceptions.size() > 0) {
final Set<PsiElement> reportedExpressions = new HashSet<>();
for (final Set<PsiElement> pool : exceptions.values()) {
pool.stream().filter(expression -> !reportedExpressions.contains(expression)).forEach(expression -> {
holder.registerProblem(this.getReportingTarget(expression), messageFinallyExceptions, ProblemHighlightType.GENERIC_ERROR);
reportedExpressions.add(expression);
});
pool.clear();
}
reportedExpressions.clear();
exceptions.clear();
}
/* report try-blocks */
if (processedRegistry.size() > 0) {
processedRegistry.stream().filter(statement -> statement instanceof Try).forEach(statement -> holder.registerProblem(statement.getFirstChild(), messageFinallyExceptions, ProblemHighlightType.GENERIC_ERROR));
processedRegistry.clear();
}
}
@Override
public void visitPhpMethod(@NotNull Method method) {
if (method.isAbstract() || this.isTestContext(method)) {
return;
}
// __toString has magic methods validation, must not raise exceptions
final PsiElement methodName = NamedElementUtil.getNameIdentifier(method);
if (null == methodName || method.getName().equals("__toString")) {
return;
}
/* collect announced cases */
final HashSet<PhpClass> annotatedExceptions = new HashSet<>();
final boolean hasPhpDoc = method.getDocComment() != null;
if (!ThrowsResolveUtil.resolveThrownExceptions(method, annotatedExceptions)) {
return;
}
HashSet<PsiElement> processedRegistry = new HashSet<>();
HashMap<PhpClass, HashSet<PsiElement>> throwsExceptions = CollectPossibleThrowsUtil.collectNestedAndWorkflowExceptions(method, processedRegistry, holder);
processedRegistry.clear();
/* exclude annotated exceptions, identify which has not been thrown */
final Set<PhpClass> annotatedButNotThrownExceptions = new HashSet<>(annotatedExceptions);
/* release bundled expressions */
/* actualize un-thrown exceptions registry */
annotatedExceptions.stream().filter(key -> hasPhpDoc && throwsExceptions.containsKey(key)).forEach(annotated -> {
/* release bundled expressions */
throwsExceptions.get(annotated).clear();
throwsExceptions.remove(annotated);
/* actualize un-thrown exceptions registry */
annotatedButNotThrownExceptions.remove(annotated);
});
/* do reporting now: exceptions annotated, but not thrown */
if (REPORT_NON_THROWN_EXCEPTIONS && annotatedButNotThrownExceptions.size() > 0) {
final List<String> toReport = annotatedButNotThrownExceptions.stream().map(PhpNamedElement::getFQN).collect(Collectors.toList());
final String message = messagePatternUnthrown.replace("%c%", String.join(", ", toReport));
holder.registerProblem(methodName, message, ProblemHighlightType.WEAK_WARNING);
toReport.clear();
}
annotatedButNotThrownExceptions.clear();
/* do reporting now: exceptions thrown but not annotated */
if (throwsExceptions.size() > 0) {
/* deeper analysis needed */
HashMap<PhpClass, HashSet<PsiElement>> unhandledExceptions = new HashMap<>();
if (!annotatedExceptions.isEmpty() && hasPhpDoc) {
/* filter what to report based on annotated exceptions */
for (final PhpClass annotated : annotatedExceptions) {
for (final Map.Entry<PhpClass, HashSet<PsiElement>> throwsExceptionsPair : throwsExceptions.entrySet()) {
final PhpClass thrown = throwsExceptionsPair.getKey();
/* already reported */
if (unhandledExceptions.containsKey(thrown)) {
continue;
}
/* check thrown parents, as annotated not processed here */
final HashSet<PhpClass> thrownVariants = InterfacesExtractUtil.getCrawlInheritanceTree(thrown, true);
if (!thrownVariants.contains(annotated)) {
unhandledExceptions.put(thrown, throwsExceptionsPair.getValue());
throwsExceptions.put(thrown, null);
}
thrownVariants.clear();
}
}
} else {
/* report all, as nothing is annotated */
for (Map.Entry<PhpClass, HashSet<PsiElement>> throwsExceptionsPair : throwsExceptions.entrySet()) {
final PhpClass thrown = throwsExceptionsPair.getKey();
/* already reported */
if (unhandledExceptions.containsKey(thrown)) {
continue;
}
unhandledExceptions.put(thrown, throwsExceptionsPair.getValue());
throwsExceptions.put(thrown, null);
}
}
if (unhandledExceptions.size() > 0) {
for (final Map.Entry<PhpClass, HashSet<PsiElement>> unhandledExceptionsPair : unhandledExceptions.entrySet()) {
final String thrown = unhandledExceptionsPair.getKey().getFQN();
final Set<PsiElement> blamedExpressions = unhandledExceptionsPair.getValue();
if (!configuration.contains(thrown)) {
final String message = messagePattern.replace("%c%", thrown);
for (final PsiElement blame : blamedExpressions) {
final LocalQuickFix fix = hasPhpDoc ? new MissingThrowAnnotationLocalFix(method, thrown) : null;
holder.registerProblem(this.getReportingTarget(blame), message, ProblemHighlightType.WEAK_WARNING, fix);
}
}
blamedExpressions.clear();
}
unhandledExceptions.clear();
}
throwsExceptions.clear();
}
annotatedExceptions.clear();
}
@NotNull
private PsiElement getReportingTarget(@NotNull PsiElement expression) {
PsiElement result = expression;
if (expression instanceof FunctionReference) {
final PsiElement nameNode = (PsiElement) ((FunctionReference) expression).getNameNode();
if (nameNode != null) {
result = nameNode;
}
} else if (expression instanceof PhpThrow) {
final PsiElement subject = ((PhpThrow) expression).getArgument();
if (subject instanceof NewExpression) {
final PsiElement reference = ((NewExpression) subject).getClassReference();
if (reference != null) {
result = reference;
}
}
}
return result;
}
};
}
use of com.jetbrains.php.config.PhpLanguageLevel in project phpinspectionsea by kalessil.
the class RandomApiMigrationInspector 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) {
final PhpLanguageLevel php = PhpProjectConfigurationFacade.getInstance(holder.getProject()).getLanguageLevel();
String suggestion = getMapping(php).get(functionName);
if (suggestion != null) {
/* random_int needs 2 parameters always, so check if mt_rand can be suggested */
if (reference.getParameters().length != 2 && suggestion.equals("random_int")) {
if (functionName.equals("rand")) {
suggestion = "mt_rand";
} else {
return;
}
}
final String message = messagePattern.replace("%o%", functionName).replace("%n%", suggestion);
holder.registerProblem(reference, message, new TheLocalFix(suggestion));
}
}
}
};
}
use of com.jetbrains.php.config.PhpLanguageLevel in project phpinspectionsea by kalessil.
the class ConstantCanBeUsedInspectorTest method testIfFindsOsFamilyPatterns.
public void testIfFindsOsFamilyPatterns() {
final PhpLanguageLevel level = PhpLanguageLevel.parse("7.3");
if (level != null && level.getVersionString().equals("7.3")) {
PhpProjectConfigurationFacade.getInstance(myFixture.getProject()).setLanguageLevel(level);
myFixture.enableInspections(new ConstantCanBeUsedInspector());
myFixture.configureByFile("testData/fixtures/api/constants-usage.php-os-family.php");
myFixture.testHighlighting(true, false, true);
}
}
use of com.jetbrains.php.config.PhpLanguageLevel in project phpinspectionsea by kalessil.
the class JsonEncodingApiUsageInspectorTest method testIfFindsErrorsHandlingPatterns.
public void testIfFindsErrorsHandlingPatterns() {
final PhpLanguageLevel level = PhpLanguageLevel.parse("7.3");
if (level != null && level.getVersionString().equals("7.3")) {
PhpProjectConfigurationFacade.getInstance(myFixture.getProject()).setLanguageLevel(level);
final JsonEncodingApiUsageInspector inspector = new JsonEncodingApiUsageInspector();
inspector.HARDEN_DECODING_RESULT_TYPE = false;
inspector.HARDEN_ERRORS_HANDLING = true;
myFixture.enableInspections(inspector);
myFixture.configureByFile("testData/fixtures/api/json-encoding-errors-handling.php");
myFixture.testHighlighting(true, false, true);
myFixture.getAllQuickFixes().forEach(fix -> myFixture.launchAction(fix));
myFixture.setTestDataPath(".");
myFixture.checkResultByFile("testData/fixtures/api/json-encoding-errors-handling.fixed.php");
}
}
Aggregations