records: Insert record type ABI checks in constructors.
* guix/records.scm (print-record-abi-mismatch-error): New procedure. <top level>: Add 'set-exception-printer!' call. (current-abi-identifier, abi-check): New procedures. (make-syntactic-constructor): Add #:abi-cookie parameter. Insert calls to 'abi-check'. (define-record-type*)[compute-abi-cookie]: New procedure. Use it and emit a definition of the 'current-abi-identifier' for TYPE. * tests/records.scm ("ABI checks"): New test.
This commit is contained in:
parent
56f9d442e0
commit
7874bbbb9f
|
@ -1,5 +1,5 @@
|
||||||
;;; GNU Guix --- Functional package management for GNU
|
;;; GNU Guix --- Functional package management for GNU
|
||||||
;;; Copyright © 2012, 2013, 2014, 2015, 2016, 2017 Ludovic Courtès <ludo@gnu.org>
|
;;; Copyright © 2012, 2013, 2014, 2015, 2016, 2017, 2018 Ludovic Courtès <ludo@gnu.org>
|
||||||
;;;
|
;;;
|
||||||
;;; This file is part of GNU Guix.
|
;;; This file is part of GNU Guix.
|
||||||
;;;
|
;;;
|
||||||
|
@ -52,13 +52,45 @@
|
||||||
((weird _ ...) ;weird!
|
((weird _ ...) ;weird!
|
||||||
(syntax-violation name "invalid field specifier" #'weird)))))
|
(syntax-violation name "invalid field specifier" #'weird)))))
|
||||||
|
|
||||||
|
(define (print-record-abi-mismatch-error port key args
|
||||||
|
default-printer)
|
||||||
|
(match args
|
||||||
|
((rtd . _)
|
||||||
|
;; The source file where this exception is thrown must be recompiled.
|
||||||
|
(format port "ERROR: ~a: record ABI mismatch; recompilation needed"
|
||||||
|
rtd))))
|
||||||
|
|
||||||
|
(set-exception-printer! 'record-abi-mismatch-error
|
||||||
|
print-record-abi-mismatch-error)
|
||||||
|
|
||||||
|
(define (current-abi-identifier type)
|
||||||
|
"Return an identifier unhygienically derived from TYPE for use as its
|
||||||
|
\"current ABI\" variable."
|
||||||
|
(let ((type-name (syntax->datum type)))
|
||||||
|
(datum->syntax
|
||||||
|
type
|
||||||
|
(string->symbol
|
||||||
|
(string-append "% " (symbol->string type-name)
|
||||||
|
" abi-cookie")))))
|
||||||
|
|
||||||
|
(define (abi-check type cookie)
|
||||||
|
"Return syntax that checks that the current \"application binary
|
||||||
|
interface\" (ABI) for TYPE is equal to COOKIE."
|
||||||
|
(with-syntax ((current-abi (current-abi-identifier type)))
|
||||||
|
#`(unless (eq? current-abi #,cookie)
|
||||||
|
(throw 'record-abi-mismatch-error #,type))))
|
||||||
|
|
||||||
(define-syntax make-syntactic-constructor
|
(define-syntax make-syntactic-constructor
|
||||||
(syntax-rules ()
|
(syntax-rules ()
|
||||||
"Make the syntactic constructor NAME for TYPE, that calls CTOR, and
|
"Make the syntactic constructor NAME for TYPE, that calls CTOR, and
|
||||||
expects all of EXPECTED fields to be initialized. DEFAULTS is the list of
|
expects all of EXPECTED fields to be initialized. DEFAULTS is the list of
|
||||||
FIELD/DEFAULT-VALUE tuples, THUNKED is the list of identifiers of thunked
|
FIELD/DEFAULT-VALUE tuples, THUNKED is the list of identifiers of thunked
|
||||||
fields, and DELAYED is the list of identifiers of delayed fields."
|
fields, and DELAYED is the list of identifiers of delayed fields.
|
||||||
|
|
||||||
|
ABI-COOKIE is the cookie (an integer) against which to check the run-time ABI
|
||||||
|
of TYPE matches the expansion-time ABI."
|
||||||
((_ type name ctor (expected ...)
|
((_ type name ctor (expected ...)
|
||||||
|
#:abi-cookie abi-cookie
|
||||||
#:thunked thunked
|
#:thunked thunked
|
||||||
#:delayed delayed
|
#:delayed delayed
|
||||||
#:innate innate
|
#:innate innate
|
||||||
|
@ -130,6 +162,7 @@ fields, and DELAYED is the list of identifiers of delayed fields."
|
||||||
(syntax-case s (inherit expected ...)
|
(syntax-case s (inherit expected ...)
|
||||||
((_ (inherit orig-record) (field value) (... ...))
|
((_ (inherit orig-record) (field value) (... ...))
|
||||||
#`(let* #,(field-bindings #'((field value) (... ...)))
|
#`(let* #,(field-bindings #'((field value) (... ...)))
|
||||||
|
#,(abi-check #'type abi-cookie)
|
||||||
#,(record-inheritance #'orig-record
|
#,(record-inheritance #'orig-record
|
||||||
#'((field value) (... ...)))))
|
#'((field value) (... ...)))))
|
||||||
((_ (field value) (... ...))
|
((_ (field value) (... ...))
|
||||||
|
@ -144,6 +177,7 @@ fields, and DELAYED is the list of identifiers of delayed fields."
|
||||||
(cond ((lset= eq? fields '(expected ...))
|
(cond ((lset= eq? fields '(expected ...))
|
||||||
#`(let* #,(field-bindings
|
#`(let* #,(field-bindings
|
||||||
#'((field value) (... ...)))
|
#'((field value) (... ...)))
|
||||||
|
#,(abi-check #'type abi-cookie)
|
||||||
(ctor #,@(map field-value '(expected ...)))))
|
(ctor #,@(map field-value '(expected ...)))))
|
||||||
((pair? (lset-difference eq? fields
|
((pair? (lset-difference eq? fields
|
||||||
'(expected ...)))
|
'(expected ...)))
|
||||||
|
@ -270,6 +304,16 @@ inherited."
|
||||||
;; The real value of that field is a promise, so force it.
|
;; The real value of that field is a promise, so force it.
|
||||||
(force (real-get x)))))))
|
(force (real-get x)))))))
|
||||||
|
|
||||||
|
(define (compute-abi-cookie field-specs)
|
||||||
|
;; Compute an "ABI cookie" for the given FIELD-SPECS. We use
|
||||||
|
;; 'string-hash' because that's a better hash function that 'hash' on a
|
||||||
|
;; list of symbols.
|
||||||
|
(syntax-case field-specs ()
|
||||||
|
(((field get properties ...) ...)
|
||||||
|
(string-hash (object->string
|
||||||
|
(syntax->datum #'((field properties ...) ...)))
|
||||||
|
most-positive-fixnum))))
|
||||||
|
|
||||||
(syntax-case s ()
|
(syntax-case s ()
|
||||||
((_ type syntactic-ctor ctor pred
|
((_ type syntactic-ctor ctor pred
|
||||||
(field get properties ...) ...)
|
(field get properties ...) ...)
|
||||||
|
@ -278,7 +322,8 @@ inherited."
|
||||||
(delayed (filter-map delayed-field? field-spec))
|
(delayed (filter-map delayed-field? field-spec))
|
||||||
(innate (filter-map innate-field? field-spec))
|
(innate (filter-map innate-field? field-spec))
|
||||||
(defaults (filter-map field-default-value
|
(defaults (filter-map field-default-value
|
||||||
#'((field properties ...) ...))))
|
#'((field properties ...) ...)))
|
||||||
|
(cookie (compute-abi-cookie field-spec)))
|
||||||
(with-syntax (((field-spec* ...)
|
(with-syntax (((field-spec* ...)
|
||||||
(map field-spec->srfi-9 field-spec))
|
(map field-spec->srfi-9 field-spec))
|
||||||
((thunked-field-accessor ...)
|
((thunked-field-accessor ...)
|
||||||
|
@ -298,10 +343,13 @@ inherited."
|
||||||
(ctor field ...)
|
(ctor field ...)
|
||||||
pred
|
pred
|
||||||
field-spec* ...)
|
field-spec* ...)
|
||||||
|
(define #,(current-abi-identifier #'type)
|
||||||
|
#,cookie)
|
||||||
thunked-field-accessor ...
|
thunked-field-accessor ...
|
||||||
delayed-field-accessor ...
|
delayed-field-accessor ...
|
||||||
(make-syntactic-constructor type syntactic-ctor ctor
|
(make-syntactic-constructor type syntactic-ctor ctor
|
||||||
(field ...)
|
(field ...)
|
||||||
|
#:abi-cookie #,cookie
|
||||||
#:thunked #,thunked
|
#:thunked #,thunked
|
||||||
#:delayed #,delayed
|
#:delayed #,delayed
|
||||||
#:innate #,innate
|
#:innate #,innate
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
;;; GNU Guix --- Functional package management for GNU
|
;;; GNU Guix --- Functional package management for GNU
|
||||||
;;; Copyright © 2012, 2013, 2014, 2015, 2016 Ludovic Courtès <ludo@gnu.org>
|
;;; Copyright © 2012, 2013, 2014, 2015, 2016, 2018 Ludovic Courtès <ludo@gnu.org>
|
||||||
;;;
|
;;;
|
||||||
;;; This file is part of GNU Guix.
|
;;; This file is part of GNU Guix.
|
||||||
;;;
|
;;;
|
||||||
|
@ -288,6 +288,34 @@
|
||||||
(and (string-match "extra.*initializer.*baz" message)
|
(and (string-match "extra.*initializer.*baz" message)
|
||||||
(eq? proc 'foo)))))
|
(eq? proc 'foo)))))
|
||||||
|
|
||||||
|
(test-assert "ABI checks"
|
||||||
|
(let ((module (test-module)))
|
||||||
|
(eval '(begin
|
||||||
|
(define-record-type* <foo> foo make-foo
|
||||||
|
foo?
|
||||||
|
(bar foo-bar (default 42)))
|
||||||
|
|
||||||
|
(define (make-me-a-record) (foo)))
|
||||||
|
module)
|
||||||
|
(unless (eval '(foo? (make-me-a-record)) module)
|
||||||
|
(error "what?" (eval '(make-me-a-record) module)))
|
||||||
|
|
||||||
|
;; Redefine <foo> with an additional field.
|
||||||
|
(eval '(define-record-type* <foo> foo make-foo
|
||||||
|
foo?
|
||||||
|
(baz foo-baz)
|
||||||
|
(bar foo-bar (default 42)))
|
||||||
|
module)
|
||||||
|
|
||||||
|
;; Now 'make-me-a-record' is out of sync because it does an
|
||||||
|
;; 'allocate-struct' that corresponds to the previous definition of <foo>.
|
||||||
|
(catch 'record-abi-mismatch-error
|
||||||
|
(lambda ()
|
||||||
|
(eval '(foo? (make-me-a-record)) module)
|
||||||
|
#f)
|
||||||
|
(lambda (key rtd . _)
|
||||||
|
(eq? rtd (eval '<foo> module))))))
|
||||||
|
|
||||||
(test-equal "recutils->alist"
|
(test-equal "recutils->alist"
|
||||||
'((("Name" . "foo")
|
'((("Name" . "foo")
|
||||||
("Version" . "0.1")
|
("Version" . "0.1")
|
||||||
|
|
Loading…
Reference in New Issue