Some time ago I did a deep dive into the racket/draw
contracts. Here is an observation that led me to some bugs: class contracts do not provide a guarantee on objects that are created from within the contract boundary.
Example 1
The numbers given to in-region?
are expected to be real?
. If you give imaginary numbers
(define reg (new region%))
(send reg in-region? 0+i 0+i)
the correct error from the region%
class contract is raised:
; in-region?: contract violation
; expected: real?
; given: 0+1i
; in: the 1st argument of
; the in-region? method in
; region%/c
However, regions can be created indirectly via the clipping region
(define r-dc (new record-dc%))
(send r-dc set-clipping-rect 0 0 550 400)
(define reg (send r-dc get-clipping-region))
(send reg in-region? 0+i 0+i)
and such will give an internal error since the region was not created through the protected region%
class:
; cairo_in_fill: given value does not fit primitive C type
; C type: _double*
; value: 0.0+1.0i
Example 2
The fifth argument to get-argb-pixels
is expected to be bytes?
(define bmp (make-bitmap 550 400))
(send bmp get-argb-pixels 0 0 0 0 #f)
and gives an error as such
; get-argb-pixels: contract violation
; expected: bytes?
; given: #f
; in: an and/c case of
; the 5th argument of
; the get-argb-pixels method in
; the range of ...
But bitmaps can be created through the make-bitmap
method of canvases
(define frame (new frame% [label "Example"]))
(define canvas (new canvas% [parent frame]))
(define bmp (send canvas make-bitmap 550 400))
(send bmp get-argb-pixels 0 0 0 0 #f)
and in such a case an internal error is raised because it's unprotected
; bytes-length: contract violation
; expected: bytes?
; given: #f
Example 3
The blink-caret
method of snips expects a dc<%>
(define sn (new snip%))
(send sn blink-caret #f #f #f)
and the contract gives this error
; blink-caret: contract violation
; expected: (is-a?/c dc<%>)
; given: #f
; in: the 1st argument of
; the blink-caret method in
; the 2nd conjunct of ...
However, cloning the snip constructs a new object that is not protected
(define sn (new snip%))
(define sn2 (send sn copy))
(send sn2 blink-caret #f #f #f)
and gives this different error
; blink-caret method of snip%: expected argument of type <dc<%> instance>; given: #f; other arguments: #f #f
which doesn't come from a contract check, but is a (usually redundant) defensive check that is present in the blink-caret
method.
Summary
These are just a few instances I found in racket/draw
βthere are probably quite a few more. I'm not sure what the takeaway is. My thought is that using (is-a?/c my-class%)
and then subsequently assuming that objects satisfy the class contract on my-class%
is dangerous since it assumes that my-class%
objects are only constructed through the protected constructor. Using object/c
contracts everywhere instead of is-a?/c
isn't a panacea either because, as far as I know, they will not get collapsed and wrappers might quickly accumulate. So while these bugs that I mentioned can be fixed individually, I feel as though there is a deeper concern here with respect to class contracts in general.