| ... |
... |
@@ -25,6 +25,7 @@ |
|
25
|
25
|
#include "Sanity.h"
|
|
26
|
26
|
#include "Schedule.h"
|
|
27
|
27
|
#include "Apply.h"
|
|
|
28
|
+#include "Prelude.h"
|
|
28
|
29
|
#include "Printer.h"
|
|
29
|
30
|
#include "Arena.h"
|
|
30
|
31
|
#include "RetainerProfile.h"
|
| ... |
... |
@@ -42,6 +43,7 @@ int isHeapAlloced ( StgPtr p); |
|
42
|
43
|
static void checkSmallBitmap ( StgPtr payload, StgWord bitmap, uint32_t );
|
|
43
|
44
|
static void checkLargeBitmap ( StgPtr payload, StgLargeBitmap*, uint32_t );
|
|
44
|
45
|
static void checkClosureShallow ( const StgClosure * );
|
|
|
46
|
+static void checkPtrTag ( const StgClosure *, bool );
|
|
45
|
47
|
|
|
46
|
48
|
static void checkCompactObjects (bdescr *bd);
|
|
47
|
49
|
|
| ... |
... |
@@ -72,6 +74,7 @@ checkSmallBitmap( StgPtr payload, StgWord bitmap, uint32_t size ) |
|
72
|
74
|
for(i = 0; i < size; i++, bitmap >>= 1 ) {
|
|
73
|
75
|
if ((bitmap & 1) == 0) {
|
|
74
|
76
|
checkClosureShallow((StgClosure *)payload[i]);
|
|
|
77
|
+ checkPtrTag((StgClosure *)payload[i], false);
|
|
75
|
78
|
}
|
|
76
|
79
|
}
|
|
77
|
80
|
}
|
| ... |
... |
@@ -89,11 +92,126 @@ checkLargeBitmap( StgPtr payload, StgLargeBitmap* large_bitmap, uint32_t size ) |
|
89
|
92
|
for(; i < size && j < BITS_IN(W_); j++, i++, bitmap >>= 1 ) {
|
|
90
|
93
|
if ((bitmap & 1) == 0) {
|
|
91
|
94
|
checkClosureShallow((StgClosure *)payload[i]);
|
|
|
95
|
+ checkPtrTag((StgClosure *)payload[i], false);
|
|
92
|
96
|
}
|
|
93
|
97
|
}
|
|
94
|
98
|
}
|
|
95
|
99
|
}
|
|
96
|
100
|
|
|
|
101
|
+/* Note [Sanity-checking pointer tags]
|
|
|
102
|
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
|
103
|
+ * checkPtrTag asserts the pointer-tagging invariant (#23173) at rest: a
|
|
|
104
|
+ * pointer to a constructor carries its constructor tag (see tagConstr in
|
|
|
105
|
+ * ClosureMacros.h and get_iptr_tag in sm/Compact.c), and a pointer to a boxed
|
|
|
106
|
+ * unlifted primitive (MVar#, MutVar#, the arrays, ...) carries tag 1 (see
|
|
|
107
|
+ * Note [Pointer tagging of unlifted boxed primitives] in GHC.StgToCmm.Prim).
|
|
|
108
|
+ * The invariant is otherwise enforced only by crashing entry code, which
|
|
|
109
|
+ * catches a stripped tag only if the pointer is subsequently entered; this
|
|
|
110
|
+ * check catches tag-stripping pointer-rewriting paths (Evac, Compact,
|
|
|
111
|
+ * NonMovingShortcut, ...) mechanically on every sanity-checked GC.
|
|
|
112
|
+ *
|
|
|
113
|
+ * It is called only on user-level fields (stack bitmap slots, PAP/AP
|
|
|
114
|
+ * payloads, constructor/fun/thunk payloads, array elements, MutVar/TVar/MVar
|
|
|
115
|
+ * values, IND indirectees), because the RTS also holds internal untagged
|
|
|
116
|
+ * links. The rules exempt:
|
|
|
117
|
+ *
|
|
|
118
|
+ * - static constructors: RTS sentinels (stg_END_TSO_QUEUE_closure, ...) are
|
|
|
119
|
+ * CONSTR_NOCAFs that C code stores untagged, e.g. as an empty MVar's
|
|
|
120
|
+ * value, so only heap-allocated constructors are checked;
|
|
|
121
|
+ *
|
|
|
122
|
+ * - large-family constructors (con_tag >= TAG_MASK): tag is capped at
|
|
|
123
|
+ * TAG_MASK, so no exact requirement is asserted;
|
|
|
124
|
+ *
|
|
|
125
|
+ * - WEAK, TSO, STACK, BLOCKING_QUEUE, PRIM, MUT_PRIM: user-level references
|
|
|
126
|
+ * (Weak#, ThreadId#, ...) to these are tagged, but legitimate untagged
|
|
|
127
|
+ * RTS-internal links (weak_ptr_list, run queues, tso->_link, STM
|
|
|
128
|
+ * structures) reach the same traversals;
|
|
|
129
|
+ *
|
|
|
130
|
+ * - C_FINALIZER_LIST nodes: although their info table is a CONSTR, they
|
|
|
131
|
+ * are RTS-internal. All references to them — StgWeak.cfinalizers and the
|
|
|
132
|
+ * nodes' link fields — are untagged links built by stg_addCFinalizerToWeakzh
|
|
|
133
|
+ * (PrimOps.cmm) and walked raw by runCFinalizers (Weak.c); user code never
|
|
|
134
|
+ * holds a reference to one. (The compacting GC preserves untaggedness:
|
|
|
135
|
+ * unthread re-applies get_iptr_tag only to originally-tagged references.)
|
|
|
136
|
+ *
|
|
|
137
|
+ * - fields of ghc-heap's Box (GHC.Internal.Heap.Closures): `data Box = Box
|
|
|
138
|
+ * Any` wraps a pointer word captured verbatim by heap/stack introspection
|
|
|
139
|
+ * (unpackClosure#, ghc-heap's stack decoding), so it carries whatever tag
|
|
|
140
|
+ * the source bits had — possibly none. Box is recognized via
|
|
|
141
|
+ * ghc_hs_iface->Box_con_info, NULL-guarded since sanity checks can run
|
|
|
142
|
+ * before ghc-internal registers the interface;
|
|
|
143
|
+ *
|
|
|
144
|
+ * - BLACKHOLE indirectees (no call site on that field): tag 0 there means
|
|
|
145
|
+ * "not yet updated". Plain IND indirectees are checked;
|
|
|
146
|
+ *
|
|
|
147
|
+ * - bitmap-walked slots (stack frames, PAP/AP payloads; heap_field =
|
|
|
148
|
+ * false): hand-written Cmm legitimately stores untagged pointers there.
|
|
|
149
|
+ * Codegen untags unlifted boxed primop arguments at the Cmm call
|
|
|
150
|
+ * boundary, and generic RTS frames save those already-untagged arguments
|
|
|
151
|
+ * on the stack (the stg_block_{take,read,put}mvar frames and the
|
|
|
152
|
+ * stg_gc_prim_* heap-check-retry frames in HeapStackCheck.cmm); Cmm code
|
|
|
153
|
+ * also keeps deliberately untagged working pointers live across calls
|
|
|
154
|
+ * (e.g. stg_compactAddWorkerzh's "p"), landing them in return-frame
|
|
|
155
|
+ * slots. Such slots hence get no constructor rule, and the unlifted-
|
|
|
156
|
+ * primitive rule is relaxed to tag 0-or-1 (still catching corrupt tags).
|
|
|
157
|
+ * The strict rules apply to heap fields, where all the tag-stripping GC
|
|
|
158
|
+ * bugs lived.
|
|
|
159
|
+ */
|
|
|
160
|
+static void
|
|
|
161
|
+checkPtrTag( const StgClosure *q, bool heap_field )
|
|
|
162
|
+{
|
|
|
163
|
+ const StgClosure *p = UNTAG_CONST_CLOSURE(q);
|
|
|
164
|
+ const StgInfoTable *raw_info = ACQUIRE_LOAD(&p->header.info);
|
|
|
165
|
+ if (IS_FORWARDING_PTR(raw_info)) return;
|
|
|
166
|
+ const StgInfoTable *info = INFO_PTR_TO_STRUCT(raw_info);
|
|
|
167
|
+
|
|
|
168
|
+ switch (info->type) {
|
|
|
169
|
+ case CONSTR:
|
|
|
170
|
+ case CONSTR_1_0:
|
|
|
171
|
+ case CONSTR_0_1:
|
|
|
172
|
+ case CONSTR_2_0:
|
|
|
173
|
+ case CONSTR_1_1:
|
|
|
174
|
+ case CONSTR_0_2:
|
|
|
175
|
+ case CONSTR_NOCAF:
|
|
|
176
|
+ {
|
|
|
177
|
+ // RTS-internal untagged links; see the C_FINALIZER_LIST bullet in
|
|
|
178
|
+ // Note [Sanity-checking pointer tags].
|
|
|
179
|
+ if (raw_info == &stg_C_FINALIZER_LIST_info) {
|
|
|
180
|
+ break;
|
|
|
181
|
+ }
|
|
|
182
|
+ StgWord con_tag = (StgWord)info->srt + 1;
|
|
|
183
|
+ if (heap_field && con_tag <= TAG_MASK && HEAP_ALLOCED((StgPtr)p)) {
|
|
|
184
|
+ ASSERT(GET_CLOSURE_TAG(q) == con_tag);
|
|
|
185
|
+ }
|
|
|
186
|
+ break;
|
|
|
187
|
+ }
|
|
|
188
|
+
|
|
|
189
|
+ case ARR_WORDS:
|
|
|
190
|
+ case MUT_ARR_PTRS_CLEAN:
|
|
|
191
|
+ case MUT_ARR_PTRS_DIRTY:
|
|
|
192
|
+ case MUT_ARR_PTRS_FROZEN_CLEAN:
|
|
|
193
|
+ case MUT_ARR_PTRS_FROZEN_DIRTY:
|
|
|
194
|
+ case SMALL_MUT_ARR_PTRS_CLEAN:
|
|
|
195
|
+ case SMALL_MUT_ARR_PTRS_DIRTY:
|
|
|
196
|
+ case SMALL_MUT_ARR_PTRS_FROZEN_CLEAN:
|
|
|
197
|
+ case SMALL_MUT_ARR_PTRS_FROZEN_DIRTY:
|
|
|
198
|
+ case MUT_VAR_CLEAN:
|
|
|
199
|
+ case MUT_VAR_DIRTY:
|
|
|
200
|
+ case MVAR_CLEAN:
|
|
|
201
|
+ case MVAR_DIRTY:
|
|
|
202
|
+ case TVAR:
|
|
|
203
|
+ if (heap_field) {
|
|
|
204
|
+ ASSERT(GET_CLOSURE_TAG(q) == 1);
|
|
|
205
|
+ } else {
|
|
|
206
|
+ ASSERT(GET_CLOSURE_TAG(q) <= 1);
|
|
|
207
|
+ }
|
|
|
208
|
+ break;
|
|
|
209
|
+
|
|
|
210
|
+ default:
|
|
|
211
|
+ break;
|
|
|
212
|
+ }
|
|
|
213
|
+}
|
|
|
214
|
+
|
|
97
|
215
|
/*
|
|
98
|
216
|
* check that it looks like a valid closure - without checking its payload
|
|
99
|
217
|
* used to avoid recursion between checking PAPs and checking stack
|
| ... |
... |
@@ -102,6 +220,8 @@ checkLargeBitmap( StgPtr payload, StgLargeBitmap* large_bitmap, uint32_t size ) |
|
102
|
220
|
static void
|
|
103
|
221
|
checkClosureShallow( const StgClosure* p )
|
|
104
|
222
|
{
|
|
|
223
|
+ // No checkPtrTag here: checkCompactObjects calls this on raw
|
|
|
224
|
+ // (necessarily untagged) object addresses, not on stored pointers.
|
|
105
|
225
|
ASSERT(LOOKS_LIKE_CLOSURE_PTR(UNTAG_CONST_CLOSURE(p)));
|
|
106
|
226
|
}
|
|
107
|
227
|
|
| ... |
... |
@@ -129,10 +249,12 @@ checkStackFrame( StgPtr c ) |
|
129
|
249
|
case STOP_FRAME:
|
|
130
|
250
|
case RET_SMALL:
|
|
131
|
251
|
case ANN_FRAME:
|
|
|
252
|
+ {
|
|
132
|
253
|
size = BITMAP_SIZE(info->i.layout.bitmap);
|
|
133
|
254
|
checkSmallBitmap((StgPtr)c + 1,
|
|
134
|
255
|
BITMAP_BITS(info->i.layout.bitmap), size);
|
|
135
|
256
|
return 1 + size;
|
|
|
257
|
+ }
|
|
136
|
258
|
|
|
137
|
259
|
case RET_BCO: {
|
|
138
|
260
|
StgBCO *bco;
|
| ... |
... |
@@ -377,6 +499,8 @@ checkClosure( const StgClosure* p ) |
|
377
|
499
|
ASSERT(LOOKS_LIKE_CLOSURE_PTR(mvar->head));
|
|
378
|
500
|
ASSERT(LOOKS_LIKE_CLOSURE_PTR(mvar->tail));
|
|
379
|
501
|
ASSERT(LOOKS_LIKE_CLOSURE_PTR(mvar->value));
|
|
|
502
|
+ // head/tail are RTS-internal TSO queue links; only value is user-level
|
|
|
503
|
+ checkPtrTag(mvar->value, true);
|
|
380
|
504
|
return sizeofW(StgMVar);
|
|
381
|
505
|
}
|
|
382
|
506
|
|
| ... |
... |
@@ -390,6 +514,7 @@ checkClosure( const StgClosure* p ) |
|
390
|
514
|
uint32_t i;
|
|
391
|
515
|
for (i = 0; i < info->layout.payload.ptrs; i++) {
|
|
392
|
516
|
ASSERT(LOOKS_LIKE_CLOSURE_PTR(((StgThunk *)p)->payload[i]));
|
|
|
517
|
+ checkPtrTag(((StgThunk *)p)->payload[i], true);
|
|
393
|
518
|
}
|
|
394
|
519
|
return thunk_sizeW_fromITBL(info);
|
|
395
|
520
|
}
|
| ... |
... |
@@ -407,14 +532,33 @@ checkClosure( const StgClosure* p ) |
|
407
|
532
|
case CONSTR_1_1:
|
|
408
|
533
|
case CONSTR_0_2:
|
|
409
|
534
|
case CONSTR_2_0:
|
|
410
|
|
- case BLACKHOLE:
|
|
411
|
|
- case PRIM:
|
|
412
|
|
- case MUT_PRIM:
|
|
413
|
535
|
case MUT_VAR_CLEAN:
|
|
414
|
536
|
case MUT_VAR_DIRTY:
|
|
415
|
537
|
case TVAR:
|
|
416
|
538
|
case THUNK_STATIC:
|
|
417
|
539
|
case FUN_STATIC:
|
|
|
540
|
+ {
|
|
|
541
|
+ // ghc-heap's Box holds a raw captured pointer word; see the Box
|
|
|
542
|
+ // bullet in Note [Sanity-checking pointer tags].
|
|
|
543
|
+ bool box = ghc_hs_iface != NULL
|
|
|
544
|
+ && ACQUIRE_LOAD(&p->header.info) == Box_con_info;
|
|
|
545
|
+ uint32_t i;
|
|
|
546
|
+ for (i = 0; i < info->layout.payload.ptrs; i++) {
|
|
|
547
|
+ ASSERT(LOOKS_LIKE_CLOSURE_PTR(p->payload[i]));
|
|
|
548
|
+ if (!box) {
|
|
|
549
|
+ checkPtrTag(p->payload[i], true);
|
|
|
550
|
+ }
|
|
|
551
|
+ }
|
|
|
552
|
+ return sizeW_fromITBL(info);
|
|
|
553
|
+ }
|
|
|
554
|
+
|
|
|
555
|
+ // As above, but without checkPtrTag: a BLACKHOLE indirectee legitimately
|
|
|
556
|
+ // carries tag 0 ("not yet updated"), and PRIM/MUT_PRIM/COMPACT_NFDATA
|
|
|
557
|
+ // payloads are RTS-internal links.
|
|
|
558
|
+ // See Note [Sanity-checking pointer tags].
|
|
|
559
|
+ case BLACKHOLE:
|
|
|
560
|
+ case PRIM:
|
|
|
561
|
+ case MUT_PRIM:
|
|
418
|
562
|
case COMPACT_NFDATA:
|
|
419
|
563
|
{
|
|
420
|
564
|
uint32_t i;
|
| ... |
... |
@@ -480,6 +624,7 @@ checkClosure( const StgClosure* p ) |
|
480
|
624
|
*/
|
|
481
|
625
|
StgInd *ind = (StgInd *)p;
|
|
482
|
626
|
ASSERT(LOOKS_LIKE_CLOSURE_PTR(ind->indirectee));
|
|
|
627
|
+ checkPtrTag(ind->indirectee, true);
|
|
483
|
628
|
return sizeofW(StgInd);
|
|
484
|
629
|
}
|
|
485
|
630
|
|
| ... |
... |
@@ -529,6 +674,7 @@ checkClosure( const StgClosure* p ) |
|
529
|
674
|
uint32_t i;
|
|
530
|
675
|
for (i = 0; i < a->ptrs; i++) {
|
|
531
|
676
|
ASSERT(LOOKS_LIKE_CLOSURE_PTR(a->payload[i]));
|
|
|
677
|
+ checkPtrTag(a->payload[i], true);
|
|
532
|
678
|
}
|
|
533
|
679
|
return mut_arr_ptrs_sizeW(a);
|
|
534
|
680
|
}
|
| ... |
... |
@@ -541,6 +687,7 @@ checkClosure( const StgClosure* p ) |
|
541
|
687
|
StgSmallMutArrPtrs *a = (StgSmallMutArrPtrs *)p;
|
|
542
|
688
|
for (uint32_t i = 0; i < a->ptrs; i++) {
|
|
543
|
689
|
ASSERT(LOOKS_LIKE_CLOSURE_PTR(a->payload[i]));
|
|
|
690
|
+ checkPtrTag(a->payload[i], true);
|
|
544
|
691
|
}
|
|
545
|
692
|
return small_mut_arr_ptrs_sizeW(a);
|
|
546
|
693
|
}
|