xref.scm 14 KB

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