Testing how array-creation routines interact with continuations, and crashing when the user does something that "is an error". Bradley Lucier (08 Mar 2023 21:19 UTC)

Testing how array-creation routines interact with continuations, and crashing when the user does something that "is an error". Bradley Lucier 08 Mar 2023 21:19 UTC

There are currently no tests in the test suite for how

array-{copy|append|stack|decurry|block}[!]*

interact with continuations, and I'd like to fix that, especially for
independent implementations of this SRFI.

I started with (pp is pretty-print to the system output port):

 > (import (srfi 231))
 > (let* ((cont #f)
        (call-cont #t)
        (domain (make-interval '#(2 2)))
        (A_ (lambda (i j)
              (call-with-current-continuation
               (lambda (c)
                 (if (= i j 0)
                     (set! cont c))
                 1))))
        (A (make-array domain A_))
        (array-list '()))
   (set! array-list (cons (array-copy A) array-list))
   (pp 'printing)
   (for-each (lambda (A) (pp (array->list* A))) array-list)
   (if call-cont
       (begin
         (set! call-cont #f)
         (cont 4)))
   (pp (apply eq? array-list)))
printing
((1 1) (1 1))
printing
((4 1) (1 1))
((1 1) (1 1))
#f

which works as expected.

Replacing array-copy with array-copy! in this test causes the library to
crash when compiled in Gambit with safety checks removed:

 > (let* ((cont #f)
        (call-cont #t)
        (domain (make-interval '#(2 2)))
        (A_ (lambda (i j)
              (call-with-current-continuation
               (lambda (c)
                 (if (= i j 0)
                     (set! cont c))
                 1))))
        (A (make-array domain A_))
        (array-list '()))
   (set! array-list (cons (array-copy! A) array-list))
   (pp 'printing)
   (for-each (lambda (A) (pp (array->list* A))) array-list)
   (if call-cont
       (begin
         (set! call-cont #f)
         (cont 4)))
   (pp (apply eq? array-list)))
printing
((1 1) (1 1))
printing
Segmentation fault (core dumped)

I didn't expect this, I just expected that the first and second elements
of array-list would be eq?.  Running the library interpreted shows the
problem:

 > (load "generic-arrays.scm")
"/home/lucier/lang/scheme/srfi-231/srfi-231-temp/generic-arrays.scm"
 > (let* ((cont #f)
        (call-cont #t)
        (domain (make-interval '#(2 2)))
        (A_ (lambda (i j)
              (call-with-current-continuation
               (lambda (c)
                 (if (= i j 0)
                     (set! cont c))
                 1))))
        (A (make-array domain A_))
        (array-list '()))
   (set! array-list (cons (array-copy! A) array-list))
   (pp 'printing)
   (for-each (lambda (A) (pp (array->list* A))) array-list)
   (if call-cont
       (begin
         (set! call-cont #f)
         (cont 4)))
   (pp (apply eq? array-list)))
printing
((1 1) (1 1))
*** ERROR IN #<procedure #2>, "generic-arrays.scm"@2534.42 -- (Argument
2, k) Out of range
(vector-set! #(4 1 1 1) 5 1)
1>

Here is the code:

                           ;; No checks needed, storage-class-setter is
vector-set!
                           (let ((body (%%array-body destination)))
                             (%%interval-for-each
                              (case (%%interval-dimension domain)
                                ((0)  (let ((index initial-offset))
                                        (lambda ()
                                          (vector-set! body index (getter))
                                          (set! index (fx+ index 1)))))
   ;; not necessary
                                ((1)  (let ((index initial-offset))
                                        (lambda (i)
                                          (vector-set! body index
(getter i))
                                          (set! index (fx+ index 1)))))
                                ((2)  (let ((index initial-offset))
                                        (lambda (i j)
                                          (vector-set! body index
(getter i j))  ;; <<<<<< problem
                                          (set! index (fx+ index 1)))))
                                ((3)  (let ((index initial-offset))
                                        (lambda (i j k)
                                          (vector-set! body index
(getter i j k))
                                          (set! index (fx+ index 1)))))
                                ((4)  (let ((index initial-offset))
                                        (lambda (i j k l)
                                          (vector-set! body index
(getter i j k l))
                                          (set! index (fx+ index 1)))))
                                (else (let ((index initial-offset))
                                        (lambda multi-index
                                          (vector-set! body index (apply
getter multi-index))
                                          (set! index (fx+ index 1))))))
                              domain))

So I was using mutated state (the value of index) in a (perhaps vain)
attempt to speed things up, but this causes index to walk off the end of
the specialized array result's body the second time the continuation is
invoked.

The specifications says "Don't do that!", don't invoke the continuation
of the argument array's getter more than once when calling array-copy!.

On the other hand, I didn't expect the library to crash the system.

Is this OK behavior, to crash when the user does something that "is an
error", or would it be better to try to catch all such instances while
possibly slowing down the general case?

Brad