Skip to content

Commit ee0da81

Browse files
mflattrmculpepper
authored andcommitted
ffi/unsafe: defend against some finalization bugs
Turn use of a finalized ffi callout into a reported error, instead of a crash. Clarify the existence of the finalizer in the docs. Fix error logging of the finalizer thread. Merge to v5.3.1 (cherry picked from commit 9708a01)
1 parent d16bc25 commit ee0da81

File tree

5 files changed

+58
-4
lines changed

5 files changed

+58
-4
lines changed

collects/ffi/unsafe.rkt

+1
Original file line numberDiff line numberDiff line change
@@ -1702,6 +1702,7 @@
17021702
(cweh
17031703
(lambda (exn)
17041704
(log-message logger
1705+
'error
17051706
(if (exn? exn)
17061707
(exn-message exn)
17071708
(format "~s" exn))

collects/scribblings/foreign/types.scrbl

+5
Original file line numberDiff line numberDiff line change
@@ -485,6 +485,11 @@ For @tech{callouts} to foreign functions with the generated type:
485485
that values managed by the Racket garbage collector might be
486486
moved in memory by the garbage collector.}
487487

488+
@item{A @tech{callout} object is finalized internally. Beware
489+
of trying to use a @tech{callout} object that is reachable
490+
only from a finalized object, since the two objects
491+
might be finalized in either order.}
492+
488493
]
489494

490495
For @tech{callbacks} to Racket functions with the generated type:
+32
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
#lang racket/base
2+
3+
;; Check for a good effort at error reporting on an attempt to
4+
;; use a foreign function that is finalized already.
5+
6+
(define src
7+
'(module m racket/base
8+
(require ffi/unsafe)
9+
(for ([i 10])
10+
(for ([i 10])
11+
(define m (get-ffi-obj 'fabs #f (_fun _double -> _double)))
12+
;; Since `m' is accessible only via the finalized value, it
13+
;; can be finalized before `(list m)':
14+
(register-finalizer (list m) (lambda (p) ((car p) 10.0))))
15+
(collect-garbage))))
16+
17+
(define l (make-logger))
18+
(define r (make-log-receiver l 'error))
19+
20+
(parameterize ([current-namespace (make-base-namespace)]
21+
[current-logger l])
22+
(eval src)
23+
(namespace-require ''m))
24+
25+
;; Print logged errors, of which there are likely to be
26+
;; some (although it's not guaranteed) if the finalizer
27+
;; thread is logging correctly:
28+
(let loop ()
29+
(define m (sync/timeout 0 r))
30+
(when m
31+
(printf "~s\n" m)
32+
(loop)))

src/foreign/foreign.c

+10-2
Original file line numberDiff line numberDiff line change
@@ -3055,7 +3055,7 @@ Scheme_Object *ffi_do_call(void *data, int argc, Scheme_Object *argv[])
30553055
#ifdef MZ_USE_PLACES
30563056
int orig_place = SCHEME_TRUEP(SCHEME_VEC_ELS(data)[7]);
30573057
#endif
3058-
int nargs = cif->nargs;
3058+
int nargs /* = cif->nargs, after checking cif */;
30593059
/* When the foreign function is called, we need an array (ivals) of nargs
30603060
* ForeignAny objects to store the actual C values that are created, and we
30613061
* need another array (avalues) for the pointers to these values (this is
@@ -3082,6 +3082,13 @@ Scheme_Object *ffi_do_call(void *data, int argc, Scheme_Object *argv[])
30823082
if (orig_place && (scheme_current_place_id == 0))
30833083
orig_place = 0;
30843084
#endif
3085+
if (!cif) {
3086+
scheme_signal_error("ffi-call: foreign-function reference was already finalized%s%s",
3087+
name ? "\n name: " : "",
3088+
name ? name : "");
3089+
return NULL;
3090+
}
3091+
nargs = cif->nargs;
30853092
if ((nargs <= MAX_QUICK_ARGS)) {
30863093
ivals = stack_ivals;
30873094
avalues = stack_avalues;
@@ -3151,8 +3158,9 @@ Scheme_Object *ffi_do_call(void *data, int argc, Scheme_Object *argv[])
31513158
}
31523159

31533160
/* see below */
3154-
void free_fficall_data(void *ignored, void *p)
3161+
void free_fficall_data(void *data, void *p)
31553162
{
3163+
SCHEME_VEC_ELS(data)[4] = NULL;
31563164
free(((ffi_cif*)p)->arg_types);
31573165
free(p);
31583166
}

src/foreign/foreign.rktc

+10-2
Original file line numberDiff line numberDiff line change
@@ -2411,7 +2411,7 @@ Scheme_Object *ffi_do_call(void *data, int argc, Scheme_Object *argv[])
24112411
#ifdef MZ_USE_PLACES
24122412
int orig_place = SCHEME_TRUEP(SCHEME_VEC_ELS(data)[7]);
24132413
#endif
2414-
int nargs = cif->nargs;
2414+
int nargs /* = cif->nargs, after checking cif */;
24152415
/* When the foreign function is called, we need an array (ivals) of nargs
24162416
* ForeignAny objects to store the actual C values that are created, and we
24172417
* need another array (avalues) for the pointers to these values (this is
@@ -2438,6 +2438,13 @@ Scheme_Object *ffi_do_call(void *data, int argc, Scheme_Object *argv[])
24382438
if (orig_place && (scheme_current_place_id == 0))
24392439
orig_place = 0;
24402440
#endif
2441+
if (!cif) {
2442+
scheme_signal_error("ffi-call: foreign-function reference was already finalized%s%s",
2443+
name ? "\n name: " : "",
2444+
name ? name : "");
2445+
return NULL;
2446+
}
2447+
nargs = cif->nargs;
24412448
if ((nargs <= MAX_QUICK_ARGS)) {
24422449
ivals = stack_ivals;
24432450
avalues = stack_avalues;
@@ -2507,8 +2514,9 @@ Scheme_Object *ffi_do_call(void *data, int argc, Scheme_Object *argv[])
25072514
}
25082515

25092516
/* see below */
2510-
void free_fficall_data(void *ignored, void *p)
2517+
void free_fficall_data(void *data, void *p)
25112518
{
2519+
SCHEME_VEC_ELS(data)[4] = NULL;
25122520
free(((ffi_cif*)p)->arg_types);
25132521
free(p);
25142522
}

0 commit comments

Comments
 (0)