lint 9.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321
  1. #!/bin/sh
  2. # aside from this initial boilerplate, this is actually -*- scheme -*- code
  3. main='(module-ref (resolve-module '\''(scripts lint)) '\'main')'
  4. exec ${GUILE-guile} -l $0 -c "(apply $main (cdr (command-line)))" "$@"
  5. !#
  6. ;;; lint --- Preemptive checks for coding errors in Guile Scheme code
  7. ;; Copyright (C) 2002, 2006 Free Software Foundation, Inc.
  8. ;;
  9. ;; This program is free software; you can redistribute it and/or
  10. ;; modify it under the terms of the GNU General Public License as
  11. ;; published by the Free Software Foundation; either version 2, or
  12. ;; (at your option) any later version.
  13. ;;
  14. ;; This program is distributed in the hope that it will be useful,
  15. ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
  16. ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  17. ;; General Public License for more details.
  18. ;;
  19. ;; You should have received a copy of the GNU General Public License
  20. ;; along with this software; see the file COPYING. If not, write to
  21. ;; the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
  22. ;; Boston, MA 02110-1301 USA
  23. ;;; Author: Neil Jerram
  24. ;;; Commentary:
  25. ;; Usage: lint FILE1 FILE2 ...
  26. ;;
  27. ;; Perform various preemptive checks for coding errors in Guile Scheme
  28. ;; code.
  29. ;;
  30. ;; Right now, there is only one check available, for unresolved free
  31. ;; variables. The intention is that future lint-like checks will be
  32. ;; implemented by adding to this script file.
  33. ;;
  34. ;; Unresolved free variables
  35. ;; -------------------------
  36. ;;
  37. ;; Free variables are those whose definitions come from outside the
  38. ;; module under investigation. In Guile, these definitions are
  39. ;; imported from other modules using `#:use-module' forms.
  40. ;;
  41. ;; This tool scans the specified files for unresolved free variables -
  42. ;; i.e. variables for which you may have forgotten the appropriate
  43. ;; `#:use-module', or for which the module that is supposed to export
  44. ;; them forgot to.
  45. ;;
  46. ;; It isn't guaranteed that the scan will find absolutely all such
  47. ;; errors. Quoted (and quasiquoted) expressions are skipped, since
  48. ;; they are most commonly used to describe constant data, not code, so
  49. ;; code that is explicitly evaluated using `eval' will not be checked.
  50. ;; For example, the `unresolved-var' in `(eval 'unresolved-var
  51. ;; (current-module))' would be missed.
  52. ;;
  53. ;; False positives are also possible. Firstly, the tool doesn't
  54. ;; understand all possible forms of implicit quoting; in particular,
  55. ;; it doesn't detect and expand uses of macros. Secondly, it picks up
  56. ;; explicit compatibility code like `(if (defined? 'x) (define y x))'.
  57. ;; Thirdly, there are occasional oddities like `next-method'.
  58. ;; However, the number of false positives for realistic code is
  59. ;; hopefully small enough that they can be individually considered and
  60. ;; ignored.
  61. ;;
  62. ;; Example
  63. ;; -------
  64. ;;
  65. ;; Note: most of the unresolved variables found in this example are
  66. ;; false positives, as you would hope. => scope for improvement.
  67. ;;
  68. ;; $ guile-tools lint `guile-tools`
  69. ;; No unresolved free variables in PROGRAM
  70. ;; No unresolved free variables in autofrisk
  71. ;; No unresolved free variables in display-commentary
  72. ;; Unresolved free variables in doc-snarf:
  73. ;; doc-snarf-version
  74. ;; No unresolved free variables in frisk
  75. ;; No unresolved free variables in generate-autoload
  76. ;; No unresolved free variables in lint
  77. ;; No unresolved free variables in punify
  78. ;; No unresolved free variables in read-scheme-source
  79. ;; Unresolved free variables in snarf-check-and-output-texi:
  80. ;; name
  81. ;; pos
  82. ;; line
  83. ;; x
  84. ;; rest
  85. ;; ...
  86. ;; do-argpos
  87. ;; do-command
  88. ;; do-args
  89. ;; type
  90. ;; num
  91. ;; file
  92. ;; do-arglist
  93. ;; req
  94. ;; opt
  95. ;; var
  96. ;; command
  97. ;; do-directive
  98. ;; s
  99. ;; ?
  100. ;; No unresolved free variables in use2dot
  101. ;;; Code:
  102. (define-module (scripts lint)
  103. #:use-module (ice-9 common-list)
  104. #:use-module (ice-9 format)
  105. #:export (lint))
  106. (define (lint filename)
  107. (let ((module-name (scan-file-for-module-name filename))
  108. (free-vars (uniq (scan-file-for-free-variables filename))))
  109. (let ((module (resolve-module module-name))
  110. (all-resolved? #t))
  111. (format #t "Resolved module: ~S\n" module)
  112. (let loop ((free-vars free-vars))
  113. (or (null? free-vars)
  114. (begin
  115. (catch #t
  116. (lambda ()
  117. (eval (car free-vars) module))
  118. (lambda args
  119. (if all-resolved?
  120. (format #t
  121. "Unresolved free variables in ~A:\n"
  122. filename))
  123. (write-char #\tab)
  124. (write (car free-vars))
  125. (newline)
  126. (set! all-resolved? #f)))
  127. (loop (cdr free-vars)))))
  128. (if all-resolved?
  129. (format #t
  130. "No unresolved free variables in ~A\n"
  131. filename)))))
  132. (define (scan-file-for-module-name filename)
  133. (with-input-from-file filename
  134. (lambda ()
  135. (let loop ((x (read)))
  136. (cond ((eof-object? x) #f)
  137. ((and (pair? x)
  138. (eq? (car x) 'define-module))
  139. (cadr x))
  140. (else (loop (read))))))))
  141. (define (scan-file-for-free-variables filename)
  142. (with-input-from-file filename
  143. (lambda ()
  144. (let loop ((x (read)) (fvlists '()))
  145. (if (eof-object? x)
  146. (apply append fvlists)
  147. (loop (read) (cons (detect-free-variables x '()) fvlists)))))))
  148. ; guile> (detect-free-variables '(let ((a 1)) a) '())
  149. ; ()
  150. ; guile> (detect-free-variables '(let ((a 1)) b) '())
  151. ; (b)
  152. ; guile> (detect-free-variables '(let ((a 1) (b a)) b) '())
  153. ; (a)
  154. ; guile> (detect-free-variables '(let* ((a 1) (b a)) b) '())
  155. ; ()
  156. ; guile> (detect-free-variables '(define a 1) '())
  157. ; ()
  158. ; guile> (detect-free-variables '(define a b) '())
  159. ; (b)
  160. ; guile> (detect-free-variables '(define (a b c) b) '())
  161. ; ()
  162. ; guile> (detect-free-variables '(define (a b c) e) '())
  163. ; (e)
  164. (define (detect-free-variables x locals)
  165. ;; Given an expression @var{x} and a list @var{locals} of local
  166. ;; variables (symbols) that are in scope for @var{x}, return a list
  167. ;; of free variable symbols.
  168. (cond ((symbol? x)
  169. (if (memq x locals) '() (list x)))
  170. ((pair? x)
  171. (case (car x)
  172. ((define-module define-generic quote quasiquote)
  173. ;; No code of interest in these expressions.
  174. '())
  175. ((let letrec)
  176. ;; Check for named let. If there is a name, transform the
  177. ;; expression so that it looks like an unnamed let with
  178. ;; the name as one of the bindings.
  179. (if (symbol? (cadr x))
  180. (set-cdr! x (cons (cons (list (cadr x) #f) (caddr x))
  181. (cdddr x))))
  182. ;; Unnamed let processing.
  183. (let ((letrec? (eq? (car x) 'letrec))
  184. (locals-for-let-body (append locals (map car (cadr x)))))
  185. (append (apply append
  186. (map (lambda (binding)
  187. (detect-free-variables (cadr binding)
  188. (if letrec?
  189. locals-for-let-body
  190. locals)))
  191. (cadr x)))
  192. (apply append
  193. (map (lambda (bodyform)
  194. (detect-free-variables bodyform
  195. locals-for-let-body))
  196. (cddr x))))))
  197. ((let* and-let*)
  198. ;; Handle bindings recursively.
  199. (if (null? (cadr x))
  200. (apply append
  201. (map (lambda (bodyform)
  202. (detect-free-variables bodyform locals))
  203. (cddr x)))
  204. (append (detect-free-variables (cadr (caadr x)) locals)
  205. (detect-free-variables `(let* ,(cdadr x) ,@(cddr x))
  206. (cons (caaadr x) locals)))))
  207. ((define define-public define-macro)
  208. (if (pair? (cadr x))
  209. (begin
  210. (set! locals (cons (caadr x) locals))
  211. (detect-free-variables `(lambda ,(cdadr x) ,@(cddr x))
  212. locals))
  213. (begin
  214. (set! locals (cons (cadr x) locals))
  215. (detect-free-variables (caddr x) locals))))
  216. ((lambda lambda*)
  217. (let ((locals-for-lambda-body (let loop ((locals locals)
  218. (args (cadr x)))
  219. (cond ((null? args) locals)
  220. ((pair? args)
  221. (loop (cons (car args) locals)
  222. (cdr args)))
  223. (else
  224. (cons args locals))))))
  225. (apply append
  226. (map (lambda (bodyform)
  227. (detect-free-variables bodyform
  228. locals-for-lambda-body))
  229. (cddr x)))))
  230. ((receive)
  231. (let ((locals-for-receive-body (append locals (cadr x))))
  232. (apply append
  233. (detect-free-variables (caddr x) locals)
  234. (map (lambda (bodyform)
  235. (detect-free-variables bodyform
  236. locals-for-receive-body))
  237. (cdddr x)))))
  238. ((define-method define*)
  239. (let ((locals-for-method-body (let loop ((locals locals)
  240. (args (cdadr x)))
  241. (cond ((null? args) locals)
  242. ((pair? args)
  243. (loop (cons (if (pair? (car args))
  244. (caar args)
  245. (car args))
  246. locals)
  247. (cdr args)))
  248. (else
  249. (cons args locals))))))
  250. (apply append
  251. (map (lambda (bodyform)
  252. (detect-free-variables bodyform
  253. locals-for-method-body))
  254. (cddr x)))))
  255. ((define-class)
  256. ;; Avoid picking up slot names at the start of slot
  257. ;; definitions.
  258. (apply append
  259. (map (lambda (slot/option)
  260. (detect-free-variables-noncar (if (pair? slot/option)
  261. (cdr slot/option)
  262. slot/option)
  263. locals))
  264. (cdddr x))))
  265. ((case)
  266. (apply append
  267. (detect-free-variables (cadr x) locals)
  268. (map (lambda (case)
  269. (detect-free-variables (cdr case) locals))
  270. (cddr x))))
  271. ((unquote unquote-splicing else =>)
  272. (detect-free-variables-noncar (cdr x) locals))
  273. (else (append (detect-free-variables (car x) locals)
  274. (detect-free-variables-noncar (cdr x) locals)))))
  275. (else '())))
  276. (define (detect-free-variables-noncar x locals)
  277. ;; Given an expression @var{x} and a list @var{locals} of local
  278. ;; variables (symbols) that are in scope for @var{x}, return a list
  279. ;; of free variable symbols.
  280. (cond ((symbol? x)
  281. (if (memq x locals) '() (list x)))
  282. ((pair? x)
  283. (case (car x)
  284. ((=>)
  285. (detect-free-variables-noncar (cdr x) locals))
  286. (else (append (detect-free-variables (car x) locals)
  287. (detect-free-variables-noncar (cdr x) locals)))))
  288. (else '())))
  289. (define (main . files)
  290. (for-each lint files))
  291. ;;; lint ends here