diff options
| author | Mattias EngdegÄrd | 2023-05-11 19:24:51 +0200 |
|---|---|---|
| committer | Mattias EngdegÄrd | 2023-05-13 11:53:25 +0200 |
| commit | bfc07100d28d0f687da0a1dd5fdfa42a92a93f88 (patch) | |
| tree | 4ca024cacb42464e68c51f07bbbae3d1fe9af4eb | |
| parent | fa598571adab4858282f337b45984517e197f8a9 (diff) | |
| download | emacs-bfc07100d28d0f687da0a1dd5fdfa42a92a93f88.tar.gz emacs-bfc07100d28d0f687da0a1dd5fdfa42a92a93f88.zip | |
Byte-compiler warning about mutation of constant values
When we can easily detect mutation of constants (quoted lists, strings
and vectors), warn. For example,
(setcdr '(1 . 2) 3)
(nreverse [1 2 3])
(put-text-property 0 3 'face 'highlight "moo")
Such code can result in surprising behaviour and problems that
are difficult to debug.
* lisp/emacs-lisp/bytecomp.el (byte-compile-form, mutating-fns):
Add the warning and a list of functions to warn about.
* etc/NEWS: Announce.
* test/lisp/emacs-lisp/bytecomp-tests.el
(bytecomp-test--with-suppressed-warnings): Add test cases.
| -rw-r--r-- | etc/NEWS | 20 | ||||
| -rw-r--r-- | lisp/emacs-lisp/bytecomp.el | 53 | ||||
| -rw-r--r-- | test/lisp/emacs-lisp/bytecomp-tests.el | 30 |
3 files changed, 103 insertions, 0 deletions
| @@ -510,6 +510,26 @@ This warning can be suppressed using 'with-suppressed-warnings' with | |||
| 510 | the warning name 'suspicious'. | 510 | the warning name 'suspicious'. |
| 511 | 511 | ||
| 512 | --- | 512 | --- |
| 513 | *** Warn about mutation of constant values. | ||
| 514 | The compiler now warns about code that modifies program constants in | ||
| 515 | some obvious cases. Examples: | ||
| 516 | |||
| 517 | (setcar '(1 2) 7) | ||
| 518 | (aset [3 4] 0 8) | ||
| 519 | (aset "abc" 1 ?d) | ||
| 520 | |||
| 521 | Such code may have unpredictable behaviour because the constants are | ||
| 522 | part of the program, not data structures generated afresh during | ||
| 523 | execution, and the compiler does not expect them to change. | ||
| 524 | |||
| 525 | To avoid the warning, operate on an object created by the program | ||
| 526 | (maybe a copy of the constant), or use a non-destructive operation | ||
| 527 | instead. | ||
| 528 | |||
| 529 | This warning can be suppressed using 'with-suppressed-warnings' with | ||
| 530 | the warning name 'suspicious'. | ||
| 531 | |||
| 532 | --- | ||
| 513 | *** Warn about more ignored function return values. | 533 | *** Warn about more ignored function return values. |
| 514 | The compiler now warns when the return value from certain functions is | 534 | The compiler now warns when the return value from certain functions is |
| 515 | implicitly ignored. Example: | 535 | implicitly ignored. Example: |
diff --git a/lisp/emacs-lisp/bytecomp.el b/lisp/emacs-lisp/bytecomp.el index 6c804056ee7..d17f1c93a76 100644 --- a/lisp/emacs-lisp/bytecomp.el +++ b/lisp/emacs-lisp/bytecomp.el | |||
| @@ -3488,6 +3488,22 @@ lambda-expression." | |||
| 3488 | (format-message "; use `%s' instead." | 3488 | (format-message "; use `%s' instead." |
| 3489 | interactive-only)) | 3489 | interactive-only)) |
| 3490 | (t ".")))) | 3490 | (t ".")))) |
| 3491 | (let ((mutargs (function-get (car form) 'mutates-arguments))) | ||
| 3492 | (when mutargs | ||
| 3493 | (dolist (idx (if (eq mutargs 'all-but-last) | ||
| 3494 | (number-sequence 1 (- (length form) 2)) | ||
| 3495 | mutargs)) | ||
| 3496 | (let ((arg (nth idx form))) | ||
| 3497 | (when (and (or (and (eq (car-safe arg) 'quote) | ||
| 3498 | (consp (nth 1 arg))) | ||
| 3499 | (arrayp arg)) | ||
| 3500 | (byte-compile-warning-enabled-p | ||
| 3501 | 'suspicious (car form))) | ||
| 3502 | (byte-compile-warn-x form "`%s' on constant %s (arg %d)" | ||
| 3503 | (car form) | ||
| 3504 | (if (consp arg) "list" (type-of arg)) | ||
| 3505 | idx)))))) | ||
| 3506 | |||
| 3491 | (if (eq (car-safe (symbol-function (car form))) 'macro) | 3507 | (if (eq (car-safe (symbol-function (car form))) 'macro) |
| 3492 | (byte-compile-report-error | 3508 | (byte-compile-report-error |
| 3493 | (format-message "`%s' defined after use in %S (missing `require' of a library file?)" | 3509 | (format-message "`%s' defined after use in %S (missing `require' of a library file?)" |
| @@ -3557,6 +3573,43 @@ lambda-expression." | |||
| 3557 | (dolist (fn important-return-value-fns) | 3573 | (dolist (fn important-return-value-fns) |
| 3558 | (put fn 'important-return-value t))) | 3574 | (put fn 'important-return-value t))) |
| 3559 | 3575 | ||
| 3576 | (let ((mutating-fns | ||
| 3577 | ;; FIXME: Should there be a function declaration for this? | ||
| 3578 | ;; | ||
| 3579 | ;; (FUNC . ARGS) means that FUNC mutates arguments whose indices are | ||
| 3580 | ;; in the list ARGS, starting at 1, or all but the last argument if | ||
| 3581 | ;; ARGS is `all-but-last'. | ||
| 3582 | '( | ||
| 3583 | (setcar 1) (setcdr 1) (aset 1) | ||
| 3584 | (nreverse 1) | ||
| 3585 | (nconc . all-but-last) | ||
| 3586 | (nbutlast 1) (ntake 2) | ||
| 3587 | (sort 1) | ||
| 3588 | (delq 2) (delete 2) | ||
| 3589 | (delete-dups 1) (delete-consecutive-dups 1) | ||
| 3590 | (plist-put 1) | ||
| 3591 | (fillarray 1) | ||
| 3592 | (store-substring 1) | ||
| 3593 | (clear-string 1) | ||
| 3594 | |||
| 3595 | (add-text-properties 4) (put-text-property 5) (set-text-properties 4) | ||
| 3596 | (remove-text-properties 4) (remove-list-of-text-properties 4) | ||
| 3597 | (alter-text-property 5) | ||
| 3598 | (add-face-text-property 5) (add-display-text-property 5) | ||
| 3599 | |||
| 3600 | (cl-delete 2) (cl-delete-if 2) (cl-delete-if-not 2) | ||
| 3601 | (cl-delete-duplicates 1) | ||
| 3602 | (cl-nsubst 3) (cl-nsubst-if 3) (cl-nsubst-if-not 3) | ||
| 3603 | (cl-nsubstitute 3) (cl-nsubstitute-if 3) (cl-nsubstitute-if-not 3) | ||
| 3604 | (cl-nsublis 2) | ||
| 3605 | (cl-nunion 1 2) (cl-nintersection 1 2) (cl-nset-difference 1 2) | ||
| 3606 | (cl-nset-exclusive-or 1 2) | ||
| 3607 | (cl-nreconc 1) | ||
| 3608 | (cl-sort 1) (cl-stable-sort 1) (cl-merge 2 3) | ||
| 3609 | ))) | ||
| 3610 | (dolist (entry mutating-fns) | ||
| 3611 | (put (car entry) 'mutates-arguments (cdr entry)))) | ||
| 3612 | |||
| 3560 | 3613 | ||
| 3561 | (defun byte-compile-normal-call (form) | 3614 | (defun byte-compile-normal-call (form) |
| 3562 | (when (and (symbolp (car form)) | 3615 | (when (and (symbolp (car form)) |
diff --git a/test/lisp/emacs-lisp/bytecomp-tests.el b/test/lisp/emacs-lisp/bytecomp-tests.el index 222065c2e4e..9136a6cd9b3 100644 --- a/test/lisp/emacs-lisp/bytecomp-tests.el +++ b/test/lisp/emacs-lisp/bytecomp-tests.el | |||
| @@ -1518,6 +1518,36 @@ literals (Bug#20852)." | |||
| 1518 | )) | 1518 | )) |
| 1519 | '((empty-body with-suppressed-warnings)) | 1519 | '((empty-body with-suppressed-warnings)) |
| 1520 | "Warning: `with-suppressed-warnings' with empty body") | 1520 | "Warning: `with-suppressed-warnings' with empty body") |
| 1521 | |||
| 1522 | (test-suppression | ||
| 1523 | '(defun zot () | ||
| 1524 | (setcar '(1 2) 3)) | ||
| 1525 | '((suspicious setcar)) | ||
| 1526 | "Warning: `setcar' on constant list (arg 1)") | ||
| 1527 | |||
| 1528 | (test-suppression | ||
| 1529 | '(defun zot () | ||
| 1530 | (aset [1 2] 1 3)) | ||
| 1531 | '((suspicious aset)) | ||
| 1532 | "Warning: `aset' on constant vector (arg 1)") | ||
| 1533 | |||
| 1534 | (test-suppression | ||
| 1535 | '(defun zot () | ||
| 1536 | (aset "abc" 1 ?d)) | ||
| 1537 | '((suspicious aset)) | ||
| 1538 | "Warning: `aset' on constant string (arg 1)") | ||
| 1539 | |||
| 1540 | (test-suppression | ||
| 1541 | '(defun zot (x y) | ||
| 1542 | (nconc x y '(1 2) '(3 4))) | ||
| 1543 | '((suspicious nconc)) | ||
| 1544 | "Warning: `nconc' on constant list (arg 3)") | ||
| 1545 | |||
| 1546 | (test-suppression | ||
| 1547 | '(defun zot () | ||
| 1548 | (put-text-property 0 2 'prop 'val "abc")) | ||
| 1549 | '((suspicious put-text-property)) | ||
| 1550 | "Warning: `put-text-property' on constant string (arg 5)") | ||
| 1521 | ) | 1551 | ) |
| 1522 | 1552 | ||
| 1523 | (ert-deftest bytecomp-tests--not-writable-directory () | 1553 | (ert-deftest bytecomp-tests--not-writable-directory () |