signal errors for bad initialization of slot definitions
authorChristophe Rhodes <c.rhodes@gold.ac.uk>
Mon, 21 Oct 2013 11:15:04 +0000 (12:15 +0100)
committerChristophe Rhodes <c.rhodes@gold.ac.uk>
Mon, 21 Oct 2013 11:15:04 +0000 (12:15 +0100)
In order to get slot definition initialization right, move the
readers/writers slots to direct slot definitions, and write code to
detect all the bad cases documented in the MOP dictionary at
initialization time.  Condition slots also need changing, to have
separate initform and initfunction information.  Moving the slots
breaks metacircle resolution, naturally, so rewrite that to find the
relevant reader/writer information from the direct slots at runtime.
The irony of having to rewrite metacircle detection and resolution for
a bug tagged "easy" on launchpad is not lost on me.

Originally reported by Bruno Haible at some point in prehistory,
probably around December 2004, lp#309072.

NEWS
package-data-list.lisp-expr
src/code/condition.lisp
src/pcl/braid.lisp
src/pcl/defs.lisp
src/pcl/dfun.lisp
src/pcl/early-low.lisp
src/pcl/init.lisp
tests/mop.pure.lisp

diff --git a/NEWS b/NEWS
index c2fe8cd..92ee4d3 100644 (file)
--- a/NEWS
+++ b/NEWS
@@ -24,6 +24,8 @@ changes relative to sbcl-1.1.12:
   * bug fix: (the [type] [constant]) now warns when [constant] matches
     [type] except for the number of values.  (Reported by Nathan Trapuzzano
     on sbcl-help)
+  * bug fix: signal errors in required cases of slot-definition initialization
+    protocol.  (lp#309072)
 
 changes in sbcl-1.1.12 relative to sbcl-1.1.11:
   * enhancement: Add sb-bsd-sockets:socket-shutdown, for calling
index 7d1b516..cbf1ab2 100644 (file)
@@ -1940,6 +1940,7 @@ is a good idea, but see SB-SYS re. blurring of boundaries."
                "CONDITION-SLOT-INITARGS"
                "CONDITION-SLOT-INITFORM"
                "CONDITION-SLOT-INITFORM-P"
+               "CONDITION-SLOT-INITFUNCTION"
                "CONDITION-SLOT-NAME" "CONDITION-SLOT-READERS"
                "CONDITION-SLOT-WRITERS"
 
index c2c2a39..a1d40d5 100644 (file)
   (writers (missing-arg) :type list)
   ;; true if :INITFORM was specified
   (initform-p (missing-arg) :type (member t nil))
-  ;; If this is a function, call it with no args. Otherwise, it's the
-  ;; actual value.
-  (initform (missing-arg) :type t)
+  ;; the initform if :INITFORM was specified, otherwise NIL
+  (initform nil :type t)
+  ;; if this is a function, call it with no args to get the initform value
+  (initfunction (missing-arg) :type t)
   ;; allocation of this slot, or NIL until defaulted
   (allocation nil :type (member :instance :class nil))
-  ;; If ALLOCATION is :CLASS, this is a cons whose car holds the value.
+  ;; If ALLOCATION is :CLASS, this is a cons whose car holds the value
   (cell nil :type (or cons null))
   ;; slot documentation
   (documentation nil :type (or string null)))
 
     ;; Otherwise use the initform of SLOT, if there is one.
     (if (condition-slot-initform-p slot)
-        (let ((initform (condition-slot-initform slot)))
-          (if (functionp initform)
-              (funcall initform)
-              initform))
+        (let ((initfun (condition-slot-initfunction slot)))
+          (aver (functionp initfun))
+          (funcall initfun))
         (error "unbound condition slot: ~S" (condition-slot-name slot)))))
 
 (defun find-condition-class-slot (condition-class slot-name)
                    (setf (condition-slot-initform-p found)
                          (condition-slot-initform-p sslot))
                    (setf (condition-slot-initform found)
-                         (condition-slot-initform sslot)))
+                         (condition-slot-initform sslot))
+                   (setf (condition-slot-initfunction sslot)
+                         (condition-slot-initfunction found)))
                  (unless (condition-slot-allocation found)
                    (setf (condition-slot-allocation found)
                          (condition-slot-allocation sslot))))
              (unless (condition-slot-cell slot)
                (setf (condition-slot-cell slot)
                      (list (if (condition-slot-initform-p slot)
-                               (let ((initform (condition-slot-initform slot)))
-                                 (if (functionp initform)
-                                     (funcall initform)
-                                     initform))
+                               (let ((initfun (condition-slot-initfunction slot)))
+                                 (aver (functionp initfun))
+                                 (funcall initfun))
                                *empty-condition-slot*))))
              (push slot (condition-classoid-class-slots class)))
             ((:instance nil)
              (setf (condition-slot-allocation slot) :instance)
-             (when (or (functionp (condition-slot-initform slot))
+             ;; FIXME: isn't this "always hairy"?
+             (when (or (functionp (condition-slot-initfunction slot))
                        (dolist (initarg (condition-slot-initargs slot) nil)
                          (when (functionp (third (assoc initarg e-def-initargs)))
                            (return t))))
                      :writers ',(writers)
                      :initform-p ',initform-p
                      :documentation ',documentation
-                     :initform ,(when initform-p
-                                  `#'(lambda () ,initform))
+                     :initform ,(when initform-p `',initform)
+                     :initfunction ,(when initform-p
+                                      `#'(lambda () ,initform))
                      :allocation ',allocation)))))
 
       (dolist (option options)
index ce6bbc1..8f1ef7b 100644 (file)
       (set-val 'initform     (get-val :initform))
       (set-val 'initfunction (get-val :initfunction))
       (set-val 'initargs     (get-val :initargs))
-      (set-val 'readers      (get-val :readers))
-      (set-val 'writers      (get-val :writers))
+      (unless effective-p
+        (set-val 'readers      (get-val :readers))
+        (set-val 'writers      (get-val :writers)))
       (set-val 'allocation   :instance)
       (set-val '%type        (or (get-val :type) t))
       (set-val '%documentation (or (get-val :documentation) ""))
            :readers ,(condition-slot-readers slot)
            :writers ,(condition-slot-writers slot)
            ,@(when (condition-slot-initform-p slot)
-               (let ((form-or-fun (condition-slot-initform slot)))
-                 (if (functionp form-or-fun)
-                     `(:initfunction ,form-or-fun)
-                     `(:initform ,form-or-fun
-                       :initfunction ,(lambda () form-or-fun)))))
+               (let ((initform (condition-slot-initform slot))
+                     (initfun (condition-slot-initfunction slot)))
+                 `(:initform ',initform :initfunction ,initfun)))
            :allocation ,(condition-slot-allocation slot)
            :documentation ,(condition-slot-documentation slot))))
     (cond ((structure-type-p name)
index f1e50b9..597a1b2 100644 (file)
     :initform nil
     :initarg :initfunction
     :accessor slot-definition-initfunction)
-   (readers
-    :initform nil
-    :initarg :readers
-    :accessor slot-definition-readers)
-   (writers
-    :initform nil
-    :initarg :writers
-    :accessor slot-definition-writers)
    (initargs
     :initform nil
     :initarg :initargs
      :accessor slot-definition-internal-writer-function)))
 
 (defclass direct-slot-definition (slot-definition)
-  ())
+  ((readers
+    :initform nil
+    :initarg :readers
+    :accessor slot-definition-readers)
+   (writers
+    :initform nil
+    :initarg :writers
+    :accessor slot-definition-writers)))
 
 (defclass effective-slot-definition (slot-definition)
   ((accessor-flags
index 05aace9..c463a91 100644 (file)
@@ -181,8 +181,13 @@ Except see also BREAK-VICIOUS-METACIRCLE.  -- CSR, 2003-05-28
 ;;; lookup machinery.
 
 (defvar *standard-classes*
+  ;; KLUDGE: order matters!  finding effective slot definitions
+  ;; involves calling slot-definition-name, and we need to do that to
+  ;; break metacycles, so STANDARD-EFFECTIVE-SLOT-DEFINITION must
+  ;; precede STANDARD-DIRECT-SLOT-DEFINITION in this list, at least
+  ;; until ACCESSES-STANDARD-CLASS-SLOT-P is generalized
   '(standard-method standard-generic-function standard-class
-    standard-effective-slot-definition))
+    standard-effective-slot-definition standard-direct-slot-definition))
 
 (defvar *standard-slot-locations* (make-hash-table :test 'equal))
 
@@ -224,6 +229,10 @@ Except see also BREAK-VICIOUS-METACIRCLE.  -- CSR, 2003-05-28
   (standard-slot-value slotd slot-name
                        *the-class-standard-effective-slot-definition*))
 
+(defun standard-slot-value/dslotd (slotd slot-name)
+  (standard-slot-value slotd slot-name
+                       *the-class-standard-direct-slot-definition*))
+
 (defun standard-slot-value/class (class slot-name)
   (standard-slot-value class slot-name *the-class-standard-class*))
 \f
@@ -1107,17 +1116,32 @@ Except see also BREAK-VICIOUS-METACIRCLE.  -- CSR, 2003-05-28
 ;;; object of the slot accessed, and ACCESSOR-TYPE is one of the symbols
 ;;; READER or WRITER describing the slot access.
 (defun accesses-standard-class-slot-p (gf)
-  (flet ((standard-class-slot-access (gf class)
-           (loop with gf-name = (standard-slot-value/gf gf 'name)
-                 for slotd in (standard-slot-value/class class 'slots)
-                 ;; FIXME: where does BOUNDP fit in here?  Is it
-                 ;; relevant?
-                 as readers = (standard-slot-value/eslotd slotd 'readers)
-                 as writers = (standard-slot-value/eslotd slotd 'writers)
-                 if (member gf-name readers :test #'equal)
-                   return (values slotd 'reader)
-                 else if (member gf-name writers :test #'equal)
-                   return (values slotd 'writer))))
+  (labels
+      ((all-dslotds (class &aux done)
+         (labels ((all-dslotds-aux (class)
+                    (if (or (member class done) (not (eq (class-of class) *the-class-standard-class*)))
+                        nil
+                        (progn
+                          (push class done)
+                          (append (standard-slot-value/class class 'direct-slots)
+                                  (mapcan #'(lambda (c)
+                                              (copy-list (all-dslotds-aux c)))
+                                          (standard-slot-value/class class 'direct-superclasses)))))))
+           (all-dslotds-aux class)))
+       (standard-class-slot-access (gf class)
+
+         (loop with gf-name = (standard-slot-value/gf gf 'name)
+            with eslotds = (standard-slot-value/class class 'slots)
+            with dslotds = (all-dslotds class)
+            for dslotd in dslotds
+            as readers = (standard-slot-value/dslotd dslotd 'readers)
+            as writers = (standard-slot-value/dslotd dslotd 'writers)
+            as name = (standard-slot-value/dslotd dslotd 'name)
+            as eslotd = (find name eslotds :key (lambda (x) (standard-slot-value/eslotd x 'name)))
+            if (member gf-name readers :test #'equal)
+            return (values eslotd 'reader)
+            else if (member gf-name writers :test #'equal)
+            return (values eslotd 'writer))))
     (dolist (class-name *standard-classes*)
       (let ((class (find-class class-name)))
         (multiple-value-bind (slotd accessor-type)
index cbe9c77..3821cfc 100644 (file)
                   *the-class-global-writer-method*
                   *the-class-global-boundp-method*
                   *the-class-standard-generic-function*
+                  *the-class-standard-direct-slot-definition*
                   *the-class-standard-effective-slot-definition*
 
                   *the-eslotd-standard-class-slots*
index 5d3a325..6b0c91a 100644 (file)
              (setf (gethash type **typecheck-cache**) fun
                    (slot-info-typecheck info) fun))))))))
 
+(define-condition slotd-initialization-error (reference-condition error)
+  ((initarg :initarg :initarg :reader slotd-initialization-error-initarg)
+   (kind :initarg :kind :reader slotd-initialization-error-kind)
+   (value :initarg :value :initform nil :reader slotd-initialization-error-value))
+  (:default-initargs :references (list '(:amop :initialization slot-definition)))
+  (:report (lambda (condition stream)
+             (let ((initarg (slotd-initialization-error-initarg condition))
+                   (kind (slotd-initialization-error-kind condition))
+                   (value (slotd-initialization-error-value condition)))
+               (format stream
+                       "~@<Invalid ~S initialization: the initialization ~
+                        argument ~S was ~
+                        ~[missing~*~;not a symbol: ~S~;constant: ~S~].~@:>"
+                       'slot-definition initarg
+                       (getf '(:missing 0 :symbol 1 :constant 2) kind)
+                       value)))))
+
+(define-condition slotd-initialization-type-error (slotd-initialization-error type-error)
+  ((value :initarg :datum))
+  (:report (lambda (condition stream)
+             (let ((initarg (slotd-initialization-error-initarg condition))
+                   (datum (type-error-datum condition))
+                   (expected-type (type-error-expected-type condition)))
+               (format stream
+                       "~@<Invalid ~S initialization: the initialization ~
+                        argument ~S was ~S, which is not of type ~S.~@:>"
+                       'slot-definition initarg
+                       datum expected-type)))))
+
+(defmethod initialize-instance :before ((slotd slot-definition)
+                                        &key (name nil namep)
+                                          (initform nil initformp)
+                                          (initfunction nil initfunp)
+                                          (type nil typep)
+                                          (allocation nil allocationp)
+                                          (initargs nil initargsp)
+                                          (documentation nil docp))
+  (unless namep
+    (error 'slotd-initialization-error :initarg :name :kind :missing))
+  (unless (symbolp name)
+    (error 'slotd-initialization-type-error :initarg :name :datum name :expected-type 'symbol))
+  (when (constantp name)
+    (error 'slotd-initialization-error :initarg :name :kind :constant :value name))
+  (when (and initformp (not initfunp))
+    (error 'slotd-initialization-error :initarg :initfunction :kind :missing))
+  (when (and initfunp (not initformp))
+    (error 'slotd-initialization-error :initarg :initform :kind :missing))
+  (when (and typep (not t))
+    ;; FIXME: do something.  Need SYNTACTICALLY-VALID-TYPE-SPECIFIER-P
+    )
+  (when (and allocationp (not (symbolp allocation)))
+    (error 'slotd-initialization-type-error :initarg :allocation :datum allocation :expected-type 'symbol))
+  (when initargsp
+    (unless (typep initargs 'list)
+      (error 'slotd-initialization-type-error :initarg :initarg :datum initargs :expected-type 'list))
+    (do ((is initargs (cdr is)))
+        ((atom is)
+         (unless (null is)
+           (error 'slotd-initialization-type-error :initarg :initarg :datum initargs :expected-type '(satisfies proper-list-p))))
+      (unless (symbolp (car is))
+        (error 'slotd-initialization-type-error :initarg :initarg :datum is :expected-type '(or null (cons symbol))))))
+  (when docp
+    (unless (typep documentation '(or null string))
+      (error 'slotd-initialization-type-error :initarg :documentation :datum documentation :expected-type '(or null string)))))
+
+(defmethod initialize-instance :before ((dslotd direct-slot-definition)
+                                        &key
+                                          (readers nil readersp)
+                                          (writers nil writersp))
+  (macrolet ((check (arg argp)
+               `(when ,argp
+                  (unless (typep ,arg 'list)
+                    (error 'slotd-initialization-type-error
+                           :initarg ,(keywordicate arg)
+                           :datum ,arg :expected-type 'list))
+                  (do ((as ,arg (cdr as)))
+                      ((atom as)
+                       (unless (null as)
+                         (error 'slotd-initialization-type-error
+                                :initarg ,(keywordicate arg)
+                                :datum ,arg :expected-type '(satisfies proper-list-p))))
+                    (unless (valid-function-name-p (car as))
+                      (error 'slotd-initialization-type-error
+                             :initarg ,(keywordicate arg)
+                             :datum ,arg :expected-type '(or null (cons (satisfies valid-function-name-p)))))))))
+    (check readers readersp)
+    (check writers writersp)))
+
 (defmethod initialize-instance :after ((slotd effective-slot-definition) &key)
   (let ((info (make-slot-info :slotd slotd)))
     (generate-slotd-typecheck slotd info)
index b35f47d..4b4bb15 100644 (file)
     (ensure-generic-function 'make-instance :method-combination mc))
   ;; Let's make sure the list works too...
   (ensure-generic-function 'make-instance :method-combination '(standard)))
+
+(with-test (:name :bug-309072)
+  ;; original reported test cases
+  (raises-error? (make-instance 'sb-mop:slot-definition))
+  (raises-error? (make-instance 'sb-mop:slot-definition :name 'pi))
+  (raises-error? (make-instance 'sb-mop:slot-definition :name 3))
+  ;; extra cases from the MOP dictionary
+  (raises-error? (make-instance 'sb-mop:slot-definition :name 'x
+                                :initform nil))
+  (raises-error? (make-instance 'sb-mop:slot-definition :name 'x
+                                :initfunction (lambda () nil)))
+  (raises-error? (make-instance 'sb-mop:slot-definition :name 'x
+                                :initfunction (lambda () nil)))
+  (raises-error? (make-instance 'sb-mop:slot-definition :name 'x
+                                :allocation ""))
+  (raises-error? (make-instance 'sb-mop:slot-definition :name 'x
+                                :initargs ""))
+  (raises-error? (make-instance 'sb-mop:slot-definition :name 'x
+                                :initargs '(foo . bar)))
+  (raises-error? (make-instance 'sb-mop:slot-definition :name 'x
+                                :initargs '(foo bar 3)))
+  (raises-error? (make-instance 'sb-mop:slot-definition :name 'x
+                                :documentation '(())))
+  ;; distinction between DIRECT- and EFFECTIVE- slot definitions
+  (raises-error? (make-instance 'sb-mop:effective-slot-definition
+                                :name 'x :readers '(foo)))
+  (raises-error? (make-instance 'sb-mop:effective-slot-definition
+                                :name 'x :writers '(foo)))
+  (make-instance 'sb-mop:direct-slot-definition
+                 :name 'x :readers '(foo))
+  (make-instance 'sb-mop:direct-slot-definition
+                 :name 'x :writers '(foo))
+  (raises-error? (make-instance 'sb-mop:direct-slot-definition
+                                :name 'x :readers ""))
+  (raises-error? (make-instance 'sb-mop:direct-slot-definition
+                                :name 'x :readers '(3)))
+  (raises-error? (make-instance 'sb-mop:direct-slot-definition
+                                :name 'x :readers '(foo . bar)))
+  (raises-error? (make-instance 'sb-mop:direct-slot-definition
+                                :name 'x :writers ""))
+  (raises-error? (make-instance 'sb-mop:direct-slot-definition
+                                :name 'x :writers '(3)))
+  (raises-error? (make-instance 'sb-mop:direct-slot-definition
+                                :name 'x :writers '(foo . bar))))