perf(parse-rule): O(n) join-lines; hash set for config dir-cycle dedup

ober

f82940aa59e97d65b2ee7d9c1861f6e33b454164

diff --git a/lib/semgrep/rule/parse-rule.sls b/lib/semgrep/rule/parse-rule.sls
index 34261e9..a7e16f5 100644
--- a/lib/semgrep/rule/parse-rule.sls
+++ b/lib/semgrep/rule/parse-rule.sls
@@ -150,12 +150,14 @@
        (cond
          [(null? lines) ""]
          [else
-          (let loop ([remaining (cdr lines)] [acc (car lines)])
-            (if (null? remaining)
-                acc
-                (loop
-                  (cdr remaining)
-                  (string-append acc "\n" (car remaining)))))]))
+          (let ([out (open-output-string)])
+            (display (car lines) out)
+            (let loop ([remaining (cdr lines)])
+              (unless (null? remaining)
+                (display #\newline out)
+                (display (car remaining) out)
+                (loop (cdr remaining))))
+            (get-output-string out))]))
   (def (line-indent line)
        (let ([len (string-length line)])
          (let loop ([i 0])
@@ -429,14 +431,12 @@
        (let ([key (cons
                     (secure-entry-device info)
                     (secure-entry-inode info))])
-         (let loop ([xs seen])
-           (and (not (null? xs))
-                (or (equal? key (car xs)) (loop (cdr xs)))))))
+         (hash-key? seen key)))
   (def (config-files-recursive handle)
        (let ([entry-count 0]
              [file-count 0]
              [path-char-count 0]
-             [seen '()])
+             [seen (make-hash-table)])
          (let walk ([relative-dir ""] [depth 0] [acc '()])
            (when (> depth max-config-walk-depth)
              (error 'parse-config-file
@@ -452,12 +452,12 @@
                (error 'parse-config-file
                  "config directory contains a cycle"
                  relative-dir))
-             (set! seen
+             (hash-put!
+               seen
                (cons
-                 (cons
-                   (secure-entry-device directory-info)
-                   (secure-entry-inode directory-info))
-                 seen))
+                 (secure-entry-device directory-info)
+                 (secure-entry-inode directory-info))
+               #t)
              (let loop ([entries (secure-directory-list
                                    handle
                                    relative-dir
diff --git a/src/semgrep/rule/parse-rule.ss b/src/semgrep/rule/parse-rule.ss
index d34d7d0..85b59e7 100644
--- a/src/semgrep/rule/parse-rule.ss
+++ b/src/semgrep/rule/parse-rule.ss
@@ -150,11 +150,14 @@
   (cond
     [(null? lines) ""]
     [else
-     (let loop ([remaining (cdr lines)] [acc (car lines)])
-       (if (null? remaining)
-           acc
-           (loop (cdr remaining)
-                 (string-append acc "\n" (car remaining)))))]))
+      (let ([out (open-output-string)])
+        (display (car lines) out)
+        (let loop ([remaining (cdr lines)])
+          (unless (null? remaining)
+            (display #\newline out)
+            (display (car remaining) out)
+            (loop (cdr remaining))))
+        (get-output-string out))]))
 
 (def (line-indent line)
   (let ([len (string-length line)])
@@ -438,16 +441,13 @@
 
 (def (config-identity-seen? info seen)
   (let ([key (cons (secure-entry-device info) (secure-entry-inode info))])
-    (let loop ([xs seen])
-      (and (not (null? xs))
-           (or (equal? key (car xs))
-               (loop (cdr xs)))))))
+    (hash-key? seen key)))
 
 (def (config-files-recursive handle)
   (let ([entry-count 0]
         [file-count 0]
         [path-char-count 0]
-        [seen '()])
+        [seen (make-hash-table)])
     (let walk ([relative-dir ""] [depth 0] [acc '()])
       (when (> depth max-config-walk-depth)
         (error 'parse-config-file "config directory exceeds depth limit"))
@@ -457,10 +457,10 @@
         (when (config-identity-seen? directory-info seen)
           (error 'parse-config-file "config directory contains a cycle"
                  relative-dir))
-        (set! seen
-              (cons (cons (secure-entry-device directory-info)
-                          (secure-entry-inode directory-info))
-                    seen))
+        (hash-put! seen
+                   (cons (secure-entry-device directory-info)
+                         (secure-entry-inode directory-info))
+                   #t)
         (let loop ([entries (secure-directory-list handle relative-dir 10000
                                                     (* 1024 1024))]
                    [current-acc acc])