@@ -838,38 +838,32 @@ predicate isStartState(State state) {
838838}
839839
840840/**
841- * A configuration for which parts of a regular expression should be considered relevant for
842- * the different predicates in `ReDoS.qll`.
843- * Used to adjust the computations for either superlinear or exponential backtracking.
841+ * Holds if `state` is a candidate for ReDoS with string `pump`.
844842 */
845- abstract class ReDoSConfiguration extends string {
846- bindingset [ this ]
847- ReDoSConfiguration ( ) { any ( ) }
843+ signature predicate isCandidateSig ( State state , string pump ) ;
848844
849- /**
850- * Holds if `state` with the pump string `pump` is a candidate for a
851- * ReDoS vulnerable state.
852- * This is used to determine which states are considered for the prefix/suffix construction.
853- */
854- abstract predicate isReDoSCandidate ( State state , string pump ) ;
855- }
856-
857- module ReDoSPruning {
845+ /**
846+ * A module for pruning candidate ReDoS states.
847+ * The candidates are specified by the `isCandidate` signature predicate.
848+ * The candidates are checked for rejecting suffixes and deduplicated,
849+ * and the resulting ReDoS states are read by the `hasReDoSResult` predicate.
850+ */
851+ module ReDoSPruning< isCandidateSig / 2 isCandidate> {
858852 /**
859853 * Holds if repeating `pump` starting at `state` is a candidate for causing backtracking.
860854 * No check whether a rejected suffix exists has been made.
861855 */
862856 private predicate isReDoSCandidate ( State state , string pump ) {
863- any ( ReDoSConfiguration conf ) . isReDoSCandidate ( state , pump ) and
857+ isCandidate ( state , pump ) and
864858 (
865- not any ( ReDoSConfiguration conf ) . isReDoSCandidate ( epsilonSucc + ( state ) , _)
859+ not isCandidate ( epsilonSucc + ( state ) , _)
866860 or
867861 epsilonSucc + ( state ) = state and
868862 state =
869863 max ( State s , Location l |
870864 s = epsilonSucc + ( state ) and
871865 l = s .getRepr ( ) .getLocation ( ) and
872- any ( ReDoSConfiguration conf ) . isReDoSCandidate ( s , _) and
866+ isCandidate ( s , _) and
873867 s .getRepr ( ) instanceof InfiniteRepetitionQuantifier
874868 |
875869 s order by l .getStartLine ( ) , l .getStartColumn ( ) , l .getEndColumn ( ) , l .getEndLine ( )
@@ -887,8 +881,11 @@ module ReDoSPruning {
887881 private predicate lastStartState ( State state ) {
888882 exists ( RegExpRoot root |
889883 state =
890- max ( StateInPumpableRegexp s , Location l |
891- isStartState ( s ) and getRoot ( s .getRepr ( ) ) = root and l = s .getRepr ( ) .getLocation ( )
884+ max ( State s , Location l |
885+ s = stateInPumpableRegexp ( ) and
886+ isStartState ( s ) and
887+ getRoot ( s .getRepr ( ) ) = root and
888+ l = s .getRepr ( ) .getLocation ( )
892889 |
893890 s
894891 order by
@@ -957,14 +954,10 @@ module ReDoSPruning {
957954 min ( string c | delta ( prev , any ( InputSymbol symbol | c = intersect ( Any ( ) , symbol ) ) , next ) )
958955 }
959956
960- /**
961- * A state within a regular expression that has a pumpable state.
962- */
963- class StateInPumpableRegexp extends State {
964- pragma [ noinline]
965- StateInPumpableRegexp ( ) {
966- exists ( State s | isReDoSCandidate ( s , _) | getRoot ( s .getRepr ( ) ) = getRoot ( this .getRepr ( ) ) )
967- }
957+ /** Gets a state within a regular expression that has a pumpable state. */
958+ pragma [ noinline]
959+ State stateInPumpableRegexp ( ) {
960+ exists ( State s | isReDoSCandidate ( s , _) | getRoot ( s .getRepr ( ) ) = getRoot ( result .getRepr ( ) ) )
968961 }
969962 }
970963
@@ -1002,26 +995,32 @@ module ReDoSPruning {
1002995 * This predicate might find impossible suffixes when searching for suffixes of length > 1, which can cause FPs.
1003996 */
1004997 pragma [ noinline]
1005- private predicate isLikelyRejectable ( StateInPumpableRegexp s ) {
1006- // exists a reject edge with some char.
1007- hasRejectEdge ( s )
1008- or
1009- hasEdgeToLikelyRejectable ( s )
1010- or
1011- // stopping here is rejection
1012- isRejectState ( s )
998+ private predicate isLikelyRejectable ( State s ) {
999+ s = stateInPumpableRegexp ( ) and
1000+ (
1001+ // exists a reject edge with some char.
1002+ hasRejectEdge ( s )
1003+ or
1004+ hasEdgeToLikelyRejectable ( s )
1005+ or
1006+ // stopping here is rejection
1007+ isRejectState ( s )
1008+ )
10131009 }
10141010
10151011 /**
10161012 * Holds if `s` is not an accept state, and there is no epsilon transition to an accept state.
10171013 */
1018- predicate isRejectState ( StateInPumpableRegexp s ) { not epsilonSucc * ( s ) = Accept ( _) }
1014+ predicate isRejectState ( State s ) {
1015+ s = stateInPumpableRegexp ( ) and not epsilonSucc * ( s ) = Accept ( _)
1016+ }
10191017
10201018 /**
10211019 * Holds if there is likely a non-empty suffix leading to rejection starting in `s`.
10221020 */
10231021 pragma [ noopt]
1024- predicate hasEdgeToLikelyRejectable ( StateInPumpableRegexp s ) {
1022+ predicate hasEdgeToLikelyRejectable ( State s ) {
1023+ s = stateInPumpableRegexp ( ) and
10251024 // all edges (at least one) with some char leads to another state that is rejectable.
10261025 // the `next` states might not share a common suffix, which can cause FPs.
10271026 exists ( string char | char = hasEdgeToLikelyRejectableHelper ( s ) |
@@ -1036,7 +1035,8 @@ module ReDoSPruning {
10361035 * and `s` has not been found to be rejectable by `hasRejectEdge` or `isRejectState`.
10371036 */
10381037 pragma [ noinline]
1039- private string hasEdgeToLikelyRejectableHelper ( StateInPumpableRegexp s ) {
1038+ private string hasEdgeToLikelyRejectableHelper ( State s ) {
1039+ s = stateInPumpableRegexp ( ) and
10401040 not hasRejectEdge ( s ) and
10411041 not isRejectState ( s ) and
10421042 deltaClosedChar ( s , result , _)
@@ -1047,7 +1047,9 @@ module ReDoSPruning {
10471047 * along epsilon edges, such that there is a transition from
10481048 * `prev` to `next` that the character symbol `char`.
10491049 */
1050- predicate deltaClosedChar ( StateInPumpableRegexp prev , string char , StateInPumpableRegexp next ) {
1050+ predicate deltaClosedChar ( State prev , string char , State next ) {
1051+ prev = stateInPumpableRegexp ( ) and
1052+ next = stateInPumpableRegexp ( ) and
10511053 deltaClosed ( prev , getAnInputSymbolMatchingRelevant ( char ) , next )
10521054 }
10531055
@@ -1148,7 +1150,7 @@ module ReDoSPruning {
11481150 exists ( int i , string c | s = Match ( term , i ) |
11491151 c =
11501152 min ( string w |
1151- any ( ReDoSConfiguration conf ) . isReDoSCandidate ( s , w ) and
1153+ isCandidate ( s , w ) and
11521154 SuffixConstruction:: reachesOnlyRejectableSuffixes ( s , w )
11531155 |
11541156 w order by w .length ( ) , w
@@ -1173,28 +1175,28 @@ module ReDoSPruning {
11731175 not exists ( PrefixConstruction:: prefix ( s ) ) and prefixMsg = ""
11741176 )
11751177 }
1176- }
11771178
1178- /**
1179- * Gets the result of backslash-escaping newlines, carriage-returns and
1180- * backslashes in `s`.
1181- */
1182- bindingset [ s]
1183- private string escape ( string s ) {
1184- result =
1185- s .replaceAll ( "\\" , "\\\\" )
1186- .replaceAll ( "\n" , "\\n" )
1187- .replaceAll ( "\r" , "\\r" )
1188- .replaceAll ( "\t" , "\\t" )
1189- }
1179+ /**
1180+ * Gets the result of backslash-escaping newlines, carriage-returns and
1181+ * backslashes in `s`.
1182+ */
1183+ bindingset [ s]
1184+ private string escape ( string s ) {
1185+ result =
1186+ s .replaceAll ( "\\" , "\\\\" )
1187+ .replaceAll ( "\n" , "\\n" )
1188+ .replaceAll ( "\r" , "\\r" )
1189+ .replaceAll ( "\t" , "\\t" )
1190+ }
11901191
1191- /**
1192- * Gets `str` with the last `i` characters moved to the front.
1193- *
1194- * We use this to adjust the pump string to match with the beginning of
1195- * a RegExpTerm, so it doesn't start in the middle of a constant.
1196- */
1197- bindingset [ str, i]
1198- private string rotate ( string str , int i ) {
1199- result = str .suffix ( str .length ( ) - i ) + str .prefix ( str .length ( ) - i )
1192+ /**
1193+ * Gets `str` with the last `i` characters moved to the front.
1194+ *
1195+ * We use this to adjust the pump string to match with the beginning of
1196+ * a RegExpTerm, so it doesn't start in the middle of a constant.
1197+ */
1198+ bindingset [ str, i]
1199+ private string rotate ( string str , int i ) {
1200+ result = str .suffix ( str .length ( ) - i ) + str .prefix ( str .length ( ) - i )
1201+ }
12001202}
0 commit comments