unofficial mirror of emacs-devel@gnu.org 
 help / color / mirror / code / Atom feed
* [PATCH] Bound index checks in cl-seq functions
@ 2017-02-04 16:40 Tino Calancha
  2017-02-04 20:42 ` Philipp Stephani
                   ` (3 more replies)
  0 siblings, 4 replies; 14+ messages in thread
From: Tino Calancha @ 2017-02-04 16:40 UTC (permalink / raw)
  To: Emacs developers; +Cc: tino.calancha


Consider following evaluations:

(cl-position 2 (list 1 2 3) :start -999)
=> -998
(cl-position 2 (list 1 2 3) :start 999)
=> nil
(cl-position 2 (list 1 2 3) :start 1 :end 0)
=> nil

*) In the first two, the indices are out of range.
*) In the 3rd, the start index is higher than the end index.

Guy L. Steele recommends to throw an error in those cases:
http://www.lispworks.com/documentation/HyperSpec/Issues/iss332_w.htm

We have partially done the job, at least for `cl-subseq':
(cl-subseq (list 1 2 3) 999)
; Signal: "Bad bounding indices: 999, nil"

(cl-subseq (list 1 2 3) -999)
; Signal: "Bad bounding indices: -999, nil"

(cl-subseq (list 1 2 3) -1)
=> (3) ; This is OK as documented in the docstring.

See:
https://lists.gnu.org/archive/html/emacs-devel/2015-08/msg00182.html

It would be nice if all the sequence functions in cl-seq.el
throw errors in those situations.

What do you think?
The following patch implements such bounding checks for the indices.
Comments are very welcome.

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
From c076a16716d6b9ad5c971d67375223974df53a40 Mon Sep 17 00:00:00 2001
From: Tino Calancha <tino.calancha@gmail.com>
Date: Sat, 4 Feb 2017 21:53:48 +0900
Subject: [PATCH 1/3] cl--parsing-keywords: Check for negative indices

* lisp/emacs-lisp/cl-seq.el (cl--parsing-keywords):
Throw and error when the user inputs a negative index for the sequence.
---
 lisp/emacs-lisp/cl-seq.el | 8 +++++++-
 1 file changed, 7 insertions(+), 1 deletion(-)

diff --git a/lisp/emacs-lisp/cl-seq.el b/lisp/emacs-lisp/cl-seq.el
index 67ff1a00bd..558adf22fe 100644
--- a/lisp/emacs-lisp/cl-seq.el
+++ b/lisp/emacs-lisp/cl-seq.el
@@ -59,7 +59,13 @@ cl--parsing-keywords
                    (setq mem `(and ,mem (setq cl-if ,mem) t)))
                (list (intern
                       (format "cl-%s" (substring (symbol-name var) 1)))
-                     (if (consp x) `(or ,mem ,(car (cdr x))) mem))))
+                     `(if (and (string-match ":\\(start\\|end\\)" (symbol-name ,var))
+                               (integerp (or ,mem ,(car (cdr-safe x))))
+                               (not (natnump (or ,mem ,(car (cdr-safe x))))))
+                          (error "Wrong negative index '%s': natnump, %s"
+                                 (substring (symbol-name ,var) 1)
+                                 (or ,mem ,(car (cdr-safe x))))
+                        (or ,mem ,(car (cdr-safe x)))))))
            kwords)
      ,@(append
         (and (not (eq other-keys t))
-- 
2.11.0

From 9898718b96109c5815b81f8dc8868de4d86db453 Mon Sep 17 00:00:00 2001
From: Tino Calancha <tino.calancha@gmail.com>
Date: Sun, 5 Feb 2017 00:04:12 +0900
Subject: [PATCH 2/3] cl--check-bound-indices: Check for unbound indices

Throw and error if the user inputs an index higher than the sequence length
or if :start value is higher than :end value.
* lisp/emacs-lisp/cl-seq.el (cl--check-bound-indices): New defun.
(cl-reduce, cl-fill, cl-replace, cl-remove, cl-delete)
(cl--delete-duplicates, cl-substitute, cl-nsubstitute, cl-position)
(cl-count, cl-mismatch, cl-search): Use it.
---
 lisp/emacs-lisp/cl-seq.el | 51 +++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 51 insertions(+)

diff --git a/lisp/emacs-lisp/cl-seq.el b/lisp/emacs-lisp/cl-seq.el
index 558adf22fe..0cb7ac7bdd 100644
--- a/lisp/emacs-lisp/cl-seq.el
+++ b/lisp/emacs-lisp/cl-seq.el
@@ -42,6 +42,7 @@
 ;;; Code:
 
 (require 'cl-lib)
+(eval-when-compile (require 'subr-x))
 
 ;; Keyword parsing.
 ;; This is special-cased here so that we can compile
@@ -118,6 +119,44 @@ cl-test
 (defvar cl-if) (defvar cl-if-not)
 (defvar cl-key)
 
+;; Throw an error when :start or :end are > sequence length.
+(defmacro cl--check-bound-indices (cl-seq1 cl-seq2 cl-keys)
+  (declare (debug t))
+  `(let ((len1 (length ,cl-seq1))
+         (len2 (and ,cl-seq2 (length ,cl-seq2)))
+         (kwds (list :start1 :start2 :start :end1 :end2 :end))
+         (keys ,cl-keys)
+         alist)
+     (while keys
+       (when (and (memq (car keys) kwds)
+                  (string-match ":\\(start\\|end\\)\\([1-2]?\\)\\'"
+                                (symbol-name (car keys))))
+         (delq (car keys) kwds) ; Ignore succesive equal keys.
+         (let* ((idx (match-string 2 (symbol-name (car keys))))
+                (len (if (equal idx "2") len2 len1)))
+           (when (integerp (cadr keys))
+             (push (cons (car keys) (cadr keys)) alist))
+           (when (> (cadr keys) len)
+             (error "Wrong bounding indices '%s', %s > (length %s), %s"
+                    (substring (symbol-name (car keys)) 1)
+                    (cadr keys)
+                    (concat "cl-seq" idx)
+                    len))))
+       (setq keys (cddr keys)))
+     ;; Check :start value > :end value.
+     (mapc (lambda (x)
+             (and-let* ((start (alist-get (car x) alist))
+                        (end (alist-get (cdr x) alist)))
+               (when (> start end)
+                 (error "Bad bounding indices '%s', '%s': %d, %d"
+                        (substring (symbol-name (car x)) 1)
+                        (substring (symbol-name (cdr x)) 1)
+                        start
+                        end))))
+           (list (cons :start :end)
+                 (cons :start1 :end1)
+                 (cons :start2 :end2)))))
+
 ;;;###autoload
 (defun cl-reduce (cl-func cl-seq &rest cl-keys)
   "Reduce two-argument FUNCTION across SEQ.
@@ -134,6 +173,7 @@ cl-reduce
 
 \n(fn FUNCTION SEQ [KEYWORD VALUE]...)"
   (cl--parsing-keywords (:from-end (:start 0) :end :initial-value :key) ()
+    (cl--check-bound-indices cl-seq nil cl-keys)
     (or (listp cl-seq) (setq cl-seq (append cl-seq nil)))
     (setq cl-seq (cl-subseq cl-seq cl-start cl-end))
     (if cl-from-end (setq cl-seq (nreverse cl-seq)))
@@ -155,6 +195,7 @@ cl-fill
 \nKeywords supported:  :start :end
 \n(fn SEQ ITEM [KEYWORD VALUE]...)"
   (cl--parsing-keywords ((:start 0) :end) ()
+    (cl--check-bound-indices cl-seq nil cl-keys)
     (if (listp cl-seq)
 	(let ((p (nthcdr cl-start cl-seq))
 	      (n (and cl-end (- cl-end cl-start))))
@@ -176,6 +217,7 @@ cl-replace
 \nKeywords supported:  :start1 :end1 :start2 :end2
 \n(fn SEQ1 SEQ2 [KEYWORD VALUE]...)"
   (cl--parsing-keywords ((:start1 0) :end1 (:start2 0) :end2) ()
+    (cl--check-bound-indices cl-seq1 cl-seq2 cl-keys)
     (if (and (eq cl-seq1 cl-seq2) (<= cl-start2 cl-start1))
 	(or (= cl-start1 cl-start2)
 	    (let* ((cl-len (length cl-seq1))
@@ -225,6 +267,7 @@ cl-remove
 \n(fn ITEM SEQ [KEYWORD VALUE]...)"
   (cl--parsing-keywords (:test :test-not :key :if :if-not :count :from-end
 			(:start 0) :end) ()
+    (cl--check-bound-indices cl-seq nil cl-keys)
     (let ((len (length cl-seq)))
       (if (<= (or cl-count (setq cl-count len)) 0)
 	cl-seq
@@ -289,6 +332,7 @@ cl-delete
 \n(fn ITEM SEQ [KEYWORD VALUE]...)"
   (cl--parsing-keywords (:test :test-not :key :if :if-not :count :from-end
 			(:start 0) :end) ()
+    (cl--check-bound-indices cl-seq nil cl-keys)
     (let ((len (length cl-seq)))
       (if (<= (or cl-count (setq cl-count len)) 0)
 	cl-seq
@@ -362,6 +406,7 @@ cl--delete-duplicates
           ;; We need to parse :if, otherwise `cl-if' is unbound.
           (:test :test-not :key (:start 0) :end :from-end :if)
 	  ()
+        (cl--check-bound-indices cl-seq nil cl-keys)
 	(if cl-from-end
 	    (let ((cl-p (nthcdr cl-start cl-seq)) cl-i)
 	      (setq cl-end (- (or cl-end (length cl-seq)) cl-start))
@@ -407,6 +452,7 @@ cl-substitute
 \n(fn NEW OLD SEQ [KEYWORD VALUE]...)"
   (cl--parsing-keywords (:test :test-not :key :if :if-not :count
 			(:start 0) :end :from-end) ()
+    (cl--check-bound-indices cl-seq nil cl-keys)
     (if (or (eq cl-old cl-new)
 	    (<= (or cl-count (setq cl-from-end nil
 				   cl-count (length cl-seq))) 0))
@@ -448,6 +494,7 @@ cl-nsubstitute
 \n(fn NEW OLD SEQ [KEYWORD VALUE]...)"
   (cl--parsing-keywords (:test :test-not :key :if :if-not :count
 			(:start 0) :end :from-end) ()
+    (cl--check-bound-indices cl-seq nil cl-keys)
     (let ((len (length cl-seq)))
       (or (eq cl-old cl-new) (<= (or cl-count (setq cl-count len)) 0)
 	  (if (and (listp cl-seq) (or (not cl-from-end) (> cl-count (/ len 2))))
@@ -524,6 +571,7 @@ cl-position
 \n(fn ITEM SEQ [KEYWORD VALUE]...)"
   (cl--parsing-keywords (:test :test-not :key :if :if-not
 			(:start 0) :end :from-end) ()
+    (cl--check-bound-indices cl-seq nil cl-keys)
     (cl--position cl-item cl-seq cl-start cl-end cl-from-end)))
 
 (defun cl--position (cl-item cl-seq cl-start &optional cl-end cl-from-end)
@@ -568,6 +616,7 @@ cl-count
 \nKeywords supported:  :test :test-not :key :start :end
 \n(fn ITEM SEQ [KEYWORD VALUE]...)"
   (cl--parsing-keywords (:test :test-not :key :if :if-not (:start 0) :end) ()
+    (cl--check-bound-indices cl-seq nil cl-keys)
     (let ((cl-count 0) cl-x)
       (or cl-end (setq cl-end (length cl-seq)))
       (if (consp cl-seq) (setq cl-seq (nthcdr cl-start cl-seq)))
@@ -600,6 +649,7 @@ cl-mismatch
 \n(fn SEQ1 SEQ2 [KEYWORD VALUE]...)"
   (cl--parsing-keywords (:test :test-not :key :from-end
 			(:start1 0) :end1 (:start2 0) :end2) ()
+    (cl--check-bound-indices cl-seq1 cl-seq2 cl-keys)
     (or cl-end1 (setq cl-end1 (length cl-seq1)))
     (or cl-end2 (setq cl-end2 (length cl-seq2)))
     (if cl-from-end
@@ -631,6 +681,7 @@ cl-search
 \n(fn SEQ1 SEQ2 [KEYWORD VALUE]...)"
   (cl--parsing-keywords (:test :test-not :key :from-end
 			(:start1 0) :end1 (:start2 0) :end2) ()
+    (cl--check-bound-indices cl-seq1 cl-seq2 cl-keys)
     (or cl-end1 (setq cl-end1 (length cl-seq1)))
     (or cl-end2 (setq cl-end2 (length cl-seq2)))
     (if (>= cl-start1 cl-end1)
-- 
2.11.0

From 50cce9d03fa96d622d20f2218ec5ddcfeb246425 Mon Sep 17 00:00:00 2001
From: Tino Calancha <tino.calancha@gmail.com>
Date: Sun, 5 Feb 2017 01:06:01 +0900
Subject: [PATCH 3/3] * test/lisp/emacs-lisp/cl-seq-tests.el
 (cl-seq-check-bounds): New test.

---
 test/lisp/emacs-lisp/cl-seq-tests.el | 91 ++++++++++++++++++++++++++++++++++++
 1 file changed, 91 insertions(+)

diff --git a/test/lisp/emacs-lisp/cl-seq-tests.el b/test/lisp/emacs-lisp/cl-seq-tests.el
index 61e3d72033..f5ecfe4b92 100644
--- a/test/lisp/emacs-lisp/cl-seq-tests.el
+++ b/test/lisp/emacs-lisp/cl-seq-tests.el
@@ -294,6 +294,7 @@ cl-seq--with-side-effects
 
 (ert-deftest cl-seq-test-bug24264 ()
   "Test for http://debbugs.gnu.org/24264 ."
+  :tags '(:expensive-test)
   (let ((list  (append (make-list 8000005 1) '(8)))
         (list2 (make-list 8000005 2)))
     (should (cl-position 8 list))
@@ -302,6 +303,96 @@ cl-seq--with-side-effects
     (should (equal '(2 8) (last (cl-replace list list2) 2)))
     (should (equal '(1 1) (last (cl-fill list 1) 2)))))
 
+(ert-deftest cl-seq-check-bounds ()
+  (let ((lst (list 1 2 3))
+        (lst2 (list 'a 'b 'c))
+        ;; t means pass, nil means fails.
+        (tests '("((lambda (x y) (cl-reduce #'max x :start 1)) . t)"
+                 "((lambda (x y) (cl-reduce #'max x :start -1)))"
+                 "((lambda (x y) (cl-reduce #'max x :start 4)))"
+                 "((lambda (x y) (cl-reduce #'max x :start 2 :end 1)))"
+                 ;;
+                 "((lambda (x y) (cl-fill x 'b :start 3)) . t)"
+                 "((lambda (x y) (cl-fill x 'b :start 4)))"
+                 "((lambda (x y) (cl-fill x 'b :start -1)))"
+                 "((lambda (x y) (cl-fill x 'b :start 2 :end 1)))"
+                 ;;
+                 "((lambda (x y) (cl-replace x y :start1 3)) . t)"
+                 "((lambda (x y) (cl-replace x y :start2 3)) . t)"
+                 "((lambda (x y) (cl-replace x y :start1 4)))"
+                 "((lambda (x y) (cl-replace x y :start2 4)))"
+                 "((lambda (x y) (cl-replace x y :start1 -1)))"
+                 "((lambda (x y) (cl-replace x y :start2 -1)))"
+                 "((lambda (x y) (cl-replace x y :start1 2 :end1 1)))"
+                 "((lambda (x y) (cl-replace x y :start2 2 :end2 1)))"
+                 ;;
+                 "((lambda (x y) (cl-remove nil x :start 3)) . t)"
+                 "((lambda (x y) (cl-remove nil x :start 4)))"
+                 "((lambda (x y) (cl-remove nil x :start -1)))"
+                 "((lambda (x y) (cl-remove nil x :start 2 :end 1)))"
+                 ;;
+                 "((lambda (x y) (cl-delete nil x :start 3)) . t)"
+                 "((lambda (x y) (cl-delete nil x :start 4)))"
+                 "((lambda (x y) (cl-delete nil x :start -1)))"
+                 "((lambda (x y) (cl-delete nil x :start 2 :end 1)))"
+                 ;;
+                 "((lambda (x y) (cl-remove-duplicates x :start 3)) . t)"
+                 "((lambda (x y) (cl-remove-duplicates x :start 4)))"
+                 "((lambda (x y) (cl-remove-duplicates x :start -1)))"
+                 "((lambda (x y) (cl-remove-duplicates x :start 2 :end 1)))"
+                 ;;
+                 "((lambda (x y) (cl-substitute 'foo 2 x :start 3)) . t)"
+                 "((lambda (x y) (cl-substitute 'foo 2 x :start 4)))"
+                 "((lambda (x y) (cl-substitute 'foo 2 x :start -1)))"
+                 "((lambda (x y) (cl-substitute 'foo 2 x :start 2 :end 1)))"
+                 ;;
+                 "((lambda (x y) (cl-nsubstitute 'foo 2 x :start 3)) . t)"
+                 "((lambda (x y) (cl-nsubstitute 'foo 2 x :start 4)))"
+                 "((lambda (x y) (cl-nsubstitute 'foo 2 x :start -1)))"
+                 "((lambda (x y) (cl-nsubstitute 'foo 2 x :start 2 :end 1)))"
+                 ;;
+                 "((lambda (x y) (cl-position 2 x :start 4)))"
+                 "((lambda (x y) (cl-position 2 x :start -1)))"
+                 "((lambda (x y) (cl-position 2 x :start 2 :end 1)))"
+                 ;;
+                 "((lambda (x y) (cl-count 2 x :start 3)) . t)"
+                 "((lambda (x y) (cl-count 2 x :start 4)))"
+                 "((lambda (x y) (cl-count 2 x :start -1)))"
+                 "((lambda (x y) (cl-count 2 x :start 2 :end 1)))"
+                 ;;
+                 "((lambda (x y) (cl-mismatch x x :start1 1 :start2 3)) . t)"
+                 "((lambda (x y) (cl-mismatch x x :start1 1 :start2 4)))"
+                 "((lambda (x y) (cl-mismatch x x :start1 4 :start2 1)))"
+                 "((lambda (x y) (cl-mismatch x x :start1 -1 :start2 1)))"
+                 "((lambda (x y) (cl-mismatch x x :start1 1 :start2 -1)))"
+                 "((lambda (x y) (cl-mismatch x x :start1 2 :end1 1)))"
+                 "((lambda (x y) (cl-mismatch x x :start2 2 :end2 1)))"
+                 ;;
+                 "((lambda (x y) (cl-search x x :start1 3 :start2 3)) . t)"
+                 "((lambda (x y) (cl-search x x :start1 4 :start2 4)))"
+                 "((lambda (x y) (cl-search x x :start1 -1 :start2 3)))"
+                 "((lambda (x y) (cl-search x x :start1 1 :start2 -1)))"
+                 "((lambda (x y) (cl-search x x :start1 2 :end1 1)))"
+                 "((lambda (x y) (cl-search x x :start2 2 :end2 1)))"
+                 ;;
+                 "((lambda (x y) (cl-subseq x -1)) . t)"
+                 "((lambda (x y) (cl-subseq x -2 -1)) . t)"
+                 "((lambda (x y) (cl-subseq x -4)))"
+                 "((lambda (x y) (cl-subseq x 2 1)))")))
+    (dolist (limit '("start" "end"))
+      (dolist (x tests)
+        (let ((form
+               (car
+                (read-from-string
+                 (cond ((string-match ":start\\([1-2]?\\) \\([0-9-]+\\) :end\\([1-2]?\\)" x)
+                        x)
+                       ((string= limit "start") x)
+                       (t
+                        (replace-regexp-in-string "start" limit x)))))))
+          (if (cdr form)
+              (should (funcall (car form) lst lst2))
+            (should-error (funcall (car form) lst lst2))))))))
+
 
 (provide 'cl-seq-tests)
 ;;; cl-seq-tests.el ends here
-- 
2.11.0

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
In GNU Emacs 26.0.50.1 (x86_64-pc-linux-gnu, GTK+ Version 3.22.7)
 of 2017-02-04
Repository revision: ff4dd0d39c3f5dfb8f4988f840c2c05621db32db



^ permalink raw reply related	[flat|nested] 14+ messages in thread

* Re: [PATCH] Bound index checks in cl-seq functions
  2017-02-04 16:40 [PATCH] Bound index checks in cl-seq functions Tino Calancha
@ 2017-02-04 20:42 ` Philipp Stephani
  2017-02-04 20:51 ` Noam Postavsky
                   ` (2 subsequent siblings)
  3 siblings, 0 replies; 14+ messages in thread
From: Philipp Stephani @ 2017-02-04 20:42 UTC (permalink / raw)
  To: Tino Calancha, Emacs developers

[-- Attachment #1: Type: text/plain, Size: 17447 bytes --]

Tino Calancha <tino.calancha@gmail.com> schrieb am Sa., 4. Feb. 2017 um
17:41 Uhr:

>
> Consider following evaluations:
>
> (cl-position 2 (list 1 2 3) :start -999)
> => -998
> (cl-position 2 (list 1 2 3) :start 999)
> => nil
> (cl-position 2 (list 1 2 3) :start 1 :end 0)
> => nil
>
> *) In the first two, the indices are out of range.
> *) In the 3rd, the start index is higher than the end index.
>
> Guy L. Steele recommends to throw an error in those cases:
> http://www.lispworks.com/documentation/HyperSpec/Issues/iss332_w.htm
>
> We have partially done the job, at least for `cl-subseq':
> (cl-subseq (list 1 2 3) 999)
> ; Signal: "Bad bounding indices: 999, nil"
>
> (cl-subseq (list 1 2 3) -999)
> ; Signal: "Bad bounding indices: -999, nil"
>
> (cl-subseq (list 1 2 3) -1)
> => (3) ; This is OK as documented in the docstring.
>
> See:
> https://lists.gnu.org/archive/html/emacs-devel/2015-08/msg00182.html
>
> It would be nice if all the sequence functions in cl-seq.el
> throw errors in those situations.
>
> What do you think?
>

I agree that these should throw errors.


> The following patch implements such bounding checks for the indices.
> Comments are very welcome.
>
>
> ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
> From c076a16716d6b9ad5c971d67375223974df53a40 Mon Sep 17 00:00:00 2001
> From: Tino Calancha <tino.calancha@gmail.com>
> Date: Sat, 4 Feb 2017 21:53:48 +0900
> Subject: [PATCH 1/3] cl--parsing-keywords: Check for negative indices
>
> * lisp/emacs-lisp/cl-seq.el (cl--parsing-keywords):
> Throw and error when the user inputs a negative index for the sequence.
> ---
>  lisp/emacs-lisp/cl-seq.el | 8 +++++++-
>  1 file changed, 7 insertions(+), 1 deletion(-)
>
> diff --git a/lisp/emacs-lisp/cl-seq.el b/lisp/emacs-lisp/cl-seq.el
> index 67ff1a00bd..558adf22fe 100644
> --- a/lisp/emacs-lisp/cl-seq.el
> +++ b/lisp/emacs-lisp/cl-seq.el
> @@ -59,7 +59,13 @@ cl--parsing-keywords
>                     (setq mem `(and ,mem (setq cl-if ,mem) t)))
>                 (list (intern
>                        (format "cl-%s" (substring (symbol-name var) 1)))
> -                     (if (consp x) `(or ,mem ,(car (cdr x))) mem))))
> +                     `(if (and (string-match ":\\(start\\|end\\)"
> (symbol-name ,var))
>

I think you can replace the string-match with (memq ,var '(:start :end))


> +                               (integerp (or ,mem ,(car (cdr-safe x))))
> +                               (not (natnump (or ,mem ,(car (cdr-safe
> x))))))
> +                          (error "Wrong negative index '%s': natnump, %s"
> +                                 (substring (symbol-name ,var) 1)
>

Please use (signal 'args-out-of-range ...) to be more specific and
consistent with similar checks.


> +                                 (or ,mem ,(car (cdr-safe x))))
> +                        (or ,mem ,(car (cdr-safe x)))))))
>             kwords)
>       ,@(append
>          (and (not (eq other-keys t))
> --
> 2.11.0
>
> From 9898718b96109c5815b81f8dc8868de4d86db453 Mon Sep 17 00:00:00 2001
> From: Tino Calancha <tino.calancha@gmail.com>
> Date: Sun, 5 Feb 2017 00:04:12 +0900
> Subject: [PATCH 2/3] cl--check-bound-indices: Check for unbound indices
>
> Throw and error if the user inputs an index higher than the sequence length
> or if :start value is higher than :end value.
> * lisp/emacs-lisp/cl-seq.el (cl--check-bound-indices): New defun.
> (cl-reduce, cl-fill, cl-replace, cl-remove, cl-delete)
> (cl--delete-duplicates, cl-substitute, cl-nsubstitute, cl-position)
> (cl-count, cl-mismatch, cl-search): Use it.
> ---
>  lisp/emacs-lisp/cl-seq.el | 51
> +++++++++++++++++++++++++++++++++++++++++++++++
>  1 file changed, 51 insertions(+)
>
> diff --git a/lisp/emacs-lisp/cl-seq.el b/lisp/emacs-lisp/cl-seq.el
> index 558adf22fe..0cb7ac7bdd 100644
> --- a/lisp/emacs-lisp/cl-seq.el
> +++ b/lisp/emacs-lisp/cl-seq.el
> @@ -42,6 +42,7 @@
>  ;;; Code:
>
>  (require 'cl-lib)
> +(eval-when-compile (require 'subr-x))
>
>  ;; Keyword parsing.
>  ;; This is special-cased here so that we can compile
> @@ -118,6 +119,44 @@ cl-test
>  (defvar cl-if) (defvar cl-if-not)
>  (defvar cl-key)
>
> +;; Throw an error when :start or :end are > sequence length.
> +(defmacro cl--check-bound-indices (cl-seq1 cl-seq2 cl-keys)
> +  (declare (debug t))
> +  `(let ((len1 (length ,cl-seq1))
> +         (len2 (and ,cl-seq2 (length ,cl-seq2)))
> +         (kwds (list :start1 :start2 :start :end1 :end2 :end))
> +         (keys ,cl-keys)
> +         alist)
> +     (while keys
> +       (when (and (memq (car keys) kwds)
> +                  (string-match ":\\(start\\|end\\)\\([1-2]?\\)\\'"
> +                                (symbol-name (car keys))))
> +         (delq (car keys) kwds) ; Ignore succesive equal keys.
> +         (let* ((idx (match-string 2 (symbol-name (car keys))))
> +                (len (if (equal idx "2") len2 len1)))
> +           (when (integerp (cadr keys))
> +             (push (cons (car keys) (cadr keys)) alist))
> +           (when (> (cadr keys) len)
> +             (error "Wrong bounding indices '%s', %s > (length %s), %s"
> +                    (substring (symbol-name (car keys)) 1)
> +                    (cadr keys)
> +                    (concat "cl-seq" idx)
> +                    len))))
> +       (setq keys (cddr keys)))
> +     ;; Check :start value > :end value.
> +     (mapc (lambda (x)
> +             (and-let* ((start (alist-get (car x) alist))
> +                        (end (alist-get (cdr x) alist)))
> +               (when (> start end)
> +                 (error "Bad bounding indices '%s', '%s': %d, %d"
> +                        (substring (symbol-name (car x)) 1)
> +                        (substring (symbol-name (cdr x)) 1)
> +                        start
> +                        end))))
> +           (list (cons :start :end)
> +                 (cons :start1 :end1)
> +                 (cons :start2 :end2)))))
> +
>  ;;;###autoload
>  (defun cl-reduce (cl-func cl-seq &rest cl-keys)
>    "Reduce two-argument FUNCTION across SEQ.
> @@ -134,6 +173,7 @@ cl-reduce
>
>  \n(fn FUNCTION SEQ [KEYWORD VALUE]...)"
>    (cl--parsing-keywords (:from-end (:start 0) :end :initial-value :key) ()
> +    (cl--check-bound-indices cl-seq nil cl-keys)
>      (or (listp cl-seq) (setq cl-seq (append cl-seq nil)))
>      (setq cl-seq (cl-subseq cl-seq cl-start cl-end))
>      (if cl-from-end (setq cl-seq (nreverse cl-seq)))
> @@ -155,6 +195,7 @@ cl-fill
>  \nKeywords supported:  :start :end
>  \n(fn SEQ ITEM [KEYWORD VALUE]...)"
>    (cl--parsing-keywords ((:start 0) :end) ()
> +    (cl--check-bound-indices cl-seq nil cl-keys)
>      (if (listp cl-seq)
>         (let ((p (nthcdr cl-start cl-seq))
>               (n (and cl-end (- cl-end cl-start))))
> @@ -176,6 +217,7 @@ cl-replace
>  \nKeywords supported:  :start1 :end1 :start2 :end2
>  \n(fn SEQ1 SEQ2 [KEYWORD VALUE]...)"
>    (cl--parsing-keywords ((:start1 0) :end1 (:start2 0) :end2) ()
> +    (cl--check-bound-indices cl-seq1 cl-seq2 cl-keys)
>      (if (and (eq cl-seq1 cl-seq2) (<= cl-start2 cl-start1))
>         (or (= cl-start1 cl-start2)
>             (let* ((cl-len (length cl-seq1))
> @@ -225,6 +267,7 @@ cl-remove
>  \n(fn ITEM SEQ [KEYWORD VALUE]...)"
>    (cl--parsing-keywords (:test :test-not :key :if :if-not :count :from-end
>                         (:start 0) :end) ()
> +    (cl--check-bound-indices cl-seq nil cl-keys)
>      (let ((len (length cl-seq)))
>        (if (<= (or cl-count (setq cl-count len)) 0)
>         cl-seq
> @@ -289,6 +332,7 @@ cl-delete
>  \n(fn ITEM SEQ [KEYWORD VALUE]...)"
>    (cl--parsing-keywords (:test :test-not :key :if :if-not :count :from-end
>                         (:start 0) :end) ()
> +    (cl--check-bound-indices cl-seq nil cl-keys)
>      (let ((len (length cl-seq)))
>        (if (<= (or cl-count (setq cl-count len)) 0)
>         cl-seq
> @@ -362,6 +406,7 @@ cl--delete-duplicates
>            ;; We need to parse :if, otherwise `cl-if' is unbound.
>            (:test :test-not :key (:start 0) :end :from-end :if)
>           ()
> +        (cl--check-bound-indices cl-seq nil cl-keys)
>         (if cl-from-end
>             (let ((cl-p (nthcdr cl-start cl-seq)) cl-i)
>               (setq cl-end (- (or cl-end (length cl-seq)) cl-start))
> @@ -407,6 +452,7 @@ cl-substitute
>  \n(fn NEW OLD SEQ [KEYWORD VALUE]...)"
>    (cl--parsing-keywords (:test :test-not :key :if :if-not :count
>                         (:start 0) :end :from-end) ()
> +    (cl--check-bound-indices cl-seq nil cl-keys)
>      (if (or (eq cl-old cl-new)
>             (<= (or cl-count (setq cl-from-end nil
>                                    cl-count (length cl-seq))) 0))
> @@ -448,6 +494,7 @@ cl-nsubstitute
>  \n(fn NEW OLD SEQ [KEYWORD VALUE]...)"
>    (cl--parsing-keywords (:test :test-not :key :if :if-not :count
>                         (:start 0) :end :from-end) ()
> +    (cl--check-bound-indices cl-seq nil cl-keys)
>      (let ((len (length cl-seq)))
>        (or (eq cl-old cl-new) (<= (or cl-count (setq cl-count len)) 0)
>           (if (and (listp cl-seq) (or (not cl-from-end) (> cl-count (/ len
> 2))))
> @@ -524,6 +571,7 @@ cl-position
>  \n(fn ITEM SEQ [KEYWORD VALUE]...)"
>    (cl--parsing-keywords (:test :test-not :key :if :if-not
>                         (:start 0) :end :from-end) ()
> +    (cl--check-bound-indices cl-seq nil cl-keys)
>      (cl--position cl-item cl-seq cl-start cl-end cl-from-end)))
>
>  (defun cl--position (cl-item cl-seq cl-start &optional cl-end cl-from-end)
> @@ -568,6 +616,7 @@ cl-count
>  \nKeywords supported:  :test :test-not :key :start :end
>  \n(fn ITEM SEQ [KEYWORD VALUE]...)"
>    (cl--parsing-keywords (:test :test-not :key :if :if-not (:start 0)
> :end) ()
> +    (cl--check-bound-indices cl-seq nil cl-keys)
>      (let ((cl-count 0) cl-x)
>        (or cl-end (setq cl-end (length cl-seq)))
>        (if (consp cl-seq) (setq cl-seq (nthcdr cl-start cl-seq)))
> @@ -600,6 +649,7 @@ cl-mismatch
>  \n(fn SEQ1 SEQ2 [KEYWORD VALUE]...)"
>    (cl--parsing-keywords (:test :test-not :key :from-end
>                         (:start1 0) :end1 (:start2 0) :end2) ()
> +    (cl--check-bound-indices cl-seq1 cl-seq2 cl-keys)
>      (or cl-end1 (setq cl-end1 (length cl-seq1)))
>      (or cl-end2 (setq cl-end2 (length cl-seq2)))
>      (if cl-from-end
> @@ -631,6 +681,7 @@ cl-search
>  \n(fn SEQ1 SEQ2 [KEYWORD VALUE]...)"
>    (cl--parsing-keywords (:test :test-not :key :from-end
>                         (:start1 0) :end1 (:start2 0) :end2) ()
> +    (cl--check-bound-indices cl-seq1 cl-seq2 cl-keys)
>      (or cl-end1 (setq cl-end1 (length cl-seq1)))
>      (or cl-end2 (setq cl-end2 (length cl-seq2)))
>      (if (>= cl-start1 cl-end1)
> --
> 2.11.0
>
> From 50cce9d03fa96d622d20f2218ec5ddcfeb246425 Mon Sep 17 00:00:00 2001
> From: Tino Calancha <tino.calancha@gmail.com>
> Date: Sun, 5 Feb 2017 01:06:01 +0900
> Subject: [PATCH 3/3] * test/lisp/emacs-lisp/cl-seq-tests.el
>  (cl-seq-check-bounds): New test.
>
> ---
>  test/lisp/emacs-lisp/cl-seq-tests.el | 91
> ++++++++++++++++++++++++++++++++++++
>  1 file changed, 91 insertions(+)
>
> diff --git a/test/lisp/emacs-lisp/cl-seq-tests.el
> b/test/lisp/emacs-lisp/cl-seq-tests.el
> index 61e3d72033..f5ecfe4b92 100644
> --- a/test/lisp/emacs-lisp/cl-seq-tests.el
> +++ b/test/lisp/emacs-lisp/cl-seq-tests.el
> @@ -294,6 +294,7 @@ cl-seq--with-side-effects
>
>  (ert-deftest cl-seq-test-bug24264 ()
>    "Test for http://debbugs.gnu.org/24264 ."
> +  :tags '(:expensive-test)
>    (let ((list  (append (make-list 8000005 1) '(8)))
>          (list2 (make-list 8000005 2)))
>      (should (cl-position 8 list))
> @@ -302,6 +303,96 @@ cl-seq--with-side-effects
>      (should (equal '(2 8) (last (cl-replace list list2) 2)))
>      (should (equal '(1 1) (last (cl-fill list 1) 2)))))
>
> +(ert-deftest cl-seq-check-bounds ()
> +  (let ((lst (list 1 2 3))
> +        (lst2 (list 'a 'b 'c))
> +        ;; t means pass, nil means fails.
> +        (tests '("((lambda (x y) (cl-reduce #'max x :start 1)) . t)"
> +                 "((lambda (x y) (cl-reduce #'max x :start -1)))"
> +                 "((lambda (x y) (cl-reduce #'max x :start 4)))"
> +                 "((lambda (x y) (cl-reduce #'max x :start 2 :end 1)))"
> +                 ;;
> +                 "((lambda (x y) (cl-fill x 'b :start 3)) . t)"
> +                 "((lambda (x y) (cl-fill x 'b :start 4)))"
> +                 "((lambda (x y) (cl-fill x 'b :start -1)))"
> +                 "((lambda (x y) (cl-fill x 'b :start 2 :end 1)))"
> +                 ;;
> +                 "((lambda (x y) (cl-replace x y :start1 3)) . t)"
> +                 "((lambda (x y) (cl-replace x y :start2 3)) . t)"
> +                 "((lambda (x y) (cl-replace x y :start1 4)))"
> +                 "((lambda (x y) (cl-replace x y :start2 4)))"
> +                 "((lambda (x y) (cl-replace x y :start1 -1)))"
> +                 "((lambda (x y) (cl-replace x y :start2 -1)))"
> +                 "((lambda (x y) (cl-replace x y :start1 2 :end1 1)))"
> +                 "((lambda (x y) (cl-replace x y :start2 2 :end2 1)))"
> +                 ;;
> +                 "((lambda (x y) (cl-remove nil x :start 3)) . t)"
> +                 "((lambda (x y) (cl-remove nil x :start 4)))"
> +                 "((lambda (x y) (cl-remove nil x :start -1)))"
> +                 "((lambda (x y) (cl-remove nil x :start 2 :end 1)))"
> +                 ;;
> +                 "((lambda (x y) (cl-delete nil x :start 3)) . t)"
> +                 "((lambda (x y) (cl-delete nil x :start 4)))"
> +                 "((lambda (x y) (cl-delete nil x :start -1)))"
> +                 "((lambda (x y) (cl-delete nil x :start 2 :end 1)))"
> +                 ;;
> +                 "((lambda (x y) (cl-remove-duplicates x :start 3)) . t)"
> +                 "((lambda (x y) (cl-remove-duplicates x :start 4)))"
> +                 "((lambda (x y) (cl-remove-duplicates x :start -1)))"
> +                 "((lambda (x y) (cl-remove-duplicates x :start 2 :end
> 1)))"
> +                 ;;
> +                 "((lambda (x y) (cl-substitute 'foo 2 x :start 3)) . t)"
> +                 "((lambda (x y) (cl-substitute 'foo 2 x :start 4)))"
> +                 "((lambda (x y) (cl-substitute 'foo 2 x :start -1)))"
> +                 "((lambda (x y) (cl-substitute 'foo 2 x :start 2 :end
> 1)))"
> +                 ;;
> +                 "((lambda (x y) (cl-nsubstitute 'foo 2 x :start 3)) . t)"
> +                 "((lambda (x y) (cl-nsubstitute 'foo 2 x :start 4)))"
> +                 "((lambda (x y) (cl-nsubstitute 'foo 2 x :start -1)))"
> +                 "((lambda (x y) (cl-nsubstitute 'foo 2 x :start 2 :end
> 1)))"
> +                 ;;
> +                 "((lambda (x y) (cl-position 2 x :start 4)))"
> +                 "((lambda (x y) (cl-position 2 x :start -1)))"
> +                 "((lambda (x y) (cl-position 2 x :start 2 :end 1)))"
> +                 ;;
> +                 "((lambda (x y) (cl-count 2 x :start 3)) . t)"
> +                 "((lambda (x y) (cl-count 2 x :start 4)))"
> +                 "((lambda (x y) (cl-count 2 x :start -1)))"
> +                 "((lambda (x y) (cl-count 2 x :start 2 :end 1)))"
> +                 ;;
> +                 "((lambda (x y) (cl-mismatch x x :start1 1 :start2 3)) .
> t)"
> +                 "((lambda (x y) (cl-mismatch x x :start1 1 :start2 4)))"
> +                 "((lambda (x y) (cl-mismatch x x :start1 4 :start2 1)))"
> +                 "((lambda (x y) (cl-mismatch x x :start1 -1 :start2 1)))"
> +                 "((lambda (x y) (cl-mismatch x x :start1 1 :start2 -1)))"
> +                 "((lambda (x y) (cl-mismatch x x :start1 2 :end1 1)))"
> +                 "((lambda (x y) (cl-mismatch x x :start2 2 :end2 1)))"
> +                 ;;
> +                 "((lambda (x y) (cl-search x x :start1 3 :start2 3)) .
> t)"
> +                 "((lambda (x y) (cl-search x x :start1 4 :start2 4)))"
> +                 "((lambda (x y) (cl-search x x :start1 -1 :start2 3)))"
> +                 "((lambda (x y) (cl-search x x :start1 1 :start2 -1)))"
> +                 "((lambda (x y) (cl-search x x :start1 2 :end1 1)))"
> +                 "((lambda (x y) (cl-search x x :start2 2 :end2 1)))"
> +                 ;;
> +                 "((lambda (x y) (cl-subseq x -1)) . t)"
> +                 "((lambda (x y) (cl-subseq x -2 -1)) . t)"
> +                 "((lambda (x y) (cl-subseq x -4)))"
> +                 "((lambda (x y) (cl-subseq x 2 1)))")))
> +    (dolist (limit '("start" "end"))
> +      (dolist (x tests)
> +        (let ((form
> +               (car
> +                (read-from-string
> +                 (cond ((string-match ":start\\([1-2]?\\) \\([0-9-]+\\)
> :end\\([1-2]?\\)" x)
> +                        x)
> +                       ((string= limit "start") x)
> +                       (t
> +                        (replace-regexp-in-string "start" limit x)))))))
> +          (if (cdr form)
> +              (should (funcall (car form) lst lst2))
> +            (should-error (funcall (car form) lst lst2))))))))
> +
>
>  (provide 'cl-seq-tests)
>  ;;; cl-seq-tests.el ends here
> --
> 2.11.0
>
>
> ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
> In GNU Emacs 26.0.50.1 (x86_64-pc-linux-gnu, GTK+ Version 3.22.7)
>  of 2017-02-04
> Repository revision: ff4dd0d39c3f5dfb8f4988f840c2c05621db32db
>
>

[-- Attachment #2: Type: text/html, Size: 29044 bytes --]

^ permalink raw reply	[flat|nested] 14+ messages in thread

* Re: [PATCH] Bound index checks in cl-seq functions
  2017-02-04 16:40 [PATCH] Bound index checks in cl-seq functions Tino Calancha
  2017-02-04 20:42 ` Philipp Stephani
@ 2017-02-04 20:51 ` Noam Postavsky
  2017-02-05  7:11   ` [PATCH v2] " Tino Calancha
  2017-02-10  7:43 ` Tino Calancha
  2017-02-12 21:26 ` [PATCH] " Johan Bockgård
  3 siblings, 1 reply; 14+ messages in thread
From: Noam Postavsky @ 2017-02-04 20:51 UTC (permalink / raw)
  To: Tino Calancha; +Cc: Emacs developers

On Sat, Feb 4, 2017 at 11:40 AM, Tino Calancha <tino.calancha@gmail.com> wrote:
> +;; Throw an error when :start or :end are > sequence length.
> +(defmacro cl--check-bound-indices (cl-seq1 cl-seq2 cl-keys)

I wonder why this is a macro and not a function.



^ permalink raw reply	[flat|nested] 14+ messages in thread

* Re: [PATCH v2] Bound index checks in cl-seq functions
  2017-02-04 20:51 ` Noam Postavsky
@ 2017-02-05  7:11   ` Tino Calancha
  2017-02-05 14:56     ` [PATCH v3] " Tino Calancha
  2017-02-05 16:11     ` [PATCH v2] " Clément Pit-Claudel
  0 siblings, 2 replies; 14+ messages in thread
From: Tino Calancha @ 2017-02-05  7:11 UTC (permalink / raw)
  To: Noam Postavsky; +Cc: Philipp Stephani, Emacs developers, tino.calancha

Noam Postavsky <npostavs@users.sourceforge.net> writes:

> On Sat, Feb 4, 2017 at 11:40 AM, Tino Calancha <tino.calancha@gmail.com> wrote:
>> +;; Throw an error when :start or :end are > sequence length.
>> +(defmacro cl--check-bound-indices (cl-seq1 cl-seq2 cl-keys)
>
> I wonder why this is a macro and not a function.
Me either.  The commit message say defun.  A possible case of Emacs
goblins...
Updated the patch with:
I) `cl--check-bound-indices' is a function.
II) `cl--check-bound-indices' returns on success the sequence[s]
    length[s], so that callers don't need to recompute them.
III) Updated manual and added NEWS entry.
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
From 72500280762c9fcf4ad13c1285732f2ac5f5e89d Mon Sep 17 00:00:00 2001
From: Tino Calancha <tino.calancha@gmail.com>
Date: Sun, 5 Feb 2017 15:56:25 +0900
Subject: [PATCH] Check for out-of-range indices in cl-seq function

Throw and error if the user inputs out of range indices
or if :start value is higher than :end value.
* lisp/emacs-lisp/cl-seq.el (cl--parsing-keywords):
Check for negative indices.
(cl--check-bound-indices): New defun; check for indices > seq length,
or start index > end index.
(cl-reduce, cl-fill, cl-replace, cl-remove, cl-delete)
(cl--delete-duplicates, cl-substitute, cl-nsubstitute, cl-position)
(cl-count, cl-mismatch, cl-search): Use it.
* doc/misc/cl.texi (Sequence Basics): Update manual.
; * etc/NEWS: Announce the change.
* test/lisp/emacs-lisp/cl-seq-tests.el (cl-seq-check-bounds): New test.
---
 doc/misc/cl.texi                     |   4 +-
 etc/NEWS                             |   4 +
 lisp/emacs-lisp/cl-seq.el            | 355 +++++++++++++++++++++--------------
 test/lisp/emacs-lisp/cl-seq-tests.el |  90 +++++++++
 4 files changed, 307 insertions(+), 146 deletions(-)

diff --git a/doc/misc/cl.texi b/doc/misc/cl.texi
index 8baa0bd88c..6f387f5cbb 100644
--- a/doc/misc/cl.texi
+++ b/doc/misc/cl.texi
@@ -3247,7 +3247,9 @@ Sequence Basics
 (exclusive) are affected by the operation.  The @var{end} argument
 may be passed @code{nil} to signify the length of the sequence;
 otherwise, both @var{start} and @var{end} must be integers, with
-@code{0 <= @var{start} <= @var{end} <= (length @var{seq})}.
+@code{0 <= @var{start} <= @var{end} <= (length @var{seq})}.  Emacs
+signals an error when this condition is not true, except for
+@code{cl-subseq} which allows negative indices.
 If the function takes two sequence arguments, the limits are
 defined by keywords @code{:start1} and @code{:end1} for the first,
 and @code{:start2} and @code{:end2} for the second.
diff --git a/etc/NEWS b/etc/NEWS
index cbf2b70c82..b14f8eca5a 100644
--- a/etc/NEWS
+++ b/etc/NEWS
@@ -706,6 +706,10 @@ processes on exit.
 * Incompatible Lisp Changes in Emacs 26.1
 
 +++
+** CL sequence functions now throw errors when the input indices
+are out of range, or if :start index is higher than :end index.
+
++++
 ** Resizing a frame no longer runs 'window-configuration-change-hook'.
 Put your function on 'window-size-change-functions' instead.
 
diff --git a/lisp/emacs-lisp/cl-seq.el b/lisp/emacs-lisp/cl-seq.el
index 67ff1a00bd..314448ce93 100644
--- a/lisp/emacs-lisp/cl-seq.el
+++ b/lisp/emacs-lisp/cl-seq.el
@@ -42,6 +42,7 @@
 ;;; Code:
 
 (require 'cl-lib)
+(eval-when-compile (require 'subr-x))
 
 ;; Keyword parsing.
 ;; This is special-cased here so that we can compile
@@ -59,7 +60,13 @@ cl--parsing-keywords
                    (setq mem `(and ,mem (setq cl-if ,mem) t)))
                (list (intern
                       (format "cl-%s" (substring (symbol-name var) 1)))
-                     (if (consp x) `(or ,mem ,(car (cdr x))) mem))))
+                     `(if (and (string-match ":\\(start\\|end\\)" (symbol-name ,var))
+                               (integerp (or ,mem ,(car (cdr-safe x))))
+                               (not (natnump (or ,mem ,(car (cdr-safe x))))))
+                          (error "Wrong negative index '%s': natnump, %s"
+                                 (substring (symbol-name ,var) 1)
+                                 (or ,mem ,(car (cdr-safe x))))
+                        (or ,mem ,(car (cdr-safe x)))))))
            kwords)
      ,@(append
         (and (not (eq other-keys t))
@@ -112,6 +119,49 @@ cl-test
 (defvar cl-if) (defvar cl-if-not)
 (defvar cl-key)
 
+;; Throw an error when :start or :end are > sequence length,
+;; or if :start > :end.
+;; If CL-SEQ2 is nil, then return (length cl-seq1), otherwise
+;; return (cons (length cl-seq1) (length cl-seq2)).
+(defun cl--check-bound-indices (cl-seq1 cl-seq2 cl-keys)
+  (let ((len1 (length cl-seq1))
+        (len2 (and cl-seq2 (length cl-seq2)))
+        (kwds (list :start1 :start2 :start :end1 :end2 :end))
+        alist)
+    (while cl-keys
+      (when (and (memq (car cl-keys) kwds)
+                 (string-match ":\\(start\\|end\\)\\([1-2]?\\)\\'"
+                               (symbol-name (car cl-keys))))
+        (delq (car cl-keys) kwds) ; Ignore succesive equal keys.
+        (let* ((idx (match-string 2 (symbol-name (car cl-keys))))
+               (len (if (equal idx "2") len2 len1)))
+          (when (integerp (cadr cl-keys))
+            (push (cons (car cl-keys) (cadr cl-keys)) alist))
+          (when (> (cadr cl-keys) len)
+            (error "Wrong bounding indices '%s', %s > (length %s), %s"
+                   (substring (symbol-name (car cl-keys)) 1)
+                   (cadr cl-keys)
+                   (concat "cl-seq" idx)
+                   len))))
+      (setq cl-keys (cddr cl-keys)))
+    ;; Check :start value > :end value.
+    (mapc (lambda (x)
+            (and-let* ((start (alist-get (car x) alist))
+                       (end (alist-get (cdr x) alist))
+                       (bad-indices (> start end)))
+              (error "Bad bounding indices '%s', '%s': %d, %d"
+                     (substring (symbol-name (car x)) 1)
+                     (substring (symbol-name (cdr x)) 1)
+                     start
+                     end)))
+          (list (cons :start :end)
+                (cons :start1 :end1)
+                (cons :start2 :end2)))
+    ;; Return sequence lengths.
+    (if len2
+        (cons len1 len2)
+      len1)))
+
 ;;;###autoload
 (defun cl-reduce (cl-func cl-seq &rest cl-keys)
   "Reduce two-argument FUNCTION across SEQ.
@@ -128,6 +178,7 @@ cl-reduce
 
 \n(fn FUNCTION SEQ [KEYWORD VALUE]...)"
   (cl--parsing-keywords (:from-end (:start 0) :end :initial-value :key) ()
+    (cl--check-bound-indices cl-seq nil cl-keys)
     (or (listp cl-seq) (setq cl-seq (append cl-seq nil)))
     (setq cl-seq (cl-subseq cl-seq cl-start cl-end))
     (if cl-from-end (setq cl-seq (nreverse cl-seq)))
@@ -149,18 +200,19 @@ cl-fill
 \nKeywords supported:  :start :end
 \n(fn SEQ ITEM [KEYWORD VALUE]...)"
   (cl--parsing-keywords ((:start 0) :end) ()
-    (if (listp cl-seq)
-	(let ((p (nthcdr cl-start cl-seq))
-	      (n (and cl-end (- cl-end cl-start))))
-	  (while (and p (or (null n) (>= (cl-decf n) 0)))
-	    (setcar p cl-item)
-	    (setq p (cdr p))))
-      (or cl-end (setq cl-end (length cl-seq)))
-      (if (and (= cl-start 0) (= cl-end (length cl-seq)))
-	  (fillarray cl-seq cl-item)
-	(while (< cl-start cl-end)
-	  (aset cl-seq cl-start cl-item)
-	  (setq cl-start (1+ cl-start)))))
+    (let ((len (cl--check-bound-indices cl-seq nil cl-keys)))
+      (if (listp cl-seq)
+          (let ((p (nthcdr cl-start cl-seq))
+                (n (and cl-end (- cl-end cl-start))))
+            (while (and p (or (null n) (>= (cl-decf n) 0)))
+              (setcar p cl-item)
+              (setq p (cdr p))))
+        (or cl-end (setq cl-end len))
+        (if (and (= cl-start 0) (= cl-end len))
+            (fillarray cl-seq cl-item)
+          (while (< cl-start cl-end)
+            (aset cl-seq cl-start cl-item)
+            (setq cl-start (1+ cl-start))))))
     cl-seq))
 
 ;;;###autoload
@@ -170,44 +222,47 @@ cl-replace
 \nKeywords supported:  :start1 :end1 :start2 :end2
 \n(fn SEQ1 SEQ2 [KEYWORD VALUE]...)"
   (cl--parsing-keywords ((:start1 0) :end1 (:start2 0) :end2) ()
-    (if (and (eq cl-seq1 cl-seq2) (<= cl-start2 cl-start1))
-	(or (= cl-start1 cl-start2)
-	    (let* ((cl-len (length cl-seq1))
-		   (cl-n (min (- (or cl-end1 cl-len) cl-start1)
-			      (- (or cl-end2 cl-len) cl-start2))))
-	      (while (>= (setq cl-n (1- cl-n)) 0)
-		(setf (elt cl-seq1 (+ cl-start1 cl-n))
-			    (elt cl-seq2 (+ cl-start2 cl-n))))))
-      (if (listp cl-seq1)
-	  (let ((cl-p1 (nthcdr cl-start1 cl-seq1))
-		(cl-n1 (and cl-end1 (- cl-end1 cl-start1))))
-	    (if (listp cl-seq2)
-		(let ((cl-p2 (nthcdr cl-start2 cl-seq2))
-		      (cl-n (cond ((and cl-n1 cl-end2)
-				   (min cl-n1 (- cl-end2 cl-start2)))
-				  ((and cl-n1 (null cl-end2)) cl-n1)
-				  ((and (null cl-n1) cl-end2) (- cl-end2 cl-start2)))))
-		  (while (and cl-p1 cl-p2 (or (null cl-n) (>= (cl-decf cl-n) 0)))
-		    (setcar cl-p1 (car cl-p2))
-		    (setq cl-p1 (cdr cl-p1) cl-p2 (cdr cl-p2))))
-	      (setq cl-end2 (if (null cl-n1)
-				(or cl-end2 (length cl-seq2))
-			      (min (or cl-end2 (length cl-seq2))
-				   (+ cl-start2 cl-n1))))
-	      (while (and cl-p1 (< cl-start2 cl-end2))
-		(setcar cl-p1 (aref cl-seq2 cl-start2))
-		(setq cl-p1 (cdr cl-p1) cl-start2 (1+ cl-start2)))))
-	(setq cl-end1 (min (or cl-end1 (length cl-seq1))
-			   (+ cl-start1 (- (or cl-end2 (length cl-seq2))
-					   cl-start2))))
-	(if (listp cl-seq2)
-	    (let ((cl-p2 (nthcdr cl-start2 cl-seq2)))
-	      (while (< cl-start1 cl-end1)
-		(aset cl-seq1 cl-start1 (car cl-p2))
-		(setq cl-p2 (cdr cl-p2) cl-start1 (1+ cl-start1))))
-	  (while (< cl-start1 cl-end1)
-	    (aset cl-seq1 cl-start1 (aref cl-seq2 cl-start2))
-	    (setq cl-start2 (1+ cl-start2) cl-start1 (1+ cl-start1))))))
+    (let*  ((lens (cl--check-bound-indices cl-seq1 cl-seq2 cl-keys))
+            (len1 (car lens))
+            (len2 (cdr lens)))
+      (if (and (eq cl-seq1 cl-seq2) (<= cl-start2 cl-start1))
+          (or (= cl-start1 cl-start2)
+              (let* ((cl-len len1)
+                     (cl-n (min (- (or cl-end1 cl-len) cl-start1)
+                                (- (or cl-end2 cl-len) cl-start2))))
+                (while (>= (setq cl-n (1- cl-n)) 0)
+                  (setf (elt cl-seq1 (+ cl-start1 cl-n))
+                        (elt cl-seq2 (+ cl-start2 cl-n))))))
+        (if (listp cl-seq1)
+            (let ((cl-p1 (nthcdr cl-start1 cl-seq1))
+                  (cl-n1 (and cl-end1 (- cl-end1 cl-start1))))
+              (if (listp cl-seq2)
+                  (let ((cl-p2 (nthcdr cl-start2 cl-seq2))
+                        (cl-n (cond ((and cl-n1 cl-end2)
+                                     (min cl-n1 (- cl-end2 cl-start2)))
+                                    ((and cl-n1 (null cl-end2)) cl-n1)
+                                    ((and (null cl-n1) cl-end2) (- cl-end2 cl-start2)))))
+                    (while (and cl-p1 cl-p2 (or (null cl-n) (>= (cl-decf cl-n) 0)))
+                      (setcar cl-p1 (car cl-p2))
+                      (setq cl-p1 (cdr cl-p1) cl-p2 (cdr cl-p2))))
+                (setq cl-end2 (if (null cl-n1)
+                                  (or cl-end2 len2)
+                                (min (or cl-end2 len2)
+                                     (+ cl-start2 cl-n1))))
+                (while (and cl-p1 (< cl-start2 cl-end2))
+                  (setcar cl-p1 (aref cl-seq2 cl-start2))
+                  (setq cl-p1 (cdr cl-p1) cl-start2 (1+ cl-start2)))))
+          (setq cl-end1 (min (or cl-end1 len1)
+                             (+ cl-start1 (- (or cl-end2 len2)
+                                             cl-start2))))
+          (if (listp cl-seq2)
+              (let ((cl-p2 (nthcdr cl-start2 cl-seq2)))
+                (while (< cl-start1 cl-end1)
+                  (aset cl-seq1 cl-start1 (car cl-p2))
+                  (setq cl-p2 (cdr cl-p2) cl-start1 (1+ cl-start1))))
+            (while (< cl-start1 cl-end1)
+              (aset cl-seq1 cl-start1 (aref cl-seq2 cl-start2))
+              (setq cl-start2 (1+ cl-start2) cl-start1 (1+ cl-start1)))))))
     cl-seq1))
 
 ;;;###autoload
@@ -219,7 +274,7 @@ cl-remove
 \n(fn ITEM SEQ [KEYWORD VALUE]...)"
   (cl--parsing-keywords (:test :test-not :key :if :if-not :count :from-end
 			(:start 0) :end) ()
-    (let ((len (length cl-seq)))
+    (let ((len (cl--check-bound-indices cl-seq nil cl-keys)))
       (if (<= (or cl-count (setq cl-count len)) 0)
 	cl-seq
         (if (or (nlistp cl-seq) (and cl-from-end (< cl-count (/ len 2))))
@@ -283,7 +338,7 @@ cl-delete
 \n(fn ITEM SEQ [KEYWORD VALUE]...)"
   (cl--parsing-keywords (:test :test-not :key :if :if-not :count :from-end
 			(:start 0) :end) ()
-    (let ((len (length cl-seq)))
+    (let ((len (cl--check-bound-indices cl-seq nil cl-keys)))
       (if (<= (or cl-count (setq cl-count len)) 0)
 	cl-seq
       (if (listp cl-seq)
@@ -356,39 +411,40 @@ cl--delete-duplicates
           ;; We need to parse :if, otherwise `cl-if' is unbound.
           (:test :test-not :key (:start 0) :end :from-end :if)
 	  ()
-	(if cl-from-end
-	    (let ((cl-p (nthcdr cl-start cl-seq)) cl-i)
-	      (setq cl-end (- (or cl-end (length cl-seq)) cl-start))
-	      (while (> cl-end 1)
-		(setq cl-i 0)
-		(while (setq cl-i (cl--position (cl--check-key (car cl-p))
-                                                (cdr cl-p) cl-i (1- cl-end)))
-		  (if cl-copy (setq cl-seq (copy-sequence cl-seq)
-				    cl-p (nthcdr cl-start cl-seq) cl-copy nil))
-		  (let ((cl-tail (nthcdr cl-i cl-p)))
-		    (setcdr cl-tail (cdr (cdr cl-tail))))
-		  (setq cl-end (1- cl-end)))
-		(setq cl-p (cdr cl-p) cl-end (1- cl-end)
-		      cl-start (1+ cl-start)))
-	      cl-seq)
-	  (setq cl-end (- (or cl-end (length cl-seq)) cl-start))
-	  (while (and (cdr cl-seq) (= cl-start 0) (> cl-end 1)
-		      (cl--position (cl--check-key (car cl-seq))
-                                    (cdr cl-seq) 0 (1- cl-end)))
-	    (setq cl-seq (cdr cl-seq) cl-end (1- cl-end)))
-	  (let ((cl-p (if (> cl-start 0) (nthcdr (1- cl-start) cl-seq)
-			(setq cl-end (1- cl-end) cl-start 1) cl-seq)))
-	    (while (and (cdr (cdr cl-p)) (> cl-end 1))
-	      (if (cl--position (cl--check-key (car (cdr cl-p)))
-                                (cdr (cdr cl-p)) 0 (1- cl-end))
-		  (progn
-		    (if cl-copy (setq cl-seq (copy-sequence cl-seq)
-				      cl-p (nthcdr (1- cl-start) cl-seq)
-				      cl-copy nil))
-		    (setcdr cl-p (cdr (cdr cl-p))))
-		(setq cl-p (cdr cl-p)))
-	      (setq cl-end (1- cl-end) cl-start (1+ cl-start)))
-	    cl-seq)))
+        (let ((len (cl--check-bound-indices cl-seq nil cl-keys)))
+          (if cl-from-end
+              (let ((cl-p (nthcdr cl-start cl-seq)) cl-i)
+                (setq cl-end (- (or cl-end len) cl-start))
+                (while (> cl-end 1)
+                  (setq cl-i 0)
+                  (while (setq cl-i (cl--position (cl--check-key (car cl-p))
+                                                  (cdr cl-p) cl-i (1- cl-end)))
+                    (if cl-copy (setq cl-seq (copy-sequence cl-seq)
+                                      cl-p (nthcdr cl-start cl-seq) cl-copy nil))
+                    (let ((cl-tail (nthcdr cl-i cl-p)))
+                      (setcdr cl-tail (cdr (cdr cl-tail))))
+                    (setq cl-end (1- cl-end)))
+                  (setq cl-p (cdr cl-p) cl-end (1- cl-end)
+                        cl-start (1+ cl-start)))
+                cl-seq)
+            (setq cl-end (- (or cl-end len) cl-start))
+            (while (and (cdr cl-seq) (= cl-start 0) (> cl-end 1)
+                        (cl--position (cl--check-key (car cl-seq))
+                                      (cdr cl-seq) 0 (1- cl-end)))
+              (setq cl-seq (cdr cl-seq) cl-end (1- cl-end)))
+            (let ((cl-p (if (> cl-start 0) (nthcdr (1- cl-start) cl-seq)
+                          (setq cl-end (1- cl-end) cl-start 1) cl-seq)))
+              (while (and (cdr (cdr cl-p)) (> cl-end 1))
+                (if (cl--position (cl--check-key (car (cdr cl-p)))
+                                  (cdr (cdr cl-p)) 0 (1- cl-end))
+                    (progn
+                      (if cl-copy (setq cl-seq (copy-sequence cl-seq)
+                                        cl-p (nthcdr (1- cl-start) cl-seq)
+                                        cl-copy nil))
+                      (setcdr cl-p (cdr (cdr cl-p))))
+                  (setq cl-p (cdr cl-p)))
+                (setq cl-end (1- cl-end) cl-start (1+ cl-start)))
+              cl-seq))))
     (let ((cl-res (cl--delete-duplicates (append cl-seq nil) cl-keys nil)))
       (if (stringp cl-seq) (concat cl-res) (vconcat cl-res)))))
 
@@ -400,21 +456,22 @@ cl-substitute
 \nKeywords supported:  :test :test-not :key :count :start :end :from-end
 \n(fn NEW OLD SEQ [KEYWORD VALUE]...)"
   (cl--parsing-keywords (:test :test-not :key :if :if-not :count
-			(:start 0) :end :from-end) ()
-    (if (or (eq cl-old cl-new)
-	    (<= (or cl-count (setq cl-from-end nil
-				   cl-count (length cl-seq))) 0))
-	cl-seq
-      (let ((cl-i (cl--position cl-old cl-seq cl-start cl-end)))
-	(if (not cl-i)
-	    cl-seq
-	  (setq cl-seq (copy-sequence cl-seq))
-	  (unless cl-from-end
-	    (setf (elt cl-seq cl-i) cl-new)
-	    (cl-incf cl-i)
-	    (cl-decf cl-count))
-	  (apply 'cl-nsubstitute cl-new cl-old cl-seq :count cl-count
-		 :start cl-i cl-keys))))))
+                               (:start 0) :end :from-end) ()
+    (let ((len (cl--check-bound-indices cl-seq nil cl-keys)))
+      (if (or (eq cl-old cl-new)
+              (<= (or cl-count (setq cl-from-end nil
+                                     cl-count len)) 0))
+          cl-seq
+        (let ((cl-i (cl--position cl-old cl-seq cl-start cl-end)))
+          (if (not cl-i)
+              cl-seq
+            (setq cl-seq (copy-sequence cl-seq))
+            (unless cl-from-end
+              (setf (elt cl-seq cl-i) cl-new)
+              (cl-incf cl-i)
+              (cl-decf cl-count))
+            (apply 'cl-nsubstitute cl-new cl-old cl-seq :count cl-count
+                   :start cl-i cl-keys)))))))
 
 ;;;###autoload
 (defun cl-substitute-if (cl-new cl-pred cl-list &rest cl-keys)
@@ -442,7 +499,7 @@ cl-nsubstitute
 \n(fn NEW OLD SEQ [KEYWORD VALUE]...)"
   (cl--parsing-keywords (:test :test-not :key :if :if-not :count
 			(:start 0) :end :from-end) ()
-    (let ((len (length cl-seq)))
+    (let ((len (cl--check-bound-indices cl-seq nil cl-keys)))
       (or (eq cl-old cl-new) (<= (or cl-count (setq cl-count len)) 0)
 	  (if (and (listp cl-seq) (or (not cl-from-end) (> cl-count (/ len 2))))
 	    (let ((cl-p (nthcdr cl-start cl-seq)))
@@ -518,7 +575,8 @@ cl-position
 \n(fn ITEM SEQ [KEYWORD VALUE]...)"
   (cl--parsing-keywords (:test :test-not :key :if :if-not
 			(:start 0) :end :from-end) ()
-    (cl--position cl-item cl-seq cl-start cl-end cl-from-end)))
+    (let ((len (cl--check-bound-indices cl-seq nil cl-keys)))
+      (cl--position cl-item cl-seq cl-start (or cl-end len) cl-from-end))))
 
 (defun cl--position (cl-item cl-seq cl-start &optional cl-end cl-from-end)
   (if (listp cl-seq)
@@ -562,8 +620,9 @@ cl-count
 \nKeywords supported:  :test :test-not :key :start :end
 \n(fn ITEM SEQ [KEYWORD VALUE]...)"
   (cl--parsing-keywords (:test :test-not :key :if :if-not (:start 0) :end) ()
-    (let ((cl-count 0) cl-x)
-      (or cl-end (setq cl-end (length cl-seq)))
+    (let ((len (cl--check-bound-indices cl-seq nil cl-keys))
+          (cl-count 0) cl-x)
+      (or cl-end (setq cl-end len))
       (if (consp cl-seq) (setq cl-seq (nthcdr cl-start cl-seq)))
       (while (< cl-start cl-end)
 	(setq cl-x (if (consp cl-seq) (pop cl-seq) (aref cl-seq cl-start)))
@@ -593,28 +652,31 @@ cl-mismatch
 \nKeywords supported:  :test :test-not :key :start1 :end1 :start2 :end2 :from-end
 \n(fn SEQ1 SEQ2 [KEYWORD VALUE]...)"
   (cl--parsing-keywords (:test :test-not :key :from-end
-			(:start1 0) :end1 (:start2 0) :end2) ()
-    (or cl-end1 (setq cl-end1 (length cl-seq1)))
-    (or cl-end2 (setq cl-end2 (length cl-seq2)))
-    (if cl-from-end
-	(progn
-	  (while (and (< cl-start1 cl-end1) (< cl-start2 cl-end2)
-		      (cl--check-match (elt cl-seq1 (1- cl-end1))
-				      (elt cl-seq2 (1- cl-end2))))
-	    (setq cl-end1 (1- cl-end1) cl-end2 (1- cl-end2)))
-	  (and (or (< cl-start1 cl-end1) (< cl-start2 cl-end2))
-	       (1- cl-end1)))
-      (let ((cl-p1 (and (listp cl-seq1) (nthcdr cl-start1 cl-seq1)))
-	    (cl-p2 (and (listp cl-seq2) (nthcdr cl-start2 cl-seq2))))
-	(while (and (< cl-start1 cl-end1) (< cl-start2 cl-end2)
-		    (cl--check-match (if cl-p1 (car cl-p1)
-				      (aref cl-seq1 cl-start1))
-				    (if cl-p2 (car cl-p2)
-				      (aref cl-seq2 cl-start2))))
-	  (setq cl-p1 (cdr cl-p1) cl-p2 (cdr cl-p2)
-		cl-start1 (1+ cl-start1) cl-start2 (1+ cl-start2)))
-	(and (or (< cl-start1 cl-end1) (< cl-start2 cl-end2))
-	     cl-start1)))))
+                               (:start1 0) :end1 (:start2 0) :end2) ()
+    (let* ((lens (cl--check-bound-indices cl-seq1 cl-seq2 cl-keys))
+           (len1 (car lens))
+           (len2 (cdr lens)))
+      (or cl-end1 (setq cl-end1 len1))
+      (or cl-end2 (setq cl-end2 len2))
+      (if cl-from-end
+          (progn
+            (while (and (< cl-start1 cl-end1) (< cl-start2 cl-end2)
+                        (cl--check-match (elt cl-seq1 (1- cl-end1))
+                                         (elt cl-seq2 (1- cl-end2))))
+              (setq cl-end1 (1- cl-end1) cl-end2 (1- cl-end2)))
+            (and (or (< cl-start1 cl-end1) (< cl-start2 cl-end2))
+                 (1- cl-end1)))
+        (let ((cl-p1 (and (listp cl-seq1) (nthcdr cl-start1 cl-seq1)))
+              (cl-p2 (and (listp cl-seq2) (nthcdr cl-start2 cl-seq2))))
+          (while (and (< cl-start1 cl-end1) (< cl-start2 cl-end2)
+                      (cl--check-match (if cl-p1 (car cl-p1)
+                                         (aref cl-seq1 cl-start1))
+                                       (if cl-p2 (car cl-p2)
+                                         (aref cl-seq2 cl-start2))))
+            (setq cl-p1 (cdr cl-p1) cl-p2 (cdr cl-p2)
+                  cl-start1 (1+ cl-start1) cl-start2 (1+ cl-start2)))
+          (and (or (< cl-start1 cl-end1) (< cl-start2 cl-end2))
+               cl-start1))))))
 
 ;;;###autoload
 (defun cl-search (cl-seq1 cl-seq2 &rest cl-keys)
@@ -624,24 +686,27 @@ cl-search
 \nKeywords supported:  :test :test-not :key :start1 :end1 :start2 :end2 :from-end
 \n(fn SEQ1 SEQ2 [KEYWORD VALUE]...)"
   (cl--parsing-keywords (:test :test-not :key :from-end
-			(:start1 0) :end1 (:start2 0) :end2) ()
-    (or cl-end1 (setq cl-end1 (length cl-seq1)))
-    (or cl-end2 (setq cl-end2 (length cl-seq2)))
-    (if (>= cl-start1 cl-end1)
-	(if cl-from-end cl-end2 cl-start2)
-      (let* ((cl-len (- cl-end1 cl-start1))
-	     (cl-first (cl--check-key (elt cl-seq1 cl-start1)))
-	     (cl-if nil) cl-pos)
-	(setq cl-end2 (- cl-end2 (1- cl-len)))
-	(while (and (< cl-start2 cl-end2)
-		    (setq cl-pos (cl--position cl-first cl-seq2
-                                               cl-start2 cl-end2 cl-from-end))
-		    (apply 'cl-mismatch cl-seq1 cl-seq2
-			   :start1 (1+ cl-start1) :end1 cl-end1
-			   :start2 (1+ cl-pos) :end2 (+ cl-pos cl-len)
-			   :from-end nil cl-keys))
-	  (if cl-from-end (setq cl-end2 cl-pos) (setq cl-start2 (1+ cl-pos))))
-	(and (< cl-start2 cl-end2) cl-pos)))))
+                               (:start1 0) :end1 (:start2 0) :end2) ()
+    (let* ((lens (cl--check-bound-indices cl-seq1 cl-seq2 cl-keys))
+           (len1 (car lens))
+           (len2 (cdr lens)))
+      (or cl-end1 (setq cl-end1 len1))
+      (or cl-end2 (setq cl-end2 len2))
+      (if (>= cl-start1 cl-end1)
+          (if cl-from-end cl-end2 cl-start2)
+        (let* ((cl-len (- cl-end1 cl-start1))
+               (cl-first (cl--check-key (elt cl-seq1 cl-start1)))
+               (cl-if nil) cl-pos)
+          (setq cl-end2 (- cl-end2 (1- cl-len)))
+          (while (and (< cl-start2 cl-end2)
+                      (setq cl-pos (cl--position cl-first cl-seq2
+                                                 cl-start2 cl-end2 cl-from-end))
+                      (apply 'cl-mismatch cl-seq1 cl-seq2
+                             :start1 (1+ cl-start1) :end1 cl-end1
+                             :start2 (1+ cl-pos) :end2 (+ cl-pos cl-len)
+                             :from-end nil cl-keys))
+            (if cl-from-end (setq cl-end2 cl-pos) (setq cl-start2 (1+ cl-pos))))
+          (and (< cl-start2 cl-end2) cl-pos))))))
 
 ;;;###autoload
 (defun cl-sort (cl-seq cl-pred &rest cl-keys)
diff --git a/test/lisp/emacs-lisp/cl-seq-tests.el b/test/lisp/emacs-lisp/cl-seq-tests.el
index 61e3d72033..7a7693a515 100644
--- a/test/lisp/emacs-lisp/cl-seq-tests.el
+++ b/test/lisp/emacs-lisp/cl-seq-tests.el
@@ -302,6 +302,96 @@ cl-seq--with-side-effects
     (should (equal '(2 8) (last (cl-replace list list2) 2)))
     (should (equal '(1 1) (last (cl-fill list 1) 2)))))
 
+(ert-deftest cl-seq-check-bounds ()
+  (let ((lst (list 1 2 3))
+        (lst2 (list 'a 'b 'c))
+        ;; t means pass, nil means fails.
+        (tests '("((lambda (x y) (cl-reduce #'max x :start 1)) . t)"
+                 "((lambda (x y) (cl-reduce #'max x :start -1)))"
+                 "((lambda (x y) (cl-reduce #'max x :start 4)))"
+                 "((lambda (x y) (cl-reduce #'max x :start 2 :end 1)))"
+                 ;;
+                 "((lambda (x y) (cl-fill x 'b :start 3)) . t)"
+                 "((lambda (x y) (cl-fill x 'b :start 4)))"
+                 "((lambda (x y) (cl-fill x 'b :start -1)))"
+                 "((lambda (x y) (cl-fill x 'b :start 2 :end 1)))"
+                 ;;
+                 "((lambda (x y) (cl-replace x y :start1 3)) . t)"
+                 "((lambda (x y) (cl-replace x y :start2 3)) . t)"
+                 "((lambda (x y) (cl-replace x y :start1 4)))"
+                 "((lambda (x y) (cl-replace x y :start2 4)))"
+                 "((lambda (x y) (cl-replace x y :start1 -1)))"
+                 "((lambda (x y) (cl-replace x y :start2 -1)))"
+                 "((lambda (x y) (cl-replace x y :start1 2 :end1 1)))"
+                 "((lambda (x y) (cl-replace x y :start2 2 :end2 1)))"
+                 ;;
+                 "((lambda (x y) (cl-remove nil x :start 3)) . t)"
+                 "((lambda (x y) (cl-remove nil x :start 4)))"
+                 "((lambda (x y) (cl-remove nil x :start -1)))"
+                 "((lambda (x y) (cl-remove nil x :start 2 :end 1)))"
+                 ;;
+                 "((lambda (x y) (cl-delete nil x :start 3)) . t)"
+                 "((lambda (x y) (cl-delete nil x :start 4)))"
+                 "((lambda (x y) (cl-delete nil x :start -1)))"
+                 "((lambda (x y) (cl-delete nil x :start 2 :end 1)))"
+                 ;;
+                 "((lambda (x y) (cl-remove-duplicates x :start 3)) . t)"
+                 "((lambda (x y) (cl-remove-duplicates x :start 4)))"
+                 "((lambda (x y) (cl-remove-duplicates x :start -1)))"
+                 "((lambda (x y) (cl-remove-duplicates x :start 2 :end 1)))"
+                 ;;
+                 "((lambda (x y) (cl-substitute 'foo 2 x :start 3)) . t)"
+                 "((lambda (x y) (cl-substitute 'foo 2 x :start 4)))"
+                 "((lambda (x y) (cl-substitute 'foo 2 x :start -1)))"
+                 "((lambda (x y) (cl-substitute 'foo 2 x :start 2 :end 1)))"
+                 ;;
+                 "((lambda (x y) (cl-nsubstitute 'foo 2 x :start 3)) . t)"
+                 "((lambda (x y) (cl-nsubstitute 'foo 2 x :start 4)))"
+                 "((lambda (x y) (cl-nsubstitute 'foo 2 x :start -1)))"
+                 "((lambda (x y) (cl-nsubstitute 'foo 2 x :start 2 :end 1)))"
+                 ;;
+                 "((lambda (x y) (cl-position 2 x :start 4)))"
+                 "((lambda (x y) (cl-position 2 x :start -1)))"
+                 "((lambda (x y) (cl-position 2 x :start 2 :end 1)))"
+                 ;;
+                 "((lambda (x y) (cl-count 2 x :start 3)) . t)"
+                 "((lambda (x y) (cl-count 2 x :start 4)))"
+                 "((lambda (x y) (cl-count 2 x :start -1)))"
+                 "((lambda (x y) (cl-count 2 x :start 2 :end 1)))"
+                 ;;
+                 "((lambda (x y) (cl-mismatch x x :start1 1 :start2 3)) . t)"
+                 "((lambda (x y) (cl-mismatch x x :start1 1 :start2 4)))"
+                 "((lambda (x y) (cl-mismatch x x :start1 4 :start2 1)))"
+                 "((lambda (x y) (cl-mismatch x x :start1 -1 :start2 1)))"
+                 "((lambda (x y) (cl-mismatch x x :start1 1 :start2 -1)))"
+                 "((lambda (x y) (cl-mismatch x x :start1 2 :end1 1)))"
+                 "((lambda (x y) (cl-mismatch x x :start2 2 :end2 1)))"
+                 ;;
+                 "((lambda (x y) (cl-search x x :start1 3 :start2 3)) . t)"
+                 "((lambda (x y) (cl-search x x :start1 4 :start2 4)))"
+                 "((lambda (x y) (cl-search x x :start1 -1 :start2 3)))"
+                 "((lambda (x y) (cl-search x x :start1 1 :start2 -1)))"
+                 "((lambda (x y) (cl-search x x :start1 2 :end1 1)))"
+                 "((lambda (x y) (cl-search x x :start2 2 :end2 1)))"
+                 ;;
+                 "((lambda (x y) (cl-subseq x -1)) . t)"
+                 "((lambda (x y) (cl-subseq x -2 -1)) . t)"
+                 "((lambda (x y) (cl-subseq x -4)))"
+                 "((lambda (x y) (cl-subseq x 2 1)))")))
+    (dolist (limit '("start" "end"))
+      (dolist (x tests)
+        (let ((form
+               (car
+                (read-from-string
+                 (cond ((string-match ":start\\([1-2]?\\) \\([0-9-]+\\) :end\\([1-2]?\\)" x)
+                        x)
+                       ((string= limit "start") x)
+                       (t
+                        (replace-regexp-in-string "start" limit x)))))))
+          (if (cdr form)
+              (should (funcall (car form) lst lst2))
+            (should-error (funcall (car form) lst lst2))))))))
+
 
 (provide 'cl-seq-tests)
 ;;; cl-seq-tests.el ends here
-- 
2.11.0

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
In GNU Emacs 26.0.50.1 (x86_64-pc-linux-gnu, GTK+ Version 3.22.7)
 of 2017-02-05
Repository revision: 148100d98319499f0ac6f57b8be08cbd14884a5c




^ permalink raw reply related	[flat|nested] 14+ messages in thread

* Re: [PATCH v3] Bound index checks in cl-seq functions
  2017-02-05  7:11   ` [PATCH v2] " Tino Calancha
@ 2017-02-05 14:56     ` Tino Calancha
  2017-02-05 16:11     ` [PATCH v2] " Clément Pit-Claudel
  1 sibling, 0 replies; 14+ messages in thread
From: Tino Calancha @ 2017-02-05 14:56 UTC (permalink / raw)
  To: Emacs developers

Tino Calancha <tino.calancha@gmail.com> writes:

I have updated the patch with following 3 dots.
(New patch below).

I) Fix mistake in previous patch:
(Following is a diff with respect patch #2)

diff --git a/lisp/emacs-lisp/cl-seq.el b/lisp/emacs-lisp/cl-seq.el
index 314448ce93..e531fcb511 100644
--- a/lisp/emacs-lisp/cl-seq.el
+++ b/lisp/emacs-lisp/cl-seq.el
@@ -136,13 +136,13 @@ cl--check-bound-indices
         (let* ((idx (match-string 2 (symbol-name (car cl-keys))))
                (len (if (equal idx "2") len2 len1)))
           (when (integerp (cadr cl-keys))
-            (push (cons (car cl-keys) (cadr cl-keys)) alist))
+            (push (cons (car cl-keys) (cadr cl-keys)) alist)
             (when (> (cadr cl-keys) len)
               (error "Wrong bounding indices '%s', %s > (length %s), %s"
                      (substring (symbol-name (car cl-keys)) 1)
                      (cadr cl-keys)
                      (concat "cl-seq" idx)
-                   len))))
+                     len)))))

II) Added test for `cl-delete-duplicates'.

III) Amend `edmacro-format-keys' to prevent `cl-mismatch' call
     with :end1/:end2 > (length seq)
My goblins informed me after some tests at
test/lisp/kmacro-tests.el
started to fail.  The new patch fix that.

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
From 50ab342682911f343e70f58b5eb30b24981a783a Mon Sep 17 00:00:00 2001
From: Tino Calancha <tino.calancha@gmail.com>
Date: Sun, 5 Feb 2017 23:39:51 +0900
Subject: [PATCH 1/2] Check for out-of-range indices in cl-seq function

Throw and error if the user inputs out of range indices
or if :start value is higher than :end value.
* lisp/emacs-lisp/cl-seq.el (cl--parsing-keywords):
Check for negative indices.
(cl--check-bound-indices): New defun; check for indices > seq length,
or start index > end index.
(cl-reduce, cl-fill, cl-replace, cl-remove, cl-delete)
(cl--delete-duplicates, cl-substitute, cl-nsubstitute, cl-position)
(cl-count, cl-mismatch, cl-search): Use it.
* doc/misc/cl.texi (Sequence Basics): Update manual.
; * etc/NEWS: Announce the change.
* test/lisp/emacs-lisp/cl-seq-tests.el (cl-seq-check-bounds): New test.
---
 doc/misc/cl.texi                     |   4 +-
 etc/NEWS                             |   4 +
 lisp/emacs-lisp/cl-seq.el            | 355 +++++++++++++++++++++--------------
 test/lisp/emacs-lisp/cl-seq-tests.el |  95 ++++++++++
 4 files changed, 312 insertions(+), 146 deletions(-)

diff --git a/doc/misc/cl.texi b/doc/misc/cl.texi
index 8baa0bd88c..6f387f5cbb 100644
--- a/doc/misc/cl.texi
+++ b/doc/misc/cl.texi
@@ -3247,7 +3247,9 @@ Sequence Basics
 (exclusive) are affected by the operation.  The @var{end} argument
 may be passed @code{nil} to signify the length of the sequence;
 otherwise, both @var{start} and @var{end} must be integers, with
-@code{0 <= @var{start} <= @var{end} <= (length @var{seq})}.
+@code{0 <= @var{start} <= @var{end} <= (length @var{seq})}.  Emacs
+signals an error when this condition is not true, except for
+@code{cl-subseq} which allows negative indices.
 If the function takes two sequence arguments, the limits are
 defined by keywords @code{:start1} and @code{:end1} for the first,
 and @code{:start2} and @code{:end2} for the second.
diff --git a/etc/NEWS b/etc/NEWS
index cbf2b70c82..b14f8eca5a 100644
--- a/etc/NEWS
+++ b/etc/NEWS
@@ -706,6 +706,10 @@ processes on exit.
 * Incompatible Lisp Changes in Emacs 26.1
 
 +++
+** CL sequence functions now throw errors when the input indices
+are out of range, or if :start index is higher than :end index.
+
++++
 ** Resizing a frame no longer runs 'window-configuration-change-hook'.
 Put your function on 'window-size-change-functions' instead.
 
diff --git a/lisp/emacs-lisp/cl-seq.el b/lisp/emacs-lisp/cl-seq.el
index 67ff1a00bd..e531fcb511 100644
--- a/lisp/emacs-lisp/cl-seq.el
+++ b/lisp/emacs-lisp/cl-seq.el
@@ -42,6 +42,7 @@
 ;;; Code:
 
 (require 'cl-lib)
+(eval-when-compile (require 'subr-x))
 
 ;; Keyword parsing.
 ;; This is special-cased here so that we can compile
@@ -59,7 +60,13 @@ cl--parsing-keywords
                    (setq mem `(and ,mem (setq cl-if ,mem) t)))
                (list (intern
                       (format "cl-%s" (substring (symbol-name var) 1)))
-                     (if (consp x) `(or ,mem ,(car (cdr x))) mem))))
+                     `(if (and (string-match ":\\(start\\|end\\)" (symbol-name ,var))
+                               (integerp (or ,mem ,(car (cdr-safe x))))
+                               (not (natnump (or ,mem ,(car (cdr-safe x))))))
+                          (error "Wrong negative index '%s': natnump, %s"
+                                 (substring (symbol-name ,var) 1)
+                                 (or ,mem ,(car (cdr-safe x))))
+                        (or ,mem ,(car (cdr-safe x)))))))
            kwords)
      ,@(append
         (and (not (eq other-keys t))
@@ -112,6 +119,49 @@ cl-test
 (defvar cl-if) (defvar cl-if-not)
 (defvar cl-key)
 
+;; Throw an error when :start or :end are > sequence length,
+;; or if :start > :end.
+;; If CL-SEQ2 is nil, then return (length cl-seq1), otherwise
+;; return (cons (length cl-seq1) (length cl-seq2)).
+(defun cl--check-bound-indices (cl-seq1 cl-seq2 cl-keys)
+  (let ((len1 (length cl-seq1))
+        (len2 (and cl-seq2 (length cl-seq2)))
+        (kwds (list :start1 :start2 :start :end1 :end2 :end))
+        alist)
+    (while cl-keys
+      (when (and (memq (car cl-keys) kwds)
+                 (string-match ":\\(start\\|end\\)\\([1-2]?\\)\\'"
+                               (symbol-name (car cl-keys))))
+        (delq (car cl-keys) kwds) ; Ignore succesive equal keys.
+        (let* ((idx (match-string 2 (symbol-name (car cl-keys))))
+               (len (if (equal idx "2") len2 len1)))
+          (when (integerp (cadr cl-keys))
+            (push (cons (car cl-keys) (cadr cl-keys)) alist)
+            (when (> (cadr cl-keys) len)
+              (error "Wrong bounding indices '%s', %s > (length %s), %s"
+                     (substring (symbol-name (car cl-keys)) 1)
+                     (cadr cl-keys)
+                     (concat "cl-seq" idx)
+                     len)))))
+      (setq cl-keys (cddr cl-keys)))
+    ;; Check :start value > :end value.
+    (mapc (lambda (x)
+            (and-let* ((start (alist-get (car x) alist))
+                       (end (alist-get (cdr x) alist))
+                       (bad-indices (> start end)))
+              (error "Bad bounding indices '%s', '%s': %d, %d"
+                     (substring (symbol-name (car x)) 1)
+                     (substring (symbol-name (cdr x)) 1)
+                     start
+                     end)))
+          (list (cons :start :end)
+                (cons :start1 :end1)
+                (cons :start2 :end2)))
+    ;; Return sequence lengths.
+    (if len2
+        (cons len1 len2)
+      len1)))
+
 ;;;###autoload
 (defun cl-reduce (cl-func cl-seq &rest cl-keys)
   "Reduce two-argument FUNCTION across SEQ.
@@ -128,6 +178,7 @@ cl-reduce
 
 \n(fn FUNCTION SEQ [KEYWORD VALUE]...)"
   (cl--parsing-keywords (:from-end (:start 0) :end :initial-value :key) ()
+    (cl--check-bound-indices cl-seq nil cl-keys)
     (or (listp cl-seq) (setq cl-seq (append cl-seq nil)))
     (setq cl-seq (cl-subseq cl-seq cl-start cl-end))
     (if cl-from-end (setq cl-seq (nreverse cl-seq)))
@@ -149,18 +200,19 @@ cl-fill
 \nKeywords supported:  :start :end
 \n(fn SEQ ITEM [KEYWORD VALUE]...)"
   (cl--parsing-keywords ((:start 0) :end) ()
-    (if (listp cl-seq)
-	(let ((p (nthcdr cl-start cl-seq))
-	      (n (and cl-end (- cl-end cl-start))))
-	  (while (and p (or (null n) (>= (cl-decf n) 0)))
-	    (setcar p cl-item)
-	    (setq p (cdr p))))
-      (or cl-end (setq cl-end (length cl-seq)))
-      (if (and (= cl-start 0) (= cl-end (length cl-seq)))
-	  (fillarray cl-seq cl-item)
-	(while (< cl-start cl-end)
-	  (aset cl-seq cl-start cl-item)
-	  (setq cl-start (1+ cl-start)))))
+    (let ((len (cl--check-bound-indices cl-seq nil cl-keys)))
+      (if (listp cl-seq)
+          (let ((p (nthcdr cl-start cl-seq))
+                (n (and cl-end (- cl-end cl-start))))
+            (while (and p (or (null n) (>= (cl-decf n) 0)))
+              (setcar p cl-item)
+              (setq p (cdr p))))
+        (or cl-end (setq cl-end len))
+        (if (and (= cl-start 0) (= cl-end len))
+            (fillarray cl-seq cl-item)
+          (while (< cl-start cl-end)
+            (aset cl-seq cl-start cl-item)
+            (setq cl-start (1+ cl-start))))))
     cl-seq))
 
 ;;;###autoload
@@ -170,44 +222,47 @@ cl-replace
 \nKeywords supported:  :start1 :end1 :start2 :end2
 \n(fn SEQ1 SEQ2 [KEYWORD VALUE]...)"
   (cl--parsing-keywords ((:start1 0) :end1 (:start2 0) :end2) ()
-    (if (and (eq cl-seq1 cl-seq2) (<= cl-start2 cl-start1))
-	(or (= cl-start1 cl-start2)
-	    (let* ((cl-len (length cl-seq1))
-		   (cl-n (min (- (or cl-end1 cl-len) cl-start1)
-			      (- (or cl-end2 cl-len) cl-start2))))
-	      (while (>= (setq cl-n (1- cl-n)) 0)
-		(setf (elt cl-seq1 (+ cl-start1 cl-n))
-			    (elt cl-seq2 (+ cl-start2 cl-n))))))
-      (if (listp cl-seq1)
-	  (let ((cl-p1 (nthcdr cl-start1 cl-seq1))
-		(cl-n1 (and cl-end1 (- cl-end1 cl-start1))))
-	    (if (listp cl-seq2)
-		(let ((cl-p2 (nthcdr cl-start2 cl-seq2))
-		      (cl-n (cond ((and cl-n1 cl-end2)
-				   (min cl-n1 (- cl-end2 cl-start2)))
-				  ((and cl-n1 (null cl-end2)) cl-n1)
-				  ((and (null cl-n1) cl-end2) (- cl-end2 cl-start2)))))
-		  (while (and cl-p1 cl-p2 (or (null cl-n) (>= (cl-decf cl-n) 0)))
-		    (setcar cl-p1 (car cl-p2))
-		    (setq cl-p1 (cdr cl-p1) cl-p2 (cdr cl-p2))))
-	      (setq cl-end2 (if (null cl-n1)
-				(or cl-end2 (length cl-seq2))
-			      (min (or cl-end2 (length cl-seq2))
-				   (+ cl-start2 cl-n1))))
-	      (while (and cl-p1 (< cl-start2 cl-end2))
-		(setcar cl-p1 (aref cl-seq2 cl-start2))
-		(setq cl-p1 (cdr cl-p1) cl-start2 (1+ cl-start2)))))
-	(setq cl-end1 (min (or cl-end1 (length cl-seq1))
-			   (+ cl-start1 (- (or cl-end2 (length cl-seq2))
-					   cl-start2))))
-	(if (listp cl-seq2)
-	    (let ((cl-p2 (nthcdr cl-start2 cl-seq2)))
-	      (while (< cl-start1 cl-end1)
-		(aset cl-seq1 cl-start1 (car cl-p2))
-		(setq cl-p2 (cdr cl-p2) cl-start1 (1+ cl-start1))))
-	  (while (< cl-start1 cl-end1)
-	    (aset cl-seq1 cl-start1 (aref cl-seq2 cl-start2))
-	    (setq cl-start2 (1+ cl-start2) cl-start1 (1+ cl-start1))))))
+    (let*  ((lens (cl--check-bound-indices cl-seq1 cl-seq2 cl-keys))
+            (len1 (car lens))
+            (len2 (cdr lens)))
+      (if (and (eq cl-seq1 cl-seq2) (<= cl-start2 cl-start1))
+          (or (= cl-start1 cl-start2)
+              (let* ((cl-len len1)
+                     (cl-n (min (- (or cl-end1 cl-len) cl-start1)
+                                (- (or cl-end2 cl-len) cl-start2))))
+                (while (>= (setq cl-n (1- cl-n)) 0)
+                  (setf (elt cl-seq1 (+ cl-start1 cl-n))
+                        (elt cl-seq2 (+ cl-start2 cl-n))))))
+        (if (listp cl-seq1)
+            (let ((cl-p1 (nthcdr cl-start1 cl-seq1))
+                  (cl-n1 (and cl-end1 (- cl-end1 cl-start1))))
+              (if (listp cl-seq2)
+                  (let ((cl-p2 (nthcdr cl-start2 cl-seq2))
+                        (cl-n (cond ((and cl-n1 cl-end2)
+                                     (min cl-n1 (- cl-end2 cl-start2)))
+                                    ((and cl-n1 (null cl-end2)) cl-n1)
+                                    ((and (null cl-n1) cl-end2) (- cl-end2 cl-start2)))))
+                    (while (and cl-p1 cl-p2 (or (null cl-n) (>= (cl-decf cl-n) 0)))
+                      (setcar cl-p1 (car cl-p2))
+                      (setq cl-p1 (cdr cl-p1) cl-p2 (cdr cl-p2))))
+                (setq cl-end2 (if (null cl-n1)
+                                  (or cl-end2 len2)
+                                (min (or cl-end2 len2)
+                                     (+ cl-start2 cl-n1))))
+                (while (and cl-p1 (< cl-start2 cl-end2))
+                  (setcar cl-p1 (aref cl-seq2 cl-start2))
+                  (setq cl-p1 (cdr cl-p1) cl-start2 (1+ cl-start2)))))
+          (setq cl-end1 (min (or cl-end1 len1)
+                             (+ cl-start1 (- (or cl-end2 len2)
+                                             cl-start2))))
+          (if (listp cl-seq2)
+              (let ((cl-p2 (nthcdr cl-start2 cl-seq2)))
+                (while (< cl-start1 cl-end1)
+                  (aset cl-seq1 cl-start1 (car cl-p2))
+                  (setq cl-p2 (cdr cl-p2) cl-start1 (1+ cl-start1))))
+            (while (< cl-start1 cl-end1)
+              (aset cl-seq1 cl-start1 (aref cl-seq2 cl-start2))
+              (setq cl-start2 (1+ cl-start2) cl-start1 (1+ cl-start1)))))))
     cl-seq1))
 
 ;;;###autoload
@@ -219,7 +274,7 @@ cl-remove
 \n(fn ITEM SEQ [KEYWORD VALUE]...)"
   (cl--parsing-keywords (:test :test-not :key :if :if-not :count :from-end
 			(:start 0) :end) ()
-    (let ((len (length cl-seq)))
+    (let ((len (cl--check-bound-indices cl-seq nil cl-keys)))
       (if (<= (or cl-count (setq cl-count len)) 0)
 	cl-seq
         (if (or (nlistp cl-seq) (and cl-from-end (< cl-count (/ len 2))))
@@ -283,7 +338,7 @@ cl-delete
 \n(fn ITEM SEQ [KEYWORD VALUE]...)"
   (cl--parsing-keywords (:test :test-not :key :if :if-not :count :from-end
 			(:start 0) :end) ()
-    (let ((len (length cl-seq)))
+    (let ((len (cl--check-bound-indices cl-seq nil cl-keys)))
       (if (<= (or cl-count (setq cl-count len)) 0)
 	cl-seq
       (if (listp cl-seq)
@@ -356,39 +411,40 @@ cl--delete-duplicates
           ;; We need to parse :if, otherwise `cl-if' is unbound.
           (:test :test-not :key (:start 0) :end :from-end :if)
 	  ()
-	(if cl-from-end
-	    (let ((cl-p (nthcdr cl-start cl-seq)) cl-i)
-	      (setq cl-end (- (or cl-end (length cl-seq)) cl-start))
-	      (while (> cl-end 1)
-		(setq cl-i 0)
-		(while (setq cl-i (cl--position (cl--check-key (car cl-p))
-                                                (cdr cl-p) cl-i (1- cl-end)))
-		  (if cl-copy (setq cl-seq (copy-sequence cl-seq)
-				    cl-p (nthcdr cl-start cl-seq) cl-copy nil))
-		  (let ((cl-tail (nthcdr cl-i cl-p)))
-		    (setcdr cl-tail (cdr (cdr cl-tail))))
-		  (setq cl-end (1- cl-end)))
-		(setq cl-p (cdr cl-p) cl-end (1- cl-end)
-		      cl-start (1+ cl-start)))
-	      cl-seq)
-	  (setq cl-end (- (or cl-end (length cl-seq)) cl-start))
-	  (while (and (cdr cl-seq) (= cl-start 0) (> cl-end 1)
-		      (cl--position (cl--check-key (car cl-seq))
-                                    (cdr cl-seq) 0 (1- cl-end)))
-	    (setq cl-seq (cdr cl-seq) cl-end (1- cl-end)))
-	  (let ((cl-p (if (> cl-start 0) (nthcdr (1- cl-start) cl-seq)
-			(setq cl-end (1- cl-end) cl-start 1) cl-seq)))
-	    (while (and (cdr (cdr cl-p)) (> cl-end 1))
-	      (if (cl--position (cl--check-key (car (cdr cl-p)))
-                                (cdr (cdr cl-p)) 0 (1- cl-end))
-		  (progn
-		    (if cl-copy (setq cl-seq (copy-sequence cl-seq)
-				      cl-p (nthcdr (1- cl-start) cl-seq)
-				      cl-copy nil))
-		    (setcdr cl-p (cdr (cdr cl-p))))
-		(setq cl-p (cdr cl-p)))
-	      (setq cl-end (1- cl-end) cl-start (1+ cl-start)))
-	    cl-seq)))
+        (let ((len (cl--check-bound-indices cl-seq nil cl-keys)))
+          (if cl-from-end
+              (let ((cl-p (nthcdr cl-start cl-seq)) cl-i)
+                (setq cl-end (- (or cl-end len) cl-start))
+                (while (> cl-end 1)
+                  (setq cl-i 0)
+                  (while (setq cl-i (cl--position (cl--check-key (car cl-p))
+                                                  (cdr cl-p) cl-i (1- cl-end)))
+                    (if cl-copy (setq cl-seq (copy-sequence cl-seq)
+                                      cl-p (nthcdr cl-start cl-seq) cl-copy nil))
+                    (let ((cl-tail (nthcdr cl-i cl-p)))
+                      (setcdr cl-tail (cdr (cdr cl-tail))))
+                    (setq cl-end (1- cl-end)))
+                  (setq cl-p (cdr cl-p) cl-end (1- cl-end)
+                        cl-start (1+ cl-start)))
+                cl-seq)
+            (setq cl-end (- (or cl-end len) cl-start))
+            (while (and (cdr cl-seq) (= cl-start 0) (> cl-end 1)
+                        (cl--position (cl--check-key (car cl-seq))
+                                      (cdr cl-seq) 0 (1- cl-end)))
+              (setq cl-seq (cdr cl-seq) cl-end (1- cl-end)))
+            (let ((cl-p (if (> cl-start 0) (nthcdr (1- cl-start) cl-seq)
+                          (setq cl-end (1- cl-end) cl-start 1) cl-seq)))
+              (while (and (cdr (cdr cl-p)) (> cl-end 1))
+                (if (cl--position (cl--check-key (car (cdr cl-p)))
+                                  (cdr (cdr cl-p)) 0 (1- cl-end))
+                    (progn
+                      (if cl-copy (setq cl-seq (copy-sequence cl-seq)
+                                        cl-p (nthcdr (1- cl-start) cl-seq)
+                                        cl-copy nil))
+                      (setcdr cl-p (cdr (cdr cl-p))))
+                  (setq cl-p (cdr cl-p)))
+                (setq cl-end (1- cl-end) cl-start (1+ cl-start)))
+              cl-seq))))
     (let ((cl-res (cl--delete-duplicates (append cl-seq nil) cl-keys nil)))
       (if (stringp cl-seq) (concat cl-res) (vconcat cl-res)))))
 
@@ -400,21 +456,22 @@ cl-substitute
 \nKeywords supported:  :test :test-not :key :count :start :end :from-end
 \n(fn NEW OLD SEQ [KEYWORD VALUE]...)"
   (cl--parsing-keywords (:test :test-not :key :if :if-not :count
-			(:start 0) :end :from-end) ()
-    (if (or (eq cl-old cl-new)
-	    (<= (or cl-count (setq cl-from-end nil
-				   cl-count (length cl-seq))) 0))
-	cl-seq
-      (let ((cl-i (cl--position cl-old cl-seq cl-start cl-end)))
-	(if (not cl-i)
-	    cl-seq
-	  (setq cl-seq (copy-sequence cl-seq))
-	  (unless cl-from-end
-	    (setf (elt cl-seq cl-i) cl-new)
-	    (cl-incf cl-i)
-	    (cl-decf cl-count))
-	  (apply 'cl-nsubstitute cl-new cl-old cl-seq :count cl-count
-		 :start cl-i cl-keys))))))
+                               (:start 0) :end :from-end) ()
+    (let ((len (cl--check-bound-indices cl-seq nil cl-keys)))
+      (if (or (eq cl-old cl-new)
+              (<= (or cl-count (setq cl-from-end nil
+                                     cl-count len)) 0))
+          cl-seq
+        (let ((cl-i (cl--position cl-old cl-seq cl-start cl-end)))
+          (if (not cl-i)
+              cl-seq
+            (setq cl-seq (copy-sequence cl-seq))
+            (unless cl-from-end
+              (setf (elt cl-seq cl-i) cl-new)
+              (cl-incf cl-i)
+              (cl-decf cl-count))
+            (apply 'cl-nsubstitute cl-new cl-old cl-seq :count cl-count
+                   :start cl-i cl-keys)))))))
 
 ;;;###autoload
 (defun cl-substitute-if (cl-new cl-pred cl-list &rest cl-keys)
@@ -442,7 +499,7 @@ cl-nsubstitute
 \n(fn NEW OLD SEQ [KEYWORD VALUE]...)"
   (cl--parsing-keywords (:test :test-not :key :if :if-not :count
 			(:start 0) :end :from-end) ()
-    (let ((len (length cl-seq)))
+    (let ((len (cl--check-bound-indices cl-seq nil cl-keys)))
       (or (eq cl-old cl-new) (<= (or cl-count (setq cl-count len)) 0)
 	  (if (and (listp cl-seq) (or (not cl-from-end) (> cl-count (/ len 2))))
 	    (let ((cl-p (nthcdr cl-start cl-seq)))
@@ -518,7 +575,8 @@ cl-position
 \n(fn ITEM SEQ [KEYWORD VALUE]...)"
   (cl--parsing-keywords (:test :test-not :key :if :if-not
 			(:start 0) :end :from-end) ()
-    (cl--position cl-item cl-seq cl-start cl-end cl-from-end)))
+    (let ((len (cl--check-bound-indices cl-seq nil cl-keys)))
+      (cl--position cl-item cl-seq cl-start (or cl-end len) cl-from-end))))
 
 (defun cl--position (cl-item cl-seq cl-start &optional cl-end cl-from-end)
   (if (listp cl-seq)
@@ -562,8 +620,9 @@ cl-count
 \nKeywords supported:  :test :test-not :key :start :end
 \n(fn ITEM SEQ [KEYWORD VALUE]...)"
   (cl--parsing-keywords (:test :test-not :key :if :if-not (:start 0) :end) ()
-    (let ((cl-count 0) cl-x)
-      (or cl-end (setq cl-end (length cl-seq)))
+    (let ((len (cl--check-bound-indices cl-seq nil cl-keys))
+          (cl-count 0) cl-x)
+      (or cl-end (setq cl-end len))
       (if (consp cl-seq) (setq cl-seq (nthcdr cl-start cl-seq)))
       (while (< cl-start cl-end)
 	(setq cl-x (if (consp cl-seq) (pop cl-seq) (aref cl-seq cl-start)))
@@ -593,28 +652,31 @@ cl-mismatch
 \nKeywords supported:  :test :test-not :key :start1 :end1 :start2 :end2 :from-end
 \n(fn SEQ1 SEQ2 [KEYWORD VALUE]...)"
   (cl--parsing-keywords (:test :test-not :key :from-end
-			(:start1 0) :end1 (:start2 0) :end2) ()
-    (or cl-end1 (setq cl-end1 (length cl-seq1)))
-    (or cl-end2 (setq cl-end2 (length cl-seq2)))
-    (if cl-from-end
-	(progn
-	  (while (and (< cl-start1 cl-end1) (< cl-start2 cl-end2)
-		      (cl--check-match (elt cl-seq1 (1- cl-end1))
-				      (elt cl-seq2 (1- cl-end2))))
-	    (setq cl-end1 (1- cl-end1) cl-end2 (1- cl-end2)))
-	  (and (or (< cl-start1 cl-end1) (< cl-start2 cl-end2))
-	       (1- cl-end1)))
-      (let ((cl-p1 (and (listp cl-seq1) (nthcdr cl-start1 cl-seq1)))
-	    (cl-p2 (and (listp cl-seq2) (nthcdr cl-start2 cl-seq2))))
-	(while (and (< cl-start1 cl-end1) (< cl-start2 cl-end2)
-		    (cl--check-match (if cl-p1 (car cl-p1)
-				      (aref cl-seq1 cl-start1))
-				    (if cl-p2 (car cl-p2)
-				      (aref cl-seq2 cl-start2))))
-	  (setq cl-p1 (cdr cl-p1) cl-p2 (cdr cl-p2)
-		cl-start1 (1+ cl-start1) cl-start2 (1+ cl-start2)))
-	(and (or (< cl-start1 cl-end1) (< cl-start2 cl-end2))
-	     cl-start1)))))
+                               (:start1 0) :end1 (:start2 0) :end2) ()
+    (let* ((lens (cl--check-bound-indices cl-seq1 cl-seq2 cl-keys))
+           (len1 (car lens))
+           (len2 (cdr lens)))
+      (or cl-end1 (setq cl-end1 len1))
+      (or cl-end2 (setq cl-end2 len2))
+      (if cl-from-end
+          (progn
+            (while (and (< cl-start1 cl-end1) (< cl-start2 cl-end2)
+                        (cl--check-match (elt cl-seq1 (1- cl-end1))
+                                         (elt cl-seq2 (1- cl-end2))))
+              (setq cl-end1 (1- cl-end1) cl-end2 (1- cl-end2)))
+            (and (or (< cl-start1 cl-end1) (< cl-start2 cl-end2))
+                 (1- cl-end1)))
+        (let ((cl-p1 (and (listp cl-seq1) (nthcdr cl-start1 cl-seq1)))
+              (cl-p2 (and (listp cl-seq2) (nthcdr cl-start2 cl-seq2))))
+          (while (and (< cl-start1 cl-end1) (< cl-start2 cl-end2)
+                      (cl--check-match (if cl-p1 (car cl-p1)
+                                         (aref cl-seq1 cl-start1))
+                                       (if cl-p2 (car cl-p2)
+                                         (aref cl-seq2 cl-start2))))
+            (setq cl-p1 (cdr cl-p1) cl-p2 (cdr cl-p2)
+                  cl-start1 (1+ cl-start1) cl-start2 (1+ cl-start2)))
+          (and (or (< cl-start1 cl-end1) (< cl-start2 cl-end2))
+               cl-start1))))))
 
 ;;;###autoload
 (defun cl-search (cl-seq1 cl-seq2 &rest cl-keys)
@@ -624,24 +686,27 @@ cl-search
 \nKeywords supported:  :test :test-not :key :start1 :end1 :start2 :end2 :from-end
 \n(fn SEQ1 SEQ2 [KEYWORD VALUE]...)"
   (cl--parsing-keywords (:test :test-not :key :from-end
-			(:start1 0) :end1 (:start2 0) :end2) ()
-    (or cl-end1 (setq cl-end1 (length cl-seq1)))
-    (or cl-end2 (setq cl-end2 (length cl-seq2)))
-    (if (>= cl-start1 cl-end1)
-	(if cl-from-end cl-end2 cl-start2)
-      (let* ((cl-len (- cl-end1 cl-start1))
-	     (cl-first (cl--check-key (elt cl-seq1 cl-start1)))
-	     (cl-if nil) cl-pos)
-	(setq cl-end2 (- cl-end2 (1- cl-len)))
-	(while (and (< cl-start2 cl-end2)
-		    (setq cl-pos (cl--position cl-first cl-seq2
-                                               cl-start2 cl-end2 cl-from-end))
-		    (apply 'cl-mismatch cl-seq1 cl-seq2
-			   :start1 (1+ cl-start1) :end1 cl-end1
-			   :start2 (1+ cl-pos) :end2 (+ cl-pos cl-len)
-			   :from-end nil cl-keys))
-	  (if cl-from-end (setq cl-end2 cl-pos) (setq cl-start2 (1+ cl-pos))))
-	(and (< cl-start2 cl-end2) cl-pos)))))
+                               (:start1 0) :end1 (:start2 0) :end2) ()
+    (let* ((lens (cl--check-bound-indices cl-seq1 cl-seq2 cl-keys))
+           (len1 (car lens))
+           (len2 (cdr lens)))
+      (or cl-end1 (setq cl-end1 len1))
+      (or cl-end2 (setq cl-end2 len2))
+      (if (>= cl-start1 cl-end1)
+          (if cl-from-end cl-end2 cl-start2)
+        (let* ((cl-len (- cl-end1 cl-start1))
+               (cl-first (cl--check-key (elt cl-seq1 cl-start1)))
+               (cl-if nil) cl-pos)
+          (setq cl-end2 (- cl-end2 (1- cl-len)))
+          (while (and (< cl-start2 cl-end2)
+                      (setq cl-pos (cl--position cl-first cl-seq2
+                                                 cl-start2 cl-end2 cl-from-end))
+                      (apply 'cl-mismatch cl-seq1 cl-seq2
+                             :start1 (1+ cl-start1) :end1 cl-end1
+                             :start2 (1+ cl-pos) :end2 (+ cl-pos cl-len)
+                             :from-end nil cl-keys))
+            (if cl-from-end (setq cl-end2 cl-pos) (setq cl-start2 (1+ cl-pos))))
+          (and (< cl-start2 cl-end2) cl-pos))))))
 
 ;;;###autoload
 (defun cl-sort (cl-seq cl-pred &rest cl-keys)
diff --git a/test/lisp/emacs-lisp/cl-seq-tests.el b/test/lisp/emacs-lisp/cl-seq-tests.el
index 61e3d72033..9c6738048a 100644
--- a/test/lisp/emacs-lisp/cl-seq-tests.el
+++ b/test/lisp/emacs-lisp/cl-seq-tests.el
@@ -302,6 +302,101 @@ cl-seq--with-side-effects
     (should (equal '(2 8) (last (cl-replace list list2) 2)))
     (should (equal '(1 1) (last (cl-fill list 1) 2)))))
 
+(ert-deftest cl-seq-check-bounds ()
+  (let ((lst (list 1 2 3))
+        (lst2 (list 'a 'b 'c))
+        ;; t means pass, nil means fails.
+        (tests '("((lambda (x y) (cl-reduce #'max x :start 1)) . t)"
+                 "((lambda (x y) (cl-reduce #'max x :start -1)))"
+                 "((lambda (x y) (cl-reduce #'max x :start 4)))"
+                 "((lambda (x y) (cl-reduce #'max x :start 2 :end 1)))"
+                 ;;
+                 "((lambda (x y) (cl-fill x 'b :start 3)) . t)"
+                 "((lambda (x y) (cl-fill x 'b :start 4)))"
+                 "((lambda (x y) (cl-fill x 'b :start -1)))"
+                 "((lambda (x y) (cl-fill x 'b :start 2 :end 1)))"
+                 ;;
+                 "((lambda (x y) (cl-replace x y :start1 3)) . t)"
+                 "((lambda (x y) (cl-replace x y :start2 3)) . t)"
+                 "((lambda (x y) (cl-replace x y :start1 4)))"
+                 "((lambda (x y) (cl-replace x y :start2 4)))"
+                 "((lambda (x y) (cl-replace x y :start1 -1)))"
+                 "((lambda (x y) (cl-replace x y :start2 -1)))"
+                 "((lambda (x y) (cl-replace x y :start1 2 :end1 1)))"
+                 "((lambda (x y) (cl-replace x y :start2 2 :end2 1)))"
+                 ;;
+                 "((lambda (x y) (cl-remove nil x :start 3)) . t)"
+                 "((lambda (x y) (cl-remove nil x :start 4)))"
+                 "((lambda (x y) (cl-remove nil x :start -1)))"
+                 "((lambda (x y) (cl-remove nil x :start 2 :end 1)))"
+                 ;;
+                 "((lambda (x y) (cl-delete nil x :start 3)) . t)"
+                 "((lambda (x y) (cl-delete nil x :start 4)))"
+                 "((lambda (x y) (cl-delete nil x :start -1)))"
+                 "((lambda (x y) (cl-delete nil x :start 2 :end 1)))"
+                 ;;
+                 "((lambda (x y) (cl-delete-duplicates x :start 3)) . t)"
+                 "((lambda (x y) (cl-delete-duplicates x :start 4)))"
+                 "((lambda (x y) (cl-delete-duplicates x :start -1)))"
+                 "((lambda (x y) (cl-delete-duplicates x :start 2 :end 1)))"
+                 ;;
+                 "((lambda (x y) (cl-remove-duplicates x :start 3)) . t)"
+                 "((lambda (x y) (cl-remove-duplicates x :start 4)))"
+                 "((lambda (x y) (cl-remove-duplicates x :start -1)))"
+                 "((lambda (x y) (cl-remove-duplicates x :start 2 :end 1)))"
+                 ;;
+                 "((lambda (x y) (cl-substitute 'foo 2 x :start 3)) . t)"
+                 "((lambda (x y) (cl-substitute 'foo 2 x :start 4)))"
+                 "((lambda (x y) (cl-substitute 'foo 2 x :start -1)))"
+                 "((lambda (x y) (cl-substitute 'foo 2 x :start 2 :end 1)))"
+                 ;;
+                 "((lambda (x y) (cl-nsubstitute 'foo 2 x :start 3)) . t)"
+                 "((lambda (x y) (cl-nsubstitute 'foo 2 x :start 4)))"
+                 "((lambda (x y) (cl-nsubstitute 'foo 2 x :start -1)))"
+                 "((lambda (x y) (cl-nsubstitute 'foo 2 x :start 2 :end 1)))"
+                 ;;
+                 "((lambda (x y) (cl-position 2 x :start 4)))"
+                 "((lambda (x y) (cl-position 2 x :start -1)))"
+                 "((lambda (x y) (cl-position 2 x :start 2 :end 1)))"
+                 ;;
+                 "((lambda (x y) (cl-count 2 x :start 3)) . t)"
+                 "((lambda (x y) (cl-count 2 x :start 4)))"
+                 "((lambda (x y) (cl-count 2 x :start -1)))"
+                 "((lambda (x y) (cl-count 2 x :start 2 :end 1)))"
+                 ;;
+                 "((lambda (x y) (cl-mismatch x x :start1 1 :start2 3)) . t)"
+                 "((lambda (x y) (cl-mismatch x x :start1 1 :start2 4)))"
+                 "((lambda (x y) (cl-mismatch x x :start1 4 :start2 1)))"
+                 "((lambda (x y) (cl-mismatch x x :start1 -1 :start2 1)))"
+                 "((lambda (x y) (cl-mismatch x x :start1 1 :start2 -1)))"
+                 "((lambda (x y) (cl-mismatch x x :start1 2 :end1 1)))"
+                 "((lambda (x y) (cl-mismatch x x :start2 2 :end2 1)))"
+                 ;;
+                 "((lambda (x y) (cl-search x x :start1 3 :start2 3)) . t)"
+                 "((lambda (x y) (cl-search x x :start1 4 :start2 4)))"
+                 "((lambda (x y) (cl-search x x :start1 -1 :start2 3)))"
+                 "((lambda (x y) (cl-search x x :start1 1 :start2 -1)))"
+                 "((lambda (x y) (cl-search x x :start1 2 :end1 1)))"
+                 "((lambda (x y) (cl-search x x :start2 2 :end2 1)))"
+                 ;;
+                 "((lambda (x y) (cl-subseq x -1)) . t)"
+                 "((lambda (x y) (cl-subseq x -2 -1)) . t)"
+                 "((lambda (x y) (cl-subseq x -4)))"
+                 "((lambda (x y) (cl-subseq x 2 1)))")))
+    (dolist (limit '("start" "end"))
+      (dolist (x tests)
+        (let ((form
+               (car
+                (read-from-string
+                 (cond ((string-match ":start\\([1-2]?\\) \\([0-9-]+\\) :end\\([1-2]?\\)" x)
+                        x)
+                       ((string= limit "start") x)
+                       (t
+                        (replace-regexp-in-string "start" limit x)))))))
+          (if (cdr form)
+              (should (funcall (car form) lst lst2))
+            (should-error (funcall (car form) lst lst2))))))))
+
 
 (provide 'cl-seq-tests)
 ;;; cl-seq-tests.el ends here
-- 
2.11.0

From 72e9692cbc02150d9ef6c97011bdb55460686df4 Mon Sep 17 00:00:00 2001
From: Tino Calancha <tino.calancha@gmail.com>
Date: Sun, 5 Feb 2017 23:41:11 +0900
Subject: [PATCH 2/2] * lisp/edmacro.el (edmacro-format-keys): Prevent :end
 index out-of-range.

---
 lisp/edmacro.el | 7 ++++---
 1 file changed, 4 insertions(+), 3 deletions(-)

diff --git a/lisp/edmacro.el b/lisp/edmacro.el
index 5fefc3102d..c3608829c0 100644
--- a/lisp/edmacro.el
+++ b/lisp/edmacro.el
@@ -560,10 +560,11 @@ edmacro-format-keys
 	(if prefix
 	    (setq desc (concat (edmacro-sanitize-for-string prefix) desc)))
 	(unless (string-match " " desc)
-	  (let ((times 1) (pos bind-len))
+	  (let ((times 1) (pos bind-len)
+	        (rest-mac-len (length rest-mac)))
 	    (while (not (cl-mismatch rest-mac rest-mac
-				     :start1 0 :end1 bind-len
-				     :start2 pos :end2 (+ bind-len pos)))
+				     :start1 0 :end1 (min bind-len rest-mac-len)
+				     :start2 pos :end2 (min (+ bind-len pos) rest-mac-len)))
 	      (cl-incf times)
 	      (cl-incf pos bind-len))
 	    (when (> times 1)
-- 
2.11.0


;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
In GNU Emacs 26.0.50.1 (x86_64-pc-linux-gnu, GTK+ Version 3.22.7)
 of 2017-02-05
Repository revision: 148100d98319499f0ac6f57b8be08cbd14884a5c



^ permalink raw reply related	[flat|nested] 14+ messages in thread

* Re: [PATCH v2] Bound index checks in cl-seq functions
  2017-02-05  7:11   ` [PATCH v2] " Tino Calancha
  2017-02-05 14:56     ` [PATCH v3] " Tino Calancha
@ 2017-02-05 16:11     ` Clément Pit-Claudel
  2017-02-06  7:00       ` [PATCH v4] " Tino Calancha
  1 sibling, 1 reply; 14+ messages in thread
From: Clément Pit-Claudel @ 2017-02-05 16:11 UTC (permalink / raw)
  To: Tino Calancha, Noam Postavsky; +Cc: Philipp Stephani, Emacs developers

On 2017-02-05 02:11, Tino Calancha wrote:
> II) `cl--check-bound-indices' returns on success the sequence[s]
>     length[s], so that callers don't need to recompute them.

This sounds like a good idea, performance wise.  But maybe it would be even better to disable these checks when (cl-declaim (optimize (safety 0))) is set?

Clément.



^ permalink raw reply	[flat|nested] 14+ messages in thread

* Re: [PATCH v4] Bound index checks in cl-seq functions
  2017-02-05 16:11     ` [PATCH v2] " Clément Pit-Claudel
@ 2017-02-06  7:00       ` Tino Calancha
  2017-02-06  7:15         ` Clément Pit-Claudel
  0 siblings, 1 reply; 14+ messages in thread
From: Tino Calancha @ 2017-02-06  7:00 UTC (permalink / raw)
  To: Clément Pit-Claudel
  Cc: Philipp Stephani, Noam Postavsky, Emacs developers, Tino Calancha

Clément Pit-Claudel <cpitclaudel@gmail.com> writes:

> On 2017-02-05 02:11, Tino Calancha wrote:
>> II) `cl--check-bound-indices' returns on success the sequence[s]
>>     length[s], so that callers don't need to recompute them.
>
> This sounds like a good idea, performance wise.  But maybe it would be
> even better to disable these checks when (cl-declaim (optimize (safety
> 0))) is set?
Yes, that sounds right.
The new patch disables those checks in that case (safety = 0).
I got ~0.92 s without checks and ~ 1.10 with checks
when i ran the following toy:
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; start
;; -*- lexical-binding: t; -*-

(require 'cl-lib)

(cl-declaim (optimize (safety 0))) ; (test) ~ 0.92
;(cl-declaim (optimize (safety 1))) ; (test) ~ 1.10
       
(defun test ()
  (let ((lst (nreverse (cons 'a (number-sequence 1 1000000)))))
    (benchmark-run 10
      (cl-position 'a lst))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; end

Following is the updated patch:

:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
From a57cc0105e315382715edba1baa9b814b947675d Mon Sep 17 00:00:00 2001
From: Tino Calancha <tino.calancha@gmail.com>
Date: Mon, 6 Feb 2017 15:20:51 +0900
Subject: [PATCH 1/2] Check for out-of-range indices in cl-seq function

Throw and error if the user inputs out of range indices
or if :start value is higher than :end value.
Suppress these checks if 'cl--optimize-safety' 0.
* lisp/emacs-lisp/cl-seq.el (cl--parsing-keywords):
Check for negative indices.
(cl--check-bound-indices): New defun; check for indices > seq length,
or start index > end index.
(cl-reduce, cl-fill, cl-replace, cl-remove, cl-delete)
(cl--delete-duplicates, cl-substitute, cl-nsubstitute, cl-position)
(cl-count, cl-mismatch, cl-search): Use it.
* doc/misc/cl.texi (Sequence Basics): Update manual.
; * etc/NEWS: Announce the change.
* test/lisp/emacs-lisp/cl-seq-tests.el (cl-seq-check-bounds): New test.
---
 doc/misc/cl.texi                     |   4 +-
 etc/NEWS                             |   4 +
 lisp/emacs-lisp/cl-seq.el            | 379 +++++++++++++++++++++--------------
 test/lisp/emacs-lisp/cl-seq-tests.el |  95 +++++++++
 4 files changed, 335 insertions(+), 147 deletions(-)

diff --git a/doc/misc/cl.texi b/doc/misc/cl.texi
index 8baa0bd88c..6f387f5cbb 100644
--- a/doc/misc/cl.texi
+++ b/doc/misc/cl.texi
@@ -3247,7 +3247,9 @@ Sequence Basics
 (exclusive) are affected by the operation.  The @var{end} argument
 may be passed @code{nil} to signify the length of the sequence;
 otherwise, both @var{start} and @var{end} must be integers, with
-@code{0 <= @var{start} <= @var{end} <= (length @var{seq})}.
+@code{0 <= @var{start} <= @var{end} <= (length @var{seq})}.  Emacs
+signals an error when this condition is not true, except for
+@code{cl-subseq} which allows negative indices.
 If the function takes two sequence arguments, the limits are
 defined by keywords @code{:start1} and @code{:end1} for the first,
 and @code{:start2} and @code{:end2} for the second.
diff --git a/etc/NEWS b/etc/NEWS
index 4d8ae091a7..19dd5d9995 100644
--- a/etc/NEWS
+++ b/etc/NEWS
@@ -706,6 +706,10 @@ processes on exit.
 * Incompatible Lisp Changes in Emacs 26.1
 
 +++
+** CL sequence functions now throw errors when the input indices
+are out of range, or if :start index is higher than :end index.
+
++++
 ** Resizing a frame no longer runs 'window-configuration-change-hook'.
 Put your function on 'window-size-change-functions' instead.
 
diff --git a/lisp/emacs-lisp/cl-seq.el b/lisp/emacs-lisp/cl-seq.el
index 67ff1a00bd..4ea9ebb063 100644
--- a/lisp/emacs-lisp/cl-seq.el
+++ b/lisp/emacs-lisp/cl-seq.el
@@ -42,6 +42,9 @@
 ;;; Code:
 
 (require 'cl-lib)
+(eval-when-compile (require 'subr-x))
+
+(defvar cl--optimize-safety)
 
 ;; Keyword parsing.
 ;; This is special-cased here so that we can compile
@@ -59,7 +62,14 @@ cl--parsing-keywords
                    (setq mem `(and ,mem (setq cl-if ,mem) t)))
                (list (intern
                       (format "cl-%s" (substring (symbol-name var) 1)))
-                     (if (consp x) `(or ,mem ,(car (cdr x))) mem))))
+                     `(if (and (not (zerop cl--optimize-safety))
+                               (string-match ":\\(start\\|end\\)" (symbol-name ,var))
+                               (integerp (or ,mem ,(car (cdr-safe x))))
+                               (not (natnump (or ,mem ,(car (cdr-safe x))))))
+                          (error "Wrong negative index '%s': natnump, %s"
+                                 (substring (symbol-name ,var) 1)
+                                 (or ,mem ,(car (cdr-safe x))))
+                        (or ,mem ,(car (cdr-safe x)))))))
            kwords)
      ,@(append
         (and (not (eq other-keys t))
@@ -112,6 +122,49 @@ cl-test
 (defvar cl-if) (defvar cl-if-not)
 (defvar cl-key)
 
+;; Throw an error when :start or :end are > sequence length,
+;; or if :start > :end.
+;; If CL-SEQ2 is nil, then return (length cl-seq1), otherwise
+;; return (cons (length cl-seq1) (length cl-seq2)).
+(defun cl--check-bound-indices (cl-seq1 cl-seq2 cl-keys)
+  (let ((len1 (length cl-seq1))
+        (len2 (and cl-seq2 (length cl-seq2)))
+        (kwds (list :start1 :start2 :start :end1 :end2 :end))
+        alist)
+    (while cl-keys
+      (when (and (memq (car cl-keys) kwds)
+                 (string-match ":\\(start\\|end\\)\\([1-2]?\\)\\'"
+                               (symbol-name (car cl-keys))))
+        (delq (car cl-keys) kwds) ; Ignore succesive equal keys.
+        (let* ((idx (match-string 2 (symbol-name (car cl-keys))))
+               (len (if (equal idx "2") len2 len1)))
+          (when (integerp (cadr cl-keys))
+            (push (cons (car cl-keys) (cadr cl-keys)) alist)
+            (when (> (cadr cl-keys) len)
+              (error "Wrong bounding indices '%s', %s > (length %s), %s"
+                     (substring (symbol-name (car cl-keys)) 1)
+                     (cadr cl-keys)
+                     (concat "cl-seq" idx)
+                     len)))))
+      (setq cl-keys (cddr cl-keys)))
+    ;; Check :start value > :end value.
+    (mapc (lambda (x)
+            (and-let* ((start (alist-get (car x) alist))
+                       (end (alist-get (cdr x) alist))
+                       (bad-indices (> start end)))
+              (error "Bad bounding indices '%s', '%s': %d, %d"
+                     (substring (symbol-name (car x)) 1)
+                     (substring (symbol-name (cdr x)) 1)
+                     start
+                     end)))
+          (list (cons :start :end)
+                (cons :start1 :end1)
+                (cons :start2 :end2)))
+    ;; Return sequence lengths.
+    (if len2
+        (cons len1 len2)
+      len1)))
+
 ;;;###autoload
 (defun cl-reduce (cl-func cl-seq &rest cl-keys)
   "Reduce two-argument FUNCTION across SEQ.
@@ -128,6 +181,7 @@ cl-reduce
 
 \n(fn FUNCTION SEQ [KEYWORD VALUE]...)"
   (cl--parsing-keywords (:from-end (:start 0) :end :initial-value :key) ()
+    (or (zerop cl--optimize-safety) (cl--check-bound-indices cl-seq nil cl-keys))
     (or (listp cl-seq) (setq cl-seq (append cl-seq nil)))
     (setq cl-seq (cl-subseq cl-seq cl-start cl-end))
     (if cl-from-end (setq cl-seq (nreverse cl-seq)))
@@ -149,18 +203,21 @@ cl-fill
 \nKeywords supported:  :start :end
 \n(fn SEQ ITEM [KEYWORD VALUE]...)"
   (cl--parsing-keywords ((:start 0) :end) ()
-    (if (listp cl-seq)
-	(let ((p (nthcdr cl-start cl-seq))
-	      (n (and cl-end (- cl-end cl-start))))
-	  (while (and p (or (null n) (>= (cl-decf n) 0)))
-	    (setcar p cl-item)
-	    (setq p (cdr p))))
-      (or cl-end (setq cl-end (length cl-seq)))
-      (if (and (= cl-start 0) (= cl-end (length cl-seq)))
-	  (fillarray cl-seq cl-item)
-	(while (< cl-start cl-end)
-	  (aset cl-seq cl-start cl-item)
-	  (setq cl-start (1+ cl-start)))))
+    (let ((len (if (zerop cl--optimize-safety)
+                   (or cl-end (length cl-seq))
+                 (cl--check-bound-indices cl-seq nil cl-keys))))
+      (if (listp cl-seq)
+          (let ((p (nthcdr cl-start cl-seq))
+                (n (and cl-end (- cl-end cl-start))))
+            (while (and p (or (null n) (>= (cl-decf n) 0)))
+              (setcar p cl-item)
+              (setq p (cdr p))))
+        (or cl-end (setq cl-end len))
+        (if (and (= cl-start 0) (= cl-end len))
+            (fillarray cl-seq cl-item)
+          (while (< cl-start cl-end)
+            (aset cl-seq cl-start cl-item)
+            (setq cl-start (1+ cl-start))))))
     cl-seq))
 
 ;;;###autoload
@@ -170,44 +227,48 @@ cl-replace
 \nKeywords supported:  :start1 :end1 :start2 :end2
 \n(fn SEQ1 SEQ2 [KEYWORD VALUE]...)"
   (cl--parsing-keywords ((:start1 0) :end1 (:start2 0) :end2) ()
-    (if (and (eq cl-seq1 cl-seq2) (<= cl-start2 cl-start1))
-	(or (= cl-start1 cl-start2)
-	    (let* ((cl-len (length cl-seq1))
-		   (cl-n (min (- (or cl-end1 cl-len) cl-start1)
-			      (- (or cl-end2 cl-len) cl-start2))))
-	      (while (>= (setq cl-n (1- cl-n)) 0)
-		(setf (elt cl-seq1 (+ cl-start1 cl-n))
-			    (elt cl-seq2 (+ cl-start2 cl-n))))))
-      (if (listp cl-seq1)
-	  (let ((cl-p1 (nthcdr cl-start1 cl-seq1))
-		(cl-n1 (and cl-end1 (- cl-end1 cl-start1))))
-	    (if (listp cl-seq2)
-		(let ((cl-p2 (nthcdr cl-start2 cl-seq2))
-		      (cl-n (cond ((and cl-n1 cl-end2)
-				   (min cl-n1 (- cl-end2 cl-start2)))
-				  ((and cl-n1 (null cl-end2)) cl-n1)
-				  ((and (null cl-n1) cl-end2) (- cl-end2 cl-start2)))))
-		  (while (and cl-p1 cl-p2 (or (null cl-n) (>= (cl-decf cl-n) 0)))
-		    (setcar cl-p1 (car cl-p2))
-		    (setq cl-p1 (cdr cl-p1) cl-p2 (cdr cl-p2))))
-	      (setq cl-end2 (if (null cl-n1)
-				(or cl-end2 (length cl-seq2))
-			      (min (or cl-end2 (length cl-seq2))
-				   (+ cl-start2 cl-n1))))
-	      (while (and cl-p1 (< cl-start2 cl-end2))
-		(setcar cl-p1 (aref cl-seq2 cl-start2))
-		(setq cl-p1 (cdr cl-p1) cl-start2 (1+ cl-start2)))))
-	(setq cl-end1 (min (or cl-end1 (length cl-seq1))
-			   (+ cl-start1 (- (or cl-end2 (length cl-seq2))
-					   cl-start2))))
-	(if (listp cl-seq2)
-	    (let ((cl-p2 (nthcdr cl-start2 cl-seq2)))
-	      (while (< cl-start1 cl-end1)
-		(aset cl-seq1 cl-start1 (car cl-p2))
-		(setq cl-p2 (cdr cl-p2) cl-start1 (1+ cl-start1))))
-	  (while (< cl-start1 cl-end1)
-	    (aset cl-seq1 cl-start1 (aref cl-seq2 cl-start2))
-	    (setq cl-start2 (1+ cl-start2) cl-start1 (1+ cl-start1))))))
+    (let*  ((lens (and (not (zerop cl--optimize-safety))
+                       (cl--check-bound-indices cl-seq1 cl-seq2 cl-keys)))
+            (len1 (if lens (car lens) (or cl-end1 (length cl-seq1))))
+            (len2 (if lens (cdr lens) (or cl-end2 (length cl-seq2)))))
+      (if (and (eq cl-seq1 cl-seq2) (<= cl-start2 cl-start1))
+          (or (= cl-start1 cl-start2)
+              (let* ((cl-len len1)
+                     (cl-n (min (- (or cl-end1 cl-len) cl-start1)
+                                (- (or cl-end2 cl-len) cl-start2))))
+                (while (>= (setq cl-n (1- cl-n)) 0)
+                  (setf (elt cl-seq1 (+ cl-start1 cl-n))
+                        (elt cl-seq2 (+ cl-start2 cl-n))))))
+        (if (listp cl-seq1)
+            (let ((cl-p1 (nthcdr cl-start1 cl-seq1))
+                  (cl-n1 (and cl-end1 (- cl-end1 cl-start1))))
+              (if (listp cl-seq2)
+                  (let ((cl-p2 (nthcdr cl-start2 cl-seq2))
+                        (cl-n (cond ((and cl-n1 cl-end2)
+                                     (min cl-n1 (- cl-end2 cl-start2)))
+                                    ((and cl-n1 (null cl-end2)) cl-n1)
+                                    ((and (null cl-n1) cl-end2) (- cl-end2 cl-start2)))))
+                    (while (and cl-p1 cl-p2 (or (null cl-n) (>= (cl-decf cl-n) 0)))
+                      (setcar cl-p1 (car cl-p2))
+                      (setq cl-p1 (cdr cl-p1) cl-p2 (cdr cl-p2))))
+                (setq cl-end2 (if (null cl-n1)
+                                  (or cl-end2 len2)
+                                (min (or cl-end2 len2)
+                                     (+ cl-start2 cl-n1))))
+                (while (and cl-p1 (< cl-start2 cl-end2))
+                  (setcar cl-p1 (aref cl-seq2 cl-start2))
+                  (setq cl-p1 (cdr cl-p1) cl-start2 (1+ cl-start2)))))
+          (setq cl-end1 (min (or cl-end1 len1)
+                             (+ cl-start1 (- (or cl-end2 len2)
+                                             cl-start2))))
+          (if (listp cl-seq2)
+              (let ((cl-p2 (nthcdr cl-start2 cl-seq2)))
+                (while (< cl-start1 cl-end1)
+                  (aset cl-seq1 cl-start1 (car cl-p2))
+                  (setq cl-p2 (cdr cl-p2) cl-start1 (1+ cl-start1))))
+            (while (< cl-start1 cl-end1)
+              (aset cl-seq1 cl-start1 (aref cl-seq2 cl-start2))
+              (setq cl-start2 (1+ cl-start2) cl-start1 (1+ cl-start1)))))))
     cl-seq1))
 
 ;;;###autoload
@@ -219,7 +280,9 @@ cl-remove
 \n(fn ITEM SEQ [KEYWORD VALUE]...)"
   (cl--parsing-keywords (:test :test-not :key :if :if-not :count :from-end
 			(:start 0) :end) ()
-    (let ((len (length cl-seq)))
+    (let ((len (if (zerop cl--optimize-safety)
+                   (length cl-seq)
+                 (cl--check-bound-indices cl-seq nil cl-keys))))
       (if (<= (or cl-count (setq cl-count len)) 0)
 	cl-seq
         (if (or (nlistp cl-seq) (and cl-from-end (< cl-count (/ len 2))))
@@ -283,7 +346,9 @@ cl-delete
 \n(fn ITEM SEQ [KEYWORD VALUE]...)"
   (cl--parsing-keywords (:test :test-not :key :if :if-not :count :from-end
 			(:start 0) :end) ()
-    (let ((len (length cl-seq)))
+    (let ((len (if (zerop cl--optimize-safety)
+                   (length cl-seq)
+                 (cl--check-bound-indices cl-seq nil cl-keys))))
       (if (<= (or cl-count (setq cl-count len)) 0)
 	cl-seq
       (if (listp cl-seq)
@@ -356,39 +421,42 @@ cl--delete-duplicates
           ;; We need to parse :if, otherwise `cl-if' is unbound.
           (:test :test-not :key (:start 0) :end :from-end :if)
 	  ()
-	(if cl-from-end
-	    (let ((cl-p (nthcdr cl-start cl-seq)) cl-i)
-	      (setq cl-end (- (or cl-end (length cl-seq)) cl-start))
-	      (while (> cl-end 1)
-		(setq cl-i 0)
-		(while (setq cl-i (cl--position (cl--check-key (car cl-p))
-                                                (cdr cl-p) cl-i (1- cl-end)))
-		  (if cl-copy (setq cl-seq (copy-sequence cl-seq)
-				    cl-p (nthcdr cl-start cl-seq) cl-copy nil))
-		  (let ((cl-tail (nthcdr cl-i cl-p)))
-		    (setcdr cl-tail (cdr (cdr cl-tail))))
-		  (setq cl-end (1- cl-end)))
-		(setq cl-p (cdr cl-p) cl-end (1- cl-end)
-		      cl-start (1+ cl-start)))
-	      cl-seq)
-	  (setq cl-end (- (or cl-end (length cl-seq)) cl-start))
-	  (while (and (cdr cl-seq) (= cl-start 0) (> cl-end 1)
-		      (cl--position (cl--check-key (car cl-seq))
-                                    (cdr cl-seq) 0 (1- cl-end)))
-	    (setq cl-seq (cdr cl-seq) cl-end (1- cl-end)))
-	  (let ((cl-p (if (> cl-start 0) (nthcdr (1- cl-start) cl-seq)
-			(setq cl-end (1- cl-end) cl-start 1) cl-seq)))
-	    (while (and (cdr (cdr cl-p)) (> cl-end 1))
-	      (if (cl--position (cl--check-key (car (cdr cl-p)))
-                                (cdr (cdr cl-p)) 0 (1- cl-end))
-		  (progn
-		    (if cl-copy (setq cl-seq (copy-sequence cl-seq)
-				      cl-p (nthcdr (1- cl-start) cl-seq)
-				      cl-copy nil))
-		    (setcdr cl-p (cdr (cdr cl-p))))
-		(setq cl-p (cdr cl-p)))
-	      (setq cl-end (1- cl-end) cl-start (1+ cl-start)))
-	    cl-seq)))
+        (let ((len (if (zerop cl--optimize-safety)
+                   (or cl-end (length cl-seq))
+                 (cl--check-bound-indices cl-seq nil cl-keys))))
+          (if cl-from-end
+              (let ((cl-p (nthcdr cl-start cl-seq)) cl-i)
+                (setq cl-end (- (or cl-end len) cl-start))
+                (while (> cl-end 1)
+                  (setq cl-i 0)
+                  (while (setq cl-i (cl--position (cl--check-key (car cl-p))
+                                                  (cdr cl-p) cl-i (1- cl-end)))
+                    (if cl-copy (setq cl-seq (copy-sequence cl-seq)
+                                      cl-p (nthcdr cl-start cl-seq) cl-copy nil))
+                    (let ((cl-tail (nthcdr cl-i cl-p)))
+                      (setcdr cl-tail (cdr (cdr cl-tail))))
+                    (setq cl-end (1- cl-end)))
+                  (setq cl-p (cdr cl-p) cl-end (1- cl-end)
+                        cl-start (1+ cl-start)))
+                cl-seq)
+            (setq cl-end (- (or cl-end len) cl-start))
+            (while (and (cdr cl-seq) (= cl-start 0) (> cl-end 1)
+                        (cl--position (cl--check-key (car cl-seq))
+                                      (cdr cl-seq) 0 (1- cl-end)))
+              (setq cl-seq (cdr cl-seq) cl-end (1- cl-end)))
+            (let ((cl-p (if (> cl-start 0) (nthcdr (1- cl-start) cl-seq)
+                          (setq cl-end (1- cl-end) cl-start 1) cl-seq)))
+              (while (and (cdr (cdr cl-p)) (> cl-end 1))
+                (if (cl--position (cl--check-key (car (cdr cl-p)))
+                                  (cdr (cdr cl-p)) 0 (1- cl-end))
+                    (progn
+                      (if cl-copy (setq cl-seq (copy-sequence cl-seq)
+                                        cl-p (nthcdr (1- cl-start) cl-seq)
+                                        cl-copy nil))
+                      (setcdr cl-p (cdr (cdr cl-p))))
+                  (setq cl-p (cdr cl-p)))
+                (setq cl-end (1- cl-end) cl-start (1+ cl-start)))
+              cl-seq))))
     (let ((cl-res (cl--delete-duplicates (append cl-seq nil) cl-keys nil)))
       (if (stringp cl-seq) (concat cl-res) (vconcat cl-res)))))
 
@@ -400,21 +468,24 @@ cl-substitute
 \nKeywords supported:  :test :test-not :key :count :start :end :from-end
 \n(fn NEW OLD SEQ [KEYWORD VALUE]...)"
   (cl--parsing-keywords (:test :test-not :key :if :if-not :count
-			(:start 0) :end :from-end) ()
-    (if (or (eq cl-old cl-new)
-	    (<= (or cl-count (setq cl-from-end nil
-				   cl-count (length cl-seq))) 0))
-	cl-seq
-      (let ((cl-i (cl--position cl-old cl-seq cl-start cl-end)))
-	(if (not cl-i)
-	    cl-seq
-	  (setq cl-seq (copy-sequence cl-seq))
-	  (unless cl-from-end
-	    (setf (elt cl-seq cl-i) cl-new)
-	    (cl-incf cl-i)
-	    (cl-decf cl-count))
-	  (apply 'cl-nsubstitute cl-new cl-old cl-seq :count cl-count
-		 :start cl-i cl-keys))))))
+                               (:start 0) :end :from-end) ()
+    (let ((len (if (zerop cl--optimize-safety)
+                   (length cl-seq)
+                 (cl--check-bound-indices cl-seq nil cl-keys))))
+      (if (or (eq cl-old cl-new)
+              (<= (or cl-count (setq cl-from-end nil
+                                     cl-count len)) 0))
+          cl-seq
+        (let ((cl-i (cl--position cl-old cl-seq cl-start cl-end)))
+          (if (not cl-i)
+              cl-seq
+            (setq cl-seq (copy-sequence cl-seq))
+            (unless cl-from-end
+              (setf (elt cl-seq cl-i) cl-new)
+              (cl-incf cl-i)
+              (cl-decf cl-count))
+            (apply 'cl-nsubstitute cl-new cl-old cl-seq :count cl-count
+                   :start cl-i cl-keys)))))))
 
 ;;;###autoload
 (defun cl-substitute-if (cl-new cl-pred cl-list &rest cl-keys)
@@ -442,7 +513,9 @@ cl-nsubstitute
 \n(fn NEW OLD SEQ [KEYWORD VALUE]...)"
   (cl--parsing-keywords (:test :test-not :key :if :if-not :count
 			(:start 0) :end :from-end) ()
-    (let ((len (length cl-seq)))
+    (let ((len (if (zerop cl--optimize-safety)
+                   (length cl-seq)
+                 (cl--check-bound-indices cl-seq nil cl-keys))))
       (or (eq cl-old cl-new) (<= (or cl-count (setq cl-count len)) 0)
 	  (if (and (listp cl-seq) (or (not cl-from-end) (> cl-count (/ len 2))))
 	    (let ((cl-p (nthcdr cl-start cl-seq)))
@@ -517,8 +590,11 @@ cl-position
 \nKeywords supported:  :test :test-not :key :start :end :from-end
 \n(fn ITEM SEQ [KEYWORD VALUE]...)"
   (cl--parsing-keywords (:test :test-not :key :if :if-not
-			(:start 0) :end :from-end) ()
-    (cl--position cl-item cl-seq cl-start cl-end cl-from-end)))
+                               (:start 0) :end :from-end) ()
+    (let ((end (if (not (zerop cl--optimize-safety))
+                   (cl--check-bound-indices cl-seq nil cl-keys)
+                 cl-end)))
+      (cl--position cl-item cl-seq cl-start end cl-from-end))))
 
 (defun cl--position (cl-item cl-seq cl-start &optional cl-end cl-from-end)
   (if (listp cl-seq)
@@ -562,8 +638,11 @@ cl-count
 \nKeywords supported:  :test :test-not :key :start :end
 \n(fn ITEM SEQ [KEYWORD VALUE]...)"
   (cl--parsing-keywords (:test :test-not :key :if :if-not (:start 0) :end) ()
-    (let ((cl-count 0) cl-x)
-      (or cl-end (setq cl-end (length cl-seq)))
+    (let ((len (if (zerop cl--optimize-safety)
+                   (or cl-end (length cl-seq))
+                 (cl--check-bound-indices cl-seq nil cl-keys)))
+          (cl-count 0) cl-x)
+      (or cl-end (setq cl-end len))
       (if (consp cl-seq) (setq cl-seq (nthcdr cl-start cl-seq)))
       (while (< cl-start cl-end)
 	(setq cl-x (if (consp cl-seq) (pop cl-seq) (aref cl-seq cl-start)))
@@ -593,28 +672,32 @@ cl-mismatch
 \nKeywords supported:  :test :test-not :key :start1 :end1 :start2 :end2 :from-end
 \n(fn SEQ1 SEQ2 [KEYWORD VALUE]...)"
   (cl--parsing-keywords (:test :test-not :key :from-end
-			(:start1 0) :end1 (:start2 0) :end2) ()
-    (or cl-end1 (setq cl-end1 (length cl-seq1)))
-    (or cl-end2 (setq cl-end2 (length cl-seq2)))
-    (if cl-from-end
-	(progn
-	  (while (and (< cl-start1 cl-end1) (< cl-start2 cl-end2)
-		      (cl--check-match (elt cl-seq1 (1- cl-end1))
-				      (elt cl-seq2 (1- cl-end2))))
-	    (setq cl-end1 (1- cl-end1) cl-end2 (1- cl-end2)))
-	  (and (or (< cl-start1 cl-end1) (< cl-start2 cl-end2))
-	       (1- cl-end1)))
-      (let ((cl-p1 (and (listp cl-seq1) (nthcdr cl-start1 cl-seq1)))
-	    (cl-p2 (and (listp cl-seq2) (nthcdr cl-start2 cl-seq2))))
-	(while (and (< cl-start1 cl-end1) (< cl-start2 cl-end2)
-		    (cl--check-match (if cl-p1 (car cl-p1)
-				      (aref cl-seq1 cl-start1))
-				    (if cl-p2 (car cl-p2)
-				      (aref cl-seq2 cl-start2))))
-	  (setq cl-p1 (cdr cl-p1) cl-p2 (cdr cl-p2)
-		cl-start1 (1+ cl-start1) cl-start2 (1+ cl-start2)))
-	(and (or (< cl-start1 cl-end1) (< cl-start2 cl-end2))
-	     cl-start1)))))
+                               (:start1 0) :end1 (:start2 0) :end2) ()
+    (let* ((lens (and (not (zerop cl--optimize-safety))
+                       (cl--check-bound-indices cl-seq1 cl-seq2 cl-keys)))
+            (len1 (if lens (car lens) (or cl-end1 (length cl-seq1))))
+            (len2 (if lens (cdr lens) (or cl-end2 (length cl-seq2)))))
+      (or cl-end1 (setq cl-end1 len1))
+      (or cl-end2 (setq cl-end2 len2))
+      (if cl-from-end
+          (progn
+            (while (and (< cl-start1 cl-end1) (< cl-start2 cl-end2)
+                        (cl--check-match (elt cl-seq1 (1- cl-end1))
+                                         (elt cl-seq2 (1- cl-end2))))
+              (setq cl-end1 (1- cl-end1) cl-end2 (1- cl-end2)))
+            (and (or (< cl-start1 cl-end1) (< cl-start2 cl-end2))
+                 (1- cl-end1)))
+        (let ((cl-p1 (and (listp cl-seq1) (nthcdr cl-start1 cl-seq1)))
+              (cl-p2 (and (listp cl-seq2) (nthcdr cl-start2 cl-seq2))))
+          (while (and (< cl-start1 cl-end1) (< cl-start2 cl-end2)
+                      (cl--check-match (if cl-p1 (car cl-p1)
+                                         (aref cl-seq1 cl-start1))
+                                       (if cl-p2 (car cl-p2)
+                                         (aref cl-seq2 cl-start2))))
+            (setq cl-p1 (cdr cl-p1) cl-p2 (cdr cl-p2)
+                  cl-start1 (1+ cl-start1) cl-start2 (1+ cl-start2)))
+          (and (or (< cl-start1 cl-end1) (< cl-start2 cl-end2))
+               cl-start1))))))
 
 ;;;###autoload
 (defun cl-search (cl-seq1 cl-seq2 &rest cl-keys)
@@ -624,24 +707,28 @@ cl-search
 \nKeywords supported:  :test :test-not :key :start1 :end1 :start2 :end2 :from-end
 \n(fn SEQ1 SEQ2 [KEYWORD VALUE]...)"
   (cl--parsing-keywords (:test :test-not :key :from-end
-			(:start1 0) :end1 (:start2 0) :end2) ()
-    (or cl-end1 (setq cl-end1 (length cl-seq1)))
-    (or cl-end2 (setq cl-end2 (length cl-seq2)))
-    (if (>= cl-start1 cl-end1)
-	(if cl-from-end cl-end2 cl-start2)
-      (let* ((cl-len (- cl-end1 cl-start1))
-	     (cl-first (cl--check-key (elt cl-seq1 cl-start1)))
-	     (cl-if nil) cl-pos)
-	(setq cl-end2 (- cl-end2 (1- cl-len)))
-	(while (and (< cl-start2 cl-end2)
-		    (setq cl-pos (cl--position cl-first cl-seq2
-                                               cl-start2 cl-end2 cl-from-end))
-		    (apply 'cl-mismatch cl-seq1 cl-seq2
-			   :start1 (1+ cl-start1) :end1 cl-end1
-			   :start2 (1+ cl-pos) :end2 (+ cl-pos cl-len)
-			   :from-end nil cl-keys))
-	  (if cl-from-end (setq cl-end2 cl-pos) (setq cl-start2 (1+ cl-pos))))
-	(and (< cl-start2 cl-end2) cl-pos)))))
+                               (:start1 0) :end1 (:start2 0) :end2) ()
+    (let* ((lens (and (not (zerop cl--optimize-safety))
+                       (cl--check-bound-indices cl-seq1 cl-seq2 cl-keys)))
+            (len1 (if lens (car lens) (or cl-end1 (length cl-seq1))))
+            (len2 (if lens (cdr lens) (or cl-end2 (length cl-seq2)))))
+      (or cl-end1 (setq cl-end1 len1))
+      (or cl-end2 (setq cl-end2 len2))
+      (if (>= cl-start1 cl-end1)
+          (if cl-from-end cl-end2 cl-start2)
+        (let* ((cl-len (- cl-end1 cl-start1))
+               (cl-first (cl--check-key (elt cl-seq1 cl-start1)))
+               (cl-if nil) cl-pos)
+          (setq cl-end2 (- cl-end2 (1- cl-len)))
+          (while (and (< cl-start2 cl-end2)
+                      (setq cl-pos (cl--position cl-first cl-seq2
+                                                 cl-start2 cl-end2 cl-from-end))
+                      (apply 'cl-mismatch cl-seq1 cl-seq2
+                             :start1 (1+ cl-start1) :end1 cl-end1
+                             :start2 (1+ cl-pos) :end2 (+ cl-pos cl-len)
+                             :from-end nil cl-keys))
+            (if cl-from-end (setq cl-end2 cl-pos) (setq cl-start2 (1+ cl-pos))))
+          (and (< cl-start2 cl-end2) cl-pos))))))
 
 ;;;###autoload
 (defun cl-sort (cl-seq cl-pred &rest cl-keys)
diff --git a/test/lisp/emacs-lisp/cl-seq-tests.el b/test/lisp/emacs-lisp/cl-seq-tests.el
index 61e3d72033..9c6738048a 100644
--- a/test/lisp/emacs-lisp/cl-seq-tests.el
+++ b/test/lisp/emacs-lisp/cl-seq-tests.el
@@ -302,6 +302,101 @@ cl-seq--with-side-effects
     (should (equal '(2 8) (last (cl-replace list list2) 2)))
     (should (equal '(1 1) (last (cl-fill list 1) 2)))))
 
+(ert-deftest cl-seq-check-bounds ()
+  (let ((lst (list 1 2 3))
+        (lst2 (list 'a 'b 'c))
+        ;; t means pass, nil means fails.
+        (tests '("((lambda (x y) (cl-reduce #'max x :start 1)) . t)"
+                 "((lambda (x y) (cl-reduce #'max x :start -1)))"
+                 "((lambda (x y) (cl-reduce #'max x :start 4)))"
+                 "((lambda (x y) (cl-reduce #'max x :start 2 :end 1)))"
+                 ;;
+                 "((lambda (x y) (cl-fill x 'b :start 3)) . t)"
+                 "((lambda (x y) (cl-fill x 'b :start 4)))"
+                 "((lambda (x y) (cl-fill x 'b :start -1)))"
+                 "((lambda (x y) (cl-fill x 'b :start 2 :end 1)))"
+                 ;;
+                 "((lambda (x y) (cl-replace x y :start1 3)) . t)"
+                 "((lambda (x y) (cl-replace x y :start2 3)) . t)"
+                 "((lambda (x y) (cl-replace x y :start1 4)))"
+                 "((lambda (x y) (cl-replace x y :start2 4)))"
+                 "((lambda (x y) (cl-replace x y :start1 -1)))"
+                 "((lambda (x y) (cl-replace x y :start2 -1)))"
+                 "((lambda (x y) (cl-replace x y :start1 2 :end1 1)))"
+                 "((lambda (x y) (cl-replace x y :start2 2 :end2 1)))"
+                 ;;
+                 "((lambda (x y) (cl-remove nil x :start 3)) . t)"
+                 "((lambda (x y) (cl-remove nil x :start 4)))"
+                 "((lambda (x y) (cl-remove nil x :start -1)))"
+                 "((lambda (x y) (cl-remove nil x :start 2 :end 1)))"
+                 ;;
+                 "((lambda (x y) (cl-delete nil x :start 3)) . t)"
+                 "((lambda (x y) (cl-delete nil x :start 4)))"
+                 "((lambda (x y) (cl-delete nil x :start -1)))"
+                 "((lambda (x y) (cl-delete nil x :start 2 :end 1)))"
+                 ;;
+                 "((lambda (x y) (cl-delete-duplicates x :start 3)) . t)"
+                 "((lambda (x y) (cl-delete-duplicates x :start 4)))"
+                 "((lambda (x y) (cl-delete-duplicates x :start -1)))"
+                 "((lambda (x y) (cl-delete-duplicates x :start 2 :end 1)))"
+                 ;;
+                 "((lambda (x y) (cl-remove-duplicates x :start 3)) . t)"
+                 "((lambda (x y) (cl-remove-duplicates x :start 4)))"
+                 "((lambda (x y) (cl-remove-duplicates x :start -1)))"
+                 "((lambda (x y) (cl-remove-duplicates x :start 2 :end 1)))"
+                 ;;
+                 "((lambda (x y) (cl-substitute 'foo 2 x :start 3)) . t)"
+                 "((lambda (x y) (cl-substitute 'foo 2 x :start 4)))"
+                 "((lambda (x y) (cl-substitute 'foo 2 x :start -1)))"
+                 "((lambda (x y) (cl-substitute 'foo 2 x :start 2 :end 1)))"
+                 ;;
+                 "((lambda (x y) (cl-nsubstitute 'foo 2 x :start 3)) . t)"
+                 "((lambda (x y) (cl-nsubstitute 'foo 2 x :start 4)))"
+                 "((lambda (x y) (cl-nsubstitute 'foo 2 x :start -1)))"
+                 "((lambda (x y) (cl-nsubstitute 'foo 2 x :start 2 :end 1)))"
+                 ;;
+                 "((lambda (x y) (cl-position 2 x :start 4)))"
+                 "((lambda (x y) (cl-position 2 x :start -1)))"
+                 "((lambda (x y) (cl-position 2 x :start 2 :end 1)))"
+                 ;;
+                 "((lambda (x y) (cl-count 2 x :start 3)) . t)"
+                 "((lambda (x y) (cl-count 2 x :start 4)))"
+                 "((lambda (x y) (cl-count 2 x :start -1)))"
+                 "((lambda (x y) (cl-count 2 x :start 2 :end 1)))"
+                 ;;
+                 "((lambda (x y) (cl-mismatch x x :start1 1 :start2 3)) . t)"
+                 "((lambda (x y) (cl-mismatch x x :start1 1 :start2 4)))"
+                 "((lambda (x y) (cl-mismatch x x :start1 4 :start2 1)))"
+                 "((lambda (x y) (cl-mismatch x x :start1 -1 :start2 1)))"
+                 "((lambda (x y) (cl-mismatch x x :start1 1 :start2 -1)))"
+                 "((lambda (x y) (cl-mismatch x x :start1 2 :end1 1)))"
+                 "((lambda (x y) (cl-mismatch x x :start2 2 :end2 1)))"
+                 ;;
+                 "((lambda (x y) (cl-search x x :start1 3 :start2 3)) . t)"
+                 "((lambda (x y) (cl-search x x :start1 4 :start2 4)))"
+                 "((lambda (x y) (cl-search x x :start1 -1 :start2 3)))"
+                 "((lambda (x y) (cl-search x x :start1 1 :start2 -1)))"
+                 "((lambda (x y) (cl-search x x :start1 2 :end1 1)))"
+                 "((lambda (x y) (cl-search x x :start2 2 :end2 1)))"
+                 ;;
+                 "((lambda (x y) (cl-subseq x -1)) . t)"
+                 "((lambda (x y) (cl-subseq x -2 -1)) . t)"
+                 "((lambda (x y) (cl-subseq x -4)))"
+                 "((lambda (x y) (cl-subseq x 2 1)))")))
+    (dolist (limit '("start" "end"))
+      (dolist (x tests)
+        (let ((form
+               (car
+                (read-from-string
+                 (cond ((string-match ":start\\([1-2]?\\) \\([0-9-]+\\) :end\\([1-2]?\\)" x)
+                        x)
+                       ((string= limit "start") x)
+                       (t
+                        (replace-regexp-in-string "start" limit x)))))))
+          (if (cdr form)
+              (should (funcall (car form) lst lst2))
+            (should-error (funcall (car form) lst lst2))))))))
+
 
 (provide 'cl-seq-tests)
 ;;; cl-seq-tests.el ends here
-- 
2.11.0

From be0427570b987e942c934ab8ee841dc326a1a0be Mon Sep 17 00:00:00 2001
From: Tino Calancha <tino.calancha@gmail.com>
Date: Mon, 6 Feb 2017 15:21:03 +0900
Subject: [PATCH 2/2] * lisp/edmacro.el (edmacro-format-keys): Prevent :end
 index out-of-range.

---
 lisp/edmacro.el | 7 ++++---
 1 file changed, 4 insertions(+), 3 deletions(-)

diff --git a/lisp/edmacro.el b/lisp/edmacro.el
index 5fefc3102d..c3608829c0 100644
--- a/lisp/edmacro.el
+++ b/lisp/edmacro.el
@@ -560,10 +560,11 @@ edmacro-format-keys
 	(if prefix
 	    (setq desc (concat (edmacro-sanitize-for-string prefix) desc)))
 	(unless (string-match " " desc)
-	  (let ((times 1) (pos bind-len))
+	  (let ((times 1) (pos bind-len)
+	        (rest-mac-len (length rest-mac)))
 	    (while (not (cl-mismatch rest-mac rest-mac
-				     :start1 0 :end1 bind-len
-				     :start2 pos :end2 (+ bind-len pos)))
+				     :start1 0 :end1 (min bind-len rest-mac-len)
+				     :start2 pos :end2 (min (+ bind-len pos) rest-mac-len)))
 	      (cl-incf times)
 	      (cl-incf pos bind-len))
 	    (when (> times 1)
-- 
2.11.0

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
In GNU Emacs 26.0.50.1 (x86_64-pc-linux-gnu, GTK+ Version 3.22.7)
 of 2017-02-06
Repository revision: d45dbccc5d2360818e70bbb0bc816c62c8cf6cbe



^ permalink raw reply related	[flat|nested] 14+ messages in thread

* Re: [PATCH v4] Bound index checks in cl-seq functions
  2017-02-06  7:00       ` [PATCH v4] " Tino Calancha
@ 2017-02-06  7:15         ` Clément Pit-Claudel
  0 siblings, 0 replies; 14+ messages in thread
From: Clément Pit-Claudel @ 2017-02-06  7:15 UTC (permalink / raw)
  To: Tino Calancha; +Cc: Philipp Stephani, Emacs developers, Noam Postavsky

On 2017-02-06 02:00, Tino Calancha wrote:
> Clément Pit-Claudel <cpitclaudel@gmail.com> writes:
>> On 2017-02-05 02:11, Tino Calancha wrote:
>>> II) `cl--check-bound-indices' returns on success the sequence[s]
>>>     length[s], so that callers don't need to recompute them.
>> This sounds like a good idea, performance wise.  But maybe it would be
>> even better to disable these checks when (cl-declaim (optimize (safety
>> 0))) is set?
> Yes, that sounds right.
> The new patch disables those checks in that case (safety = 0).
> I got ~0.92 s without checks and ~ 1.10 with checks
> when i ran the following toy:

Great, thanks! :)



^ permalink raw reply	[flat|nested] 14+ messages in thread

* Re: [PATCH v4] Bound index checks in cl-seq functions
  2017-02-04 16:40 [PATCH] Bound index checks in cl-seq functions Tino Calancha
  2017-02-04 20:42 ` Philipp Stephani
  2017-02-04 20:51 ` Noam Postavsky
@ 2017-02-10  7:43 ` Tino Calancha
  2017-03-03  4:47   ` Tino Calancha
  2017-02-12 21:26 ` [PATCH] " Johan Bockgård
  3 siblings, 1 reply; 14+ messages in thread
From: Tino Calancha @ 2017-02-10  7:43 UTC (permalink / raw)
  To: John Wiegley, Eli Zaretskii
  Cc: Noam Postavsky, Philipp Stephani, tino.calancha,
	Clément Pit-Claudel, Emacs developers

Tino Calancha <tino.calancha@gmail.com> writes:

> Consider following evaluations:
>
> (cl-position 2 (list 1 2 3) :start -999)
> => -998
> (cl-position 2 (list 1 2 3) :start 999)
> => nil
> (cl-position 2 (list 1 2 3) :start 1 :end 0)
> => nil
>
> *) In the first two, the indices are out of range.
> *) In the 3rd, the start index is higher than the end index.
>
> Guy L. Steele recommends to throw an error in those cases:
> http://www.lispworks.com/documentation/HyperSpec/Issues/iss332_w.htm
>
> We have partially done the job, at least for `cl-subseq':
> (cl-subseq (list 1 2 3) 999)
> ; Signal: "Bad bounding indices: 999, nil"
>
> (cl-subseq (list 1 2 3) -999)
> ; Signal: "Bad bounding indices: -999, nil"
>
> (cl-subseq (list 1 2 3) -1)
> => (3) ; This is OK as documented in the docstring.
>
> See:
> https://lists.gnu.org/archive/html/emacs-devel/2015-08/msg00182.html
>
> It would be nice if all the sequence functions in cl-seq.el
> throw errors in those situations.
Dear maintainers,

do you agree with pushing to master the patch v4 for this thread ?
Here:
https://lists.gnu.org/archive/html/emacs-devel/2017-02/msg00191.html

Any further suggestion/comment or something to address?

Thanks,
Tino



^ permalink raw reply	[flat|nested] 14+ messages in thread

* Re: [PATCH] Bound index checks in cl-seq functions
  2017-02-04 16:40 [PATCH] Bound index checks in cl-seq functions Tino Calancha
                   ` (2 preceding siblings ...)
  2017-02-10  7:43 ` Tino Calancha
@ 2017-02-12 21:26 ` Johan Bockgård
  3 siblings, 0 replies; 14+ messages in thread
From: Johan Bockgård @ 2017-02-12 21:26 UTC (permalink / raw)
  To: Tino Calancha; +Cc: Emacs developers

Tino Calancha <tino.calancha@gmail.com> writes:

> Guy L. Steele recommends to throw an error in those cases:
> http://www.lispworks.com/documentation/HyperSpec/Issues/iss332_w.htm

FWIW, the phrase "is an error" means that the consequences are
undefined—it does not mean that an error must be signaled.



^ permalink raw reply	[flat|nested] 14+ messages in thread

* Re: Bound index checks in cl-seq functions
  2017-02-10  7:43 ` Tino Calancha
@ 2017-03-03  4:47   ` Tino Calancha
  2017-03-03 13:52     ` Eli Zaretskii
  0 siblings, 1 reply; 14+ messages in thread
From: Tino Calancha @ 2017-03-03  4:47 UTC (permalink / raw)
  To: John Wiegley, Eli Zaretskii; +Cc: tino.calancha, Emacs developers

Tino Calancha <tino.calancha@gmail.com> writes:

> Tino Calancha <tino.calancha@gmail.com> writes:
>
>> Consider following evaluations:
>>
>> (cl-position 2 (list 1 2 3) :start -999)
>> => -998
>> (cl-position 2 (list 1 2 3) :start 999)
>> => nil
>> (cl-position 2 (list 1 2 3) :start 1 :end 0)
>> => nil
>>
>> *) In the first two, the indices are out of range.
>> *) In the 3rd, the start index is higher than the end index.
>>
>> Guy L. Steele recommends to throw an error in those cases:
>> http://www.lispworks.com/documentation/HyperSpec/Issues/iss332_w.htm
>>
>> We have partially done the job, at least for `cl-subseq':
>> (cl-subseq (list 1 2 3) 999)
>> ; Signal: "Bad bounding indices: 999, nil"
>>
>> (cl-subseq (list 1 2 3) -999)
>> ; Signal: "Bad bounding indices: -999, nil"
>>
>> (cl-subseq (list 1 2 3) -1)
>> => (3) ; This is OK as documented in the docstring.
>>
>> See:
>> https://lists.gnu.org/archive/html/emacs-devel/2015-08/msg00182.html
>>
>> It would be nice if all the sequence functions in cl-seq.el
>> throw errors in those situations.
> Dear maintainers,
>
> do you agree with pushing to master the patch v4 for this thread ?
> Here:
> https://lists.gnu.org/archive/html/emacs-devel/2017-02/msg00191.html
>
> Any further suggestion/comment or something to address?
Dear John and Eli,

i am curious if you have any opinion on this thread.  Currently just
`cl-subseq' performs the bounding check index.  I don't see a
clear reason why just this function do it.
Are you in favour or against doing those checks in other functions
handling sequences?

Regards,
Tino



^ permalink raw reply	[flat|nested] 14+ messages in thread

* Re: Bound index checks in cl-seq functions
  2017-03-03  4:47   ` Tino Calancha
@ 2017-03-03 13:52     ` Eli Zaretskii
  2017-04-14 22:01       ` John Wiegley
  0 siblings, 1 reply; 14+ messages in thread
From: Eli Zaretskii @ 2017-03-03 13:52 UTC (permalink / raw)
  To: Tino Calancha; +Cc: jwiegley, emacs-devel

> From: Tino Calancha <tino.calancha@gmail.com>
> Cc: Emacs developers <emacs-devel@gnu.org>, tino.calancha@gmail.com
> Date: Fri, 03 Mar 2017 13:47:45 +0900
> 
> i am curious if you have any opinion on this thread.  Currently just
> `cl-subseq' performs the bounding check index.  I don't see a
> clear reason why just this function do it.
> Are you in favour or against doing those checks in other functions
> handling sequences?

I don't have any firm opinions on this.  I do note, however, that your
motivation for signaling an error was contested.  So I wonder whether
the cause for this is strong enough to make that change.  But I'm
willing to defer to people who use these features more than I do.



^ permalink raw reply	[flat|nested] 14+ messages in thread

* Re: Bound index checks in cl-seq functions
  2017-03-03 13:52     ` Eli Zaretskii
@ 2017-04-14 22:01       ` John Wiegley
  2017-04-25 11:14         ` Tino Calancha
  0 siblings, 1 reply; 14+ messages in thread
From: John Wiegley @ 2017-04-14 22:01 UTC (permalink / raw)
  To: Eli Zaretskii; +Cc: emacs-devel, Tino Calancha

>>>>> Eli Zaretskii <eliz@gnu.org> writes:

>> i am curious if you have any opinion on this thread.  Currently just
>> `cl-subseq' performs the bounding check index.  I don't see a
>> clear reason why just this function do it.
>> Are you in favour or against doing those checks in other functions
>> handling sequences?

> I don't have any firm opinions on this. I do note, however, that your
> motivation for signaling an error was contested. So I wonder whether the
> cause for this is strong enough to make that change. But I'm willing to
> defer to people who use these features more than I do.

I also don't have a strong opinion. Bounds checking is usually helpful, if it
would help those who are using these functions. Otherwise, I wouldn't make the
change "just to make the change", even though it sounds like a good idea.

-- 
John Wiegley                  GPG fingerprint = 4710 CF98 AF9B 327B B80F
http://newartisans.com                          60E1 46C4 BD1A 7AC1 4BA2



^ permalink raw reply	[flat|nested] 14+ messages in thread

* Re: Bound index checks in cl-seq functions
  2017-04-14 22:01       ` John Wiegley
@ 2017-04-25 11:14         ` Tino Calancha
  0 siblings, 0 replies; 14+ messages in thread
From: Tino Calancha @ 2017-04-25 11:14 UTC (permalink / raw)
  To: John Wiegley; +Cc: Eli Zaretskii, emacs-devel, Tino Calancha



On Fri, 14 Apr 2017, John Wiegley wrote:

>>>>>> Eli Zaretskii <eliz@gnu.org> writes:
>
>>> i am curious if you have any opinion on this thread.  Currently just
>>> `cl-subseq' performs the bounding check index.  I don't see a
>>> clear reason why just this function do it.
>>> Are you in favour or against doing those checks in other functions
>>> handling sequences?
>
>> I don't have any firm opinions on this. I do note, however, that your
>> motivation for signaling an error was contested. So I wonder whether the
>> cause for this is strong enough to make that change. But I'm willing to
>> defer to people who use these features more than I do.
>
> I also don't have a strong opinion. Bounds checking is usually helpful, if it
> would help those who are using these functions. Otherwise, I wouldn't make the
> change "just to make the change", even though it sounds like a good idea.
Thank you.
I am fine with not adding this patch.



^ permalink raw reply	[flat|nested] 14+ messages in thread

end of thread, other threads:[~2017-04-25 11:14 UTC | newest]

Thread overview: 14+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2017-02-04 16:40 [PATCH] Bound index checks in cl-seq functions Tino Calancha
2017-02-04 20:42 ` Philipp Stephani
2017-02-04 20:51 ` Noam Postavsky
2017-02-05  7:11   ` [PATCH v2] " Tino Calancha
2017-02-05 14:56     ` [PATCH v3] " Tino Calancha
2017-02-05 16:11     ` [PATCH v2] " Clément Pit-Claudel
2017-02-06  7:00       ` [PATCH v4] " Tino Calancha
2017-02-06  7:15         ` Clément Pit-Claudel
2017-02-10  7:43 ` Tino Calancha
2017-03-03  4:47   ` Tino Calancha
2017-03-03 13:52     ` Eli Zaretskii
2017-04-14 22:01       ` John Wiegley
2017-04-25 11:14         ` Tino Calancha
2017-02-12 21:26 ` [PATCH] " Johan Bockgård

Code repositories for project(s) associated with this public inbox

	https://git.savannah.gnu.org/cgit/emacs.git

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).