Skip to content

Commit fd64bd6

Browse files
committed
FIX: avoid a stack overflow in case of deep recursion recycle
resolves: Oldes/Rebol-issues#2072
1 parent f95e6f2 commit fd64bd6

File tree

4 files changed

+47
-7
lines changed

4 files changed

+47
-7
lines changed

src/core/m-gc.c

+32-6
Original file line numberDiff line numberDiff line change
@@ -274,6 +274,19 @@ static void Mark_Series(REBSER *series, REBCNT depth);
274274
}
275275
}
276276

277+
/***********************************************************************
278+
**
279+
*/ static void Queue_Mark_Series(REBSER* series)
280+
/*
281+
** Instead of directly marking all series, queue them for later
282+
** to avoid a stack overflow in case of deep recursion.
283+
**
284+
***********************************************************************/
285+
{
286+
if (SERIES_FULL(GC_Mark_Queue)) Extend_Series(GC_Mark_Queue, 8);
287+
((REBSER**)GC_Mark_Queue->data)[GC_Mark_Queue->tail++] = series;
288+
}
289+
277290
/***********************************************************************
278291
**
279292
*/ static void Mark_Series(REBSER *series, REBCNT depth)
@@ -371,9 +384,10 @@ static void Mark_Series(REBSER *series, REBCNT depth);
371384
// Object is just a block with special first value (context):
372385
mark_obj:
373386
if (!IS_MARK_SERIES(VAL_OBJ_FRAME(val))) {
374-
Mark_Series(VAL_OBJ_FRAME(val), depth);
375-
if (SERIES_TAIL(VAL_OBJ_FRAME(val)) >= 1)
376-
; //Dump_Frame(VAL_OBJ_FRAME(val), 4);
387+
if (depth >= 64) Queue_Mark_Series(VAL_OBJ_FRAME(val));
388+
else Mark_Series(VAL_OBJ_FRAME(val), depth);
389+
//if (SERIES_TAIL(VAL_OBJ_FRAME(val)) >= 1)
390+
// Dump_Frame(VAL_OBJ_FRAME(val), 4);
377391
}
378392
break;
379393

@@ -467,12 +481,12 @@ static void Mark_Series(REBSER *series, REBCNT depth);
467481
#endif
468482
if (SERIES_WIDE(ser) != sizeof(REBVAL) && SERIES_WIDE(ser) != 4 && SERIES_WIDE(ser) != 0)
469483
Crash(RP_BAD_WIDTH, 16, SERIES_WIDE(ser), VAL_TYPE(val));
470-
CHECK_MARK(ser, depth);
484+
QUEUE_CHECK_MARK(ser, depth);
471485
break;
472486

473487
case REB_MAP:
474488
ser = VAL_SERIES(val);
475-
CHECK_MARK(ser, depth);
489+
QUEUE_CHECK_MARK(ser, depth);
476490
if (ser->series) {
477491
MARK_SERIES(ser->series);
478492
}
@@ -664,7 +678,7 @@ static void Mark_Series(REBSER *series, REBCNT depth);
664678
PG_Reb_Stats->Recycle_Counter++;
665679
PG_Reb_Stats->Recycle_Series = Mem_Pools[SERIES_POOL].free;
666680

667-
PG_Reb_Stats->Mark_Count = 0;
681+
//PG_Reb_Stats->Mark_Count = 0;
668682

669683
// WARNING: These terminate existing open blocks. This could
670684
// be a problem if code is building a new value at the tail,
@@ -701,12 +715,19 @@ static void Mark_Series(REBSER *series, REBCNT depth);
701715
}
702716
}
703717

718+
MARK_SERIES(GC_Mark_Queue);
719+
704720
// Mark all root series:
705721
Mark_Series(VAL_SERIES(ROOT_ROOT), 0);
706722
Mark_Series(Task_Series, 0);
707723

708724
// Mark all devices:
709725
Mark_Devices(0);
726+
727+
// Mark series queued to avoid a stack overflow in case of deep recursion
728+
while (GC_Mark_Queue->tail > 0) {
729+
Mark_Series(((REBSER**)GC_Mark_Queue->data)[--GC_Mark_Queue->tail], 0);
730+
}
710731

711732
count = Sweep_Series();
712733
count += Sweep_Gobs();
@@ -802,6 +823,11 @@ static void Mark_Series(REBSER *series, REBCNT depth);
802823

803824
GC_Series = Make_Series(60, sizeof(REBSER *), FALSE);
804825
KEEP_SERIES(GC_Series, "gc guarded");
826+
827+
// Series queued to be marked in case of deep recursion.
828+
GC_Mark_Queue = Make_Series(15, sizeof(REBSER*), FALSE);
829+
BARE_SERIES(GC_Mark_Queue);
830+
LABEL_SERIES(GC_Mark_Queue, "gc mark queue");
805831
}
806832

807833
/***********************************************************************

src/include/sys-globals.h

+1
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@ TVAR REBINT GC_Ballast; // Bytes allocated to force automatic GC
8484
TVAR REBOOL GC_Active; // TRUE when recycle is enabled (set by RECYCLE func)
8585
TVAR REBSER *GC_Protect; // A stack of protected series (removed by pop)
8686
TVAR REBSER *GC_Series; // An array of protected series (removed by address)
87+
TVAR REBSER *GC_Mark_Queue; // An array of series to be marked (to avoid deep recursion in GC)
8788
TVAR REBSER **GC_Infants; // A small list of last N series created (nursery)
8889
TVAR REBINT GC_Last_Infant; // Index to last infant above (circular)
8990
TVAR REBFLG GC_Stay_Dirty; // Do not free memory, fill it with 0xBB

src/include/sys-value.h

+7
Original file line numberDiff line numberDiff line change
@@ -520,6 +520,13 @@ enum {
520520
#define CHECK_MARK(s,d) if (!IS_MARK_SERIES(s)) Mark_Series(s, d);
521521
#endif
522522

523+
// Using the mark queue only if we are deep enough
524+
#define QUEUE_CHECK_MARK(s,d) \
525+
if (!IS_MARK_SERIES(s)) {\
526+
if (depth >= 64) Mark_Series(s, d); \
527+
else Queue_Mark_Series(s);\
528+
}
529+
523530
//#define LABEL_SERIES(s,l) s->label = (l)
524531
#define IS_BLOCK_SERIES(s) (SERIES_WIDE(s) == sizeof(REBVAL))
525532

src/tests/units/crash-test.r3

+7-1
Original file line numberDiff line numberDiff line change
@@ -232,6 +232,7 @@ foreach [n s] system/schemes [
232232
;@@ https://github.com/Oldes/Rebol-issues/issues/767
233233
--assert same? :greater? copy :greater? ;-no crash
234234

235+
if system/version > 3.17.0 [
235236
--test-- "recursive bind"
236237
;@@ https://github.com/Oldes/Rebol-issues/issues/2278
237238
obj: make object! [x: 10]
@@ -244,7 +245,12 @@ foreach [n s] system/schemes [
244245
10 == get blk/2/1
245246
10 == get blk/2/2/1
246247
]
247-
248+
--test-- "deep recursion recycle crash"
249+
;@@ https://github.com/Oldes/Rebol-issues/issues/2278
250+
blk: copy [] loop 200000 [blk: append/only copy [] blk]
251+
--assert block? blk
252+
blk: none recycle
253+
]
248254
===end-group===
249255

250256
~~~end-file~~~

0 commit comments

Comments
 (0)