mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
RegExp: new "Exponential backtracking" inspection
This commit is contained in:
@@ -34,5 +34,7 @@
|
||||
implementationClass="org.intellij.lang.regexp.inspection.SingleCharAlternationInspection"/>
|
||||
<localInspection groupName="RegExp" language="RegExp" shortName="OctalEscape" displayName="Octal escape" enabledByDefault="false"
|
||||
level="WARNING" implementationClass="org.intellij.lang.regexp.inspection.OctalEscapeInspection"/>
|
||||
<localInspection groupName="RegExp" language="RegExp" shortName="ReDoS" displayName="Exponential backtracking" enabledByDefault="true"
|
||||
level="WARNING" implementationClass="org.intellij.lang.regexp.inspection.ReDoSInspection"/>
|
||||
</extensions>
|
||||
</idea-plugin>
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
<html>
|
||||
<body>
|
||||
Reports exponential backtracking in a RegExp, which can lead to extremely slow matching.
|
||||
Exponential backtracking can happen when a single string can be matched in multiple ways, leading to exponential runtime.
|
||||
Exponential backtracking is also known as Regular Expression Denial of Service (ReDoS), catastrophic backtracking, explosive quantifiers
|
||||
and exponential matching.
|
||||
This inspection only detects a limited form of exponential backtracking,
|
||||
so the absence of a warning is not a guarantee the pattern does not contain a exponential backtracking problem.
|
||||
|
||||
<!-- tooltip end -->
|
||||
<p>
|
||||
<small>New in 2017.1</small>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,105 @@
|
||||
/*
|
||||
* Copyright 2000-2017 JetBrains s.r.o.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.intellij.lang.regexp.inspection;
|
||||
|
||||
import com.intellij.codeInspection.LocalInspectionTool;
|
||||
import com.intellij.codeInspection.LocalInspectionToolSession;
|
||||
import com.intellij.codeInspection.ProblemsHolder;
|
||||
import com.intellij.lang.ASTNode;
|
||||
import com.intellij.psi.PsiElementVisitor;
|
||||
import com.intellij.psi.util.PsiTreeUtil;
|
||||
import org.intellij.lang.regexp.RegExpTT;
|
||||
import org.intellij.lang.regexp.psi.*;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* @author Bas Leijdekkers
|
||||
*/
|
||||
public class ReDoSInspection extends LocalInspectionTool {
|
||||
|
||||
@Override
|
||||
public void inspectionFinished(@NotNull LocalInspectionToolSession session, @NotNull ProblemsHolder problemsHolder) {
|
||||
super.inspectionFinished(session, problemsHolder);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public PsiElementVisitor buildVisitor(@NotNull ProblemsHolder holder,
|
||||
boolean isOnTheFly,
|
||||
@NotNull LocalInspectionToolSession session) {
|
||||
return new ReDoSVisitor(holder);
|
||||
}
|
||||
|
||||
private static class ReDoSVisitor extends RegExpElementVisitor {
|
||||
|
||||
private final ProblemsHolder myHolder;
|
||||
|
||||
public ReDoSVisitor(ProblemsHolder holder) {
|
||||
myHolder = holder;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitRegExpClosure(RegExpClosure closure) {
|
||||
if (!hasSuspiciousQuantifier(closure)) {
|
||||
return;
|
||||
}
|
||||
RegExpClosure parent = PsiTreeUtil.getParentOfType(closure, RegExpClosure.class);
|
||||
while (parent != null) {
|
||||
if (hasSuspiciousQuantifier(parent)) {
|
||||
if (isAtomic(closure)) {
|
||||
return;
|
||||
}
|
||||
myHolder.registerProblem(parent, "Potential exponential backtracking");
|
||||
return;
|
||||
}
|
||||
parent = PsiTreeUtil.getParentOfType(parent, RegExpClosure.class);
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean hasSuspiciousQuantifier(RegExpClosure closure) {
|
||||
final RegExpQuantifier quantifier = closure.getQuantifier();
|
||||
if (!quantifier.isCounted()) {
|
||||
final ASTNode token = quantifier.getToken();
|
||||
return !(token == null || token.getElementType() == RegExpTT.QUEST);
|
||||
}
|
||||
final RegExpNumber max = quantifier.getMax();
|
||||
if (max == null) {
|
||||
return true;
|
||||
}
|
||||
final Number value = max.getValue();
|
||||
return value == null || value.doubleValue() >= 10;
|
||||
}
|
||||
|
||||
private static boolean isAtomic(RegExpAtom element) {
|
||||
while (element != null) {
|
||||
if (element instanceof RegExpClosure) {
|
||||
final RegExpClosure closure = (RegExpClosure)element;
|
||||
if (closure.getQuantifier().isPossessive()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else if (element instanceof RegExpGroup) {
|
||||
final RegExpGroup group = (RegExpGroup)element;
|
||||
if (group.getType() == RegExpGroup.Type.ATOMIC) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
element = PsiTreeUtil.getParentOfType(element, RegExpClosure.class, RegExpGroup.class);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -15,11 +15,14 @@
|
||||
*/
|
||||
package org.intellij.lang.regexp.psi;
|
||||
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
/**
|
||||
* Number consisting only of one or more decimal digits. No negation or decimal points.
|
||||
* @author Bas Leijdekkers
|
||||
*/
|
||||
public interface RegExpNumber extends RegExpElement {
|
||||
|
||||
|
||||
@Nullable
|
||||
Number getValue();
|
||||
}
|
||||
|
||||
@@ -16,8 +16,10 @@
|
||||
package org.intellij.lang.regexp.psi.impl;
|
||||
|
||||
import com.intellij.lang.ASTNode;
|
||||
import org.intellij.lang.regexp.RegExpLanguageHosts;
|
||||
import org.intellij.lang.regexp.psi.RegExpElementVisitor;
|
||||
import org.intellij.lang.regexp.psi.RegExpNumber;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
/**
|
||||
* @author Bas Leijdekkers
|
||||
@@ -28,6 +30,12 @@ public class RegExpNumberImpl extends RegExpElementImpl implements RegExpNumber
|
||||
super(node);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public Number getValue() {
|
||||
return RegExpLanguageHosts.getInstance().getQuantifierValue(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void accept(RegExpElementVisitor visitor) {
|
||||
visitor.visitRegExpNumber(this);
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
* Copyright 2000-2017 JetBrains s.r.o.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.intellij.lang.regexp.inspection;
|
||||
|
||||
import com.intellij.codeInspection.LocalInspectionTool;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* @author Bas Leijdekkers
|
||||
*/
|
||||
public class ReDoSInspectionTest extends RegExpInspectionTestCase {
|
||||
|
||||
public void testNestedQuantifiers1() {
|
||||
highlightTest("<warning descr=\"Potential exponential backtracking\">(.*a){10}</warning>");
|
||||
}
|
||||
|
||||
public void testNestedQuantifiers2() {
|
||||
highlightTest("<warning descr=\"Potential exponential backtracking\">(a+)+</warning>");
|
||||
}
|
||||
|
||||
public void testPossessiveQuantifier() {
|
||||
highlightTest("(a+)++");
|
||||
}
|
||||
|
||||
public void testAtomicGroup() {
|
||||
highlightTest("(?>a+)+");
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
protected LocalInspectionTool getInspection() {
|
||||
return new ReDoSInspection();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user