Merge commit '750ac8c592e792e627444f476877f282525b132e'
[bpt/guile.git] / module / system / xref.scm
index acf5ed2..2b943fd 100644 (file)
@@ -1,4 +1,4 @@
-;;;;   Copyright (C) 2009, 2010 Free Software Foundation, Inc.
+;;;;   Copyright (C) 2009, 2010, 2013 Free Software Foundation, Inc.
 ;;;;
 ;;;; This library is free software; you can redistribute it and/or
 ;;;; modify it under the terms of the GNU Lesser General Public
 \f
 
 (define-module (system xref)
-  #:use-module (system base pmatch)
   #:use-module (system base compile)
   #:use-module (system vm program)
+  #:use-module (system vm disassembler)
+  #:use-module (ice-9 match)
   #:use-module (srfi srfi-1)
   #:export (*xref-ignored-modules*
             procedure-callees
-            procedure-callers))
+            procedure-callers
+            source-closures
+            source-procedures))
+
+;;;
+;;; The cross-reference database: who calls whom.
+;;;
+
+(define (nested-procedures prog)
+  (define (cons-uniq x y)
+    (if (memq x y) y (cons x y)))
+  (if (program? prog)
+      (reverse
+       (fold-program-code (lambda (elt out)
+                            (match elt
+                              (('static-ref dst proc)
+                               (if (program? proc)
+                                   (fold cons-uniq
+                                         (cons proc out)
+                                         (nested-procedures prog))
+                                   out))
+                              (_ out)))
+                          (list prog)
+                          prog))
+      (list prog)))
 
 (define (program-callee-rev-vars prog)
   (define (cons-uniq x y)
     (if (memq x y) y (cons x y)))
-  (cond
-   ((program-objects prog)
-    => (lambda (objects)
-          (let ((n (vector-length objects))
-                (progv (make-vector (vector-length objects) #f))
-                (asm (decompile (program-objcode prog) #:to 'assembly)))
-            (pmatch asm
-              ((load-program ,labels ,len . ,body)
-               (for-each
-                (lambda (x)
-                  (pmatch x
-                    ((toplevel-ref ,n) (vector-set! progv n #t))
-                    ((toplevel-set ,n) (vector-set! progv n #t))))
-                body)))
-            (let lp ((i 0) (out '()))
-              (cond
-               ((= i n) out)
-               ((program? (vector-ref objects i))
-                (lp (1+ i)
-                    (fold cons-uniq out
-                          (program-callee-rev-vars (vector-ref objects i)))))
-               ((vector-ref progv i)
-                (let ((obj (vector-ref objects i)))
-                  (if (variable? obj)
-                      (lp (1+ i) (cons-uniq obj out))
-                      ;; otherwise it's an unmemoized binding
-                      (pmatch obj
-                        (,sym (guard (symbol? sym))
-                         (let ((v (module-variable (or (program-module prog)
-                                                       the-root-module)
-                                                   sym)))
-                           (lp (1+ i) (if v (cons-uniq v out) out))))
-                        ((,mod ,sym ,public?)
-                         ;; hm, hacky.
-                         (let* ((m (nested-ref-module (resolve-module '() #f)
-                                                      mod))
-                                (v (and m
-                                        (module-variable
-                                         (if public?
-                                             (module-public-interface m)
-                                             m)
-                                         sym))))
-                           (lp (1+ i)
-                               (if v (cons-uniq v out) out))))))))
-               (else (lp (1+ i) out)))))))
-   (else '())))
+  (fold (lambda (prog out)
+          (fold-program-code
+           (lambda (elt out)
+             (match elt
+               (('toplevel-box dst var mod sym bound?)
+                (let ((var (or var (and mod (module-variable mod sym)))))
+                  (if var
+                      (cons-uniq var out)
+                      out)))
+               (('module-box dst var public? mod-name sym bound?)
+                (let ((var (or var
+                               (module-variable (if public?
+                                                    (resolve-interface mod-name)
+                                                    (resolve-module mod-name))
+                                                sym))))
+                  (if var
+                      (cons-uniq var out)
+                      out)))
+               (_ out)))
+           out
+           prog))
+        '()
+        (nested-procedures prog)))
 
 (define (procedure-callee-rev-vars proc)
   (cond
 (define (ensure-callers-db mod-name)
   (let ((mod (and mod-name (resolve-module mod-name)))
         (visited #f))
-    (define (visit-variable var recurse mod-name)
+    (define (visit-variable var mod-name)
       (if (variable-bound? var)
           (let ((x (variable-ref var)))
             (cond
                 (for-each (lambda (callee)
                             (add-caller callee x mod-name))
                           callees)
-                (add-callees callees mod-name)))
-             ((and recurse (module? x))
-              (visit-module x #t))))))
+                (add-callees callees mod-name)))))))
 
-    (define (visit-module mod recurse)
+    (define (visit-module mod)
       (if visited (hashq-set! visited mod #t))
       (if (not (memq on-module-modified (module-observers mod)))
           (module-observe mod on-module-modified))
       (let ((name (module-name mod)))
         (module-for-each (lambda (sym var)
-                           (visit-variable var recurse name))
+                           (visit-variable var name))
                          mod)))
 
+    (define (visit-submodules mod)
+      (hash-for-each
+       (lambda (name sub)
+         (if (not (and visited (hashq-ref visited sub)))
+             (begin
+               (visit-module sub)
+               (visit-submodules sub))))
+       (module-submodules mod)))
+
     (cond ((and (not mod-name) (not *callers-db*))
            (set! *callers-db* (make-hash-table 1000))
            (set! visited (make-hash-table 1000))
-           (visit-module the-root-module #t))
-          (mod-name (visit-module mod #f)))))
+           (visit-submodules (resolve-module '() #f)))
+          (mod-name (visit-module mod)))))
 
 (define (procedure-callers var)
   "Returns an association list, keyed by module name, of known callers
@@ -173,10 +182,188 @@ pair of the form (module-name . variable-name), "
   (let ((v (cond ((variable? var) var)
                  ((symbol? var) (module-variable (current-module) var))
                  (else
-                  (pmatch var
-                    ((,modname . ,sym)
+                  (match var
+                    ((modname . sym)
                      (module-variable (resolve-module modname) sym))
-                    (else
+                    (_
                      (error "expected a variable, symbol, or (modname . sym)" var)))))))
     (untaint-modules)
     (hashq-ref *callers-db* v '())))
+
+\f
+
+;;;
+;;; The source database: procedures defined at a given source location.
+;;;
+
+;; FIXME: refactor to share code with the xref database.
+
+;; ((ip file line . col) ...)
+(define (procedure-sources proc)
+  (cond
+   ((program? proc) (program-sources proc))
+   (else '())))
+
+;; file -> line -> (proc ...)
+(define *closure-sources-db* #f)
+;; file -> line -> (proc ...)
+(define *sources-db* #f)
+;; module-name -> proc -> sources
+(define *module-sources-db* (make-hash-table))
+;; (module-name ...)
+(define *tainted-sources* '())
+
+(define (on-source-modified m)
+  (let ((name (module-name m)))
+    (if (and (not (member name *xref-ignored-modules*))
+             (not (member name *tainted-sources*))
+             (pair? name))
+        (set! *tainted-sources* (cons name *tainted-sources*)))))
+
+(define (add-source proc file line db)
+  (let ((file-table (or (hash-ref db file)
+                        (let ((table (make-hash-table)))
+                          (hash-set! db file table)
+                          table))))
+    (hashv-set! file-table
+                line
+                (cons proc (hashv-ref file-table line '())))))
+
+(define (forget-source proc file line db)
+  (let ((file-table (hash-ref db file)))
+    (if file-table
+        (let ((procs (delq proc (hashv-ref file-table line '()))))
+          (if (pair? procs)
+              (hashv-set! file-table line procs)
+              (hashv-remove! file-table line))))))
+
+(define (add-sources proc mod-name db)
+  (let ((sources (procedure-sources proc)))
+    (if (pair? sources)
+        (begin
+          ;; Add proc to *module-sources-db*, for book-keeping.
+          (hashq-set! (or (hash-ref *module-sources-db* mod-name)
+                          (let ((table (make-hash-table)))
+                            (hash-set! *module-sources-db* mod-name table)
+                            table))
+                      proc
+                      sources)
+          ;; Actually add the source entries.
+          (for-each (lambda (source)
+                      (match source
+                        ((ip file line . col)
+                         (add-source proc file line db))
+                        (_ (error "unexpected source format" source))))
+                    sources)))
+    ;; Add source entries for nested procedures.
+    (for-each (lambda (obj)
+                (add-sources obj mod-name *closure-sources-db*))
+              (cdr (nested-procedures proc)))))
+
+(define (forget-sources proc mod-name db)
+  (let ((mod-table (hash-ref *module-sources-db* mod-name)))
+    (when mod-table
+      ;; Forget source entries.
+      (for-each (lambda (source)
+                  (match source
+                    ((ip file line . col)
+                     (forget-source proc file line db))
+                    (_ (error "unexpected source format" source))))
+                (hashq-ref mod-table proc '()))
+      ;; Forget the proc.
+      (hashq-remove! mod-table proc)
+      ;; Forget source entries for nested procedures.
+      (for-each (lambda (obj)
+                  (forget-sources obj mod-name *closure-sources-db*))
+                (cdr (nested-procedures proc))))))
+
+(define (untaint-sources)
+  (define (untaint m)
+    (for-each (lambda (proc) (forget-sources proc m *sources-db*))
+              (cond
+               ((hash-ref *module-sources-db* m)
+                => (lambda (table)
+                     (hash-for-each (lambda (proc sources) proc) table)))
+               (else '())))
+    (ensure-sources-db m))
+  (ensure-sources-db #f)
+  (for-each untaint *tainted-sources*)
+  (set! *tainted-sources* '()))
+
+(define (ensure-sources-db mod-name)
+  (define (visit-module mod)
+    (if (not (memq on-source-modified (module-observers mod)))
+        (module-observe mod on-source-modified))
+    (let ((name (module-name mod)))
+      (module-for-each
+       (lambda (sym var)
+         (if (variable-bound? var)
+             (let ((x (variable-ref var)))
+               (if (procedure? x)
+                   (add-sources x name *sources-db*)))))
+       mod)))
+
+  (define visit-submodules
+    (let ((visited #f))
+      (lambda (mod)
+        (if (not visited)
+            (set! visited (make-hash-table)))
+        (hash-for-each
+         (lambda (name sub)
+           (if (not (hashq-ref visited sub))
+               (begin
+                 (hashq-set! visited sub #t)
+                 (visit-module sub)
+                 (visit-submodules sub))))
+         (module-submodules mod)))))
+
+  (cond ((and (not mod-name) (not *sources-db*) (not *closure-sources-db*))
+         (set! *closure-sources-db* (make-hash-table 1000))
+         (set! *sources-db* (make-hash-table 1000))
+         (visit-submodules (resolve-module '() #f)))
+        (mod-name (visit-module (resolve-module mod-name)))))
+
+(define (lines->ranges file-table)
+  (let ((ranges (make-hash-table)))
+    (hash-for-each
+     (lambda (line procs)
+       (for-each
+        (lambda (proc)
+          (cond
+           ((hashq-ref ranges proc)
+            => (lambda (pair)
+                 (if (< line (car pair))
+                     (set-car! pair line))
+                 (if (> line (cdr pair))
+                     (set-cdr! pair line))))
+           (else
+            (hashq-set! ranges proc (cons line line)))))
+        procs))
+     file-table)
+    (sort! (hash-map->list cons ranges)
+           (lambda (x y) (< (cadr x) (cadr y))))))
+
+(define* (lookup-source-procedures canon-file line db)
+  (let ((file-table (hash-ref db canon-file)))
+    (let lp ((ranges (if file-table (lines->ranges file-table) '()))
+             (procs '()))
+      (cond
+       ((null? ranges) (reverse procs))
+       ((<= (cadar ranges) line (cddar ranges))
+        (lp (cdr ranges) (cons (caar ranges) procs)))
+       (else
+        (lp (cdr ranges) procs))))))
+
+(define* (source-closures file line #:key (canonicalization 'relative))
+  (ensure-sources-db #f)
+  (let* ((port (with-fluids ((%file-port-name-canonicalization canonicalization))
+                 (false-if-exception (open-input-file file))))
+         (file (if port (port-filename port) file)))
+    (lookup-source-procedures file line *closure-sources-db*)))
+
+(define* (source-procedures file line #:key (canonicalization 'relative))
+  (ensure-sources-db #f)
+  (let* ((port (with-fluids ((%file-port-name-canonicalization canonicalization))
+                 (false-if-exception (open-input-file file))))
+         (file (if port (port-filename port) file)))
+    (lookup-source-procedures file line *sources-db*)))