/* redos_atomic - demonstrates the one performance feature that is a * genuine, user-facing correctness lever rather than an internal * implementation detail: atomic groups (?>...) as a defense against * catastrophic backtracking (ReDoS), on the textbook (a+)+b shape. * * README.md "Implementation status" explains why this shape is not fixed * by this build's memoization alone: (a+)+b has no backreference, so it * is memoized and therefore no longer exponential, but the inner a+'s * repeat is followed by the group's closing save, not by a simple atom, * so the precomputed skip-ahead table that makes a*b linear does not * apply to it either, leaving it empirically quadratic. Wrapping the * inner repetition in an atomic group removes the ambiguity a+ was * causing (once a+ has consumed as many a's as it can, an atomic group * forbids ever giving any of them back to try a shorter run instead), * which is a real, structural difference the pattern author controls, * not a performance tweak this library makes automatically. * * This program compiles both forms of the same pattern and times a search * against an adversarial subject (a long run of 'a' characters with no * trailing 'b', the one input that forces the most backtracking) at a * size chosen to make the quadratic form's cost visible in a few seconds * without making it so large the program is unpleasant to run. */ #define _POSIX_C_SOURCE 199309L #include "regexx.h" #include #include #include #include static double now_seconds(void) { struct timespec ts; clock_gettime(CLOCK_MONOTONIC, &ts); return (double)ts.tv_sec + (double)ts.tv_nsec / 1e9; } static void run_case(const char *label, const char *pattern, const char *subject, size_t subjlen) { PatternError err; memset(&err, 0, sizeof err); Pattern *pat = re_compile(pattern, strlen(pattern), ASCII, &err); if (!pat) { fprintf(stderr, "compile error for %s: %s\n", pattern, err.msg); PatternError_free(&err); return; } Input *in = Input_from_buffer((const uint8_t *)subject, subjlen); Match m; memset(&m, 0, sizeof m); double t0 = now_seconds(); int rc = Pattern_search(pat, in, 0, -1, &m); double t1 = now_seconds(); printf("%-28s pattern=%-14s n=%-8zu rc=%-2d time=%.4fs\n", label, pattern, subjlen, rc, t1 - t0); if (rc == 1) Match_free(&m); Input_free(in); Pattern_free(pat); } int main(void) { size_t n = 20000; char *subject = malloc(n + 1); memset(subject, 'a', n); subject[n] = '\0'; /* no trailing 'b' anywhere: the worst case for both patterns */ printf("Searching %zu characters of 'a' with no trailing 'b' (never matches):\n\n", n); run_case("plain nested quantifier", "(a+)+b", subject, n); run_case("atomic inner group", "(?>a+)+b", subject, n); run_case("possessive inner quant.", "(a++)+b", subject, n); printf("\nAll three patterns are equivalent in what they can match (none of\n" "them ever matches this subject). The atomic and possessive forms\n" "finish close to instantly regardless of n because they never explore\n" "more than one way of splitting the run of 'a' characters; the plain\n" "form's cost grows with n because memoization alone (README.md\n" "\"Implementation status\") makes this shape quadratic, not linear,\n" "and not exponential only because it lacks a backreference.\n"); free(subject); return 0; }