xref.scm 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370
  1. ;;;; Copyright (C) 2009, 2010, 2013 Free Software Foundation, Inc.
  2. ;;;;
  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.
  7. ;;;;
  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.
  12. ;;;;
  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
  16. ;;;;
  17. (define-module (system xref)
  18. #:use-module (system base compile)
  19. #:use-module (system vm program)
  20. #:use-module (system vm disassembler)
  21. #:use-module (ice-9 match)
  22. #:use-module (srfi srfi-1)
  23. #:export (*xref-ignored-modules*
  24. procedure-callees
  25. procedure-callers
  26. source-closures
  27. source-procedures))
  28. ;;;
  29. ;;; The cross-reference database: who calls whom.
  30. ;;;
  31. (define (nested-procedures prog)
  32. (define (cons-uniq x y)
  33. (if (memq x y) y (cons x y)))
  34. (if (program? prog)
  35. (reverse
  36. (fold-program-code (lambda (elt out)
  37. (match elt
  38. (('static-ref dst proc)
  39. (if (program? proc)
  40. (fold cons-uniq
  41. (cons proc out)
  42. (nested-procedures prog))
  43. out))
  44. (_ out)))
  45. (list prog)
  46. prog))
  47. (list prog)))
  48. (define (program-callee-rev-vars prog)
  49. (define (cons-uniq x y)
  50. (if (memq x y) y (cons x y)))
  51. (fold (lambda (prog out)
  52. (fold-program-code
  53. (lambda (elt out)
  54. (match elt
  55. (('toplevel-box dst var mod sym bound?)
  56. (let ((var (or var (and mod (module-variable mod sym)))))
  57. (if var
  58. (cons-uniq var out)
  59. out)))
  60. (('module-box dst var public? mod-name sym bound?)
  61. (let ((var (or var
  62. (module-variable (if public?
  63. (resolve-interface mod-name)
  64. (resolve-module mod-name))
  65. sym))))
  66. (if var
  67. (cons-uniq var out)
  68. out)))
  69. (_ out)))
  70. out
  71. prog))
  72. '()
  73. (nested-procedures prog)))
  74. (define (procedure-callee-rev-vars proc)
  75. (cond
  76. ((program? proc) (program-callee-rev-vars proc))
  77. (else '())))
  78. (define (procedure-callees prog)
  79. "Evaluates to a list of the given program callees."
  80. (let lp ((in (procedure-callee-rev-vars prog)) (out '()))
  81. (cond ((null? in) out)
  82. ((variable-bound? (car in))
  83. (lp (cdr in) (cons (variable-ref (car in)) out)))
  84. (else (lp (cdr in) out)))))
  85. ;; var -> ((module-name caller ...) ...)
  86. (define *callers-db* #f)
  87. ;; module-name -> (callee ...)
  88. (define *module-callees-db* (make-hash-table))
  89. ;; (module-name ...)
  90. (define *tainted-modules* '())
  91. (define *xref-ignored-modules* '((value-history)))
  92. (define (on-module-modified m)
  93. (let ((name (module-name m)))
  94. (if (and (not (member name *xref-ignored-modules*))
  95. (not (member name *tainted-modules*))
  96. (pair? name))
  97. (set! *tainted-modules* (cons name *tainted-modules*)))))
  98. (define (add-caller callee caller mod-name)
  99. (let ((all-callers (hashq-ref *callers-db* callee)))
  100. (if (not all-callers)
  101. (hashq-set! *callers-db* callee `((,mod-name ,caller)))
  102. (let ((callers (assoc mod-name all-callers)))
  103. (if callers
  104. (if (not (member caller callers))
  105. (set-cdr! callers (cons caller (cdr callers))))
  106. (hashq-set! *callers-db* callee
  107. (cons `(,mod-name ,caller) all-callers)))))))
  108. (define (forget-callers callee mod-name)
  109. (hashq-set! *callers-db* callee
  110. (assoc-remove! (hashq-ref *callers-db* callee '()) mod-name)))
  111. (define (add-callees callees mod-name)
  112. (hash-set! *module-callees-db* mod-name
  113. (append callees (hash-ref *module-callees-db* mod-name '()))))
  114. (define (untaint-modules)
  115. (define (untaint m)
  116. (for-each (lambda (callee) (forget-callers callee m))
  117. (hash-ref *module-callees-db* m '()))
  118. (ensure-callers-db m))
  119. (ensure-callers-db #f)
  120. (for-each untaint *tainted-modules*)
  121. (set! *tainted-modules* '()))
  122. (define (ensure-callers-db mod-name)
  123. (let ((mod (and mod-name (resolve-module mod-name)))
  124. (visited #f))
  125. (define (visit-variable var mod-name)
  126. (if (variable-bound? var)
  127. (let ((x (variable-ref var)))
  128. (cond
  129. ((and visited (hashq-ref visited x)))
  130. ((procedure? x)
  131. (if visited (hashq-set! visited x #t))
  132. (let ((callees (filter variable-bound?
  133. (procedure-callee-rev-vars x))))
  134. (for-each (lambda (callee)
  135. (add-caller callee x mod-name))
  136. callees)
  137. (add-callees callees mod-name)))))))
  138. (define (visit-module mod)
  139. (if visited (hashq-set! visited mod #t))
  140. (if (not (memq on-module-modified (module-observers mod)))
  141. (module-observe mod on-module-modified))
  142. (let ((name (module-name mod)))
  143. (module-for-each (lambda (sym var)
  144. (visit-variable var name))
  145. mod)))
  146. (define (visit-submodules mod)
  147. (hash-for-each
  148. (lambda (name sub)
  149. (if (not (and visited (hashq-ref visited sub)))
  150. (begin
  151. (visit-module sub)
  152. (visit-submodules sub))))
  153. (module-submodules mod)))
  154. (cond ((and (not mod-name) (not *callers-db*))
  155. (set! *callers-db* (make-hash-table 1000))
  156. (set! visited (make-hash-table 1000))
  157. (visit-submodules (resolve-module '() #f)))
  158. (mod-name (visit-module mod)))))
  159. (define (procedure-callers var)
  160. "Returns an association list, keyed by module name, of known callers
  161. of the given procedure. The latter can specified directly as a
  162. variable, a symbol (which gets resolved in the current module) or a
  163. pair of the form (module-name . variable-name), "
  164. (let ((v (cond ((variable? var) var)
  165. ((symbol? var) (module-variable (current-module) var))
  166. (else
  167. (match var
  168. ((modname . sym)
  169. (module-variable (resolve-module modname) sym))
  170. (_
  171. (error "expected a variable, symbol, or (modname . sym)" var)))))))
  172. (untaint-modules)
  173. (hashq-ref *callers-db* v '())))
  174. ;;;
  175. ;;; The source database: procedures defined at a given source location.
  176. ;;;
  177. ;; FIXME: refactor to share code with the xref database.
  178. ;; ((ip file line . col) ...)
  179. (define (procedure-sources proc)
  180. (cond
  181. ((program? proc) (program-sources proc))
  182. (else '())))
  183. ;; file -> line -> (proc ...)
  184. (define *closure-sources-db* #f)
  185. ;; file -> line -> (proc ...)
  186. (define *sources-db* #f)
  187. ;; module-name -> proc -> sources
  188. (define *module-sources-db* (make-hash-table))
  189. ;; (module-name ...)
  190. (define *tainted-sources* '())
  191. (define (on-source-modified m)
  192. (let ((name (module-name m)))
  193. (if (and (not (member name *xref-ignored-modules*))
  194. (not (member name *tainted-sources*))
  195. (pair? name))
  196. (set! *tainted-sources* (cons name *tainted-sources*)))))
  197. (define (add-source proc file line db)
  198. (let ((file-table (or (hash-ref db file)
  199. (let ((table (make-hash-table)))
  200. (hash-set! db file table)
  201. table))))
  202. (hashv-set! file-table
  203. line
  204. (cons proc (hashv-ref file-table line '())))))
  205. (define (forget-source proc file line db)
  206. (let ((file-table (hash-ref db file)))
  207. (if file-table
  208. (let ((procs (delq proc (hashv-ref file-table line '()))))
  209. (if (pair? procs)
  210. (hashv-set! file-table line procs)
  211. (hashv-remove! file-table line))))))
  212. (define (add-sources proc mod-name db)
  213. (let ((sources (procedure-sources proc)))
  214. (if (pair? sources)
  215. (begin
  216. ;; Add proc to *module-sources-db*, for book-keeping.
  217. (hashq-set! (or (hash-ref *module-sources-db* mod-name)
  218. (let ((table (make-hash-table)))
  219. (hash-set! *module-sources-db* mod-name table)
  220. table))
  221. proc
  222. sources)
  223. ;; Actually add the source entries.
  224. (for-each (lambda (source)
  225. (match source
  226. ((ip file line . col)
  227. (add-source proc file line db))
  228. (_ (error "unexpected source format" source))))
  229. sources)))
  230. ;; Add source entries for nested procedures.
  231. (for-each (lambda (obj)
  232. (add-sources obj mod-name *closure-sources-db*))
  233. (cdr (nested-procedures proc)))))
  234. (define (forget-sources proc mod-name db)
  235. (let ((mod-table (hash-ref *module-sources-db* mod-name)))
  236. (when mod-table
  237. ;; Forget source entries.
  238. (for-each (lambda (source)
  239. (match source
  240. ((ip file line . col)
  241. (forget-source proc file line db))
  242. (_ (error "unexpected source format" source))))
  243. (hashq-ref mod-table proc '()))
  244. ;; Forget the proc.
  245. (hashq-remove! mod-table proc)
  246. ;; Forget source entries for nested procedures.
  247. (for-each (lambda (obj)
  248. (forget-sources obj mod-name *closure-sources-db*))
  249. (cdr (nested-procedures proc))))))
  250. (define (untaint-sources)
  251. (define (untaint m)
  252. (for-each (lambda (proc) (forget-sources proc m *sources-db*))
  253. (cond
  254. ((hash-ref *module-sources-db* m)
  255. => (lambda (table)
  256. (hash-for-each (lambda (proc sources) proc) table)))
  257. (else '())))
  258. (ensure-sources-db m))
  259. (ensure-sources-db #f)
  260. (for-each untaint *tainted-sources*)
  261. (set! *tainted-sources* '()))
  262. (define (ensure-sources-db mod-name)
  263. (define (visit-module mod)
  264. (if (not (memq on-source-modified (module-observers mod)))
  265. (module-observe mod on-source-modified))
  266. (let ((name (module-name mod)))
  267. (module-for-each
  268. (lambda (sym var)
  269. (if (variable-bound? var)
  270. (let ((x (variable-ref var)))
  271. (if (procedure? x)
  272. (add-sources x name *sources-db*)))))
  273. mod)))
  274. (define visit-submodules
  275. (let ((visited #f))
  276. (lambda (mod)
  277. (if (not visited)
  278. (set! visited (make-hash-table)))
  279. (hash-for-each
  280. (lambda (name sub)
  281. (if (not (hashq-ref visited sub))
  282. (begin
  283. (hashq-set! visited sub #t)
  284. (visit-module sub)
  285. (visit-submodules sub))))
  286. (module-submodules mod)))))
  287. (cond ((and (not mod-name) (not *sources-db*) (not *closure-sources-db*))
  288. (set! *closure-sources-db* (make-hash-table 1000))
  289. (set! *sources-db* (make-hash-table 1000))
  290. (visit-submodules (resolve-module '() #f)))
  291. (mod-name (visit-module (resolve-module mod-name)))))
  292. (define (lines->ranges file-table)
  293. (let ((ranges (make-hash-table)))
  294. (hash-for-each
  295. (lambda (line procs)
  296. (for-each
  297. (lambda (proc)
  298. (cond
  299. ((hashq-ref ranges proc)
  300. => (lambda (pair)
  301. (if (< line (car pair))
  302. (set-car! pair line))
  303. (if (> line (cdr pair))
  304. (set-cdr! pair line))))
  305. (else
  306. (hashq-set! ranges proc (cons line line)))))
  307. procs))
  308. file-table)
  309. (sort! (hash-map->list cons ranges)
  310. (lambda (x y) (< (cadr x) (cadr y))))))
  311. (define* (lookup-source-procedures canon-file line db)
  312. (let ((file-table (hash-ref db canon-file)))
  313. (let lp ((ranges (if file-table (lines->ranges file-table) '()))
  314. (procs '()))
  315. (cond
  316. ((null? ranges) (reverse procs))
  317. ((<= (cadar ranges) line (cddar ranges))
  318. (lp (cdr ranges) (cons (caar ranges) procs)))
  319. (else
  320. (lp (cdr ranges) procs))))))
  321. (define* (source-closures file line #:key (canonicalization 'relative))
  322. (ensure-sources-db #f)
  323. (let* ((port (with-fluids ((%file-port-name-canonicalization canonicalization))
  324. (false-if-exception (open-input-file file))))
  325. (file (if port (port-filename port) file)))
  326. (lookup-source-procedures file line *closure-sources-db*)))
  327. (define* (source-procedures file line #:key (canonicalization 'relative))
  328. (ensure-sources-db #f)
  329. (let* ((port (with-fluids ((%file-port-name-canonicalization canonicalization))
  330. (false-if-exception (open-input-file file))))
  331. (file (if port (port-filename port) file)))
  332. (lookup-source-procedures file line *sources-db*)))