PageRenderTime 44ms CodeModel.GetById 20ms RepoModel.GetById 0ms app.codeStats 0ms

/lisp/emacs-lisp/unsafep.el

https://gitlab.com/RobertCochran/emacs
Emacs Lisp | 258 lines | 144 code | 22 blank | 92 comment | 8 complexity | ca02b01477ac98cb50628c034879bda0 MD5 | raw file
  1. ;;;; unsafep.el -- Determine whether a Lisp form is safe to evaluate
  2. ;; Copyright (C) 2002-2019 Free Software Foundation, Inc.
  3. ;; Author: Jonathan Yavner <jyavner@member.fsf.org>
  4. ;; Keywords: safety lisp utility
  5. ;; This file is part of GNU Emacs.
  6. ;; GNU Emacs is free software: you can redistribute it and/or modify
  7. ;; it under the terms of the GNU General Public License as published by
  8. ;; the Free Software Foundation, either version 3 of the License, or
  9. ;; (at your option) any later version.
  10. ;; GNU Emacs is distributed in the hope that it will be useful,
  11. ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. ;; GNU General Public License for more details.
  14. ;; You should have received a copy of the GNU General Public License
  15. ;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>.
  16. ;;; Commentary:
  17. ;; This is a simplistic implementation that does not allow any modification of
  18. ;; buffers or global variables. It does no dataflow analysis, so functions
  19. ;; like `funcall' and `setcar' are completely disallowed. It is designed
  20. ;; for "pure Lisp" formulas, like those in spreadsheets, that don't make any
  21. ;; use of the text editing capabilities of Emacs.
  22. ;; A formula is safe if:
  23. ;; 1. It's an atom.
  24. ;; 2. It's a function call to a safe function and all arguments are safe
  25. ;; formulas.
  26. ;; 3. It's a special form whose arguments are like a function's (and,
  27. ;; catch, if, or, prog1, prog2, progn, while, unwind-protect).
  28. ;; 4. It's a special form or macro that creates safe temporary bindings
  29. ;; (condition-case, dolist, dotimes, lambda, let, let*).
  30. ;; 4. It's one of (cond, quote) that have special parsing.
  31. ;; 5. It's one of (add-to-list, setq, push, pop) and the assignment variable
  32. ;; is safe.
  33. ;; 6. It's one of (apply, mapc, mapcar, mapconcat) and its first arg is a
  34. ;; quoted safe function.
  35. ;;
  36. ;; A function is safe if:
  37. ;; 1. It's a lambda containing safe formulas.
  38. ;; 2. It's a member of list `safe-functions', so the user says it's safe.
  39. ;; 3. It's a symbol with the `side-effect-free' property, defined by the
  40. ;; byte compiler or function author.
  41. ;; 4. It's a symbol with the `safe-function' property, defined here or by
  42. ;; the function author. Value t indicates a function that is safe but
  43. ;; has innocuous side effects. Other values will someday indicate
  44. ;; functions with side effects that are not always safe.
  45. ;; The `side-effect-free' and `safe-function' properties are provided for
  46. ;; built-in functions and for functions and macros defined in subr.el.
  47. ;;
  48. ;; A temporary binding is unsafe if its symbol:
  49. ;; 1. Has the `risky-local-variable' property.
  50. ;; 2. Has a name that ends with -command, font-lock-keywords(-[0-9]+)?,
  51. ;; font-lock-syntactic-keywords, -form, -forms, -frame-alist, -function,
  52. ;; -functions, -history, -hook, -hooks, -map, -map-alist, -mode-alist,
  53. ;; -predicate, or -program.
  54. ;;
  55. ;; An assignment variable is unsafe if:
  56. ;; 1. It would be unsafe as a temporary binding.
  57. ;; 2. It doesn't already have a temporary or buffer-local binding.
  58. ;; There are unsafe forms that `unsafep' cannot detect. Beware of these:
  59. ;; 1. The form's result is a string with a display property containing a
  60. ;; form to be evaluated later, and you insert this result into a
  61. ;; buffer. Always remove display properties before inserting!
  62. ;; 2. The form alters a risky variable that was recently added to Emacs and
  63. ;; is not yet marked with the `risky-local-variable' property.
  64. ;; 3. The form uses undocumented features of built-in functions that have
  65. ;; the `side-effect-free' property. For example, in Emacs-20 if you
  66. ;; passed a circular list to `assoc', Emacs would crash. Historically,
  67. ;; problems of this kind have been few and short-lived.
  68. ;;; Code:
  69. (provide 'unsafep)
  70. (require 'byte-opt) ;Set up the `side-effect-free' properties
  71. (defcustom safe-functions nil
  72. "A list of assumed-safe functions, or t to disable `unsafep'."
  73. :group 'lisp
  74. :type '(choice (const :tag "No" nil) (const :tag "Yes" t) hook))
  75. (defvar unsafep-vars nil
  76. "Dynamically-bound list of variables with lexical bindings at this point
  77. in the parse.")
  78. (put 'unsafep-vars 'risky-local-variable t)
  79. ;;Other safe functions
  80. (dolist (x '(;;Special forms
  81. and catch if or prog1 prog2 progn while unwind-protect
  82. ;;Safe subrs that have some side-effects
  83. ding error random signal sleep-for string-match throw
  84. ;;Defsubst functions from subr.el
  85. caar cadr cdar cddr
  86. ;;Macros from subr.el
  87. save-match-data unless when
  88. ;;Functions from subr.el that have side effects
  89. split-string replace-regexp-in-string play-sound-file))
  90. (put x 'safe-function t))
  91. ;;;###autoload
  92. (defun unsafep (form &optional unsafep-vars)
  93. "Return nil if evaluating FORM couldn't possibly do any harm.
  94. Otherwise result is a reason why FORM is unsafe.
  95. UNSAFEP-VARS is a list of symbols with local bindings."
  96. (catch 'unsafep
  97. (if (or (eq safe-functions t) ;User turned off safety-checking
  98. (atom form)) ;Atoms are never unsafe
  99. (throw 'unsafep nil))
  100. (let* ((fun (car form))
  101. (reason (unsafep-function fun))
  102. arg)
  103. (cond
  104. ((not reason)
  105. ;;It's a normal function - unsafe if any arg is
  106. (unsafep-progn (cdr form)))
  107. ((eq fun 'quote)
  108. ;;Never unsafe
  109. nil)
  110. ((memq fun '(apply mapc mapcar mapconcat))
  111. ;;Unsafe if 1st arg isn't a quoted lambda
  112. (setq arg (cadr form))
  113. (cond
  114. ((memq (car-safe arg) '(quote function))
  115. (setq reason (unsafep-function (cadr arg))))
  116. ((eq (car-safe arg) 'lambda)
  117. ;;Self-quoting lambda
  118. (setq reason (unsafep arg unsafep-vars)))
  119. (t
  120. (setq reason `(unquoted ,arg))))
  121. (or reason (unsafep-progn (cddr form))))
  122. ((eq fun 'lambda)
  123. ;;First arg is temporary bindings
  124. (mapc #'(lambda (x)
  125. (or (memq x '(&optional &rest))
  126. (let ((y (unsafep-variable x t)))
  127. (if y (throw 'unsafep y))
  128. (push x unsafep-vars))))
  129. (cadr form))
  130. (unsafep-progn (cddr form)))
  131. ((eq fun 'let)
  132. ;;Creates temporary bindings in one step
  133. (setq unsafep-vars (nconc (mapcar #'unsafep-let (cadr form))
  134. unsafep-vars))
  135. (unsafep-progn (cddr form)))
  136. ((eq fun 'let*)
  137. ;;Creates temporary bindings iteratively
  138. (dolist (x (cadr form))
  139. (push (unsafep-let x) unsafep-vars))
  140. (unsafep-progn (cddr form)))
  141. ((eq fun 'setq)
  142. ;;Safe if odd arguments are local-var syms, evens are safe exprs
  143. (setq arg (cdr form))
  144. (while arg
  145. (setq reason (or (unsafep-variable (car arg) nil)
  146. (unsafep (cadr arg) unsafep-vars)))
  147. (if reason (throw 'unsafep reason))
  148. (setq arg (cddr arg))))
  149. ((eq fun 'pop)
  150. ;;safe if arg is local-var sym
  151. (unsafep-variable (cadr form) nil))
  152. ((eq fun 'push)
  153. ;;Safe if 2nd arg is a local-var sym
  154. (or (unsafep (cadr form) unsafep-vars)
  155. (unsafep-variable (nth 2 form) nil)))
  156. ((eq fun 'add-to-list)
  157. ;;Safe if first arg is a quoted local-var sym
  158. (setq arg (cadr form))
  159. (if (not (eq (car-safe arg) 'quote))
  160. `(unquoted ,arg)
  161. (or (unsafep-variable (cadr arg) nil)
  162. (unsafep-progn (cddr form)))))
  163. ((eq fun 'cond)
  164. ;;Special form with unusual syntax - safe if all args are
  165. (dolist (x (cdr form))
  166. (setq reason (unsafep-progn x))
  167. (if reason (throw 'unsafep reason))))
  168. ((memq fun '(dolist dotimes))
  169. ;;Safe if COUNT and RESULT are safe. VAR is bound while checking BODY.
  170. (setq arg (cadr form))
  171. (or (unsafep-progn (cdr arg))
  172. (let ((unsafep-vars (cons (car arg) unsafep-vars)))
  173. (unsafep-progn (cddr form)))))
  174. ((eq fun 'condition-case)
  175. ;;Special form with unusual syntax - safe if all args are
  176. (or (unsafep-variable (cadr form) t)
  177. (unsafep (nth 2 form) unsafep-vars)
  178. (let ((unsafep-vars (cons (cadr form) unsafep-vars)))
  179. ;;var is bound only during handlers
  180. (dolist (x (nthcdr 3 form))
  181. (setq reason (unsafep-progn (cdr x)))
  182. (if reason (throw 'unsafep reason))))))
  183. ((eq fun '\`)
  184. ;; Backquoted form - safe if its expansion is.
  185. (unsafep (cdr (backquote-process (cadr form)))))
  186. (t
  187. ;;First unsafep-function call above wasn't nil, no special case applies
  188. reason)))))
  189. (defun unsafep-function (fun)
  190. "Return nil if FUN is a safe function.
  191. \(Either a safe lambda or a symbol that names a safe function).
  192. Otherwise result is a reason code."
  193. (cond
  194. ((eq (car-safe fun) 'lambda)
  195. (unsafep fun unsafep-vars))
  196. ((not (and (symbolp fun)
  197. (or (get fun 'side-effect-free)
  198. (eq (get fun 'safe-function) t)
  199. (eq safe-functions t)
  200. (memq fun safe-functions))))
  201. `(function ,fun))))
  202. (defun unsafep-progn (list)
  203. "Return nil if all forms in LIST are safe.
  204. Else, return the reason for the first unsafe form."
  205. (catch 'unsafep-progn
  206. (let (reason)
  207. (dolist (x list)
  208. (setq reason (unsafep x unsafep-vars))
  209. (if reason (throw 'unsafep-progn reason))))))
  210. (defun unsafep-let (clause)
  211. "Check the safety of a let binding.
  212. CLAUSE is a let-binding, either SYM or (SYM) or (SYM VAL).
  213. Check VAL and throw a reason to `unsafep' if unsafe.
  214. Return SYM."
  215. (let (reason sym)
  216. (if (atom clause)
  217. (setq sym clause)
  218. (setq sym (car clause)
  219. reason (unsafep (cadr clause) unsafep-vars)))
  220. (setq reason (or (unsafep-variable sym t) reason))
  221. (if reason (throw 'unsafep reason))
  222. sym))
  223. (defun unsafep-variable (sym to-bind)
  224. "Return nil if SYM is safe to set or bind, or a reason why not.
  225. If TO-BIND is nil, check whether SYM is safe to set.
  226. If TO-BIND is t, check whether SYM is safe to bind."
  227. (cond
  228. ((not (symbolp sym))
  229. `(variable ,sym))
  230. ((risky-local-variable-p sym nil)
  231. `(risky-local-variable ,sym))
  232. ((not (or to-bind
  233. (memq sym unsafep-vars)
  234. (local-variable-p sym)))
  235. `(global-variable ,sym))))
  236. ;;; unsafep.el ends here