From mboxrd@z Thu Jan 1 00:00:00 1970 Path: news.gmane.org!not-for-mail From: Chong Yidong Newsgroups: gmane.emacs.devel Subject: Re: Risky local variable mechanism Date: Fri, 10 Feb 2006 00:34:38 -0500 Message-ID: <87k6c3ojyp.fsf@stupidchicken.com> References: <87mzhbly89.fsf-monnier+emacs@gnu.org> NNTP-Posting-Host: deer.gmane.org Mime-Version: 1.0 Content-Type: text/plain; charset=us-ascii X-Trace: sea.gmane.org 1139550009 17036 80.91.229.6 (10 Feb 2006 05:40:09 GMT) X-Complaints-To: usenet@sea.gmane.org NNTP-Posting-Date: Fri, 10 Feb 2006 05:40:09 +0000 (UTC) Cc: emacs-devel@gnu.org, monnier@iro.umontreal.ca, "Kim F. Storm" Original-X-From: emacs-devel-bounces+ged-emacs-devel=m.gmane.org@gnu.org Fri Feb 10 06:39:59 2006 Return-path: Original-Received: from lists.gnu.org ([199.232.76.165]) by deer.gmane.org with esmtp (Exim 3.35 #1 (Debian)) id 1F7R0g-00036y-00 for ; Fri, 10 Feb 2006 06:39:58 +0100 Original-Received: from localhost ([127.0.0.1] helo=lists.gnu.org) by lists.gnu.org with esmtp (Exim 4.43) id 1F7R0e-0006U1-MY for ged-emacs-devel@m.gmane.org; Fri, 10 Feb 2006 00:39:56 -0500 Original-Received: from mailman by lists.gnu.org with tmda-scanned (Exim 4.43) id 1F7Qvc-0003vf-L6 for emacs-devel@gnu.org; Fri, 10 Feb 2006 00:34:44 -0500 Original-Received: from exim by lists.gnu.org with spam-scanned (Exim 4.43) id 1F7Qvb-0003vS-Ol for emacs-devel@gnu.org; Fri, 10 Feb 2006 00:34:44 -0500 Original-Received: from [199.232.76.173] (helo=monty-python.gnu.org) by lists.gnu.org with esmtp (Exim 4.43) id 1F7Qvb-0003vP-L3 for emacs-devel@gnu.org; Fri, 10 Feb 2006 00:34:43 -0500 Original-Received: from [18.95.6.197] (helo=localhost.localdomain) by monty-python.gnu.org with esmtp (Exim 4.52) id 1F7QzF-00051s-Dh; Fri, 10 Feb 2006 00:38:29 -0500 Original-Received: by localhost.localdomain (Postfix, from userid 1000) id 648031E4302; Fri, 10 Feb 2006 00:34:38 -0500 (EST) Original-To: rms@gnu.org In-Reply-To: (Richard M. Stallman's message of "Fri, 03 Feb 2006 18:43:12 -0500") User-Agent: Gnus/5.11 (Gnus v5.11) Emacs/22.0.50 (gnu/linux) X-BeenThere: emacs-devel@gnu.org X-Mailman-Version: 2.1.5 Precedence: list List-Id: "Emacs development discussions." List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Original-Sender: emacs-devel-bounces+ged-emacs-devel=m.gmane.org@gnu.org Errors-To: emacs-devel-bounces+ged-emacs-devel=m.gmane.org@gnu.org Xref: news.gmane.org gmane.emacs.devel:50305 Archived-At: Here's another proposed patch for the risky variables mechanism. This is the system I propose: Instead of setting the local variables as soon as they are read in, `hack-local-variables' and `hack-local-variables-prop-line' constructs a list of local variables, and decides what to do with it at the very end of the `hack-local-variables' function. Each local variable can be safe, risky, or neither. A variable-value pair is safe if it is found in the new customizable alist `safe-local-variables', or the variable's `safe-local-variables' property is t, or the variable's `safe-local-variables' property is a function that evaluates to t with that value. IF a variable is not safe, it is risky IF its `risky-local-variable' property is set, or its name ends in "-hooks", "-functions", etc. (One small change: `safe-local-variables' property evaluating to nil does not automatically mean the variable is risky.) If the local variables are all safe, they are set automatically. Otherwise, we raise a prompt asking whether to set them as a unit. The user can answer y, n, or !, where ! means to also allow these variable-value pairs for future sessions, via customize-save-variable. However, if a variable is risky, it won't be saved in this way, though it can still be set. (This last part is a little dubious. If we decide to save all variables, that removes the justification for "risky" variables, so the `risky-local-variable' property becomes obsolete. I don't know if this will break anything.) *** emacs/lisp/files.el.~1.804.~ 2006-02-09 23:32:56.000000000 -0500 --- emacs/lisp/files.el 2006-02-10 00:05:20.000000000 -0500 *************** *** 2213,2254 **** (goto-char beg) end)))) ! (defun hack-local-variables-confirm (string flag-to-check) ! (or (eq flag-to-check t) ! (and flag-to-check ! (save-window-excursion ! (condition-case nil ! (switch-to-buffer (current-buffer)) ! (error ! ;; If we fail to switch in the selected window, ! ;; it is probably a minibuffer or dedicated window. ! ;; So try another window. ! (let ((pop-up-frames nil)) ! ;; Refrain from popping up frames since it can't ! ;; be undone by save-window-excursion. ! (pop-to-buffer (current-buffer))))) ! (save-excursion ! (beginning-of-line) ! (set-window-start (selected-window) (point))) ! (y-or-n-p (format string ! (if buffer-file-name ! (file-name-nondirectory buffer-file-name) ! (concat "buffer " (buffer-name))))))))) (defun hack-local-variables-prop-line (&optional mode-only) ! "Set local variables specified in the -*- line. Ignore any specification for `mode:' and `coding:'; `set-auto-mode' should already have handled `mode:', `set-auto-coding' should already have handled `coding:'. If MODE-ONLY is non-nil, all we do is check whether the major mode ! is specified, returning t if it is specified." (save-excursion (goto-char (point-min)) ! (let ((result nil) ! (end (set-auto-mode-1)) ! mode-specified ! (enable-local-variables ! (and local-enable-local-variables enable-local-variables))) ;; Parse the -*- line into the RESULT alist. ;; Also set MODE-SPECIFIED if we see a spec or `mode'. (cond ((not end) --- 2213,2270 ---- (goto-char beg) end)))) ! (defun hack-local-variables-confirm (vars maybe-safe) ! (if noninteractive ! t ! (let (char) ! (save-window-excursion ! (with-output-to-temp-buffer "*Local Variables*" ! (princ "Some local variables are specified ! Do you want to set them?\n ! You can type ! y -- to set these variables. ! n -- to ignore these variables. ! ! -- to set these variables, and mark these values as safe ! (in the future, they can be set without asking you.)\n\n") ! (dolist (elt vars) ! (princ (car elt)) ! (princ " : ") ! (princ (cdr elt)) ! (princ "\n"))) ! (message "Please type y, n, or !: ") ! (let ((inhibit-quit t) ! (cursor-in-echo-area t)) ! (while (or (not (numberp (setq char (read-event)))) ! (not (memq (downcase char) ! '(?! ?y ?n ? ?\C-g)))) ! (message "Please type y, n, or !: ")) ! (if (= char ?\C-g) ! (setq quit-flag nil))) ! (setq char (downcase char)) ! (when (= char ?!) ! (dolist (elt maybe-safe) ! (push elt safe-local-variables)) ! (customize-save-variable ! 'safe-local-variables ! safe-local-variables)) ! (or (= char ?!) ! (= char ? ) ! (= char ?y)))))) (defun hack-local-variables-prop-line (&optional mode-only) ! "Return local variables specified in the -*- line. Ignore any specification for `mode:' and `coding:'; `set-auto-mode' should already have handled `mode:', `set-auto-coding' should already have handled `coding:'. + If MODE-ONLY is non-nil, all we do is check whether the major mode ! is specified, returning t if it is specified. Otherwise, return ! an alist of elements (VAR . VAL), where VAR is the variable and VAL ! is the specified value." (save-excursion (goto-char (point-min)) ! (let ((end (set-auto-mode-1)) ! result mode-specified) ;; Parse the -*- line into the RESULT alist. ;; Also set MODE-SPECIFIED if we see a spec or `mode'. (cond ((not end) *************** *** 2283,2317 **** (setq result (cons (cons key val) result))) (if (equal (downcase (symbol-name key)) "mode") (setq mode-specified t)) ! (skip-chars-forward " \t;"))) ! (setq result (nreverse result)))) ! (if mode-only mode-specified ! (if (and result ! (or mode-only ! (hack-local-variables-confirm ! "Set local variables as specified in -*- line of %s? " ! enable-local-variables))) ! (let ((enable-local-eval enable-local-eval)) ! (while result ! (hack-one-local-variable (car (car result)) (cdr (car result))) ! (setq result (cdr result))))) ! nil)))) (defvar hack-local-variables-hook nil "Normal hook run after processing a file's local variables specs. Major modes can use this to examine user-specified local variables in order to initialize other data structure based on them.") (defun hack-local-variables (&optional mode-only) "Parse and put into effect this buffer's local variables spec. If MODE-ONLY is non-nil, all we do is check whether the major mode is specified, returning t if it is specified." ! (let ((mode-specified ! ;; If MODE-ONLY is t, we check here for specifying the mode ! ;; in the -*- line. If MODE-ONLY is nil, we process ! ;; the -*- line here. ! (hack-local-variables-prop-line mode-only)) (enable-local-variables (and local-enable-local-variables enable-local-variables))) ;; Look for "Local variables:" line in last page. --- 2299,2327 ---- (setq result (cons (cons key val) result))) (if (equal (downcase (symbol-name key)) "mode") (setq mode-specified t)) ! (skip-chars-forward " \t;"))))) ! (if mode-only ! mode-specified ! result)))) (defvar hack-local-variables-hook nil "Normal hook run after processing a file's local variables specs. Major modes can use this to examine user-specified local variables in order to initialize other data structure based on them.") + (defcustom safe-local-variables nil + "Alist of safe local variables. + Each element is a cons cell (VAR . VAL), where VAR is the + variable symbol and VAL is a value considered safe." + :group 'find-file + :type 'alist) + (defun hack-local-variables (&optional mode-only) "Parse and put into effect this buffer's local variables spec. If MODE-ONLY is non-nil, all we do is check whether the major mode is specified, returning t if it is specified." ! (let ((result (hack-local-variables-prop-line mode-only)) (enable-local-variables (and local-enable-local-variables enable-local-variables))) ;; Look for "Local variables:" line in last page. *************** *** 2321,2329 **** (when (let ((case-fold-search t)) (and (search-forward "Local Variables:" nil t) (or mode-only ! (hack-local-variables-confirm ! "Set local variables as specified at end of %s? " ! enable-local-variables)))) (skip-chars-forward " \t") (let ((enable-local-eval enable-local-eval) ;; suffix is what comes after "local variables:" in its line. --- 2331,2337 ---- (when (let ((case-fold-search t)) (and (search-forward "Local Variables:" nil t) (or mode-only ! enable-local-variables))) (skip-chars-forward " \t") (let ((enable-local-eval enable-local-eval) ;; suffix is what comes after "local variables:" in its line. *************** *** 2384,2397 **** (setq val (read (current-buffer))) (if mode-only (if (eq var 'mode) ! (setq mode-specified t)) ! ;; Set the variable. "Variables" mode and eval are funny. ! (with-current-buffer thisbuf ! (hack-one-local-variable var val)))) (forward-line 1))))))) ! (unless mode-only ! (run-hooks 'hack-local-variables-hook)) ! mode-specified)) (defvar ignored-local-variables () "Variables to be ignored in a file's local variable spec.") --- 2392,2425 ---- (setq val (read (current-buffer))) (if mode-only (if (eq var 'mode) ! (setq result t)) ! (unless (eq var 'coding) ! (push (cons var val) result)))) (forward-line 1))))))) ! ;; We've read all the local variables. Now, return whether the ! ;; mode is specified (if MODE-ONLY is non-nil), or set the ! ;; variables (if MODE-ONLY is nil.) ! (if mode-only ! result ! (when enable-local-variables ! (setq result (nreverse result)) ! (dolist (ignored ignored-local-variables) ! (setq result (assq-delete-all ignored result))) ! ;; Find those variables that we may want to save to ! ;; `safe-local-variables'. ! (let (maybe-safe risky) ! (dolist (elt result) ! (or (safe-local-variable-p (car elt) (cdr elt)) ! (and (risky-local-variable-p (car elt) (cdr elt)) ! (setq risky t)) ! (push elt maybe-safe))) ! (if (or (and (eq enable-local-variables t) ! (null maybe-safe) ! (not risky)) ! (hack-local-variables-confirm result maybe-safe)) ! (dolist (elt result) ! (hack-one-local-variable (car elt) (cdr elt))))) ! (run-hooks 'hack-local-variables-hook))))) (defvar ignored-local-variables () "Variables to be ignored in a file's local variable spec.") *************** *** 2451,2458 **** (put 'display-time-string 'risky-local-variable t) (put 'parse-time-rules 'risky-local-variable t) ! ;; This case is safe because the user gets to check it before it is used. ! (put 'compile-command 'safe-local-variable 'stringp) (defun risky-local-variable-p (sym &optional val) "Non-nil if SYM could be dangerous as a file-local variable with value VAL. --- 2479,2510 ---- (put 'display-time-string 'risky-local-variable t) (put 'parse-time-rules 'risky-local-variable t) ! ;; Commonly-encountered local variables that are safe: ! (mapc (lambda (pair) ! (put (car pair) 'safe-local-variable (cdr pair))) ! '((compile-command . stringp) ! (fill-column . integerp) ! (fill-prefix . t) ! (indent-tabs-mode . t) ! (page-delimiter . t) ! (paragraph-separate . t) ! (sentence-end . t) ! (sentence-end-double-space . t) ! (tab-width . integerp) ! (version-control . t))) ! ! (defun safe-local-variable-p (sym val) ! "Non-nil if SYM is safe as a file-local variable with value VAL." ! (or (member (cons sym val) safe-local-variables) ! (eq sym 'mode) ! (progn ! (condition-case nil ! (setq sym (indirect-variable sym)) ! (error nil)) ! (let ((safep (get sym 'safe-local-variable))) ! (and safep ! (or (eq safep t) ! (funcall safep val))))))) (defun risky-local-variable-p (sym &optional val) "Non-nil if SYM could be dangerous as a file-local variable with value VAL. *************** *** 2462,2477 **** (condition-case nil (setq sym (indirect-variable sym)) (error nil)) ! (let ((safep (get sym 'safe-local-variable))) ! (or (get sym 'risky-local-variable) ! (and (string-match "-hooks?$\\|-functions?$\\|-forms?$\\|-program$\\|-commands?$\\|-predicates?$\\|font-lock-keywords$\\|font-lock-keywords-[0-9]+$\\|font-lock-syntactic-keywords$\\|-frame-alist$\\|-mode-alist$\\|-map$\\|-map-alist$" ! (symbol-name sym)) ! (not safep)) ! ;; If the safe-local-variable property isn't t or nil, ! ;; then it must return non-nil on the proposed value to be safe. ! (and (not (memq safep '(t nil))) ! (or (null val) ! (not (funcall safep val))))))) (defcustom safe-local-eval-forms nil "*Expressions that are considered \"safe\" in an `eval:' local variable. --- 2514,2522 ---- (condition-case nil (setq sym (indirect-variable sym)) (error nil)) ! (or (get sym 'risky-local-variable) ! (string-match "-hooks?$\\|-functions?$\\|-forms?$\\|-program$\\|-commands?$\\|-predicates?$\\|font-lock-keywords$\\|font-lock-keywords-[0-9]+$\\|font-lock-syntactic-keywords$\\|-frame-alist$\\|-mode-alist$\\|-map$\\|-map-alist$" ! (symbol-name sym)))) (defcustom safe-local-eval-forms nil "*Expressions that are considered \"safe\" in an `eval:' local variable. *************** *** 2534,2561 **** (cond ((eq var 'mode) (funcall (intern (concat (downcase (symbol-name val)) "-mode")))) ! ((eq var 'coding) ! ;; We have already handled coding: tag in set-auto-coding. ! nil) ! ((memq var ignored-local-variables) ! nil) ! ;; "Setting" eval means either eval it or do nothing. ! ;; Likewise for setting hook variables. ! ((risky-local-variable-p var val) ! ;; Permit evalling a put of a harmless property. ! ;; if the args do nothing tricky. ! (if (or (and (eq var 'eval) ! (hack-one-local-variable-eval-safep val)) ! ;; Permit eval if not root and user says ok. ! (and (not (zerop (user-uid))) ! (hack-local-variables-confirm ! "Process `eval' or hook local variables in %s? " ! enable-local-eval))) ! (if (eq var 'eval) ! (save-excursion (eval val)) ! (make-local-variable var) ! (set var val)) ! (message "Ignoring risky spec in the local variables list"))) ;; Ordinary variable, really set it. (t (make-local-variable var) ;; Make sure the string has no text properties. --- 2579,2587 ---- (cond ((eq var 'mode) (funcall (intern (concat (downcase (symbol-name val)) "-mode")))) ! ((eq var 'eval) ! (if (hack-one-local-variable-eval-safep val) ! (save-excursion (eval val)))) ;; Ordinary variable, really set it. (t (make-local-variable var) ;; Make sure the string has no text properties.