1 ;;;; Copyright (C) 2009, 2010, 2013 Free Software Foundation, Inc.
3 ;;;; This library is free software; you can redistribute it and/or
4 ;;;; modify it under the terms of the GNU Lesser General Public
5 ;;;; License as published by the Free Software Foundation; either
6 ;;;; version 2.1 of the License, or (at your option) any later version.
8 ;;;; This library is distributed in the hope that it will be useful,
9 ;;;; but WITHOUT ANY WARRANTY; without even the implied warranty of
10 ;;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
11 ;;;; Lesser General Public License for more details.
13 ;;;; You should have received a copy of the GNU Lesser General Public
14 ;;;; License along with this library; if not, write to the Free Software
15 ;;;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
19 (define-module (system xref)
20 #:use-module (system base compile)
21 #:use-module (system vm program)
22 #:use-module (system vm disassembler)
23 #:use-module (ice-9 match)
24 #:use-module (srfi srfi-1)
25 #:export (*xref-ignored-modules*
32 ;;; The cross-reference database: who calls whom.
35 (define (nested-procedures prog)
36 (define (cons-uniq x y)
37 (if (memq x y) y (cons x y)))
40 (fold-program-code (lambda (elt out)
42 (('static-ref dst proc)
46 (nested-procedures prog))
53 (define (program-callee-rev-vars prog)
54 (define (cons-uniq x y)
55 (if (memq x y) y (cons x y)))
56 (fold (lambda (prog out)
60 (('toplevel-box dst var mod sym bound?)
61 (let ((var (or var (and mod (module-variable mod sym)))))
65 (('module-box dst var public? mod-name sym bound?)
67 (module-variable (if public?
68 (resolve-interface mod-name)
69 (resolve-module mod-name))
78 (nested-procedures prog)))
80 (define (procedure-callee-rev-vars proc)
82 ((program? proc) (program-callee-rev-vars proc))
85 (define (procedure-callees prog)
86 "Evaluates to a list of the given program callees."
87 (let lp ((in (procedure-callee-rev-vars prog)) (out '()))
88 (cond ((null? in) out)
89 ((variable-bound? (car in))
90 (lp (cdr in) (cons (variable-ref (car in)) out)))
91 (else (lp (cdr in) out)))))
93 ;; var -> ((module-name caller ...) ...)
94 (define *callers-db* #f)
95 ;; module-name -> (callee ...)
96 (define *module-callees-db* (make-hash-table))
98 (define *tainted-modules* '())
100 (define *xref-ignored-modules* '((value-history)))
101 (define (on-module-modified m)
102 (let ((name (module-name m)))
103 (if (and (not (member name *xref-ignored-modules*))
104 (not (member name *tainted-modules*))
106 (set! *tainted-modules* (cons name *tainted-modules*)))))
108 (define (add-caller callee caller mod-name)
109 (let ((all-callers (hashq-ref *callers-db* callee)))
110 (if (not all-callers)
111 (hashq-set! *callers-db* callee `((,mod-name ,caller)))
112 (let ((callers (assoc mod-name all-callers)))
114 (if (not (member caller callers))
115 (set-cdr! callers (cons caller (cdr callers))))
116 (hashq-set! *callers-db* callee
117 (cons `(,mod-name ,caller) all-callers)))))))
119 (define (forget-callers callee mod-name)
120 (hashq-set! *callers-db* callee
121 (assoc-remove! (hashq-ref *callers-db* callee '()) mod-name)))
123 (define (add-callees callees mod-name)
124 (hash-set! *module-callees-db* mod-name
125 (append callees (hash-ref *module-callees-db* mod-name '()))))
127 (define (untaint-modules)
129 (for-each (lambda (callee) (forget-callers callee m))
130 (hash-ref *module-callees-db* m '()))
131 (ensure-callers-db m))
132 (ensure-callers-db #f)
133 (for-each untaint *tainted-modules*)
134 (set! *tainted-modules* '()))
136 (define (ensure-callers-db mod-name)
137 (let ((mod (and mod-name (resolve-module mod-name)))
139 (define (visit-variable var mod-name)
140 (if (variable-bound? var)
141 (let ((x (variable-ref var)))
143 ((and visited (hashq-ref visited x)))
145 (if visited (hashq-set! visited x #t))
146 (let ((callees (filter variable-bound?
147 (procedure-callee-rev-vars x))))
148 (for-each (lambda (callee)
149 (add-caller callee x mod-name))
151 (add-callees callees mod-name)))))))
153 (define (visit-module mod)
154 (if visited (hashq-set! visited mod #t))
155 (if (not (memq on-module-modified (module-observers mod)))
156 (module-observe mod on-module-modified))
157 (let ((name (module-name mod)))
158 (module-for-each (lambda (sym var)
159 (visit-variable var name))
162 (define (visit-submodules mod)
165 (if (not (and visited (hashq-ref visited sub)))
168 (visit-submodules sub))))
169 (module-submodules mod)))
171 (cond ((and (not mod-name) (not *callers-db*))
172 (set! *callers-db* (make-hash-table 1000))
173 (set! visited (make-hash-table 1000))
174 (visit-submodules (resolve-module '() #f)))
175 (mod-name (visit-module mod)))))
177 (define (procedure-callers var)
178 "Returns an association list, keyed by module name, of known callers
179 of the given procedure. The latter can specified directly as a
180 variable, a symbol (which gets resolved in the current module) or a
181 pair of the form (module-name . variable-name), "
182 (let ((v (cond ((variable? var) var)
183 ((symbol? var) (module-variable (current-module) var))
187 (module-variable (resolve-module modname) sym))
189 (error "expected a variable, symbol, or (modname . sym)" var)))))))
191 (hashq-ref *callers-db* v '())))
196 ;;; The source database: procedures defined at a given source location.
199 ;; FIXME: refactor to share code with the xref database.
201 ;; ((ip file line . col) ...)
202 (define (procedure-sources proc)
204 ((program? proc) (program-sources proc))
207 ;; file -> line -> (proc ...)
208 (define *closure-sources-db* #f)
209 ;; file -> line -> (proc ...)
210 (define *sources-db* #f)
211 ;; module-name -> proc -> sources
212 (define *module-sources-db* (make-hash-table))
214 (define *tainted-sources* '())
216 (define (on-source-modified m)
217 (let ((name (module-name m)))
218 (if (and (not (member name *xref-ignored-modules*))
219 (not (member name *tainted-sources*))
221 (set! *tainted-sources* (cons name *tainted-sources*)))))
223 (define (add-source proc file line db)
224 (let ((file-table (or (hash-ref db file)
225 (let ((table (make-hash-table)))
226 (hash-set! db file table)
228 (hashv-set! file-table
230 (cons proc (hashv-ref file-table line '())))))
232 (define (forget-source proc file line db)
233 (let ((file-table (hash-ref db file)))
235 (let ((procs (delq proc (hashv-ref file-table line '()))))
237 (hashv-set! file-table line procs)
238 (hashv-remove! file-table line))))))
240 (define (add-sources proc mod-name db)
241 (let ((sources (procedure-sources proc)))
244 ;; Add proc to *module-sources-db*, for book-keeping.
245 (hashq-set! (or (hash-ref *module-sources-db* mod-name)
246 (let ((table (make-hash-table)))
247 (hash-set! *module-sources-db* mod-name table)
251 ;; Actually add the source entries.
252 (for-each (lambda (source)
254 ((ip file line . col)
255 (add-source proc file line db))
256 (_ (error "unexpected source format" source))))
258 ;; Add source entries for nested procedures.
259 (for-each (lambda (obj)
260 (add-sources obj mod-name *closure-sources-db*))
261 (cdr (nested-procedures proc)))))
263 (define (forget-sources proc mod-name db)
264 (let ((mod-table (hash-ref *module-sources-db* mod-name)))
266 ;; Forget source entries.
267 (for-each (lambda (source)
269 ((ip file line . col)
270 (forget-source proc file line db))
271 (_ (error "unexpected source format" source))))
272 (hashq-ref mod-table proc '()))
274 (hashq-remove! mod-table proc)
275 ;; Forget source entries for nested procedures.
276 (for-each (lambda (obj)
277 (forget-sources obj mod-name *closure-sources-db*))
278 (cdr (nested-procedures proc))))))
280 (define (untaint-sources)
282 (for-each (lambda (proc) (forget-sources proc m *sources-db*))
284 ((hash-ref *module-sources-db* m)
286 (hash-for-each (lambda (proc sources) proc) table)))
288 (ensure-sources-db m))
289 (ensure-sources-db #f)
290 (for-each untaint *tainted-sources*)
291 (set! *tainted-sources* '()))
293 (define (ensure-sources-db mod-name)
294 (define (visit-module mod)
295 (if (not (memq on-source-modified (module-observers mod)))
296 (module-observe mod on-source-modified))
297 (let ((name (module-name mod)))
300 (if (variable-bound? var)
301 (let ((x (variable-ref var)))
303 (add-sources x name *sources-db*)))))
306 (define visit-submodules
310 (set! visited (make-hash-table)))
313 (if (not (hashq-ref visited sub))
315 (hashq-set! visited sub #t)
317 (visit-submodules sub))))
318 (module-submodules mod)))))
320 (cond ((and (not mod-name) (not *sources-db*) (not *closure-sources-db*))
321 (set! *closure-sources-db* (make-hash-table 1000))
322 (set! *sources-db* (make-hash-table 1000))
323 (visit-submodules (resolve-module '() #f)))
324 (mod-name (visit-module (resolve-module mod-name)))))
326 (define (lines->ranges file-table)
327 (let ((ranges (make-hash-table)))
333 ((hashq-ref ranges proc)
335 (if (< line (car pair))
336 (set-car! pair line))
337 (if (> line (cdr pair))
338 (set-cdr! pair line))))
340 (hashq-set! ranges proc (cons line line)))))
343 (sort! (hash-map->list cons ranges)
344 (lambda (x y) (< (cadr x) (cadr y))))))
346 (define* (lookup-source-procedures canon-file line db)
347 (let ((file-table (hash-ref db canon-file)))
348 (let lp ((ranges (if file-table (lines->ranges file-table) '()))
351 ((null? ranges) (reverse procs))
352 ((<= (cadar ranges) line (cddar ranges))
353 (lp (cdr ranges) (cons (caar ranges) procs)))
355 (lp (cdr ranges) procs))))))
357 (define* (source-closures file line #:key (canonicalization 'relative))
358 (ensure-sources-db #f)
359 (let* ((port (with-fluids ((%file-port-name-canonicalization canonicalization))
360 (false-if-exception (open-input-file file))))
361 (file (if port (port-filename port) file)))
362 (lookup-source-procedures file line *closure-sources-db*)))
364 (define* (source-procedures file line #:key (canonicalization 'relative))
365 (ensure-sources-db #f)
366 (let* ((port (with-fluids ((%file-port-name-canonicalization canonicalization))
367 (false-if-exception (open-input-file file))))
368 (file (if port (port-filename port) file)))
369 (lookup-source-procedures file line *sources-db*)))