agora inbox for [email protected]help / color / mirror / Atom feed
[PATCH v14 6/7] Restructure TupleTableSlot to allow tuples other than HeapTuple 114+ messages / 2 participants [nested] [flat]
* [PATCH v14 6/7] Restructure TupleTableSlot to allow tuples other than HeapTuple @ 2018-11-13 20:58 Andres Freund <[email protected]> 0 siblings, 0 replies; 114+ messages in thread From: Andres Freund @ 2018-11-13 20:58 UTC (permalink / raw) Author: Andres Freund and Ashutosh Bapat, with changes by Amit Khandekar Discussion: https://postgr.es/m/[email protected] --- src/backend/access/common/heaptuple.c | 184 +-- src/backend/catalog/index.c | 4 +- src/backend/executor/execCurrent.c | 18 +- src/backend/executor/execExpr.c | 5 +- src/backend/executor/execExprInterp.c | 13 +- src/backend/executor/execGrouping.c | 5 +- src/backend/executor/execReplication.c | 41 +- src/backend/executor/execScan.c | 4 +- src/backend/executor/execTuples.c | 1665 ++++++++++++++++-------- src/backend/executor/nodeAgg.c | 5 +- src/backend/executor/nodeHashjoin.c | 17 +- src/backend/executor/nodeModifyTable.c | 13 + src/backend/executor/nodeSubplan.c | 1 + src/backend/jit/llvm/llvmjit.c | 8 +- src/backend/jit/llvm/llvmjit_deform.c | 52 +- src/backend/jit/llvm/llvmjit_expr.c | 3 +- src/backend/jit/llvm/llvmjit_types.c | 4 +- src/include/access/htup_details.h | 2 - src/include/executor/executor.h | 1 + src/include/executor/tuptable.h | 305 ++++- src/include/jit/llvmjit.h | 7 +- src/include/nodes/execnodes.h | 1 + 22 files changed, 1562 insertions(+), 796 deletions(-) diff --git a/src/backend/access/common/heaptuple.c b/src/backend/access/common/heaptuple.c index 28127b311f5..ccb69bdd616 100644 --- a/src/backend/access/common/heaptuple.c +++ b/src/backend/access/common/heaptuple.c @@ -71,6 +71,8 @@ #define VARLENA_ATT_IS_PACKABLE(att) \ ((att)->attstorage != 'p') +static Datum getmissingattr(TupleDesc tupleDesc, int attnum, bool *isnull); + /* ---------------------------------------------------------------- * misc support routines @@ -80,7 +82,7 @@ /* * Return the missing value of an attribute, or NULL if there isn't one. */ -Datum +static Datum getmissingattr(TupleDesc tupleDesc, int attnum, bool *isnull) { @@ -1350,186 +1352,6 @@ heap_deform_tuple(HeapTuple tuple, TupleDesc tupleDesc, values[attnum] = getmissingattr(tupleDesc, attnum + 1, &isnull[attnum]); } -/* - * slot_deform_tuple - * Given a TupleTableSlot, extract data from the slot's physical tuple - * into its Datum/isnull arrays. Data is extracted up through the - * natts'th column (caller must ensure this is a legal column number). - * - * This is essentially an incremental version of heap_deform_tuple: - * on each call we extract attributes up to the one needed, without - * re-computing information about previously extracted attributes. - * slot->tts_nvalid is the number of attributes already extracted. - */ -void -slot_deform_tuple(TupleTableSlot *slot, int natts) -{ - HeapTuple tuple = slot->tts_tuple; - TupleDesc tupleDesc = slot->tts_tupleDescriptor; - Datum *values = slot->tts_values; - bool *isnull = slot->tts_isnull; - HeapTupleHeader tup = tuple->t_data; - bool hasnulls = HeapTupleHasNulls(tuple); - int attnum; - char *tp; /* ptr to tuple data */ - uint32 off; /* offset in tuple data */ - bits8 *bp = tup->t_bits; /* ptr to null bitmap in tuple */ - bool slow; /* can we use/set attcacheoff? */ - - /* - * Check whether the first call for this tuple, and initialize or restore - * loop state. - */ - attnum = slot->tts_nvalid; - if (attnum == 0) - { - /* Start from the first attribute */ - off = 0; - slow = false; - } - else - { - /* Restore state from previous execution */ - off = slot->tts_off; - slow = TTS_SLOW(slot); - } - - tp = (char *) tup + tup->t_hoff; - - for (; attnum < natts; attnum++) - { - Form_pg_attribute thisatt = TupleDescAttr(tupleDesc, attnum); - - if (hasnulls && att_isnull(attnum, bp)) - { - values[attnum] = (Datum) 0; - isnull[attnum] = true; - slow = true; /* can't use attcacheoff anymore */ - continue; - } - - isnull[attnum] = false; - - if (!slow && thisatt->attcacheoff >= 0) - off = thisatt->attcacheoff; - else if (thisatt->attlen == -1) - { - /* - * We can only cache the offset for a varlena attribute if the - * offset is already suitably aligned, so that there would be no - * pad bytes in any case: then the offset will be valid for either - * an aligned or unaligned value. - */ - if (!slow && - off == att_align_nominal(off, thisatt->attalign)) - thisatt->attcacheoff = off; - else - { - off = att_align_pointer(off, thisatt->attalign, -1, - tp + off); - slow = true; - } - } - else - { - /* not varlena, so safe to use att_align_nominal */ - off = att_align_nominal(off, thisatt->attalign); - - if (!slow) - thisatt->attcacheoff = off; - } - - values[attnum] = fetchatt(thisatt, tp + off); - - off = att_addlength_pointer(off, thisatt->attlen, tp + off); - - if (thisatt->attlen <= 0) - slow = true; /* can't use attcacheoff anymore */ - } - - /* - * Save state for next execution - */ - slot->tts_nvalid = attnum; - slot->tts_off = off; - if (slow) - slot->tts_flags |= TTS_FLAG_SLOW; - else - slot->tts_flags &= ~TTS_FLAG_SLOW; -} - -/* - * slot_attisnull - * Detect whether an attribute of the slot is null, without - * actually fetching it. - */ -bool -slot_attisnull(TupleTableSlot *slot, int attnum) -{ - HeapTuple tuple = slot->tts_tuple; - TupleDesc tupleDesc = slot->tts_tupleDescriptor; - - /* - * system attributes are handled by heap_attisnull - */ - if (attnum <= 0) - { - if (tuple == NULL) /* internal error */ - elog(ERROR, "cannot extract system attribute from virtual tuple"); - if (tuple == &(slot->tts_minhdr)) /* internal error */ - elog(ERROR, "cannot extract system attribute from minimal tuple"); - return heap_attisnull(tuple, attnum, tupleDesc); - } - - /* - * fast path if desired attribute already cached - */ - if (attnum <= slot->tts_nvalid) - return slot->tts_isnull[attnum - 1]; - - /* - * return NULL if attnum is out of range according to the tupdesc - */ - if (attnum > tupleDesc->natts) - return true; - - /* - * otherwise we had better have a physical tuple (tts_nvalid should equal - * natts in all virtual-tuple cases) - */ - if (tuple == NULL) /* internal error */ - elog(ERROR, "cannot extract attribute from empty tuple slot"); - - /* and let the tuple tell it */ - return heap_attisnull(tuple, attnum, tupleDesc); -} - -/* - * slot_getsysattr - * This function fetches a system attribute of the slot's current tuple. - * Unlike slot_getattr, if the slot does not contain system attributes, - * this will return false (with a NULL attribute value) instead of - * throwing an error. - */ -bool -slot_getsysattr(TupleTableSlot *slot, int attnum, - Datum *value, bool *isnull) -{ - HeapTuple tuple = slot->tts_tuple; - - Assert(attnum < 0); /* else caller error */ - if (tuple == NULL || - tuple == &(slot->tts_minhdr)) - { - /* No physical tuple, or minimal tuple, so fail */ - *value = (Datum) 0; - *isnull = true; - return false; - } - *value = heap_getsysattr(tuple, attnum, slot->tts_tupleDescriptor, isnull); - return true; -} - /* * heap_freetuple */ diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c index 21bdf794da6..a980202a7b1 100644 --- a/src/backend/catalog/index.c +++ b/src/backend/catalog/index.c @@ -2041,7 +2041,9 @@ FormIndexDatum(IndexInfo *indexInfo, Datum iDatum; bool isNull; - if (keycol != 0) + if (keycol < 0) + iDatum = slot_getsysattr(slot, keycol, &isNull); + else if (keycol != 0) { /* * Plain index column; get the value we need directly from the diff --git a/src/backend/executor/execCurrent.c b/src/backend/executor/execCurrent.c index aadf7493827..39c462a4e59 100644 --- a/src/backend/executor/execCurrent.c +++ b/src/backend/executor/execCurrent.c @@ -218,27 +218,25 @@ execCurrentOf(CurrentOfExpr *cexpr, ItemPointer tuple_tid; #ifdef USE_ASSERT_CHECKING - if (!slot_getsysattr(scanstate->ss_ScanTupleSlot, - TableOidAttributeNumber, - &ldatum, - &lisnull)) + ldatum = slot_getsysattr(scanstate->ss_ScanTupleSlot, + TableOidAttributeNumber, + &lisnull); + if (lisnull) ereport(ERROR, (errcode(ERRCODE_INVALID_CURSOR_STATE), errmsg("cursor \"%s\" is not a simply updatable scan of table \"%s\"", cursor_name, table_name))); - Assert(!lisnull); Assert(DatumGetObjectId(ldatum) == table_oid); #endif - if (!slot_getsysattr(scanstate->ss_ScanTupleSlot, - SelfItemPointerAttributeNumber, - &ldatum, - &lisnull)) + ldatum = slot_getsysattr(scanstate->ss_ScanTupleSlot, + SelfItemPointerAttributeNumber, + &lisnull); + if (lisnull) ereport(ERROR, (errcode(ERRCODE_INVALID_CURSOR_STATE), errmsg("cursor \"%s\" is not a simply updatable scan of table \"%s\"", cursor_name, table_name))); - Assert(!lisnull); tuple_tid = (ItemPointer) DatumGetPointer(ldatum); *current_tid = *tuple_tid; diff --git a/src/backend/executor/execExpr.c b/src/backend/executor/execExpr.c index a4099c23176..914d04186bd 100644 --- a/src/backend/executor/execExpr.c +++ b/src/backend/executor/execExpr.c @@ -3313,6 +3313,7 @@ ExecBuildAggTransCall(ExprState *state, AggState *aggstate, */ ExprState * ExecBuildGroupingEqual(TupleDesc ldesc, TupleDesc rdesc, + const TupleTableSlotOps *lops, const TupleTableSlotOps *rops, int numCols, AttrNumber *keyColIdx, Oid *eqfunctions, @@ -3354,7 +3355,7 @@ ExecBuildGroupingEqual(TupleDesc ldesc, TupleDesc rdesc, scratch.d.fetch.last_var = maxatt; scratch.d.fetch.fixed = false; scratch.d.fetch.known_desc = ldesc; - scratch.d.fetch.kind = NULL; + scratch.d.fetch.kind = lops; ExecComputeSlotInfo(state, &scratch); ExprEvalPushStep(state, &scratch); @@ -3362,7 +3363,7 @@ ExecBuildGroupingEqual(TupleDesc ldesc, TupleDesc rdesc, scratch.d.fetch.last_var = maxatt; scratch.d.fetch.fixed = false; scratch.d.fetch.known_desc = rdesc; - scratch.d.fetch.kind = NULL; + scratch.d.fetch.kind = rops; ExecComputeSlotInfo(state, &scratch); ExprEvalPushStep(state, &scratch); diff --git a/src/backend/executor/execExprInterp.c b/src/backend/executor/execExprInterp.c index 357eae41cc3..dbf2445a21d 100644 --- a/src/backend/executor/execExprInterp.c +++ b/src/backend/executor/execExprInterp.c @@ -428,7 +428,6 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull) { CheckOpSlotCompatibility(op, innerslot); - /* XXX: worthwhile to check tts_nvalid inline first? */ slot_getsomeattrs(innerslot, op->d.fetch.last_var); EEO_NEXT(); @@ -4026,15 +4025,15 @@ void ExecEvalSysVar(ExprState *state, ExprEvalStep *op, ExprContext *econtext, TupleTableSlot *slot) { - bool success; + Datum d; /* slot_getsysattr has sufficient defenses against bad attnums */ - success = slot_getsysattr(slot, - op->d.var.attnum, - op->resvalue, - op->resnull); + d = slot_getsysattr(slot, + op->d.var.attnum, + op->resnull); + *op->resvalue = d; /* this ought to be unreachable, but it's cheap enough to check */ - if (unlikely(!success)) + if (unlikely(*op->resnull)) elog(ERROR, "failed to fetch attribute from slot"); } diff --git a/src/backend/executor/execGrouping.c b/src/backend/executor/execGrouping.c index 5bc200e4dc3..abce1e95cb6 100644 --- a/src/backend/executor/execGrouping.c +++ b/src/backend/executor/execGrouping.c @@ -75,7 +75,8 @@ execTuplesMatchPrepare(TupleDesc desc, eqFunctions[i] = get_opcode(eqOperators[i]); /* build actual expression */ - expr = ExecBuildGroupingEqual(desc, desc, numCols, keyColIdx, eqFunctions, + expr = ExecBuildGroupingEqual(desc, desc, NULL, NULL, + numCols, keyColIdx, eqFunctions, parent); return expr; @@ -206,7 +207,9 @@ BuildTupleHashTable(PlanState *parent, &TTSOpsMinimalTuple); /* build comparator for all columns */ + /* XXX: should we support non-minimal tuples for the inputslot? */ hashtable->tab_eq_func = ExecBuildGroupingEqual(inputDesc, inputDesc, + &TTSOpsMinimalTuple, &TTSOpsMinimalTuple, numCols, keyColIdx, eqfuncoids, parent); diff --git a/src/backend/executor/execReplication.c b/src/backend/executor/execReplication.c index 071ba8762d4..727770eab56 100644 --- a/src/backend/executor/execReplication.c +++ b/src/backend/executor/execReplication.c @@ -170,8 +170,11 @@ retry: HeapUpdateFailureData hufd; HTSU_Result res; HeapTupleData locktup; + HeapTupleTableSlot *hslot = (HeapTupleTableSlot *)outslot; - ItemPointerCopy(&outslot->tts_tuple->t_self, &locktup.t_self); + /* Only a heap tuple has item pointers. */ + Assert(TTS_IS_HEAPTUPLE(outslot) || TTS_IS_BUFFERTUPLE(outslot)); + ItemPointerCopy(&hslot->tuple->t_self, &locktup.t_self); PushActiveSnapshot(GetLatestSnapshot()); @@ -334,8 +337,12 @@ retry: HeapUpdateFailureData hufd; HTSU_Result res; HeapTupleData locktup; + HeapTupleTableSlot *hslot = (HeapTupleTableSlot *)outslot; - ItemPointerCopy(&outslot->tts_tuple->t_self, &locktup.t_self); + /* Only a heap tuple has item pointers. */ + Assert(TTS_IS_HEAPTUPLE(outslot) || TTS_IS_BUFFERTUPLE(outslot)); + + ItemPointerCopy(&hslot->tuple->t_self, &locktup.t_self); PushActiveSnapshot(GetLatestSnapshot()); @@ -456,6 +463,12 @@ ExecSimpleRelationUpdate(EState *estate, EPQState *epqstate, HeapTuple tuple; ResultRelInfo *resultRelInfo = estate->es_result_relation_info; Relation rel = resultRelInfo->ri_RelationDesc; + HeapTupleTableSlot *hsearchslot = (HeapTupleTableSlot *)searchslot; + HeapTupleTableSlot *hslot = (HeapTupleTableSlot *)slot; + + /* We expect both searchslot and the slot to contain a heap tuple. */ + Assert(TTS_IS_HEAPTUPLE(searchslot) || TTS_IS_BUFFERTUPLE(searchslot)); + Assert(TTS_IS_HEAPTUPLE(slot) || TTS_IS_BUFFERTUPLE(slot)); /* For now we support only tables. */ Assert(rel->rd_rel->relkind == RELKIND_RELATION); @@ -467,8 +480,7 @@ ExecSimpleRelationUpdate(EState *estate, EPQState *epqstate, resultRelInfo->ri_TrigDesc->trig_update_before_row) { slot = ExecBRUpdateTriggers(estate, epqstate, resultRelInfo, - &searchslot->tts_tuple->t_self, - NULL, slot); + &hsearchslot->tuple->t_self, NULL, slot); if (slot == NULL) /* "do nothing" */ skip_tuple = true; @@ -488,19 +500,18 @@ ExecSimpleRelationUpdate(EState *estate, EPQState *epqstate, tuple = ExecFetchSlotHeapTuple(slot, true, NULL); /* OK, update the tuple and index entries for it */ - simple_heap_update(rel, &searchslot->tts_tuple->t_self, - slot->tts_tuple); + simple_heap_update(rel, &hsearchslot->tuple->t_self, hslot->tuple); if (resultRelInfo->ri_NumIndices > 0 && - !HeapTupleIsHeapOnly(slot->tts_tuple)) + !HeapTupleIsHeapOnly(hslot->tuple)) recheckIndexes = ExecInsertIndexTuples(slot, &(tuple->t_self), estate, false, NULL, NIL); /* AFTER ROW UPDATE Triggers */ ExecARUpdateTriggers(estate, resultRelInfo, - &searchslot->tts_tuple->t_self, - NULL, tuple, recheckIndexes, NULL); + &hsearchslot->tuple->t_self, NULL, tuple, + recheckIndexes, NULL); list_free(recheckIndexes); } @@ -519,9 +530,11 @@ ExecSimpleRelationDelete(EState *estate, EPQState *epqstate, bool skip_tuple = false; ResultRelInfo *resultRelInfo = estate->es_result_relation_info; Relation rel = resultRelInfo->ri_RelationDesc; + HeapTupleTableSlot *hsearchslot = (HeapTupleTableSlot *)searchslot; - /* For now we support only tables. */ + /* For now we support only tables and heap tuples. */ Assert(rel->rd_rel->relkind == RELKIND_RELATION); + Assert(TTS_IS_HEAPTUPLE(searchslot) || TTS_IS_BUFFERTUPLE(searchslot)); CheckCmdReplicaIdentity(rel, CMD_DELETE); @@ -530,8 +543,8 @@ ExecSimpleRelationDelete(EState *estate, EPQState *epqstate, resultRelInfo->ri_TrigDesc->trig_delete_before_row) { skip_tuple = !ExecBRDeleteTriggers(estate, epqstate, resultRelInfo, - &searchslot->tts_tuple->t_self, - NULL, NULL); + &hsearchslot->tuple->t_self, NULL, + NULL); } if (!skip_tuple) @@ -539,11 +552,11 @@ ExecSimpleRelationDelete(EState *estate, EPQState *epqstate, List *recheckIndexes = NIL; /* OK, delete the tuple */ - simple_heap_delete(rel, &searchslot->tts_tuple->t_self); + simple_heap_delete(rel, &hsearchslot->tuple->t_self); /* AFTER ROW DELETE Triggers */ ExecARDeleteTriggers(estate, resultRelInfo, - &searchslot->tts_tuple->t_self, NULL, NULL); + &hsearchslot->tuple->t_self, NULL, NULL); list_free(recheckIndexes); } diff --git a/src/backend/executor/execScan.c b/src/backend/executor/execScan.c index 233cc280608..d90bb16b570 100644 --- a/src/backend/executor/execScan.c +++ b/src/backend/executor/execScan.c @@ -78,8 +78,8 @@ ExecScanFetch(ScanState *node, return ExecClearTuple(slot); /* Store test tuple in the plan node's scan slot */ - ExecStoreHeapTuple(estate->es_epqTuple[scanrelid - 1], - slot, false); + ExecForceStoreHeapTuple(estate->es_epqTuple[scanrelid - 1], + slot); /* Check if it meets the access-method conditions */ if (!(*recheckMtd) (node, slot)) diff --git a/src/backend/executor/execTuples.c b/src/backend/executor/execTuples.c index bb618e94f5d..bf49c4789cb 100644 --- a/src/backend/executor/execTuples.c +++ b/src/backend/executor/execTuples.c @@ -71,12 +71,1024 @@ static TupleDesc ExecTypeFromTLInternal(List *targetList, bool hasoid, bool skipjunk); +static void tts_buffer_heap_store_tuple(TupleTableSlot *slot, HeapTuple tuple, Buffer buffer); const TupleTableSlotOps TTSOpsVirtual; const TupleTableSlotOps TTSOpsHeapTuple; const TupleTableSlotOps TTSOpsMinimalTuple; const TupleTableSlotOps TTSOpsBufferTuple; +/* + * slot_deform_heap_tuple + * Given a TupleTableSlot, extract data from the slot's physical tuple + * into its Datum/isnull arrays. Data is extracted up through the + * natts'th column (caller must ensure this is a legal column number). + * + * This is essentially an incremental version of heap_deform_tuple: + * on each call we extract attributes up to the one needed, without + * re-computing information about previously extracted attributes. + * slot->tts_nvalid is the number of attributes already extracted. + */ +static inline void __attribute__((always_inline)) +slot_deform_heap_tuple(TupleTableSlot *slot, HeapTuple tuple, uint32 *offp, + int natts) +{ + TupleDesc tupleDesc = slot->tts_tupleDescriptor; + Datum *values = slot->tts_values; + bool *isnull = slot->tts_isnull; + HeapTupleHeader tup = tuple->t_data; + bool hasnulls = HeapTupleHasNulls(tuple); + int attnum; + char *tp; /* ptr to tuple data */ + uint32 off; /* offset in tuple data */ + bits8 *bp = tup->t_bits; /* ptr to null bitmap in tuple */ + bool slow; /* can we use/set attcacheoff? */ + + /* We can only fetch as many attributes as the tuple has. */ + natts = Min(HeapTupleHeaderGetNatts(tuple->t_data), natts); + + /* + * Check whether the first call for this tuple, and initialize or restore + * loop state. + */ + attnum = slot->tts_nvalid; + if (attnum == 0) + { + /* Start from the first attribute */ + off = 0; + slow = false; + } + else + { + /* Restore state from previous execution */ + off = *offp; + slow = TTS_SLOW(slot); + } + + tp = (char *) tup + tup->t_hoff; + + for (; attnum < natts; attnum++) + { + Form_pg_attribute thisatt = TupleDescAttr(tupleDesc, attnum); + + if (hasnulls && att_isnull(attnum, bp)) + { + values[attnum] = (Datum) 0; + isnull[attnum] = true; + slow = true; /* can't use attcacheoff anymore */ + continue; + } + + isnull[attnum] = false; + + if (!slow && thisatt->attcacheoff >= 0) + off = thisatt->attcacheoff; + else if (thisatt->attlen == -1) + { + /* + * We can only cache the offset for a varlena attribute if the + * offset is already suitably aligned, so that there would be no + * pad bytes in any case: then the offset will be valid for either + * an aligned or unaligned value. + */ + if (!slow && + off == att_align_nominal(off, thisatt->attalign)) + thisatt->attcacheoff = off; + else + { + off = att_align_pointer(off, thisatt->attalign, -1, + tp + off); + slow = true; + } + } + else + { + /* not varlena, so safe to use att_align_nominal */ + off = att_align_nominal(off, thisatt->attalign); + + if (!slow) + thisatt->attcacheoff = off; + } + + values[attnum] = fetchatt(thisatt, tp + off); + + off = att_addlength_pointer(off, thisatt->attlen, tp + off); + + if (thisatt->attlen <= 0) + slow = true; /* can't use attcacheoff anymore */ + } + + /* + * Save state for next execution + */ + slot->tts_nvalid = attnum; + *offp = off; + if (slow) + slot->tts_flags |= TTS_FLAG_SLOW; + else + slot->tts_flags &= ~TTS_FLAG_SLOW; +} + +/* + * TupleTableSlotOps implementations. + */ + +/* + * TupleTableSlotOps implementation for VirtualTupleTableSlot. + */ +static void +tts_virtual_init(TupleTableSlot *slot) +{ +} + +static void +tts_virtual_release(TupleTableSlot *slot) +{ +} + +static void +tts_virtual_clear(TupleTableSlot *slot) +{ + if (unlikely(TTS_SHOULDFREE(slot))) + { + VirtualTupleTableSlot *vslot = (VirtualTupleTableSlot *) slot; + + pfree(vslot->data); + vslot->data = NULL; + + slot->tts_flags = ~TTS_FLAG_SHOULDFREE; + } + + slot->tts_nvalid = 0; + slot->tts_flags |= TTS_FLAG_EMPTY; +} + +/* + * Attribute values are readily available in tts_values and tts_isnull array + * in a VirtualTupleTableSlot. So there should be no need to call either of the + * following two functions. + */ +static void +tts_virtual_getsomeattrs(TupleTableSlot *slot, int natts) +{ + elog(ERROR, "getsomeattrs is not required to be called on a virtual tuple table slot"); +} + +static Datum +tts_virtual_getsysattr(TupleTableSlot *slot, int attnum, bool *isnull) +{ + elog(ERROR, "virtual tuple table slot does not have system atttributes"); +} + +/* + * To materialize a virtual slot all the datums that aren't passed by value + * have to be copied into the slot's memory context. To do so, count the + * required size, and allocate enough memory to store all attributes. That + * both gains efficiency, and makes it easier to release all the memory at + * once. + */ +static void +tts_virtual_materialize(TupleTableSlot *slot) +{ + VirtualTupleTableSlot *vslot = (VirtualTupleTableSlot *) slot; + TupleDesc desc = slot->tts_tupleDescriptor; + Size sz = 0; + char *data; + + /* already materialized */ + if (TTS_SHOULDFREE(slot)) + return; + + /* compute size of memory required */ + for (int natt = 0; natt < desc->natts; natt++) + { + Form_pg_attribute att = TupleDescAttr(desc, natt); + Datum val; + + if (att->attbyval || slot->tts_isnull[natt]) + continue; + + val = slot->tts_values[natt]; + + if (att->attlen == -1 && + VARATT_IS_EXTERNAL_EXPANDED(DatumGetPointer(val))) + { + /* + * We want to flatten the expanded value so that the materialized + * slot doesn't depend on it. + */ + sz = att_align_nominal(sz, att->attalign); + sz += EOH_get_flat_size(DatumGetEOHP(val)); + } + else + { + sz = att_align_nominal(sz, att->attalign); + sz = att_addlength_datum(sz, att->attlen, val); + } + + /* FIXME: any reason to expand default args? */ + } + + /* all data is byval */ + if (sz == 0) + return; + + /* allocate memory */ + vslot->data = data = MemoryContextAlloc(slot->tts_mcxt, sz); + slot->tts_flags |= TTS_FLAG_SHOULDFREE; + + /* and copy all attributes into the pre-allocated space */ + for (int natt = 0; natt < desc->natts; natt++) + { + Form_pg_attribute att = TupleDescAttr(desc, natt); + Datum val; + + if (att->attbyval || slot->tts_isnull[natt]) + continue; + + val = slot->tts_values[natt]; + + if (att->attlen == -1 && + VARATT_IS_EXTERNAL_EXPANDED(DatumGetPointer(val))) + { + Size data_length; + + /* + * We want to flatten the expanded value so that the materialized + * slot doesn't depend on it. + */ + ExpandedObjectHeader *eoh = DatumGetEOHP(val); + + data = (char *) att_align_nominal(data, + att->attalign); + data_length = EOH_get_flat_size(eoh); + EOH_flatten_into(eoh, data, data_length); + + slot->tts_values[natt] = PointerGetDatum(data); + data += data_length; + } + else + { + Size data_length = 0; + + data = (char *) att_align_nominal(data, att->attalign); + data_length = att_addlength_datum(data_length, att->attlen, val); + + memcpy(data, DatumGetPointer(val), data_length); + + slot->tts_values[natt] = PointerGetDatum(data); + data += data_length; + } + /* FIXME: any reason to expand default args? */ + } +} + +/* + * A virtual tuple table slot can not copy the datums into its own storage. So + * it can not copy contents of the other slot into its own memory context. + */ +static void +tts_virtual_copyslot(TupleTableSlot *dstslot, TupleTableSlot *srcslot) +{ + TupleDesc srcdesc = dstslot->tts_tupleDescriptor; + + Assert(srcdesc->natts <= dstslot->tts_tupleDescriptor->natts); + + tts_virtual_clear(dstslot); + + slot_getallattrs(srcslot); + + for (int natt = 0; natt < srcdesc->natts; natt++) + { + dstslot->tts_values[natt] = srcslot->tts_values[natt]; + dstslot->tts_isnull[natt] = srcslot->tts_isnull[natt]; + } + + dstslot->tts_nvalid = srcdesc->natts; + dstslot->tts_flags &= ~TTS_FLAG_EMPTY; + + /* make sure storage doesn't depend on external memory */ + tts_virtual_materialize(dstslot); +} + +static HeapTuple +tts_virtual_copy_heap_tuple(TupleTableSlot *slot) +{ + Assert(!TTS_EMPTY(slot)); + + return heap_form_tuple(slot->tts_tupleDescriptor, + slot->tts_values, + slot->tts_isnull); + +} + +static MinimalTuple +tts_virtual_copy_minimal_tuple(TupleTableSlot *slot) +{ + Assert(!TTS_EMPTY(slot)); + + return heap_form_minimal_tuple(slot->tts_tupleDescriptor, + slot->tts_values, + slot->tts_isnull); +} + +/* + * TupleTableSlotOps implementation for HeapTupleTableSlot. + */ + +static void +tts_heap_init(TupleTableSlot *slot) +{ +} + +static void +tts_heap_release(TupleTableSlot *slot) +{ +} + +static void +tts_heap_clear(TupleTableSlot *slot) +{ + HeapTupleTableSlot *hslot = (HeapTupleTableSlot *) slot; + + /* Free the memory for the heap tuple if it's allowed. */ + if (TTS_SHOULDFREE(slot)) + { + heap_freetuple(hslot->tuple); + slot->tts_flags &= ~TTS_FLAG_SHOULDFREE; + } + + slot->tts_nvalid = 0; + slot->tts_flags |= TTS_FLAG_EMPTY; + hslot->off = 0; + hslot->tuple = NULL; +} + +static void +tts_heap_getsomeattrs(TupleTableSlot *slot, int natts) +{ + HeapTupleTableSlot *hslot = (HeapTupleTableSlot *) slot; + + Assert(!TTS_EMPTY(slot)); + + slot_deform_heap_tuple(slot, hslot->tuple, &hslot->off, natts); +} + +static Datum +tts_heap_getsysattr(TupleTableSlot *slot, int attnum, bool *isnull) +{ + HeapTupleTableSlot *hslot = (HeapTupleTableSlot *) slot; + + return heap_getsysattr(hslot->tuple, attnum, + slot->tts_tupleDescriptor, isnull); +} + +/* + * Materialize the heap tuple contained in the given slot into its own memory + * context. + */ +static void +tts_heap_materialize(TupleTableSlot *slot) +{ + HeapTupleTableSlot *hslot = (HeapTupleTableSlot *) slot; + MemoryContext oldContext; + + Assert(!TTS_EMPTY(slot)); + + /* This slot has it's tuple already materialized. Nothing to do. */ + if (TTS_SHOULDFREE(slot)) + return; + + slot->tts_flags |= TTS_FLAG_SHOULDFREE; + + oldContext = MemoryContextSwitchTo(slot->tts_mcxt); + + if (!hslot->tuple) + hslot->tuple = heap_form_tuple(slot->tts_tupleDescriptor, + slot->tts_values, + slot->tts_isnull); + else + { + /* + * The tuple contained in this slot is not allocated in the memory + * context of the given slot (else it would have TTS_SHOULDFREE set). + * Copy the tuple into the given slot's memory context. + */ + hslot->tuple = heap_copytuple(hslot->tuple); + } + + slot->tts_nvalid = 0; + hslot->off = 0; + + MemoryContextSwitchTo(oldContext); +} + +/* + * Copy contents of the srcslot into dstslot, on which this callback has been + * invoked, in dstslot's memory context. We do this by creating a heap tuple + * from the source slot's contents in the memory context of the destination + * slot and then storing that heap tuple in the destination slot. + */ +static void +tts_heap_copyslot(TupleTableSlot *dstslot, TupleTableSlot *srcslot) +{ + HeapTuple tuple; + MemoryContext oldcontext; + + oldcontext = MemoryContextSwitchTo(dstslot->tts_mcxt); + tuple = ExecCopySlotTuple(srcslot); + MemoryContextSwitchTo(oldcontext); + + ExecStoreHeapTuple(tuple, dstslot, true); +} + +/* + * Return the heap tuple in the slot as is if it contains one. Otherwise, + * materialize a heap tuple using contents of the slot and return it. + */ +static HeapTuple +tts_heap_get_heap_tuple(TupleTableSlot *slot) +{ + HeapTupleTableSlot *hslot = (HeapTupleTableSlot *) slot; + + Assert(!TTS_EMPTY(slot)); + if (!hslot->tuple) + tts_heap_materialize(slot); + + return hslot->tuple; +} + +/* + * Return a copy of heap tuple contained in the slot, materialising one if + * necessary. + */ +static HeapTuple +tts_heap_copy_heap_tuple(TupleTableSlot *slot) +{ + HeapTupleTableSlot *hslot = (HeapTupleTableSlot *) slot; + + Assert(!TTS_EMPTY(slot)); + if (!hslot->tuple) + tts_heap_materialize(slot); + + return heap_copytuple(hslot->tuple); +} + +/* + * Return a minimal tuple constructed from the contents of the slot. + * + * We always return a new minimal tuple so no copy, per say, is needed. + */ +static MinimalTuple +tts_heap_copy_minimal_tuple(TupleTableSlot *slot) +{ + HeapTupleTableSlot *hslot = (HeapTupleTableSlot *) slot; + + if (!hslot->tuple) + tts_heap_materialize(slot); + + return minimal_tuple_from_heap_tuple(hslot->tuple); +} + +/* + * Store the given tuple into the given HeapTupleTableSlot. If the slot + * already contains a tuple, we will free the memory for the same before + * storing a new one there. + */ +static void +tts_heap_store_tuple(TupleTableSlot *slot, HeapTuple tuple, bool shouldFree) +{ + HeapTupleTableSlot *hslot = (HeapTupleTableSlot *) slot; + + tts_heap_clear(slot); + + slot->tts_nvalid = 0; + hslot->tuple = tuple; + hslot->off = 0; + slot->tts_flags &= ~TTS_FLAG_EMPTY; + + if (shouldFree) + slot->tts_flags |= TTS_FLAG_SHOULDFREE; +} + + +static void +tts_minimal_init(TupleTableSlot *slot) +{ + MinimalTupleTableSlot *mslot = (MinimalTupleTableSlot *) slot; + + /* + * Initialize the heap tuple pointer to access attributes of the minimal + * tuple contained in the slot as if its a heap tuple. + */ + mslot->tuple = &mslot->minhdr; +} + +static void +tts_minimal_release(TupleTableSlot *slot) +{ +} + +static void +tts_minimal_clear(TupleTableSlot *slot) +{ + MinimalTupleTableSlot *mslot = (MinimalTupleTableSlot *) slot; + + if (TTS_SHOULDFREE(slot)) + { + heap_free_minimal_tuple(mslot->mintuple); + slot->tts_flags &= ~TTS_FLAG_SHOULDFREE; + } + + slot->tts_nvalid = 0; + slot->tts_flags |= TTS_FLAG_EMPTY; + mslot->off = 0; + mslot->mintuple = NULL; +} + +static void +tts_minimal_getsomeattrs(TupleTableSlot *slot, int natts) +{ + MinimalTupleTableSlot *mslot = (MinimalTupleTableSlot *) slot; + + Assert(!TTS_EMPTY(slot)); + + slot_deform_heap_tuple(slot, mslot->tuple, &mslot->off, natts); +} + +static Datum +tts_minimal_getsysattr(TupleTableSlot *slot, int attnum, bool *isnull) +{ + elog(ERROR, "minimal tuple table slot does not have system atttributes"); +} + +/* + * Materialize the minimal tuple contained in the given slot into its own + * memory context. + */ +static void +tts_minimal_materialize(TupleTableSlot *slot) +{ + MinimalTupleTableSlot *mslot = (MinimalTupleTableSlot *) slot; + MemoryContext oldContext; + + Assert(!TTS_EMPTY(slot)); + + /* This slot has it's tuple already materialized. Nothing to do. */ + if (TTS_SHOULDFREE(slot)) + return; + + slot->tts_flags |= TTS_FLAG_SHOULDFREE; + oldContext = MemoryContextSwitchTo(slot->tts_mcxt); + + if (!mslot->mintuple) + mslot->mintuple = heap_form_minimal_tuple(slot->tts_tupleDescriptor, + slot->tts_values, + slot->tts_isnull); + else + { + /* + * The minimal tuple contained in this slot is not allocated in the + * memory context of the given slot (else it would have TTS_SHOULDFREE + * set). Copy the minimal tuple into the given slot's memory context. + */ + mslot->mintuple = heap_copy_minimal_tuple(mslot->mintuple); + } + + Assert(mslot->tuple == &mslot->minhdr); + + mslot->minhdr.t_len = mslot->mintuple->t_len + MINIMAL_TUPLE_OFFSET; + mslot->minhdr.t_data = (HeapTupleHeader) ((char *) mslot->mintuple - MINIMAL_TUPLE_OFFSET); + + MemoryContextSwitchTo(oldContext); + + slot->tts_nvalid = 0; + mslot->off = 0; +} + +/* + * Copy contents of the srcslot into dstslot, on which this callback has been + * invoked, in dstslot's memory context. We do this by creating a minimal tuple + * from the source slot's contents in the memory context of the destination + * slot and then storing that minimal tuple in the destination slot. + */ +static void +tts_minimal_copyslot(TupleTableSlot *dstslot, TupleTableSlot *srcslot) +{ + MemoryContext oldcontext; + MinimalTuple mintuple; + + oldcontext = MemoryContextSwitchTo(dstslot->tts_mcxt); + mintuple = ExecCopySlotMinimalTuple(srcslot); + MemoryContextSwitchTo(oldcontext); + + ExecStoreMinimalTuple(mintuple, dstslot, true); +} + +/* + * Return the minimal tuple if slot contains one. Otherwise materialize the + * contents of slot into a minimal tuple and return that. + */ +static MinimalTuple +tts_minimal_get_minimal_tuple(TupleTableSlot *slot) +{ + MinimalTupleTableSlot *mslot = (MinimalTupleTableSlot *) slot; + + Assert(!TTS_EMPTY(slot)); + + if (!mslot->mintuple) + tts_minimal_materialize(slot); + + return mslot->mintuple; +} + +/* + * Return a heap tuple constructed from the minimal tuple contained in the slot. + * + * We always construct a new heap tuple, so there is nothing to copy as such. + */ +static HeapTuple +tts_minimal_copy_heap_tuple(TupleTableSlot *slot) +{ + MinimalTupleTableSlot *mslot = (MinimalTupleTableSlot *) slot; + + /* Materialize the minimal tuple if not already present. */ + if (!mslot->mintuple) + tts_minimal_materialize(slot); + + return heap_tuple_from_minimal_tuple(mslot->mintuple); +} + +/* + * Return a copy of minimal tuple contained in the slot, materializing one if + * necessary. + */ +static MinimalTuple +tts_minimal_copy_minimal_tuple(TupleTableSlot *slot) +{ + MinimalTupleTableSlot *mslot = (MinimalTupleTableSlot *) slot; + + if (!mslot->mintuple) + tts_minimal_materialize(slot); + + return heap_copy_minimal_tuple(mslot->mintuple); +} + +/* + * Store the given minimal tuple into the given MinimalTupleTableSlot. If the + * slot already contains a tuple, we will free the memory for the same before + * storing a new one there. + */ +static void +tts_minimal_store_tuple(TupleTableSlot *slot, MinimalTuple mtup, bool shouldFree) +{ + MinimalTupleTableSlot *mslot = (MinimalTupleTableSlot *) slot; + + tts_minimal_clear(slot); + + Assert(!TTS_SHOULDFREE(slot)); + Assert(TTS_EMPTY(slot)); + + slot->tts_flags &= ~TTS_FLAG_EMPTY; + slot->tts_nvalid = 0; + mslot->off = 0; + + mslot->mintuple = mtup; + Assert(mslot->tuple == &mslot->minhdr); + mslot->minhdr.t_len = mtup->t_len + MINIMAL_TUPLE_OFFSET; + mslot->minhdr.t_data = (HeapTupleHeader) ((char *) mtup - MINIMAL_TUPLE_OFFSET); + /* no need to set t_self or t_tableOid since we won't allow access */ + + if (shouldFree) + slot->tts_flags |= TTS_FLAG_SHOULDFREE; + else + Assert(!TTS_SHOULDFREE(slot)); +} + +/* + * TupleTableSlotOps implementation for BufferHeapTupleTableSlot. + */ + +static void +tts_buffer_heap_init(TupleTableSlot *slot) +{ +} + +static void +tts_buffer_heap_release(TupleTableSlot *slot) +{ +} + +static void +tts_buffer_heap_clear(TupleTableSlot *slot) +{ + BufferHeapTupleTableSlot *bslot = (BufferHeapTupleTableSlot *) slot; + + /* + * Free the memory for heap tuple if allowed. A tuple coming from buffer + * can never be freed. But we may have materialized a tuple from buffer. + * Such a tuple can be freed. + */ + if (TTS_SHOULDFREE(slot)) + { + /* We should have unpinned the buffer while materializing the tuple. */ + Assert(!BufferIsValid(bslot->buffer)); + + heap_freetuple(bslot->base.tuple); + slot->tts_flags &= ~TTS_FLAG_SHOULDFREE; + + Assert(!BufferIsValid(bslot->buffer)); + } + + if (BufferIsValid(bslot->buffer)) + ReleaseBuffer(bslot->buffer); + + slot->tts_nvalid = 0; + slot->tts_flags |= TTS_FLAG_EMPTY; + bslot->base.tuple = NULL; + bslot->base.off = 0; + bslot->buffer = InvalidBuffer; +} + +static void +tts_buffer_heap_getsomeattrs(TupleTableSlot *slot, int natts) +{ + BufferHeapTupleTableSlot *bslot = (BufferHeapTupleTableSlot *) slot; + + Assert(!TTS_EMPTY(slot)); + + slot_deform_heap_tuple(slot, bslot->base.tuple, &bslot->base.off, natts); +} + +static Datum +tts_buffer_heap_getsysattr(TupleTableSlot *slot, int attnum, bool *isnull) +{ + BufferHeapTupleTableSlot *bslot = (BufferHeapTupleTableSlot *) slot; + + return heap_getsysattr(bslot->base.tuple, attnum, + slot->tts_tupleDescriptor, isnull); +} + +/* + * Materialize the heap tuple contained in the given slot into its own memory + * context. + */ +static void +tts_buffer_heap_materialize(TupleTableSlot *slot) +{ + BufferHeapTupleTableSlot *bslot = (BufferHeapTupleTableSlot *) slot; + MemoryContext oldContext; + + Assert(!TTS_EMPTY(slot)); + + /* If already materialized nothing to do. */ + if (TTS_SHOULDFREE(slot)) + return; + + slot->tts_flags |= TTS_FLAG_SHOULDFREE; + oldContext = MemoryContextSwitchTo(slot->tts_mcxt); + + if (!bslot->base.tuple) + { + /* + * A heap tuple stored in a BufferHeapTupleTableSlot should have a + * buffer associated with it, unless it's materialized. Thus it will + * never happen that there is no heap tuple stored in this slot but + * tts_values and tts_isnull is set. IOW, we should never require to construct a + * tuple from tts_values and tts_isnull in this slot. + */ + elog(ERROR, "a buffer tuple table slot should never require to construct a tuple from datums"); + } + bslot->base.tuple = heap_copytuple(bslot->base.tuple); + MemoryContextSwitchTo(oldContext); + + /* + * A heap tuple stored in a BufferHeapTupleTableSlot should have a buffer + * associated with it, unless it's materialized. + */ + Assert(BufferIsValid(bslot->buffer)); + ReleaseBuffer(bslot->buffer); + bslot->buffer = InvalidBuffer; + + bslot->base.off = 0; + slot->tts_nvalid = 0; +} + +/* + * A buffer tuple table slot is used to store an on-disk tuple in it, at least + * to start with. So a need to copy into a buffer tuple table slot should not + * arise. May be we could support copying from a buffer tuple table slot, but + * it is not clear why it's required to do that. + */ +static void +tts_buffer_heap_copyslot(TupleTableSlot *dstslot, TupleTableSlot *srcslot) +{ + BufferHeapTupleTableSlot *bsrcslot = (BufferHeapTupleTableSlot *) srcslot; + BufferHeapTupleTableSlot *bdstslot = (BufferHeapTupleTableSlot *) dstslot; + + Assert(dstslot->tts_cb == srcslot->tts_cb); + + if (TTS_SHOULDFREE(srcslot)) + { + MemoryContext oldContext; + + ExecClearTuple(dstslot); + dstslot->tts_flags |= TTS_FLAG_SHOULDFREE; + dstslot->tts_flags &= ~TTS_FLAG_EMPTY; + oldContext = MemoryContextSwitchTo(dstslot->tts_mcxt); + bdstslot->base.tuple = heap_copytuple(bsrcslot->base.tuple); + MemoryContextSwitchTo(oldContext); + } + else + { + tts_buffer_heap_store_tuple(dstslot, bsrcslot->base.tuple, bsrcslot->buffer); + tts_buffer_heap_materialize(dstslot); + } +} + +/* + * Return the heap tuple in the slot as is if it contains one. Otherwise, + * materialize a heap tuple using contents of the slot and return it. + */ +static HeapTuple +tts_buffer_heap_get_heap_tuple(TupleTableSlot *slot) +{ + BufferHeapTupleTableSlot *bslot = (BufferHeapTupleTableSlot *) slot; + + Assert(!TTS_EMPTY(slot)); + + /* Is this even possible for a buffer tuple? */ + if (!bslot->base.tuple) + tts_buffer_heap_materialize(slot); + + return bslot->base.tuple; +} + +/* + * Return a copy of heap tuple contained in the slot, materialising one if + * necessary. + */ +static HeapTuple +tts_buffer_heap_copy_heap_tuple(TupleTableSlot *slot) +{ + BufferHeapTupleTableSlot *bslot = (BufferHeapTupleTableSlot *) slot; + + Assert(!TTS_EMPTY(slot)); + + /* Is this even possible for a buffer tuple? */ + if (!bslot->base.tuple) + tts_buffer_heap_materialize(slot); + + return heap_copytuple(bslot->base.tuple); +} + +/* + * Return a minimal tuple constructed from the contents of the slot. + * + * We always return a new minimal tuple so no copy, per say, is needed. + */ +static MinimalTuple +tts_buffer_heap_copy_minimal_tuple(TupleTableSlot *slot) +{ + BufferHeapTupleTableSlot *bslot = (BufferHeapTupleTableSlot *) slot; + + Assert(!TTS_EMPTY(slot)); + + /* Is this even possible for a buffer tuple? */ + if (!bslot->base.tuple) + tts_buffer_heap_materialize(slot); + + return minimal_tuple_from_heap_tuple(bslot->base.tuple); +} + +/* + * Store the given tuple into the given BufferHeapTupleTableSlot and pin the + * given buffer. If the tuple already contained in the slot can be freed free + * it. + */ +static void +tts_buffer_heap_store_tuple(TupleTableSlot *slot, HeapTuple tuple, Buffer buffer) +{ + BufferHeapTupleTableSlot *bslot = (BufferHeapTupleTableSlot *) slot; + + if (TTS_SHOULDFREE(slot)) + { + /* + * A heap tuple stored in a BufferHeapTupleTableSlot should have a + * buffer associated with it, unless it's materialized. + */ + Assert(!BufferIsValid(bslot->buffer)); + + heap_freetuple(bslot->base.tuple); + slot->tts_flags &= ~TTS_FLAG_SHOULDFREE; + } + + slot->tts_flags &= ~TTS_FLAG_EMPTY; + slot->tts_nvalid = 0; + bslot->base.tuple = tuple; + bslot->base.off = 0; + + /* + * If tuple is on a disk page, keep the page pinned as long as we hold a + * pointer into it. We assume the caller already has such a pin. + * + * This is coded to optimize the case where the slot previously held a + * tuple on the same disk page: in that case releasing and re-acquiring + * the pin is a waste of cycles. This is a common situation during + * seqscans, so it's worth troubling over. + */ + if (bslot->buffer != buffer) + { + if (BufferIsValid(bslot->buffer)) + ReleaseBuffer(bslot->buffer); + bslot->buffer = buffer; + IncrBufferRefCount(buffer); + } +} + +/* + * TupleTableSlotOps for each of TupleTableSlotTypes. These are used to + * identify the type of slot. + */ +const TupleTableSlotOps TTSOpsVirtual = { + .base_slot_size = sizeof(VirtualTupleTableSlot), + .init = tts_virtual_init, + .release = tts_virtual_release, + .clear = tts_virtual_clear, + .getsomeattrs = tts_virtual_getsomeattrs, + .getsysattr = tts_virtual_getsysattr, + .materialize = tts_virtual_materialize, + .copyslot = tts_virtual_copyslot, + + /* + * A virtual tuple table slot can not "own" a heap tuple or a minimal + * tuple. + */ + .get_heap_tuple = NULL, + .get_minimal_tuple = NULL, + .copy_heap_tuple = tts_virtual_copy_heap_tuple, + .copy_minimal_tuple = tts_virtual_copy_minimal_tuple +}; + +const TupleTableSlotOps TTSOpsHeapTuple = { + .base_slot_size = sizeof(HeapTupleTableSlot), + .init = tts_heap_init, + .release = tts_heap_release, + .clear = tts_heap_clear, + .getsomeattrs = tts_heap_getsomeattrs, + .getsysattr = tts_heap_getsysattr, + .materialize = tts_heap_materialize, + .copyslot = tts_heap_copyslot, + .get_heap_tuple = tts_heap_get_heap_tuple, + + /* A heap tuple table slot can not "own" a minimal tuple. */ + .get_minimal_tuple = NULL, + .copy_heap_tuple = tts_heap_copy_heap_tuple, + .copy_minimal_tuple = tts_heap_copy_minimal_tuple +}; + +const TupleTableSlotOps TTSOpsMinimalTuple = { + .base_slot_size = sizeof(MinimalTupleTableSlot), + .init = tts_minimal_init, + .release = tts_minimal_release, + .clear = tts_minimal_clear, + .getsomeattrs = tts_minimal_getsomeattrs, + .getsysattr = tts_minimal_getsysattr, + .materialize = tts_minimal_materialize, + .copyslot = tts_minimal_copyslot, + + /* + * A minimal tuple table slot can not "own" a heap tuple. As mentioned in + * the prologue of MinimalTupleData, a minimal tuple may be presented as a + * heap tuple, but such a representation does not have memory to hold + * system attributes in it and should be used for accessing/manipulating + * tuple through a slot. Hence we do not return heap tuple representation + * crafted from a minimal tuple to be returned in get_heap_tuple callback + * of this TupleTableSlotType. + */ + .get_heap_tuple = NULL, + .get_minimal_tuple = tts_minimal_get_minimal_tuple, + .copy_heap_tuple = tts_minimal_copy_heap_tuple, + .copy_minimal_tuple = tts_minimal_copy_minimal_tuple +}; + +const TupleTableSlotOps TTSOpsBufferTuple = { + .base_slot_size = sizeof(BufferHeapTupleTableSlot), + .init = tts_buffer_heap_init, + .release = tts_buffer_heap_release, + .clear = tts_buffer_heap_clear, + .getsomeattrs = tts_buffer_heap_getsomeattrs, + .getsysattr = tts_buffer_heap_getsysattr, + .materialize = tts_buffer_heap_materialize, + .copyslot = tts_buffer_heap_copyslot, + .get_heap_tuple = tts_buffer_heap_get_heap_tuple, + + /* A buffer heap tuple table slot can not "own" a minimal tuple. */ + .get_minimal_tuple = NULL, + .copy_heap_tuple = tts_buffer_heap_copy_heap_tuple, + .copy_minimal_tuple = tts_buffer_heap_copy_minimal_tuple +}; + /* ---------------------------------------------------------------- * tuple table create/delete functions * ---------------------------------------------------------------- @@ -85,58 +1097,60 @@ const TupleTableSlotOps TTSOpsBufferTuple; /* -------------------------------- * MakeTupleTableSlot * - * Basic routine to make an empty TupleTableSlot. If tupleDesc is - * specified the slot's descriptor is fixed for it's lifetime, gaining - * some efficiency. If that's undesirable, pass NULL. + * Basic routine to make an empty TupleTableSlot of given + * TupleTableSlotType. If tupleDesc is specified the slot's descriptor is + * fixed for it's lifetime, gaining some efficiency. If that's + * undesirable, pass NULL. * -------------------------------- */ TupleTableSlot * MakeTupleTableSlot(TupleDesc tupleDesc, const TupleTableSlotOps *tts_cb) { - Size sz; + Size basesz, allocsz; TupleTableSlot *slot; + basesz = tts_cb->base_slot_size; /* * When a fixed descriptor is specified, we can reduce overhead by * allocating the entire slot in one go. */ if (tupleDesc) - sz = MAXALIGN(sizeof(TupleTableSlot)) + + allocsz = MAXALIGN(basesz) + MAXALIGN(tupleDesc->natts * sizeof(Datum)) + MAXALIGN(tupleDesc->natts * sizeof(bool)); else - sz = sizeof(TupleTableSlot); + allocsz = basesz; - slot = palloc0(sz); + slot = palloc0(allocsz); /* const for optimization purposes, OK to modify at allocation time */ *((const TupleTableSlotOps **) &slot->tts_cb) = tts_cb; slot->type = T_TupleTableSlot; slot->tts_flags |= TTS_FLAG_EMPTY; if (tupleDesc != NULL) slot->tts_flags |= TTS_FLAG_FIXED; - slot->tts_tuple = NULL; slot->tts_tupleDescriptor = tupleDesc; slot->tts_mcxt = CurrentMemoryContext; - slot->tts_buffer = InvalidBuffer; slot->tts_nvalid = 0; - slot->tts_values = NULL; - slot->tts_isnull = NULL; - slot->tts_mintuple = NULL; if (tupleDesc != NULL) { slot->tts_values = (Datum *) (((char *) slot) - + MAXALIGN(sizeof(TupleTableSlot))); + + MAXALIGN(basesz)); slot->tts_isnull = (bool *) (((char *) slot) - + MAXALIGN(sizeof(TupleTableSlot)) + + MAXALIGN(basesz) + MAXALIGN(tupleDesc->natts * sizeof(Datum))); PinTupleDesc(tupleDesc); } + /* + * And allow slot type specific initialization. + */ + slot->tts_cb->init(slot); + return slot; } @@ -178,6 +1192,7 @@ ExecResetTupleTable(List *tupleTable, /* tuple table */ /* Always release resources and reset the slot to empty */ ExecClearTuple(slot); + slot->tts_cb->release(slot); if (slot->tts_tupleDescriptor) { ReleaseTupleDesc(slot->tts_tupleDescriptor); @@ -234,6 +1249,7 @@ ExecDropSingleTupleTableSlot(TupleTableSlot *slot) /* This should match ExecResetTupleTable's processing of one slot */ Assert(IsA(slot, TupleTableSlot)); ExecClearTuple(slot); + slot->tts_cb->release(slot); if (slot->tts_tupleDescriptor) ReleaseTupleDesc(slot->tts_tupleDescriptor); if (!TTS_FIXED(slot)) @@ -334,36 +1350,9 @@ ExecStoreHeapTuple(HeapTuple tuple, Assert(slot != NULL); Assert(slot->tts_tupleDescriptor != NULL); - /* - * Free any old physical tuple belonging to the slot. - */ - if (TTS_SHOULDFREE(slot)) - { - heap_freetuple(slot->tts_tuple); - slot->tts_flags &= ~TTS_FLAG_SHOULDFREE; - } - if (TTS_SHOULDFREEMIN(slot)) - { - heap_free_minimal_tuple(slot->tts_mintuple); - slot->tts_flags &= ~TTS_FLAG_SHOULDFREEMIN; - } - - /* - * Store the new tuple into the specified slot. - */ - slot->tts_flags &= ~TTS_FLAG_EMPTY; - if (shouldFree) - slot->tts_flags |= TTS_FLAG_SHOULDFREE; - slot->tts_tuple = tuple; - slot->tts_mintuple = NULL; - - /* Mark extracted state invalid */ - slot->tts_nvalid = 0; - - /* Unpin any buffer pinned by the slot. */ - if (BufferIsValid(slot->tts_buffer)) - ReleaseBuffer(slot->tts_buffer); - slot->tts_buffer = InvalidBuffer; + if (!TTS_IS_HEAPTUPLE(slot)) + elog(ERROR, "trying to store a heap tuple into wrong type of slot"); + tts_heap_store_tuple(slot, tuple, shouldFree); return slot; } @@ -397,46 +1386,9 @@ ExecStoreBufferHeapTuple(HeapTuple tuple, Assert(slot->tts_tupleDescriptor != NULL); Assert(BufferIsValid(buffer)); - /* - * Free any old physical tuple belonging to the slot. - */ - if (TTS_SHOULDFREE(slot)) - { - heap_freetuple(slot->tts_tuple); - slot->tts_flags &= ~TTS_FLAG_SHOULDFREE; - } - if (TTS_SHOULDFREEMIN(slot)) - { - heap_free_minimal_tuple(slot->tts_mintuple); - slot->tts_flags &= ~TTS_FLAG_SHOULDFREEMIN; - } - - /* - * Store the new tuple into the specified slot. - */ - slot->tts_flags &= ~TTS_FLAG_EMPTY; - slot->tts_tuple = tuple; - slot->tts_mintuple = NULL; - - /* Mark extracted state invalid */ - slot->tts_nvalid = 0; - - /* - * Keep the disk page containing the given tuple pinned as long as we hold - * a pointer into it. We assume the caller already has such a pin. - * - * This is coded to optimize the case where the slot previously held a - * tuple on the same disk page: in that case releasing and re-acquiring the - * pin is a waste of cycles. This is a common situation during seqscans, - * so it's worth troubling over. - */ - if (slot->tts_buffer != buffer) - { - if (BufferIsValid(slot->tts_buffer)) - ReleaseBuffer(slot->tts_buffer); - slot->tts_buffer = buffer; - IncrBufferRefCount(buffer); - } + if (!TTS_IS_BUFFERTUPLE(slot)) + elog(ERROR, "trying to store an on-disk heap tuple into wrong type of slot"); + tts_buffer_heap_store_tuple(slot, tuple, buffer); return slot; } @@ -461,93 +1413,9 @@ ExecStoreMinimalTuple(MinimalTuple mtup, Assert(slot != NULL); Assert(slot->tts_tupleDescriptor != NULL); - /* - * Free any old physical tuple belonging to the slot. - */ - if (TTS_SHOULDFREE(slot)) - { - heap_freetuple(slot->tts_tuple); - slot->tts_flags &= ~TTS_FLAG_SHOULDFREE; - } - if (TTS_SHOULDFREEMIN(slot)) - { - heap_free_minimal_tuple(slot->tts_mintuple); - slot->tts_flags &= ~TTS_FLAG_SHOULDFREEMIN; - } - - /* - * Drop the pin on the referenced buffer, if there is one. - */ - if (BufferIsValid(slot->tts_buffer)) - ReleaseBuffer(slot->tts_buffer); - - slot->tts_buffer = InvalidBuffer; - - /* - * Store the new tuple into the specified slot. - */ - slot->tts_flags &= ~TTS_FLAG_EMPTY; - if (shouldFree) - slot->tts_flags |= TTS_FLAG_SHOULDFREEMIN; - slot->tts_tuple = &slot->tts_minhdr; - slot->tts_mintuple = mtup; - - slot->tts_minhdr.t_len = mtup->t_len + MINIMAL_TUPLE_OFFSET; - slot->tts_minhdr.t_data = (HeapTupleHeader) ((char *) mtup - MINIMAL_TUPLE_OFFSET); - /* no need to set t_self or t_tableOid since we won't allow access */ - - /* Mark extracted state invalid */ - slot->tts_nvalid = 0; - - return slot; -} - -/* -------------------------------- - * ExecClearTuple - * - * This function is used to clear out a slot in the tuple table. - * - * NB: only the tuple is cleared, not the tuple descriptor (if any). - * -------------------------------- - */ -TupleTableSlot * /* return: slot passed */ -ExecClearTuple(TupleTableSlot *slot) /* slot in which to store tuple */ -{ - /* - * sanity checks - */ - Assert(slot != NULL); - - /* - * Free the old physical tuple if necessary. - */ - if (TTS_SHOULDFREE(slot)) - { - heap_freetuple(slot->tts_tuple); - slot->tts_flags &= ~TTS_FLAG_SHOULDFREE; - } - if (TTS_SHOULDFREEMIN(slot)) - { - heap_free_minimal_tuple(slot->tts_mintuple); - slot->tts_flags &= ~TTS_FLAG_SHOULDFREEMIN; - } - - slot->tts_tuple = NULL; - slot->tts_mintuple = NULL; - - /* - * Drop the pin on the referenced buffer, if there is one. - */ - if (BufferIsValid(slot->tts_buffer)) - ReleaseBuffer(slot->tts_buffer); - - slot->tts_buffer = InvalidBuffer; - - /* - * Mark it empty. - */ - slot->tts_flags |= TTS_FLAG_EMPTY; - slot->tts_nvalid = 0; + if (!TTS_IS_MINIMALTUPLE(slot)) + elog(ERROR, "trying to store a minimal tuple into wrong type of slot"); + tts_minimal_store_tuple(slot, mtup, shouldFree); return slot; } @@ -611,79 +1479,19 @@ ExecStoreAllNullTuple(TupleTableSlot *slot) } /* -------------------------------- - * ExecCopySlotTuple - * Obtain a copy of a slot's regular physical tuple. The copy is - * palloc'd in the current memory context. - * The slot itself is undisturbed. + * ExecFetchSlotTuple + * Fetch the slot's regular physical tuple. * - * This works even if the slot contains a virtual or minimal tuple; - * however the "system columns" of the result will not be meaningful. + * This is a thin wrapper around TupleTableSlotType specific + * get_heap_tuple callback. The callback is expected to return a heap + * tuple as is if it holds one and continue to have ownership of the heap + * tuple. If materialize is true, the function calls TupleTableSlotType + * specific materialize() callback which is expected to materialize the + * tuple within the slot so that the slot "owns" it. If materialize is + * false, the returned tuple is not guaranteed to have storage local to + * the slot and hence should be treated as read-only. * -------------------------------- */ -HeapTuple -ExecCopySlotTuple(TupleTableSlot *slot) -{ - /* - * sanity checks - */ - Assert(slot != NULL); - Assert(!TTS_EMPTY(slot)); - - /* - * If we have a physical tuple (either format) then just copy it. - */ - if (TTS_HAS_PHYSICAL_TUPLE(slot)) - return heap_copytuple(slot->tts_tuple); - if (slot->tts_mintuple) - return heap_tuple_from_minimal_tuple(slot->tts_mintuple); - - /* - * Otherwise we need to build a tuple from the Datum array. - */ - return heap_form_tuple(slot->tts_tupleDescriptor, - slot->tts_values, - slot->tts_isnull); -} - -/* -------------------------------- - * ExecCopySlotMinimalTuple - * Obtain a copy of a slot's minimal physical tuple. The copy is - * palloc'd in the current memory context. - * The slot itself is undisturbed. - * -------------------------------- - */ -MinimalTuple -ExecCopySlotMinimalTuple(TupleTableSlot *slot) -{ - /* - * sanity checks - */ - Assert(slot != NULL); - Assert(!TTS_EMPTY(slot)); - - /* - * If we have a physical tuple then just copy it. Prefer to copy - * tts_mintuple since that's a tad cheaper. - */ - if (slot->tts_mintuple) - return heap_copy_minimal_tuple(slot->tts_mintuple); - if (slot->tts_tuple) - { - if (HeapTupleHeaderGetNatts(slot->tts_tuple->t_data) - < slot->tts_tupleDescriptor->natts) - return minimal_expand_tuple(slot->tts_tuple, - slot->tts_tupleDescriptor); - else - return minimal_tuple_from_heap_tuple(slot->tts_tuple); - } - - /* - * Otherwise we need to build a tuple from the Datum array. - */ - return heap_form_minimal_tuple(slot->tts_tupleDescriptor, - slot->tts_values, - slot->tts_isnull); -} /* * ExecFetchSlotHeapTuple - fetch HeapTuple representing the slot's content @@ -713,89 +1521,67 @@ ExecFetchSlotHeapTuple(TupleTableSlot *slot, bool materialize, bool *shouldFree) Assert(slot != NULL); Assert(!TTS_EMPTY(slot)); - if (shouldFree) - *shouldFree = false; + /* Materialize the tuple so that the slot "owns" it, if requested. */ + if (materialize) + slot->tts_cb->materialize(slot); - /* - * If we have a regular physical tuple then just return it. - */ - if (TTS_HAS_PHYSICAL_TUPLE(slot)) + if (slot->tts_cb->get_heap_tuple == NULL) { - if (HeapTupleHeaderGetNatts(slot->tts_tuple->t_data) < - slot->tts_tupleDescriptor->natts) - { - HeapTuple tuple; - MemoryContext oldContext = MemoryContextSwitchTo(slot->tts_mcxt); - - tuple = heap_expand_tuple(slot->tts_tuple, - slot->tts_tupleDescriptor); - MemoryContextSwitchTo(oldContext); - slot = ExecStoreHeapTuple(tuple, slot, true); - } - return slot->tts_tuple; + if (shouldFree) + *shouldFree = true; + return slot->tts_cb->copy_heap_tuple(slot); + } + else + { + if (shouldFree) + *shouldFree = false; + return slot->tts_cb->get_heap_tuple(slot); } - - /* - * Otherwise materialize the slot... - */ - ExecMaterializeSlot(slot); - - return slot->tts_tuple; } /* -------------------------------- * ExecFetchSlotMinimalTuple * Fetch the slot's minimal physical tuple. * - * If the slot contains a virtual tuple, we convert it to minimal - * physical form. The slot retains ownership of the minimal tuple. - * If it contains a regular tuple we convert to minimal form and store - * that in addition to the regular tuple (not instead of, because - * callers may hold pointers to Datums within the regular tuple). + * If the given tuple table slot can hold a minimal tuple, indicated by a + * non-NULL get_minimal_tuple callback, the function returns the minimal + * tuple returned by that callback. It assumes that the minimal tuple + * returned by the callback is "owned" by the slot i.e. the slot is + * responsible for freeing the memory consumed by the tuple. Hence it sets + * *shouldFree to false, indicating that the caller should not free the + * memory consumed by the minimal tuple. In this case the returned minimal + * tuple should be considered as read-only. * - * As above, the result must be treated as read-only. + * If that callback is not supported, it calls copy_minimal_tuple callback + * which is expected to return a copy of minimal tuple represnting the + * contents of the slot. In this case *shouldFree is set to true, + * indicating the caller that it should free the memory consumed by the + * minimal tuple. In this case the returned minimal tuple may be written + * up. * -------------------------------- */ MinimalTuple -ExecFetchSlotMinimalTuple(TupleTableSlot *slot, bool *shouldFree) +ExecFetchSlotMinimalTuple(TupleTableSlot *slot, + bool *shouldFree) { - MemoryContext oldContext; - /* * sanity checks */ Assert(slot != NULL); Assert(!TTS_EMPTY(slot)); - if (shouldFree) - *shouldFree = false; - - /* - * If we have a minimal physical tuple (local or not) then just return it. - */ - if (slot->tts_mintuple) - return slot->tts_mintuple; - - /* - * Otherwise, copy or build a minimal tuple, and store it into the slot. - * - * We may be called in a context that is shorter-lived than the tuple - * slot, but we have to ensure that the materialized tuple will survive - * anyway. - */ - oldContext = MemoryContextSwitchTo(slot->tts_mcxt); - slot->tts_mintuple = ExecCopySlotMinimalTuple(slot); - slot->tts_flags |= TTS_FLAG_SHOULDFREEMIN; - MemoryContextSwitchTo(oldContext); - - /* - * Note: we may now have a situation where we have a local minimal tuple - * attached to a virtual or non-local physical tuple. There seems no harm - * in that at the moment, but if any materializes, we should change this - * function to force the slot into minimal-tuple-only state. - */ - - return slot->tts_mintuple; + if (slot->tts_cb->get_minimal_tuple) + { + if (shouldFree) + *shouldFree = false; + return slot->tts_cb->get_minimal_tuple(slot); + } + else + { + if (shouldFree) + *shouldFree = true; + return slot->tts_cb->copy_minimal_tuple(slot); + } } /* -------------------------------- @@ -826,103 +1612,58 @@ ExecFetchSlotHeapTupleDatum(TupleTableSlot *slot) return ret; } -/* ExecMaterializeSlot - force a slot into the "materialized" state. - * - * This causes the slot's tuple to be a local copy not dependent on any - * external storage (i.e. pointing into a Buffer, or having allocations in - * another memory context). - * - * A typical use for this operation is to prepare a computed tuple for being - * stored on disk. The original data may or may not be virtual, but in any - * case we need a private copy for heap_insert to scribble on. - */ void -ExecMaterializeSlot(TupleTableSlot *slot) +ExecForceStoreHeapTuple(HeapTuple tuple, + TupleTableSlot *slot) { - MemoryContext oldContext; + if (TTS_IS_HEAPTUPLE(slot)) + { + ExecStoreHeapTuple(tuple, slot, false); + } + else if (TTS_IS_BUFFERTUPLE(slot)) + { + MemoryContext oldContext; + BufferHeapTupleTableSlot *bslot = (BufferHeapTupleTableSlot *) slot; - /* - * sanity checks - */ - Assert(slot != NULL); - Assert(!TTS_EMPTY(slot)); - - /* - * If we have a regular physical tuple, and it's locally palloc'd, we have - * nothing to do. - */ - if (slot->tts_tuple && TTS_SHOULDFREE(slot)) - return; - - /* - * Otherwise, copy or build a physical tuple, and store it into the slot. - * - * We may be called in a context that is shorter-lived than the tuple - * slot, but we have to ensure that the materialized tuple will survive - * anyway. - */ - oldContext = MemoryContextSwitchTo(slot->tts_mcxt); - slot->tts_tuple = ExecCopySlotTuple(slot); - slot->tts_flags |= TTS_FLAG_SHOULDFREE; - MemoryContextSwitchTo(oldContext); - - /* - * Drop the pin on the referenced buffer, if there is one. - */ - if (BufferIsValid(slot->tts_buffer)) - ReleaseBuffer(slot->tts_buffer); - - slot->tts_buffer = InvalidBuffer; - - /* - * Mark extracted state invalid. This is important because the slot is - * not supposed to depend any more on the previous external data; we - * mustn't leave any dangling pass-by-reference datums in tts_values. - * However, we have not actually invalidated any such datums, if there - * happen to be any previously fetched from the slot. (Note in particular - * that we have not pfree'd tts_mintuple, if there is one.) - */ - slot->tts_nvalid = 0; - - /* - * On the same principle of not depending on previous remote storage, - * forget the mintuple if it's not local storage. (If it is local - * storage, we must not pfree it now, since callers might have already - * fetched datum pointers referencing it.) - */ - if (!TTS_SHOULDFREEMIN(slot)) - slot->tts_mintuple = NULL; + ExecClearTuple(slot); + slot->tts_flags |= TTS_FLAG_SHOULDFREE; + slot->tts_flags &= ~TTS_FLAG_EMPTY; + oldContext = MemoryContextSwitchTo(slot->tts_mcxt); + bslot->base.tuple = heap_copytuple(tuple); + MemoryContextSwitchTo(oldContext); + } + else + { + ExecClearTuple(slot); + heap_deform_tuple(tuple, slot->tts_tupleDescriptor, + slot->tts_values, slot->tts_isnull); + ExecStoreVirtualTuple(slot); + } } -/* -------------------------------- - * ExecCopySlot - * Copy the source slot's contents into the destination slot. - * - * The destination acquires a private copy that will not go away - * if the source is cleared. - * - * The caller must ensure the slots have compatible tupdescs. - * -------------------------------- - */ -TupleTableSlot * -ExecCopySlot(TupleTableSlot *dstslot, TupleTableSlot *srcslot) +void +ExecForceStoreMinimalTuple(MinimalTuple mtup, + TupleTableSlot *slot, + bool shouldFree) { - HeapTuple newTuple; - MemoryContext oldContext; + if (TTS_IS_MINIMALTUPLE(slot)) + { + tts_minimal_store_tuple(slot, mtup, shouldFree); + } + else + { + HeapTupleData htup; - /* - * There might be ways to optimize this when the source is virtual, but - * for now just always build a physical copy. Make sure it is in the - * right context. - */ - oldContext = MemoryContextSwitchTo(dstslot->tts_mcxt); - newTuple = ExecCopySlotTuple(srcslot); - MemoryContextSwitchTo(oldContext); + ExecClearTuple(slot); - return ExecStoreHeapTuple(newTuple, dstslot, true); + htup.t_len = mtup->t_len + MINIMAL_TUPLE_OFFSET; + htup.t_data = (HeapTupleHeader) ((char *) mtup - MINIMAL_TUPLE_OFFSET); + heap_deform_tuple(&htup, slot->tts_tupleDescriptor, + slot->tts_values, slot->tts_isnull); + ExecStoreVirtualTuple(slot); + } } - /* ---------------------------------------------------------------- * convenience initialization routines * ---------------------------------------------------------------- @@ -1062,7 +1803,6 @@ void slot_getmissingattrs(TupleTableSlot *slot, int startAttNum, int lastAttNum) { AttrMissing *attrmiss = NULL; - int missattnum; if (slot->tts_tupleDescriptor->constr) attrmiss = slot->tts_tupleDescriptor->constr->missing; @@ -1077,6 +1817,8 @@ slot_getmissingattrs(TupleTableSlot *slot, int startAttNum, int lastAttNum) } else { + int missattnum; + /* if there is a missing values array we must process them one by one */ for (missattnum = startAttNum; missattnum < lastAttNum; @@ -1085,143 +1827,36 @@ slot_getmissingattrs(TupleTableSlot *slot, int startAttNum, int lastAttNum) slot->tts_values[missattnum] = attrmiss[missattnum].am_value; slot->tts_isnull[missattnum] = !attrmiss[missattnum].am_present; } + } } /* - * slot_getattr - * This function fetches an attribute of the slot's current tuple. - * It is functionally equivalent to heap_getattr, but fetches of - * multiple attributes of the same tuple will be optimized better, - * because we avoid O(N^2) behavior from multiple calls of - * nocachegetattr(), even when attcacheoff isn't usable. + * slot_getsomeattrs_int * - * A difference from raw heap_getattr is that attnums beyond the - * slot's tupdesc's last attribute will be considered NULL even - * when the physical tuple is longer than the tupdesc. - */ -Datum -slot_getattr(TupleTableSlot *slot, int attnum, bool *isnull) -{ - HeapTuple tuple = slot->tts_tuple; - TupleDesc tupleDesc = slot->tts_tupleDescriptor; - HeapTupleHeader tup; - - /* - * system attributes are handled by heap_getsysattr - */ - if (attnum <= 0) - { - if (tuple == NULL) /* internal error */ - elog(ERROR, "cannot extract system attribute from virtual tuple"); - if (tuple == &(slot->tts_minhdr)) /* internal error */ - elog(ERROR, "cannot extract system attribute from minimal tuple"); - return heap_getsysattr(tuple, attnum, tupleDesc, isnull); - } - - /* - * fast path if desired attribute already cached - */ - if (attnum <= slot->tts_nvalid) - { - *isnull = slot->tts_isnull[attnum - 1]; - return slot->tts_values[attnum - 1]; - } - - /* - * return NULL if attnum is out of range according to the tupdesc - */ - if (attnum > tupleDesc->natts) - { - *isnull = true; - return (Datum) 0; - } - - /* - * otherwise we had better have a physical tuple (tts_nvalid should equal - * natts in all virtual-tuple cases) - */ - if (tuple == NULL) /* internal error */ - elog(ERROR, "cannot extract attribute from empty tuple slot"); - - /* - * return NULL or missing value if attnum is out of range according to the - * tuple - * - * (We have to check this separately because of various inheritance and - * table-alteration scenarios: the tuple could be either longer or shorter - * than the tupdesc.) - */ - tup = tuple->t_data; - if (attnum > HeapTupleHeaderGetNatts(tup)) - return getmissingattr(slot->tts_tupleDescriptor, attnum, isnull); - - /* - * check if target attribute is null: no point in groveling through tuple - */ - if (HeapTupleHasNulls(tuple) && att_isnull(attnum - 1, tup->t_bits)) - { - *isnull = true; - return (Datum) 0; - } - - /* - * Extract the attribute, along with any preceding attributes. - */ - slot_deform_tuple(slot, attnum); - - /* - * The result is acquired from tts_values array. - */ - *isnull = slot->tts_isnull[attnum - 1]; - return slot->tts_values[attnum - 1]; -} - -/* - * slot_getsomeattrs - * This function forces the entries of the slot's Datum/isnull - * arrays to be valid at least up through the attnum'th entry. */ void -slot_getsomeattrs(TupleTableSlot *slot, int attnum) +slot_getsomeattrs_int(TupleTableSlot *slot, int attnum) { - HeapTuple tuple; - int attno; + /* Check for caller errors */ + Assert(slot->tts_nvalid < attnum); /* slot_getsomeattr checked */ + Assert(attnum > 0); - /* Quick out if we have 'em all already */ - if (slot->tts_nvalid >= attnum) - return; - - /* Check for caller error */ - if (attnum <= 0 || attnum > slot->tts_tupleDescriptor->natts) + if (unlikely(attnum > slot->tts_tupleDescriptor->natts)) elog(ERROR, "invalid attribute number %d", attnum); - /* - * otherwise we had better have a physical tuple (tts_nvalid should equal - * natts in all virtual-tuple cases) - */ - tuple = slot->tts_tuple; - if (tuple == NULL) /* internal error */ - elog(ERROR, "cannot extract attribute from empty tuple slot"); + /* Fetch as many attributes as possible from the underlying tuple. */ + slot->tts_cb->getsomeattrs(slot, attnum); /* - * load up any slots available from physical tuple + * If the underlying tuple doesn't have enough attributes, tuple descriptor + * must have the missing attributes. */ - attno = HeapTupleHeaderGetNatts(tuple->t_data); - attno = Min(attno, attnum); - - slot_deform_tuple(slot, attno); - - attno = slot->tts_nvalid; - - /* - * If tuple doesn't have all the atts indicated by attnum, read the rest - * as NULLs or missing values - */ - if (attno < attnum) - slot_getmissingattrs(slot, attno, attnum); - - slot->tts_nvalid = attnum; + if (unlikely(slot->tts_nvalid < attnum)) + { + slot_getmissingattrs(slot, slot->tts_nvalid, attnum); + slot->tts_nvalid = attnum; + } } /* ---------------------------------------------------------------- diff --git a/src/backend/executor/nodeAgg.c b/src/backend/executor/nodeAgg.c index 20d6d8e9cbb..2a1a6123d3b 100644 --- a/src/backend/executor/nodeAgg.c +++ b/src/backend/executor/nodeAgg.c @@ -1800,9 +1800,8 @@ agg_retrieve_direct(AggState *aggstate) * reserved for it. The tuple will be deleted when it is * cleared from the slot. */ - ExecStoreHeapTuple(aggstate->grp_firstTuple, - firstSlot, - true); + ExecForceStoreHeapTuple(aggstate->grp_firstTuple, + firstSlot); aggstate->grp_firstTuple = NULL; /* don't keep two pointers */ /* set up for first advance_aggregates call */ diff --git a/src/backend/executor/nodeHashjoin.c b/src/backend/executor/nodeHashjoin.c index d9afd0bdded..8fb477f45c3 100644 --- a/src/backend/executor/nodeHashjoin.c +++ b/src/backend/executor/nodeHashjoin.c @@ -929,9 +929,10 @@ ExecParallelHashJoinOuterGetTuple(PlanState *outerNode, hashvalue); if (tuple != NULL) { - slot = ExecStoreMinimalTuple(tuple, - hjstate->hj_OuterTupleSlot, - false); + ExecForceStoreMinimalTuple(tuple, + hjstate->hj_OuterTupleSlot, + false); + slot = hjstate->hj_OuterTupleSlot; return slot; } else @@ -1158,9 +1159,10 @@ ExecParallelHashJoinNewBatch(HashJoinState *hjstate) while ((tuple = sts_parallel_scan_next(inner_tuples, &hashvalue))) { - slot = ExecStoreMinimalTuple(tuple, - hjstate->hj_HashTupleSlot, - false); + ExecForceStoreMinimalTuple(tuple, + hjstate->hj_HashTupleSlot, + false); + slot = hjstate->hj_HashTupleSlot; ExecParallelHashTableInsertCurrentBatch(hashtable, slot, hashvalue); } @@ -1294,7 +1296,8 @@ ExecHashJoinGetSavedTuple(HashJoinState *hjstate, ereport(ERROR, (errcode_for_file_access(), errmsg("could not read from hash-join temporary file: %m"))); - return ExecStoreMinimalTuple(tuple, tupleSlot, true); + ExecForceStoreMinimalTuple(tuple, tupleSlot, true); + return tupleSlot; } diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c index 9cc499d6533..d29162bab79 100644 --- a/src/backend/executor/nodeModifyTable.c +++ b/src/backend/executor/nodeModifyTable.c @@ -2059,6 +2059,15 @@ ExecModifyTable(PlanState *pstate) break; } + /* + * Ensure input tuple is the right format for the target relation. + */ + if (node->mt_scans[node->mt_whichplan]->tts_cb != planSlot->tts_cb) + { + ExecCopySlot(node->mt_scans[node->mt_whichplan], planSlot); + planSlot = node->mt_scans[node->mt_whichplan]; + } + /* * If resultRelInfo->ri_usesFdwDirectModify is true, all we need to do * here is compute the RETURNING expressions. @@ -2242,6 +2251,7 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags) mtstate->mt_plans = (PlanState **) palloc0(sizeof(PlanState *) * nplans); mtstate->resultRelInfo = estate->es_result_relations + node->resultRelIndex; + mtstate->mt_scans = (TupleTableSlot **) palloc0(sizeof(TupleTableSlot *) * nplans); /* If modifying a partitioned table, initialize the root table info */ if (node->rootResultRelIndex >= 0) @@ -2308,6 +2318,9 @@ ExecInitModifyTable(ModifyTable *node, EState *estate, int eflags) /* Now init the plan for this result rel */ estate->es_result_relation_info = resultRelInfo; mtstate->mt_plans[i] = ExecInitNode(subplan, estate, eflags); + mtstate->mt_scans[i] = + ExecInitExtraTupleSlot(mtstate->ps.state, ExecGetResultType(mtstate->mt_plans[i]), + &TTSOpsHeapTuple); /* Also let FDWs init themselves for foreign-table result rels */ if (!resultRelInfo->ri_usesFdwDirectModify && diff --git a/src/backend/executor/nodeSubplan.c b/src/backend/executor/nodeSubplan.c index 62811ed0302..b42e60576e3 100644 --- a/src/backend/executor/nodeSubplan.c +++ b/src/backend/executor/nodeSubplan.c @@ -988,6 +988,7 @@ ExecInitSubPlan(SubPlan *subplan, PlanState *parent) * across-type comparison). */ sstate->cur_eq_comp = ExecBuildGroupingEqual(tupDescLeft, tupDescRight, + &TTSOpsVirtual, &TTSOpsMinimalTuple, ncols, sstate->keyColIdx, sstate->tab_eq_funcoids, diff --git a/src/backend/jit/llvm/llvmjit.c b/src/backend/jit/llvm/llvmjit.c index 168072afd2f..162d1be89bd 100644 --- a/src/backend/jit/llvm/llvmjit.c +++ b/src/backend/jit/llvm/llvmjit.c @@ -65,6 +65,8 @@ LLVMTypeRef StructFormPgAttribute; LLVMTypeRef StructTupleConstr; LLVMTypeRef StructtupleDesc; LLVMTypeRef StructTupleTableSlot; +LLVMTypeRef StructHeapTupleTableSlot; +LLVMTypeRef StructMinimalTupleTableSlot; LLVMTypeRef StructMemoryContextData; LLVMTypeRef StructPGFinfoRecord; LLVMTypeRef StructFmgrInfo; @@ -79,7 +81,7 @@ LLVMTypeRef StructAggStatePerTransData; LLVMValueRef AttributeTemplate; LLVMValueRef FuncStrlen; LLVMValueRef FuncVarsizeAny; -LLVMValueRef FuncSlotGetsomeattrs; +LLVMValueRef FuncSlotGetsomeattrsInt; LLVMValueRef FuncSlotGetmissingattrs; LLVMValueRef FuncMakeExpandedObjectReadOnlyInternal; LLVMValueRef FuncExecEvalArrayRefSubscript; @@ -811,6 +813,8 @@ llvm_create_types(void) StructFunctionCallInfoData = load_type(mod, "StructFunctionCallInfoData"); StructMemoryContextData = load_type(mod, "StructMemoryContextData"); StructTupleTableSlot = load_type(mod, "StructTupleTableSlot"); + StructHeapTupleTableSlot = load_type(mod, "StructHeapTupleTableSlot"); + StructMinimalTupleTableSlot = load_type(mod, "StructMinimalTupleTableSlot"); StructHeapTupleData = load_type(mod, "StructHeapTupleData"); StructtupleDesc = load_type(mod, "StructtupleDesc"); StructAggState = load_type(mod, "StructAggState"); @@ -820,7 +824,7 @@ llvm_create_types(void) AttributeTemplate = LLVMGetNamedFunction(mod, "AttributeTemplate"); FuncStrlen = LLVMGetNamedFunction(mod, "strlen"); FuncVarsizeAny = LLVMGetNamedFunction(mod, "varsize_any"); - FuncSlotGetsomeattrs = LLVMGetNamedFunction(mod, "slot_getsomeattrs"); + FuncSlotGetsomeattrsInt = LLVMGetNamedFunction(mod, "slot_getsomeattrs_int"); FuncSlotGetmissingattrs = LLVMGetNamedFunction(mod, "slot_getmissingattrs"); FuncMakeExpandedObjectReadOnlyInternal = LLVMGetNamedFunction(mod, "MakeExpandedObjectReadOnlyInternal"); FuncExecEvalArrayRefSubscript = LLVMGetNamedFunction(mod, "ExecEvalArrayRefSubscript"); diff --git a/src/backend/jit/llvm/llvmjit_deform.c b/src/backend/jit/llvm/llvmjit_deform.c index 59e38d2d955..e430cd9eda4 100644 --- a/src/backend/jit/llvm/llvmjit_deform.c +++ b/src/backend/jit/llvm/llvmjit_deform.c @@ -31,7 +31,7 @@ * Create a function that deforms a tuple of type desc up to natts columns. */ LLVMValueRef -slot_compile_deform(LLVMJitContext *context, TupleDesc desc, int natts) +slot_compile_deform(LLVMJitContext *context, TupleDesc desc, const TupleTableSlotOps *ops, int natts) { char *funcname; @@ -88,6 +88,16 @@ slot_compile_deform(LLVMJitContext *context, TupleDesc desc, int natts) int attnum; + if (ops != &TTSOpsHeapTuple && ops != &TTSOpsBufferTuple && + ops != &TTSOpsMinimalTuple) + { + /* + * Decline to JIT for slot types we don't know to handle, or don't + * want to handle (say virtual slots). + */ + return NULL; + } + mod = llvm_mutable_module(context); funcname = llvm_expand_funcname(context, "deform"); @@ -166,14 +176,44 @@ slot_compile_deform(LLVMJitContext *context, TupleDesc desc, int natts) v_tts_nulls = l_load_struct_gep(b, v_slot, FIELDNO_TUPLETABLESLOT_ISNULL, "tts_ISNULL"); - - v_slotoffp = LLVMBuildStructGEP(b, v_slot, FIELDNO_TUPLETABLESLOT_OFF, ""); v_flagsp = LLVMBuildStructGEP(b, v_slot, FIELDNO_TUPLETABLESLOT_FLAGS, ""); v_nvalidp = LLVMBuildStructGEP(b, v_slot, FIELDNO_TUPLETABLESLOT_NVALID, ""); - v_tupleheaderp = - l_load_struct_gep(b, v_slot, FIELDNO_TUPLETABLESLOT_TUPLE, - "tupleheader"); + if (ops == &TTSOpsHeapTuple || ops == &TTSOpsBufferTuple) + { + LLVMValueRef v_heapslot; + + v_heapslot = + LLVMBuildBitCast(b, + v_slot, + l_ptr(StructHeapTupleTableSlot), + "heapslot"); + v_slotoffp = LLVMBuildStructGEP(b, v_heapslot, FIELDNO_HEAPTUPLETABLESLOT_OFF, ""); + v_tupleheaderp = + l_load_struct_gep(b, v_heapslot, FIELDNO_HEAPTUPLETABLESLOT_TUPLE, + "tupleheader"); + + } + else if (ops == &TTSOpsMinimalTuple) + { + LLVMValueRef v_minimalslot; + + v_minimalslot = + LLVMBuildBitCast(b, + v_slot, + l_ptr(StructMinimalTupleTableSlot), + "minimalslotslot"); + v_slotoffp = LLVMBuildStructGEP(b, v_minimalslot, FIELDNO_MINIMALTUPLETABLESLOT_OFF, ""); + v_tupleheaderp = + l_load_struct_gep(b, v_minimalslot, FIELDNO_MINIMALTUPLETABLESLOT_TUPLE, + "tupleheader"); + } + else + { + /* should've returned at the start of the function */ + pg_unreachable(); + } + v_tuplep = l_load_struct_gep(b, v_tupleheaderp, FIELDNO_HEAPTUPLEDATA_DATA, "tuple"); diff --git a/src/backend/jit/llvm/llvmjit_expr.c b/src/backend/jit/llvm/llvmjit_expr.c index 63b921517c6..f3414ef9caf 100644 --- a/src/backend/jit/llvm/llvmjit_expr.c +++ b/src/backend/jit/llvm/llvmjit_expr.c @@ -324,6 +324,7 @@ llvm_compile_expr(ExprState *state) { l_jit_deform = slot_compile_deform(context, desc, + tts_cb, op->d.fetch.last_var); } @@ -344,7 +345,7 @@ llvm_compile_expr(ExprState *state) params[1] = l_int32_const(op->d.fetch.last_var); LLVMBuildCall(b, - llvm_get_decl(mod, FuncSlotGetsomeattrs), + llvm_get_decl(mod, FuncSlotGetsomeattrsInt), params, lengthof(params), ""); } diff --git a/src/backend/jit/llvm/llvmjit_types.c b/src/backend/jit/llvm/llvmjit_types.c index 855a6977ee5..2df1882b750 100644 --- a/src/backend/jit/llvm/llvmjit_types.c +++ b/src/backend/jit/llvm/llvmjit_types.c @@ -59,6 +59,8 @@ FunctionCallInfoData StructFunctionCallInfoData; HeapTupleData StructHeapTupleData; MemoryContextData StructMemoryContextData; TupleTableSlot StructTupleTableSlot; +HeapTupleTableSlot StructHeapTupleTableSlot; +MinimalTupleTableSlot StructMinimalTupleTableSlot; struct tupleDesc StructtupleDesc; @@ -97,7 +99,7 @@ void *referenced_functions[] = { strlen, varsize_any, - slot_getsomeattrs, + slot_getsomeattrs_int, slot_getmissingattrs, MakeExpandedObjectReadOnlyInternal, ExecEvalArrayRefSubscript, diff --git a/src/include/access/htup_details.h b/src/include/access/htup_details.h index 97d240fdbb0..1867a70f6f3 100644 --- a/src/include/access/htup_details.h +++ b/src/include/access/htup_details.h @@ -835,7 +835,5 @@ extern MinimalTuple minimal_tuple_from_heap_tuple(HeapTuple htup); extern size_t varsize_any(void *p); extern HeapTuple heap_expand_tuple(HeapTuple sourceTuple, TupleDesc tupleDesc); extern MinimalTuple minimal_expand_tuple(HeapTuple sourceTuple, TupleDesc tupleDesc); -struct TupleTableSlot; -extern void slot_deform_tuple(struct TupleTableSlot *slot, int natts); #endif /* HTUP_DETAILS_H */ diff --git a/src/include/executor/executor.h b/src/include/executor/executor.h index 4f156f4a5e7..8b48a23d13e 100644 --- a/src/include/executor/executor.h +++ b/src/include/executor/executor.h @@ -249,6 +249,7 @@ extern List *ExecInitExprList(List *nodes, PlanState *parent); extern ExprState *ExecBuildAggTrans(AggState *aggstate, struct AggStatePerPhaseData *phase, bool doSort, bool doHash); extern ExprState *ExecBuildGroupingEqual(TupleDesc ldesc, TupleDesc rdesc, + const TupleTableSlotOps *lops, const TupleTableSlotOps *rops, int numCols, AttrNumber *keyColIdx, Oid *eqfunctions, diff --git a/src/include/executor/tuptable.h b/src/include/executor/tuptable.h index a6f6084f0a9..f78853488a9 100644 --- a/src/include/executor/tuptable.h +++ b/src/include/executor/tuptable.h @@ -65,7 +65,7 @@ * ie, only as needed. This serves to avoid repeated extraction of data * from the physical tuple. * - * A TupleTableSlot can also be "empty", indicated by flag TTS_EMPTY set in + * A TupleTableSlot can also be "empty", indicated by flag TTS_FLAG_EMPTY set in * tts_flags, holding no valid data. This is the only valid state for a * freshly-created slot that has not yet had a tuple descriptor assigned to it. * In this state, TTS_SHOULDFREE should not be set in tts_flag, tts_tuple must @@ -116,25 +116,22 @@ #define TTS_FLAG_EMPTY (1 << 1) #define TTS_EMPTY(slot) (((slot)->tts_flags & TTS_FLAG_EMPTY) != 0) -/* should pfree tts_tuple? */ +/* should pfree tuple "owned" by the slot? */ #define TTS_FLAG_SHOULDFREE (1 << 2) #define TTS_SHOULDFREE(slot) (((slot)->tts_flags & TTS_FLAG_SHOULDFREE) != 0) -/* should pfree tts_mintuple? */ -#define TTS_FLAG_SHOULDFREEMIN (1 << 3) -#define TTS_SHOULDFREEMIN(slot) (((slot)->tts_flags & TTS_FLAG_SHOULDFREEMIN) != 0) - /* saved state for slot_deform_tuple */ -#define TTS_FLAG_SLOW (1 << 4) +#define TTS_FLAG_SLOW (1 << 3) #define TTS_SLOW(slot) (((slot)->tts_flags & TTS_FLAG_SLOW) != 0) /* fixed tuple descriptor */ -#define TTS_FLAG_FIXED (1 << 5) +#define TTS_FLAG_FIXED (1 << 4) #define TTS_FIXED(slot) (((slot)->tts_flags & TTS_FLAG_FIXED) != 0) struct TupleTableSlotOps; typedef struct TupleTableSlotOps TupleTableSlotOps; +/* virtual or base type */ typedef struct TupleTableSlot { NodeTag type; @@ -142,26 +139,98 @@ typedef struct TupleTableSlot uint16 tts_flags; /* Boolean states */ #define FIELDNO_TUPLETABLESLOT_NVALID 2 AttrNumber tts_nvalid; /* # of valid values in tts_values */ -#define FIELDNO_TUPLETABLESLOT_TUPLE 3 - HeapTuple tts_tuple; /* physical tuple, or NULL if virtual */ const TupleTableSlotOps *const tts_cb; /* implementation of slot */ -#define FIELDNO_TUPLETABLESLOT_TUPLEDESCRIPTOR 5 +#define FIELDNO_TUPLETABLESLOT_TUPLEDESCRIPTOR 4 TupleDesc tts_tupleDescriptor; /* slot's tuple descriptor */ - MemoryContext tts_mcxt; /* slot itself is in this context */ - Buffer tts_buffer; /* tuple's buffer, or InvalidBuffer */ -#define FIELDNO_TUPLETABLESLOT_OFF 8 - uint32 tts_off; /* saved state for slot_deform_tuple */ -#define FIELDNO_TUPLETABLESLOT_VALUES 9 +#define FIELDNO_TUPLETABLESLOT_VALUES 5 Datum *tts_values; /* current per-attribute values */ -#define FIELDNO_TUPLETABLESLOT_ISNULL 10 +#define FIELDNO_TUPLETABLESLOT_ISNULL 6 bool *tts_isnull; /* current per-attribute isnull flags */ - MinimalTuple tts_mintuple; /* minimal tuple, or NULL if none */ - HeapTupleData tts_minhdr; /* workspace for minimal-tuple-only case */ + MemoryContext tts_mcxt; /* slot itself is in this context */ } TupleTableSlot; /* routines for a TupleTableSlot implementation */ struct TupleTableSlotOps { + /* Minimum size of the slot */ + size_t base_slot_size; + + /* Initialization. */ + void (*init)(TupleTableSlot *slot); + + /* Destruction. */ + void (*release)(TupleTableSlot *slot); + + /* + * Clear the contents of the slot. Only the contents are expected to be + * cleared and not the tuple descriptor. Typically an implementation of + * this callback should free the memory allocated for the tuple contained + * in the slot. + */ + void (*clear)(TupleTableSlot *slot); + + /* + * Fill up first natts entries of tts_values and tts_isnull arrays with + * values from the tuple contained in the slot. The function may be called + * with natts more than the number of attributes available in the tuple, + * in which case it should set tts_nvalid to the number of returned + * columns. + */ + void (*getsomeattrs)(TupleTableSlot *slot, int natts); + + /* + * Returns value of the given system attribute as a datum and sets isnull + * to false, if it's not NULL. Throws an error if the slot type does not + * support system attributes. + */ + Datum (*getsysattr)(TupleTableSlot *slot, int attnum, bool *isnull); + + /* + * Make the contents of the slot solely depend on the slot, and not on + * underlying resources (like another memory context, buffers, etc). + */ + void (*materialize)(TupleTableSlot *slot); + + /* + * Copy the contents of the source slot into the destination slot's own + * context. Invoked using callback of the destination slot. + */ + void (*copyslot) (TupleTableSlot *dstslot, TupleTableSlot *srcslot); + + /* + * Return a heap tuple "owned" by the slot. It is slot's responsibility to + * free the memory consumed by the heap tuple. If the slot can not "own" a + * heap tuple, it should not implement this callback and should set it as + * NULL. + */ + HeapTuple (*get_heap_tuple)(TupleTableSlot *slot); + + /* + * Return a minimal tuple "owned" by the slot. It is slot's responsibility + * to free the memory consumed by the minimal tuple. If the slot can not + * "own" a minimal tuple, it should not implement this callback and should + * set it as NULL. + */ + MinimalTuple (*get_minimal_tuple)(TupleTableSlot *slot); + + /* + * Return a copy of heap tuple representing the contents of the slot. The + * returned heap tuple should be writable. The copy should be palloc'd in + * the current memory context. The slot itself is expected to remain + * undisturbed. It is *not* expected to have meaningful "system columns" + * in the copy. The copy is not be "owned" by the slot i.e. the caller has + * to take responsibilty to free memory consumed by the slot. + */ + HeapTuple (*copy_heap_tuple)(TupleTableSlot *slot); + + /* + * Return a copy of minimal tuple representing the contents of the slot. + * The returned minimal tuple should be writable. The copy should be + * palloc'd in the current memory context. The slot itself is expected to + * remain undisturbed. The copy is not be "owned" by the slot i.e. the + * caller has to take responsibilty to free memory consumed by the slot. + */ + MinimalTuple (*copy_minimal_tuple)(TupleTableSlot *slot); }; /* @@ -173,8 +242,44 @@ extern PGDLLIMPORT const TupleTableSlotOps TTSOpsHeapTuple; extern PGDLLIMPORT const TupleTableSlotOps TTSOpsMinimalTuple; extern PGDLLIMPORT const TupleTableSlotOps TTSOpsBufferTuple; -#define TTS_HAS_PHYSICAL_TUPLE(slot) \ - ((slot)->tts_tuple != NULL && (slot)->tts_tuple != &((slot)->tts_minhdr)) +#define TTS_IS_VIRTUAL(slot) ((slot)->tts_cb == &TTSOpsVirtual) +#define TTS_IS_HEAPTUPLE(slot) ((slot)->tts_cb == &TTSOpsHeapTuple) +#define TTS_IS_MINIMALTUPLE(slot) ((slot)->tts_cb == &TTSOpsMinimalTuple) +#define TTS_IS_BUFFERTUPLE(slot) ((slot)->tts_cb == &TTSOpsBufferTuple) + + +typedef struct VirtualTupleTableSlot +{ + TupleTableSlot base; + char *data; /* data for materialized slots */ +} VirtualTupleTableSlot; + +typedef struct HeapTupleTableSlot +{ + TupleTableSlot base; +#define FIELDNO_HEAPTUPLETABLESLOT_TUPLE 1 + HeapTuple tuple; /* physical tuple */ +#define FIELDNO_HEAPTUPLETABLESLOT_OFF 2 + uint32 off; /* saved state for slot_deform_tuple */ +} HeapTupleTableSlot; + +/* heap tuple residing in a buffer */ +typedef struct BufferHeapTupleTableSlot +{ + HeapTupleTableSlot base; + Buffer buffer; /* tuple's buffer, or InvalidBuffer */ +} BufferHeapTupleTableSlot; + +typedef struct MinimalTupleTableSlot +{ + TupleTableSlot base; +#define FIELDNO_MINIMALTUPLETABLESLOT_TUPLE 1 + HeapTuple tuple; /* tuple wrapper */ + MinimalTuple mintuple; /* minimal tuple, or NULL if none */ + HeapTupleData minhdr; /* workspace for minimal-tuple-only case */ +#define FIELDNO_MINIMALTUPLETABLESLOT_OFF 4 + uint32 off; /* saved state for slot_deform_tuple */ +} MinimalTupleTableSlot; /* * TupIsNull -- is a TupleTableSlot empty? @@ -195,39 +300,38 @@ extern void ExecSetSlotDescriptor(TupleTableSlot *slot, TupleDesc tupdesc); extern TupleTableSlot *ExecStoreHeapTuple(HeapTuple tuple, TupleTableSlot *slot, bool shouldFree); +extern void ExecForceStoreHeapTuple(HeapTuple tuple, TupleTableSlot *slot); extern TupleTableSlot *ExecStoreBufferHeapTuple(HeapTuple tuple, TupleTableSlot *slot, Buffer buffer); extern TupleTableSlot *ExecStoreMinimalTuple(MinimalTuple mtup, TupleTableSlot *slot, bool shouldFree); -extern TupleTableSlot *ExecClearTuple(TupleTableSlot *slot); +extern void ExecForceStoreMinimalTuple(MinimalTuple mtup, TupleTableSlot *slot, + bool shouldFree); extern TupleTableSlot *ExecStoreVirtualTuple(TupleTableSlot *slot); extern TupleTableSlot *ExecStoreAllNullTuple(TupleTableSlot *slot); -extern HeapTuple ExecCopySlotTuple(TupleTableSlot *slot); -extern MinimalTuple ExecCopySlotMinimalTuple(TupleTableSlot *slot); -extern HeapTuple ExecFetchSlotHeapTuple(TupleTableSlot *slot, bool materialize, bool *shoulFree); +extern HeapTuple ExecFetchSlotHeapTuple(TupleTableSlot *slot, bool materialize, bool *shouldFree); extern MinimalTuple ExecFetchSlotMinimalTuple(TupleTableSlot *slot, bool *shouldFree); extern Datum ExecFetchSlotHeapTupleDatum(TupleTableSlot *slot); -extern void ExecMaterializeSlot(TupleTableSlot *slot); -extern TupleTableSlot *ExecCopySlot(TupleTableSlot *dstslot, - TupleTableSlot *srcslot); extern void slot_getmissingattrs(TupleTableSlot *slot, int startAttNum, int lastAttNum); -extern Datum slot_getattr(TupleTableSlot *slot, int attnum, - bool *isnull); -extern void slot_getsomeattrs(TupleTableSlot *slot, int attnum); - -/* in access/common/heaptuple.c */ -extern bool slot_attisnull(TupleTableSlot *slot, int attnum); -extern bool slot_getsysattr(TupleTableSlot *slot, int attnum, - Datum *value, bool *isnull); -extern Datum getmissingattr(TupleDesc tupleDesc, - int attnum, bool *isnull); +extern void slot_getsomeattrs_int(TupleTableSlot *slot, int attnum); #ifndef FRONTEND +/* + * This function forces the entries of the slot's Datum/isnull arrays to be + * valid at least up through the attnum'th entry. + */ +static inline void +slot_getsomeattrs(TupleTableSlot *slot, int attnum) +{ + if (slot->tts_nvalid < attnum) + slot_getsomeattrs_int(slot, attnum); +} + /* * slot_getallattrs * This function forces all the entries of the slot's Datum/isnull @@ -240,6 +344,129 @@ slot_getallattrs(TupleTableSlot *slot) slot_getsomeattrs(slot, slot->tts_tupleDescriptor->natts); } -#endif + +/* + * slot_attisnull + * + * Detect whether an attribute of the slot is null, without actually fetching + * it. + */ +static inline bool +slot_attisnull(TupleTableSlot *slot, int attnum) +{ + AssertArg(attnum > 0); + + if (attnum > slot->tts_nvalid) + slot_getsomeattrs(slot, attnum); + + return slot->tts_isnull[attnum - 1]; +} + +static inline Datum +slot_getattr(TupleTableSlot *slot, int attnum, + bool *isnull) +{ + AssertArg(attnum > 0); + + if (attnum > slot->tts_nvalid) + slot_getsomeattrs(slot, attnum); + + *isnull = slot->tts_isnull[attnum - 1]; + + return slot->tts_values[attnum - 1]; +} + +/* + * slot_getsysattr + * This function fetches a system attribute of the slot's current tuple. + * If the slot type does not contain system attributes, this will throw + * an error. Hence before calling this function, callers should make sure + * that the slot type is the one that supports system attributes, namely, + * heap tuple slot and buffer tuple slot. + */ +static inline Datum +slot_getsysattr(TupleTableSlot *slot, int attnum, bool *isnull) +{ + AssertArg(attnum < 0); /* caller error */ + + /* Fetch the system attribute from the underlying tuple. */ + return slot->tts_cb->getsysattr(slot, attnum, isnull); +} + +/* + * ExecClearTuple + * + * A thin wrapper around calling TupleTableSlotType specific clear() method. + */ +static inline TupleTableSlot * +ExecClearTuple(TupleTableSlot *slot) +{ + slot->tts_cb->clear(slot); + + return slot; +} + +/* ExecMaterializeSlot - force a slot into the "materialized" state. + * + * This causes the slot's tuple to be a local copy not dependent on any + * external storage (i.e. pointing into a Buffer, or having allocations in + * another memory context). + * + * A typical use for this operation is to prepare a computed tuple for being + * stored on disk. The original data may or may not be virtual, but in any + * case we need a private copy for heap_insert to scribble on. + */ +static inline void +ExecMaterializeSlot(TupleTableSlot *slot) +{ + slot->tts_cb->materialize(slot); +} + +/* + * ExecCopySlotTuple + * + * A thin wrapper calling TupleTableSlotType specific copy_heap_tuple() + * method. + */ +static inline HeapTuple +ExecCopySlotTuple(TupleTableSlot *slot) +{ + /* + * sanity checks + */ + Assert(slot != NULL); + Assert(!TTS_EMPTY(slot)); + + return slot->tts_cb->copy_heap_tuple(slot); +} + +/* + * ExecCopySlotMinimalTuple + * + * A thin wrapper around calling TupleTableSlotType specific + * copy_minimal_tuple() method. + */ +static inline MinimalTuple +ExecCopySlotMinimalTuple(TupleTableSlot *slot) +{ + return slot->tts_cb->copy_minimal_tuple(slot); +} + +/* + * ExecCopySlot + * + * A thin wrapper calling TupleTableSlotType specific copyslot callback. + * + * The caller must ensure the slots have compatible tupdescs. + */ +static inline TupleTableSlot * +ExecCopySlot(TupleTableSlot *dstslot, TupleTableSlot *srcslot) +{ + dstslot->tts_cb->copyslot(dstslot, srcslot); + + return dstslot; +} + +#endif /* FRONTEND */ #endif /* TUPTABLE_H */ diff --git a/src/include/jit/llvmjit.h b/src/include/jit/llvmjit.h index f3ea2492835..b4b0a16d1ae 100644 --- a/src/include/jit/llvmjit.h +++ b/src/include/jit/llvmjit.h @@ -65,6 +65,8 @@ extern LLVMTypeRef TypeStorageBool; extern LLVMTypeRef StructtupleDesc; extern LLVMTypeRef StructHeapTupleData; extern LLVMTypeRef StructTupleTableSlot; +extern LLVMTypeRef StructHeapTupleTableSlot; +extern LLVMTypeRef StructMinimalTupleTableSlot; extern LLVMTypeRef StructMemoryContextData; extern LLVMTypeRef StructFunctionCallInfoData; extern LLVMTypeRef StructExprContext; @@ -77,7 +79,7 @@ extern LLVMTypeRef StructAggStatePerGroupData; extern LLVMValueRef AttributeTemplate; extern LLVMValueRef FuncStrlen; extern LLVMValueRef FuncVarsizeAny; -extern LLVMValueRef FuncSlotGetsomeattrs; +extern LLVMValueRef FuncSlotGetsomeattrsInt; extern LLVMValueRef FuncSlotGetmissingattrs; extern LLVMValueRef FuncMakeExpandedObjectReadOnlyInternal; extern LLVMValueRef FuncExecEvalArrayRefSubscript; @@ -111,7 +113,8 @@ extern void llvm_inline(LLVMModuleRef mod); **************************************************************************** */ extern bool llvm_compile_expr(struct ExprState *state); -extern LLVMValueRef slot_compile_deform(struct LLVMJitContext *context, TupleDesc desc, int natts); +struct TupleTableSlotOps; +extern LLVMValueRef slot_compile_deform(struct LLVMJitContext *context, TupleDesc desc, const struct TupleTableSlotOps *ops, int natts); /* **************************************************************************** diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h index 59fb3c15833..149916093a2 100644 --- a/src/include/nodes/execnodes.h +++ b/src/include/nodes/execnodes.h @@ -1100,6 +1100,7 @@ typedef struct ModifyTableState PlanState **mt_plans; /* subplans (one per target rel) */ int mt_nplans; /* number of plans in the array */ int mt_whichplan; /* which one is being executed (0..n-1) */ + TupleTableSlot** mt_scans; /* input tuple for underlying plan */ ResultRelInfo *resultRelInfo; /* per-subplan target relations */ ResultRelInfo *rootResultRelInfo; /* root target relation (partitioned * table root) */ -- 2.18.0.rc2.dirty --ll5lqoiis2grgqzk Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v14-0007-Rationalize-expression-context-reset-in-ExecModi.patch" ^ permalink raw reply [nested|flat] 114+ messages in thread
* [PATCH 4/6] Add CONCURRENTLY option to REPACK command. @ 2026-01-12 16:30 Antonin Houska <[email protected]> 0 siblings, 0 replies; 114+ messages in thread From: Antonin Houska @ 2026-01-12 16:30 UTC (permalink / raw) The REPACK command copies the relation data into a new file, creates new indexes and eventually swaps the files. To make sure that the old file does not change during the copying, the relation is locked in an exclusive mode, which prevents applications from both reading and writing. (To keep the data consistent, we'd only need to prevent the applications from writing, but even reading needs to be blocked before we can swap the files - otherwise some applications could continue using the old file. Since we should not request a stronger lock without releasing the weaker one first, we acquire the exclusive lock in the beginning and keep it till the end of the processing.) This patch introduces an alternative workflow, which only requires the exclusive lock when the relation (and index) files are being swapped. (Supposedly, the swapping should be pretty fast.) On the other hand, when we copy the data to the new file, we allow applications to read from the relation and even to write to it. First, we scan the relation using a "historic snapshot", and insert all the tuples satisfying this snapshot into the new file. Second, logical decoding is used to capture the data changes done by applications during the copying (i.e. changes that do not satisfy the historic snapshot mentioned above), and those are applied to the new file before we acquire the exclusive lock that we need to swap the files. (Of course, more data changes can take place while we are waiting for the lock - these will be applied to the new file after we have acquired the lock, before we swap the files.) Since the logical decoding system, during its startup, waits until all the transactions which already have XID assigned have finished, there is a risk of deadlock if a transaction that already changed anything in the database tries to acquire a conflicting lock on the table REPACK CONCURRENTLY is working on. As an example, consider transaction running CREATE INDEX command on the table that is being REPACKed CONCURRENTLY. On the other hand, DML commands (INSERT, UPDATE, DELETE) are not a problem as their lock does not conflict with REPACK CONCURRENTLY. The current approach is that we accept the risk. If we tried to avoid it, it'd be necessary to unlock the table before the logical decoding is setup and lock it again afterwards. Such temporary unlocking would imply re-checking if the table still meets all the requirements for REPACK CONCURRENTLY. Like the existing implementation of REPACK, the variant with the CONCURRENTLY option also requires an extra space for the new relation and index files (which coexist with the old files for some time). In addition, the CONCURRENTLY option might introduce a lag in releasing WAL segments for archiving / recycling. This is due to the decoding of the data changes done by applications concurrently. When copying the table contents into the new file, we check the lag periodically. If it exceeds the size of a WAL segment, we decode all the available WAL before resuming the copying. (Of course, the changes are not applied until the whole table contents is copied.) A background worker might be a better approach for the decoding - let's consider implementing it in the future. The WAL records produced by running DML commands on the new relation do not contain enough information to be processed by the logical decoding system. All we need from the new relation is the file (relfilenode), while the actual relation is eventually dropped. Thus there is no point in replaying the DMLs anywhere. --- doc/src/sgml/monitoring.sgml | 37 +- doc/src/sgml/mvcc.sgml | 12 +- doc/src/sgml/ref/repack.sgml | 129 +- src/Makefile | 1 + src/backend/access/heap/heapam.c | 34 +- src/backend/access/heap/heapam_handler.c | 227 ++- src/backend/access/heap/rewriteheap.c | 6 +- src/backend/catalog/system_views.sql | 19 +- src/backend/commands/cluster.c | 1633 +++++++++++++++-- src/backend/commands/matview.c | 2 +- src/backend/commands/tablecmds.c | 1 + src/backend/commands/vacuum.c | 12 +- src/backend/meson.build | 1 + src/backend/replication/logical/decode.c | 37 +- src/backend/replication/logical/snapbuild.c | 21 + .../replication/pgoutput_repack/Makefile | 32 + .../replication/pgoutput_repack/meson.build | 18 + .../pgoutput_repack/pgoutput_repack.c | 241 +++ .../storage/lmgr/generate-lwlocknames.pl | 2 +- src/backend/utils/time/snapmgr.c | 3 +- src/bin/psql/tab-complete.in.c | 4 +- src/include/access/heapam.h | 5 +- src/include/access/heapam_xlog.h | 2 + src/include/access/tableam.h | 10 + src/include/commands/cluster.h | 88 +- src/include/commands/progress.h | 17 +- src/include/replication/snapbuild.h | 1 + src/include/storage/lockdefs.h | 4 +- src/include/utils/snapmgr.h | 2 + src/test/modules/injection_points/Makefile | 3 + .../injection_points/expected/repack.out | 113 ++ .../expected/repack_toast.out | 64 + .../modules/injection_points/logical.conf | 1 + src/test/modules/injection_points/meson.build | 4 + .../injection_points/specs/repack.spec | 142 ++ .../injection_points/specs/repack_toast.spec | 105 ++ src/test/regress/expected/rules.out | 19 +- src/tools/pgindent/typedefs.list | 5 + 38 files changed, 2815 insertions(+), 242 deletions(-) create mode 100644 src/backend/replication/pgoutput_repack/Makefile create mode 100644 src/backend/replication/pgoutput_repack/meson.build create mode 100644 src/backend/replication/pgoutput_repack/pgoutput_repack.c create mode 100644 src/test/modules/injection_points/expected/repack.out create mode 100644 src/test/modules/injection_points/expected/repack_toast.out create mode 100644 src/test/modules/injection_points/logical.conf create mode 100644 src/test/modules/injection_points/specs/repack.spec create mode 100644 src/test/modules/injection_points/specs/repack_toast.spec diff --git a/doc/src/sgml/monitoring.sgml b/doc/src/sgml/monitoring.sgml index b07fe3294cd..ae56b09aeba 100644 --- a/doc/src/sgml/monitoring.sgml +++ b/doc/src/sgml/monitoring.sgml @@ -6202,14 +6202,35 @@ FROM pg_stat_get_backend_idset() AS backendid; <row> <entry role="catalog_table_entry"><para role="column_definition"> - <structfield>heap_tuples_written</structfield> <type>bigint</type> + <structfield>heap_tuples_inserted</structfield> <type>bigint</type> </para> <para> - Number of heap tuples written. + Number of heap tuples inserted. This counter only advances when the phase is <literal>seq scanning heap</literal>, - <literal>index scanning heap</literal> - or <literal>writing new heap</literal>. + <literal>index scanning heap</literal>, + <literal>writing new heap</literal> + or <literal>catch-up</literal>. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>heap_tuples_updated</structfield> <type>bigint</type> + </para> + <para> + Number of heap tuples updated. + This counter only advances when the phase is <literal>catch-up</literal>. + </para></entry> + </row> + + <row> + <entry role="catalog_table_entry"><para role="column_definition"> + <structfield>heap_tuples_deleted</structfield> <type>bigint</type> + </para> + <para> + Number of heap tuples deleted. + This counter only advances when the phase is <literal>catch-up</literal>. </para></entry> </row> @@ -6290,6 +6311,14 @@ FROM pg_stat_get_backend_idset() AS backendid; <command>REPACK</command> is currently writing the new heap. </entry> </row> + <row> + <entry><literal>catch-up</literal></entry> + <entry> + <command>REPACK CONCURRENTLY</command> is currently processing the DML + commands that other transactions executed during any of the preceding + phases. + </entry> + </row> <row> <entry><literal>swapping relation files</literal></entry> <entry> diff --git a/doc/src/sgml/mvcc.sgml b/doc/src/sgml/mvcc.sgml index 049ee75a4ba..0f5c34af542 100644 --- a/doc/src/sgml/mvcc.sgml +++ b/doc/src/sgml/mvcc.sgml @@ -1833,15 +1833,17 @@ SELECT pg_advisory_lock(q.id) FROM <title>Caveats</title> <para> - Some DDL commands, currently only <link linkend="sql-truncate"><command>TRUNCATE</command></link> and the - table-rewriting forms of <link linkend="sql-altertable"><command>ALTER TABLE</command></link>, are not + Some commands, currently only <link linkend="sql-truncate"><command>TRUNCATE</command></link>, the + table-rewriting forms of <link linkend="sql-altertable"><command>ALTER + TABLE</command></link> and <command>REPACK</command> with + the <literal>CONCURRENTLY</literal> option, are not MVCC-safe. This means that after the truncation or rewrite commits, the table will appear empty to concurrent transactions, if they are using a - snapshot taken before the DDL command committed. This will only be an + snapshot taken before the command committed. This will only be an issue for a transaction that did not access the table in question - before the DDL command started — any transaction that has done so + before the command started — any transaction that has done so would hold at least an <literal>ACCESS SHARE</literal> table lock, - which would block the DDL command until that transaction completes. + which would block the truncating or rewriting command until that transaction completes. So these commands will not cause any apparent inconsistency in the table contents for successive queries on the target table, but they could cause visible inconsistency between the contents of the target diff --git a/doc/src/sgml/ref/repack.sgml b/doc/src/sgml/ref/repack.sgml index 61d5c2cdef1..30c43c49069 100644 --- a/doc/src/sgml/ref/repack.sgml +++ b/doc/src/sgml/ref/repack.sgml @@ -28,6 +28,7 @@ REPACK [ ( <replaceable class="parameter">option</replaceable> [, ...] ) ] USING VERBOSE [ <replaceable class="parameter">boolean</replaceable> ] ANALYZE [ <replaceable class="parameter">boolean</replaceable> ] + CONCURRENTLY [ <replaceable class="parameter">boolean</replaceable> ] <phrase>and <replaceable class="parameter">table_and_columns</replaceable> is:</phrase> @@ -54,7 +55,8 @@ REPACK [ ( <replaceable class="parameter">option</replaceable> [, ...] ) ] USING processes every table and materialized view in the current database that the current user has the <literal>MAINTAIN</literal> privilege on. This form of <command>REPACK</command> cannot be executed inside a transaction - block. + block. Also, this form is not allowed if + the <literal>CONCURRENTLY</literal> option is used. </para> <para> @@ -67,7 +69,8 @@ REPACK [ ( <replaceable class="parameter">option</replaceable> [, ...] ) ] USING When a table is being repacked, an <literal>ACCESS EXCLUSIVE</literal> lock is acquired on it. This prevents any other database operations (both reads and writes) from operating on the table until the <command>REPACK</command> - is finished. + is finished. If you want to keep the table accessible during the repacking, + consider using the <literal>CONCURRENTLY</literal> option. </para> <refsect2 id="sql-repack-notes-on-clustering" xreflabel="Notes on Clustering"> @@ -195,6 +198,128 @@ REPACK [ ( <replaceable class="parameter">option</replaceable> [, ...] ) ] USING </listitem> </varlistentry> + <varlistentry> + <term><literal>CONCURRENTLY</literal></term> + <listitem> + <para> + Allow other transactions to use the table while it is being repacked. + </para> + + <para> + Internally, <command>REPACK</command> copies the contents of the table + (ignoring dead tuples) into a new file, sorted by the specified index, + and also creates a new file for each index. Then it swaps the old and + new files for the table and all the indexes, and deletes the old + files. The <literal>ACCESS EXCLUSIVE</literal> lock is needed to make + sure that the old files do not change during the processing because the + changes would get lost due to the swap. + </para> + + <para> + With the <literal>CONCURRENTLY</literal> option, the <literal>ACCESS + EXCLUSIVE</literal> lock is only acquired to swap the table and index + files. The data changes that took place during the creation of the new + table and index files are captured using logical decoding + (<xref linkend="logicaldecoding"/>) and applied before + the <literal>ACCESS EXCLUSIVE</literal> lock is requested. Thus the lock + is typically held only for the time needed to swap the files, which + should be pretty short. However, the time might still be noticeable if + too many data changes have been done to the table while + <command>REPACK</command> was waiting for the lock: those changes must + be processed just before the files are swapped, while the + <literal>ACCESS EXCLUSIVE</literal> lock is being held. + </para> + + <para> + Note that <command>REPACK</command> with the + <literal>CONCURRENTLY</literal> option does not try to order the rows + inserted into the table after the repacking started. Also + note <command>REPACK</command> might fail to complete due to DDL + commands executed on the table by other transactions during the + repacking. + </para> + + <note> + <para> + In addition to the temporary space requirements explained in + <xref linkend="sql-repack-notes-on-resources"/>, + the <literal>CONCURRENTLY</literal> option can add to the usage of + temporary space a bit more. The reason is that other transactions can + perform DML operations which cannot be applied to the new file until + <command>REPACK</command> has copied all the tuples from the old + file. Thus the tuples inserted into the old file during the copying are + also stored separately in a temporary file, so they can eventually be + applied to the new file. + </para> + + <para> + Furthermore, the data changes performed during the copying are + extracted from <link linkend="wal">write-ahead log</link> (WAL), and + this extraction (decoding) only takes place when certain amount of WAL + has been written. Therefore, WAL removal can be delayed by this + threshold. Currently the threshold is equal to the value of + the <link linkend="guc-wal-segment-size"><varname>wal_segment_size</varname></link> + configuration parameter. + </para> + </note> + + <para> + The <literal>CONCURRENTLY</literal> option cannot be used in the + following cases: + + <itemizedlist> + <listitem> + <para> + The table is <literal>UNLOGGED</literal>. + </para> + </listitem> + + <listitem> + <para> + The table is partitioned. + </para> + </listitem> + + <listitem> + <para> + The table is a system catalog or a <acronym>TOAST</acronym> table. + </para> + </listitem> + + <listitem> + <para> + <command>REPACK</command> is executed inside a transaction block. + </para> + </listitem> + + <listitem> + <para> + The <link linkend="guc-wal-level"><varname>wal_level</varname></link> + configuration parameter is less than <literal>logical</literal>. + </para> + </listitem> + + <listitem> + <para> + The <link linkend="guc-max-replication-slots"><varname>max_replication_slots</varname></link> + configuration parameter does not allow for creation of an additional + replication slot. + </para> + </listitem> + </itemizedlist> + </para> + + <warning> + <para> + <command>REPACK</command> with the <literal>CONCURRENTLY</literal> + option is not MVCC-safe, see <xref linkend="mvcc-caveats"/> for + details. + </para> + </warning> + + </listitem> + </varlistentry> + <varlistentry> <term><literal>VERBOSE</literal></term> <listitem> diff --git a/src/Makefile b/src/Makefile index 2f31a2f20a7..b18c9a14ffa 100644 --- a/src/Makefile +++ b/src/Makefile @@ -23,6 +23,7 @@ SUBDIRS = \ interfaces \ backend/replication/libpqwalreceiver \ backend/replication/pgoutput \ + backend/replication/pgoutput_repack \ fe_utils \ bin \ pl \ diff --git a/src/backend/access/heap/heapam.c b/src/backend/access/heap/heapam.c index ad9d6338ec2..f1cdd5d1d87 100644 --- a/src/backend/access/heap/heapam.c +++ b/src/backend/access/heap/heapam.c @@ -60,7 +60,8 @@ static HeapTuple heap_prepare_insert(Relation relation, HeapTuple tup, static XLogRecPtr log_heap_update(Relation reln, Buffer oldbuf, Buffer newbuf, HeapTuple oldtup, HeapTuple newtup, HeapTuple old_key_tuple, - bool all_visible_cleared, bool new_all_visible_cleared); + bool all_visible_cleared, bool new_all_visible_cleared, + bool walLogical); #ifdef USE_ASSERT_CHECKING static void check_lock_if_inplace_updateable_rel(Relation relation, const ItemPointerData *otid, @@ -2806,7 +2807,7 @@ xmax_infomask_changed(uint16 new_infomask, uint16 old_infomask) TM_Result heap_delete(Relation relation, const ItemPointerData *tid, CommandId cid, Snapshot crosscheck, bool wait, - TM_FailureData *tmfd, bool changingPart) + TM_FailureData *tmfd, bool changingPart, bool walLogical) { TM_Result result; TransactionId xid = GetCurrentTransactionId(); @@ -3053,7 +3054,8 @@ l1: * Compute replica identity tuple before entering the critical section so * we don't PANIC upon a memory allocation failure. */ - old_key_tuple = ExtractReplicaIdentity(relation, &tp, true, &old_key_copied); + old_key_tuple = walLogical ? + ExtractReplicaIdentity(relation, &tp, true, &old_key_copied) : NULL; /* * If this is the first possibly-multixact-able operation in the current @@ -3143,6 +3145,15 @@ l1: xlrec.flags |= XLH_DELETE_CONTAINS_OLD_KEY; } + /* + * Unlike UPDATE, DELETE is decoded even if there is no old key, so it + * does not help to clear both XLH_DELETE_CONTAINS_OLD_TUPLE and + * XLH_DELETE_CONTAINS_OLD_KEY. Thus we need an extra flag. TODO + * Consider not decoding tuples w/o the old tuple/key instead. + */ + if (!walLogical) + xlrec.flags |= XLH_DELETE_NO_LOGICAL; + XLogBeginInsert(); XLogRegisterData(&xlrec, SizeOfHeapDelete); @@ -3235,7 +3246,8 @@ simple_heap_delete(Relation relation, const ItemPointerData *tid) result = heap_delete(relation, tid, GetCurrentCommandId(true), InvalidSnapshot, true /* wait for commit */ , - &tmfd, false /* changingPart */ ); + &tmfd, false, /* changingPart */ + true /* walLogical */ ); switch (result) { case TM_SelfModified: @@ -3276,7 +3288,7 @@ TM_Result heap_update(Relation relation, const ItemPointerData *otid, HeapTuple newtup, CommandId cid, Snapshot crosscheck, bool wait, TM_FailureData *tmfd, LockTupleMode *lockmode, - TU_UpdateIndexes *update_indexes) + TU_UpdateIndexes *update_indexes, bool walLogical) { TM_Result result; TransactionId xid = GetCurrentTransactionId(); @@ -4169,7 +4181,8 @@ l2: newbuf, &oldtup, heaptup, old_key_tuple, all_visible_cleared, - all_visible_cleared_new); + all_visible_cleared_new, + walLogical); if (newbuf != buffer) { PageSetLSN(BufferGetPage(newbuf), recptr); @@ -4527,7 +4540,8 @@ simple_heap_update(Relation relation, const ItemPointerData *otid, HeapTuple tup result = heap_update(relation, otid, tup, GetCurrentCommandId(true), InvalidSnapshot, true /* wait for commit */ , - &tmfd, &lockmode, update_indexes); + &tmfd, &lockmode, update_indexes, + true /* walLogical */ ); switch (result) { case TM_SelfModified: @@ -8883,7 +8897,8 @@ static XLogRecPtr log_heap_update(Relation reln, Buffer oldbuf, Buffer newbuf, HeapTuple oldtup, HeapTuple newtup, HeapTuple old_key_tuple, - bool all_visible_cleared, bool new_all_visible_cleared) + bool all_visible_cleared, bool new_all_visible_cleared, + bool walLogical) { xl_heap_update xlrec; xl_heap_header xlhdr; @@ -8894,7 +8909,8 @@ log_heap_update(Relation reln, Buffer oldbuf, suffixlen = 0; XLogRecPtr recptr; Page page = BufferGetPage(newbuf); - bool need_tuple_data = RelationIsLogicallyLogged(reln); + bool need_tuple_data = RelationIsLogicallyLogged(reln) && + walLogical; bool init; int bufflags; diff --git a/src/backend/access/heap/heapam_handler.c b/src/backend/access/heap/heapam_handler.c index 778377b9866..3526b6adcb5 100644 --- a/src/backend/access/heap/heapam_handler.c +++ b/src/backend/access/heap/heapam_handler.c @@ -33,6 +33,7 @@ #include "catalog/index.h" #include "catalog/storage.h" #include "catalog/storage_xlog.h" +#include "commands/cluster.h" #include "commands/progress.h" #include "executor/executor.h" #include "miscadmin.h" @@ -309,7 +310,8 @@ heapam_tuple_delete(Relation relation, ItemPointer tid, CommandId cid, * the storage itself is cleaning the dead tuples by itself, it is the * time to call the index tuple deletion also. */ - return heap_delete(relation, tid, cid, crosscheck, wait, tmfd, changingPart); + return heap_delete(relation, tid, cid, crosscheck, wait, tmfd, changingPart, + true); } @@ -328,7 +330,7 @@ heapam_tuple_update(Relation relation, ItemPointer otid, TupleTableSlot *slot, tuple->t_tableOid = slot->tts_tableOid; result = heap_update(relation, otid, tuple, cid, crosscheck, wait, - tmfd, lockmode, update_indexes); + tmfd, lockmode, update_indexes, true); ItemPointerCopy(&tuple->t_self, &slot->tts_tid); /* @@ -685,13 +687,15 @@ static void heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap, Relation OldIndex, bool use_sort, TransactionId OldestXmin, + Snapshot snapshot, + LogicalDecodingContext *decoding_ctx, TransactionId *xid_cutoff, MultiXactId *multi_cutoff, double *num_tuples, double *tups_vacuumed, double *tups_recently_dead) { - RewriteState rwstate; + RewriteState rwstate = NULL; IndexScanDesc indexScan; TableScanDesc tableScan; HeapScanDesc heapScan; @@ -705,6 +709,8 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap, bool *isnull; BufferHeapTupleTableSlot *hslot; BlockNumber prev_cblock = InvalidBlockNumber; + bool concurrent = snapshot != NULL; + XLogRecPtr end_of_wal_prev = GetFlushRecPtr(NULL); /* Remember if it's a system catalog */ is_system_catalog = IsSystemRelation(OldHeap); @@ -720,9 +726,12 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap, values = palloc_array(Datum, natts); isnull = palloc_array(bool, natts); - /* Initialize the rewrite operation */ - rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin, *xid_cutoff, - *multi_cutoff); + /* + * Initialize the rewrite operation. + */ + if (!concurrent) + rwstate = begin_heap_rewrite(OldHeap, NewHeap, OldestXmin, + *xid_cutoff, *multi_cutoff); /* Set up sorting if wanted */ @@ -737,6 +746,9 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap, * Prepare to scan the OldHeap. To ensure we see recently-dead tuples * that still need to be copied, we scan with SnapshotAny and use * HeapTupleSatisfiesVacuum for the visibility test. + * + * In the CONCURRENTLY case, we do regular MVCC visibility tests, using + * the snapshot passed by the caller. */ if (OldIndex != NULL && !use_sort) { @@ -753,7 +765,9 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap, tableScan = NULL; heapScan = NULL; - indexScan = index_beginscan(OldHeap, OldIndex, SnapshotAny, NULL, 0, 0); + indexScan = index_beginscan(OldHeap, OldIndex, + snapshot ? snapshot : SnapshotAny, + NULL, 0, 0); index_rescan(indexScan, NULL, 0, NULL, 0); } else @@ -762,7 +776,9 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap, pgstat_progress_update_param(PROGRESS_REPACK_PHASE, PROGRESS_REPACK_PHASE_SEQ_SCAN_HEAP); - tableScan = table_beginscan(OldHeap, SnapshotAny, 0, (ScanKey) NULL); + tableScan = table_beginscan(OldHeap, + snapshot ? snapshot : SnapshotAny, + 0, (ScanKey) NULL); heapScan = (HeapScanDesc) tableScan; indexScan = NULL; @@ -837,70 +853,77 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap, tuple = ExecFetchSlotHeapTuple(slot, false, NULL); buf = hslot->buffer; - LockBuffer(buf, BUFFER_LOCK_SHARE); - - switch (HeapTupleSatisfiesVacuum(tuple, OldestXmin, buf)) + /* + * Regarding CONCURRENTLY, see the comments on MVCC snapshot above. + */ + if (!concurrent) { - case HEAPTUPLE_DEAD: - /* Definitely dead */ - isdead = true; - break; - case HEAPTUPLE_RECENTLY_DEAD: - *tups_recently_dead += 1; - /* fall through */ - case HEAPTUPLE_LIVE: - /* Live or recently dead, must copy it */ - isdead = false; - break; - case HEAPTUPLE_INSERT_IN_PROGRESS: + LockBuffer(buf, BUFFER_LOCK_SHARE); - /* - * Since we hold exclusive lock on the relation, normally the - * only way to see this is if it was inserted earlier in our - * own transaction. However, it can happen in system - * catalogs, since we tend to release write lock before commit - * there. Give a warning if neither case applies; but in any - * case we had better copy it. - */ - if (!is_system_catalog && - !TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetXmin(tuple->t_data))) - elog(WARNING, "concurrent insert in progress within table \"%s\"", - RelationGetRelationName(OldHeap)); - /* treat as live */ - isdead = false; - break; - case HEAPTUPLE_DELETE_IN_PROGRESS: + switch (HeapTupleSatisfiesVacuum(tuple, OldestXmin, buf)) + { + case HEAPTUPLE_DEAD: + /* Definitely dead */ + isdead = true; + break; + case HEAPTUPLE_RECENTLY_DEAD: + *tups_recently_dead += 1; + /* fall through */ + case HEAPTUPLE_LIVE: + /* Live or recently dead, must copy it */ + isdead = false; + break; + case HEAPTUPLE_INSERT_IN_PROGRESS: - /* - * Similar situation to INSERT_IN_PROGRESS case. - */ - if (!is_system_catalog && - !TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetUpdateXid(tuple->t_data))) - elog(WARNING, "concurrent delete in progress within table \"%s\"", - RelationGetRelationName(OldHeap)); - /* treat as recently dead */ - *tups_recently_dead += 1; - isdead = false; - break; - default: - elog(ERROR, "unexpected HeapTupleSatisfiesVacuum result"); - isdead = false; /* keep compiler quiet */ - break; - } + /* + * As long as we hold exclusive lock on the relation, + * normally the only way to see this is if it was inserted + * earlier in our own transaction. However, it can happen + * in system catalogs, since we tend to release write lock + * before commit there. Give a warning if neither case + * applies; but in any case we had better copy it. + */ + if (!is_system_catalog && + !TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetXmin(tuple->t_data))) + elog(WARNING, "concurrent insert in progress within table \"%s\"", + RelationGetRelationName(OldHeap)); + /* treat as live */ + isdead = false; + break; + case HEAPTUPLE_DELETE_IN_PROGRESS: + + /* + * Similar situation to INSERT_IN_PROGRESS case. + */ + if (!is_system_catalog && + !TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetUpdateXid(tuple->t_data))) + elog(WARNING, "concurrent delete in progress within table \"%s\"", + RelationGetRelationName(OldHeap)); + /* treat as recently dead */ + *tups_recently_dead += 1; + isdead = false; + break; + default: + elog(ERROR, "unexpected HeapTupleSatisfiesVacuum result"); + isdead = false; /* keep compiler quiet */ + break; + } - LockBuffer(buf, BUFFER_LOCK_UNLOCK); + LockBuffer(buf, BUFFER_LOCK_UNLOCK); - if (isdead) - { - *tups_vacuumed += 1; - /* heap rewrite module still needs to see it... */ - if (rewrite_heap_dead_tuple(rwstate, tuple)) + if (isdead) { - /* A previous recently-dead tuple is now known dead */ *tups_vacuumed += 1; - *tups_recently_dead -= 1; + /* heap rewrite module still needs to see it... */ + if (rewrite_heap_dead_tuple(rwstate, tuple)) + { + /* A previous recently-dead tuple is now known dead */ + *tups_vacuumed += 1; + *tups_recently_dead -= 1; + } + + continue; } - continue; } *num_tuples += 1; @@ -919,7 +942,7 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap, { const int ct_index[] = { PROGRESS_REPACK_HEAP_TUPLES_SCANNED, - PROGRESS_REPACK_HEAP_TUPLES_WRITTEN + PROGRESS_REPACK_HEAP_TUPLES_INSERTED }; int64 ct_val[2]; @@ -934,6 +957,31 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap, ct_val[1] = *num_tuples; pgstat_progress_update_multi_param(2, ct_index, ct_val); } + + /* + * Process the WAL produced by the load, as well as by other + * transactions, so that the replication slot can advance and WAL does + * not pile up. Use wal_segment_size as a threshold so that we do not + * introduce the decoding overhead too often. + * + * Of course, we must not apply the changes until the initial load has + * completed. + * + * Note that our insertions into the new table should not be decoded + * as we (intentionally) do not write the logical decoding specific + * information to WAL. + */ + if (concurrent) + { + XLogRecPtr end_of_wal; + + end_of_wal = GetFlushRecPtr(NULL); + if ((end_of_wal - end_of_wal_prev) > wal_segment_size) + { + repack_decode_concurrent_changes(decoding_ctx, end_of_wal); + end_of_wal_prev = end_of_wal; + } + } } if (indexScan != NULL) @@ -977,15 +1025,32 @@ heapam_relation_copy_for_cluster(Relation OldHeap, Relation NewHeap, values, isnull, rwstate); /* Report n_tuples */ - pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_WRITTEN, + pgstat_progress_update_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, n_tuples); + + /* + * Try to keep the amount of not-yet-decoded WAL small, like + * above. + */ + if (concurrent) + { + XLogRecPtr end_of_wal; + + end_of_wal = GetFlushRecPtr(NULL); + if ((end_of_wal - end_of_wal_prev) > wal_segment_size) + { + repack_decode_concurrent_changes(decoding_ctx, end_of_wal); + end_of_wal_prev = end_of_wal; + } + } } tuplesort_end(tuplesort); } /* Write out any remaining tuples, and fsync if needed */ - end_heap_rewrite(rwstate); + if (rwstate) + end_heap_rewrite(rwstate); /* Clean up */ pfree(values); @@ -2376,6 +2441,10 @@ heapam_scan_sample_next_tuple(TableScanDesc scan, SampleScanState *scanstate, * SET WITHOUT OIDS. * * So, we must reconstruct the tuple from component Datums. + * + * If rwstate=NULL, use simple_heap_insert() instead of rewriting - in that + * case we still need to deform/form the tuple. TODO Shouldn't we rename the + * function, as might not do any rewrite? */ static void reform_and_rewrite_tuple(HeapTuple tuple, @@ -2398,8 +2467,28 @@ reform_and_rewrite_tuple(HeapTuple tuple, copiedTuple = heap_form_tuple(newTupDesc, values, isnull); - /* The heap rewrite module does the rest */ - rewrite_heap_tuple(rwstate, tuple, copiedTuple); + if (rwstate) + /* The heap rewrite module does the rest */ + rewrite_heap_tuple(rwstate, tuple, copiedTuple); + else + { + /* + * Insert tuple when processing REPACK CONCURRENTLY. + * + * rewriteheap.c is not used in the CONCURRENTLY case because it'd be + * difficult to do the same in the catch-up phase (as the logical + * decoding does not provide us with sufficient visibility + * information). Thus we must use heap_insert() both during the + * catch-up and here. + * + * The following is like simple_heap_insert() except that we pass the + * flag to skip logical decoding: as soon as REPACK CONCURRENTLY swaps + * the relation files, it drops this relation, so no logical + * replication subscription should need the data. + */ + heap_insert(NewHeap, copiedTuple, GetCurrentCommandId(true), + HEAP_INSERT_NO_LOGICAL, NULL); + } heap_freetuple(copiedTuple); } diff --git a/src/backend/access/heap/rewriteheap.c b/src/backend/access/heap/rewriteheap.c index bae3a2da77a..45892da1e14 100644 --- a/src/backend/access/heap/rewriteheap.c +++ b/src/backend/access/heap/rewriteheap.c @@ -617,9 +617,9 @@ raw_heap_insert(RewriteState state, HeapTuple tup) int options = HEAP_INSERT_SKIP_FSM; /* - * While rewriting the heap for VACUUM FULL / CLUSTER, make sure data - * for the TOAST table are not logically decoded. The main heap is - * WAL-logged as XLOG FPI records, which are not logically decoded. + * While rewriting the heap for REPACK, make sure data for the TOAST + * table are not logically decoded. The main heap is WAL-logged as + * XLOG FPI records, which are not logically decoded. */ options |= HEAP_INSERT_NO_LOGICAL; diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 3f05ba3083a..d79eab5670c 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -1298,16 +1298,19 @@ CREATE VIEW pg_stat_progress_repack AS WHEN 2 THEN 'index scanning heap' WHEN 3 THEN 'sorting tuples' WHEN 4 THEN 'writing new heap' - WHEN 5 THEN 'swapping relation files' - WHEN 6 THEN 'rebuilding index' - WHEN 7 THEN 'performing final cleanup' + WHEN 5 THEN 'catch-up' + WHEN 6 THEN 'swapping relation files' + WHEN 7 THEN 'rebuilding index' + WHEN 8 THEN 'performing final cleanup' END AS phase, CAST(S.param3 AS oid) AS repack_index_relid, S.param4 AS heap_tuples_scanned, - S.param5 AS heap_tuples_written, - S.param6 AS heap_blks_total, - S.param7 AS heap_blks_scanned, - S.param8 AS index_rebuild_count + S.param5 AS heap_tuples_inserted, + S.param6 AS heap_tuples_updated, + S.param7 AS heap_tuples_deleted, + S.param8 AS heap_blks_total, + S.param9 AS heap_blks_scanned, + S.param10 AS index_rebuild_count FROM pg_stat_get_progress_info('REPACK') AS S LEFT JOIN pg_database D ON S.datid = D.oid; @@ -1325,7 +1328,7 @@ CREATE VIEW pg_stat_progress_cluster AS phase, repack_index_relid AS cluster_index_relid, heap_tuples_scanned, - heap_tuples_written, + heap_tuples_inserted + heap_tuples_updated AS heap_tuples_written, heap_blks_total, heap_blks_scanned, index_rebuild_count diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c index e19675a6d05..9f61620a279 100644 --- a/src/backend/commands/cluster.c +++ b/src/backend/commands/cluster.c @@ -1,8 +1,23 @@ /*------------------------------------------------------------------------- * * cluster.c - * CLUSTER a table on an index. This is now also used for VACUUM FULL and - * REPACK. + * Implementation of REPACK [CONCURRENTLY], also known as CLUSTER and + * VACUUM FULL. + * + * There are two somewhat different ways to rewrite a table. In non- + * concurrent mode, it's easy: take AccessExclusiveLock, create a new + * transient relation, copy the tuples over to the relfilenode of the new + * relation, swap the relfilenodes, then drop the old relation. + * + * In concurrent mode, we lock the table with only ShareUpdateExclusiveLock, + * then do an initial copy as above. However, while the tuples are being + * copied, concurrent transactions could modify the table. To cope with those + * changes, we rely on logical decoding to obtain them from WAL. The changes + * are accumulated in a tuplestore. Once the initial copy is complete, we + * read the changes from the tuplestore and re-apply them on the new heap. + * Then we upgrade our ShareUpdateExclusiveLock to AccessExclusiveLock and + * swap the relfilenodes. This way, the time we hold a strong lock on the + * table is much reduced, and the bloat is eliminated. * * There is hardly anything left of Paul Brown's original implementation... * @@ -26,6 +41,10 @@ #include "access/toast_internals.h" #include "access/transam.h" #include "access/xact.h" +#include "access/xlog.h" +#include "access/xlog_internal.h" +#include "access/xloginsert.h" +#include "access/xlogutils.h" #include "catalog/catalog.h" #include "catalog/dependency.h" #include "catalog/heap.h" @@ -33,6 +52,7 @@ #include "catalog/namespace.h" #include "catalog/objectaccess.h" #include "catalog/pg_am.h" +#include "catalog/pg_control.h" #include "catalog/pg_inherits.h" #include "catalog/toasting.h" #include "commands/cluster.h" @@ -40,15 +60,21 @@ #include "commands/progress.h" #include "commands/tablecmds.h" #include "commands/vacuum.h" +#include "executor/executor.h" #include "miscadmin.h" #include "optimizer/optimizer.h" #include "pgstat.h" +#include "replication/decode.h" +#include "replication/logical.h" +#include "replication/snapbuild.h" #include "storage/bufmgr.h" +#include "storage/ipc.h" #include "storage/lmgr.h" #include "storage/predicate.h" #include "utils/acl.h" #include "utils/fmgroids.h" #include "utils/guc.h" +#include "utils/injection_point.h" #include "utils/inval.h" #include "utils/lsyscache.h" #include "utils/memutils.h" @@ -68,12 +94,62 @@ typedef struct Oid indexOid; } RelToCluster; +/* + * The following definitions are used for concurrent processing. + */ + +/* + * The locators are used to avoid logical decoding of data that we do not need + * for our table. + */ +static RelFileLocator repacked_rel_locator = {.relNumber = InvalidOid}; +static RelFileLocator repacked_rel_toast_locator = {.relNumber = InvalidOid}; + +/* + * Everything we need to call ExecInsertIndexTuples(). + */ +typedef struct IndexInsertState +{ + ResultRelInfo *rri; + EState *estate; +} IndexInsertState; + +/* The WAL segment being decoded. */ +static XLogSegNo repack_current_segment = 0; + +/* + * Information needed to apply concurrent data changes. + */ +typedef struct ChangeDest +{ + /* The relation the changes are applied to. */ + Relation rel; + + /* + * The following is needed to find the existing tuple if the change is + * UPDATE or DELETE. 'ident_key' should have all the fields except for + * 'sk_argument' initialized. + */ + Relation ident_index; + ScanKey ident_key; + int ident_key_nentries; + + /* Needed to update indexes of rel_dst. */ + IndexInsertState *iistate; +} ChangeDest; + static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap, - Oid indexOid, Oid userid, int options); -static void rebuild_relation(Relation OldHeap, Relation index, bool verbose); + Oid indexOid, Oid userid, LOCKMODE lmode, + int options); +static void check_repack_concurrently_requirements(Relation rel); +static void rebuild_relation(Relation OldHeap, Relation index, bool verbose, + bool concurrent); static void copy_table_data(Relation NewHeap, Relation OldHeap, Relation OldIndex, - bool verbose, bool *pSwapToastByContent, - TransactionId *pFreezeXid, MultiXactId *pCutoffMulti); + Snapshot snapshot, LogicalDecodingContext *decoding_ctx, + bool verbose, + bool *pSwapToastByContent, + TransactionId *pFreezeXid, + MultiXactId *pCutoffMulti); static List *get_tables_to_repack(RepackCommand cmd, bool usingindex, MemoryContext permcxt); static List *get_tables_to_repack_partitioned(RepackCommand cmd, @@ -81,13 +157,50 @@ static List *get_tables_to_repack_partitioned(RepackCommand cmd, MemoryContext permcxt); static bool cluster_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid); + +static void begin_concurrent_repack(Relation rel); +static void end_concurrent_repack(void); +static LogicalDecodingContext *setup_logical_decoding(Oid relid); +static HeapTuple get_changed_tuple(char *change); +static void apply_concurrent_changes(RepackDecodingState *dstate, + ChangeDest *dest); +static void apply_concurrent_insert(Relation rel, HeapTuple tup, + IndexInsertState *iistate, + TupleTableSlot *index_slot); +static void apply_concurrent_update(Relation rel, HeapTuple tup, + HeapTuple tup_target, + IndexInsertState *iistate, + TupleTableSlot *index_slot); +static void apply_concurrent_delete(Relation rel, HeapTuple tup_target); +static HeapTuple find_target_tuple(Relation rel, ChangeDest *dest, + HeapTuple tup_key, + TupleTableSlot *ident_slot); +static void process_concurrent_changes(LogicalDecodingContext *decoding_ctx, + XLogRecPtr end_of_wal, + ChangeDest *dest); +static IndexInsertState *get_index_insert_state(Relation relation, + Oid ident_index_id, + Relation *ident_index_p); +static ScanKey build_identity_key(Oid ident_idx_oid, Relation rel_src, + int *nentries); +static void free_index_insert_state(IndexInsertState *iistate); +static void cleanup_logical_decoding(LogicalDecodingContext *ctx); +static void rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap, + LogicalDecodingContext *decoding_ctx, + TransactionId frozenXid, + MultiXactId cutoffMulti); +static List *build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes); static Relation process_single_relation(RepackStmt *stmt, + LOCKMODE lockmode, + bool isTopLevel, ClusterParams *params); static Oid determine_clustered_index(Relation rel, bool usingindex, const char *indexname); static const char *RepackCommandAsString(RepackCommand cmd); +#define REPL_PLUGIN_NAME "pgoutput_repack" + /* * The repack code allows for processing multiple tables at once. Because * of this, we cannot just run everything on a single transaction, or we @@ -117,6 +230,7 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel) ClusterParams params = {0}; Relation rel = NULL; MemoryContext repack_context; + LOCKMODE lockmode; List *rtcs; /* Parse option list */ @@ -127,6 +241,16 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel) else if (strcmp(opt->defname, "analyze") == 0 || strcmp(opt->defname, "analyse") == 0) params.options |= defGetBoolean(opt) ? CLUOPT_ANALYZE : 0; + else if (strcmp(opt->defname, "concurrently") == 0 && + defGetBoolean(opt)) + { + if (stmt->command != REPACK_COMMAND_REPACK) + ereport(ERROR, + errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("CONCURRENTLY option not supported for %s", + RepackCommandAsString(stmt->command))); + params.options |= CLUOPT_CONCURRENT; + } else ereport(ERROR, errcode(ERRCODE_SYNTAX_ERROR), @@ -136,13 +260,25 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel) parser_errposition(pstate, opt->location)); } + /* + * Determine the lock mode expected by cluster_rel(). + * + * In the exclusive case, we obtain AccessExclusiveLock right away to + * avoid lock-upgrade hazard in the single-transaction case. In the + * CONCURRENTLY case, the AccessExclusiveLock will only be used at the end + * of processing, supposedly for very short time. Until then, we'll have + * to unlock the relation temporarily, so there's no lock-upgrade hazard. + */ + lockmode = (params.options & CLUOPT_CONCURRENT) == 0 ? + AccessExclusiveLock : ShareUpdateExclusiveLock; + /* * If a single relation is specified, process it and we're done ... unless * the relation is a partitioned table, in which case we fall through. */ if (stmt->relation != NULL) { - rel = process_single_relation(stmt, ¶ms); + rel = process_single_relation(stmt, lockmode, isTopLevel, ¶ms); if (rel == NULL) return; /* all done */ } @@ -157,10 +293,29 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel) errmsg("cannot %s multiple tables", "REPACK (ANALYZE)")); /* - * By here, we know we are in a multi-table situation. In order to avoid - * holding locks for too long, we want to process each table in its own - * transaction. This forces us to disallow running inside a user - * transaction block. + * By here, we know we are in a multi-table situation. + * + * Concurrent processing is currently considered rather special (e.g. in + * terms of resources consumed) so it is not performed in bulk. + */ + if (params.options & CLUOPT_CONCURRENT) + { + if (rel != NULL) + { + Assert(rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE); + ereport(ERROR, + errmsg("REPACK CONCURRENTLY not supported for partitioned tables"), + errhint("Consider running the command for individual partitions.")); + } + else + ereport(ERROR, + errmsg("REPACK CONCURRENTLY requires explicit table name")); + } + + /* + * In order to avoid holding locks for too long, we want to process each + * table in its own transaction. This forces us to disallow running + * inside a user transaction block. */ PreventInTransactionBlock(isTopLevel, RepackCommandAsString(stmt->command)); @@ -244,7 +399,7 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel) * Open the target table, coping with the case where it has been * dropped. */ - rel = try_table_open(rtc->tableOid, AccessExclusiveLock); + rel = try_table_open(rtc->tableOid, lockmode); if (rel == NULL) { CommitTransactionCommand(); @@ -255,7 +410,7 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel) PushActiveSnapshot(GetTransactionSnapshot()); /* Process this table */ - cluster_rel(stmt->command, rel, rtc->indexOid, ¶ms); + cluster_rel(stmt->command, rel, rtc->indexOid, ¶ms, isTopLevel); /* cluster_rel closes the relation, but keeps lock */ PopActiveSnapshot(); @@ -284,22 +439,53 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel) * If indexOid is InvalidOid, the table will be rewritten in physical order * instead of index order. * + * Note that, in the concurrent case, the function releases the lock at some + * point, in order to get AccessExclusiveLock for the final steps (i.e. to + * swap the relation files). To make things simpler, the caller should expect + * OldHeap to be closed on return, regardless CLUOPT_CONCURRENT. (The + * AccessExclusiveLock is kept till the end of the transaction.) + * * 'cmd' indicates which command is being executed, to be used for error * messages. */ void cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid, - ClusterParams *params) + ClusterParams *params, bool isTopLevel) { Oid tableOid = RelationGetRelid(OldHeap); + Relation index; + LOCKMODE lmode; Oid save_userid; int save_sec_context; int save_nestlevel; bool verbose = ((params->options & CLUOPT_VERBOSE) != 0); bool recheck = ((params->options & CLUOPT_RECHECK) != 0); - Relation index; + bool concurrent = ((params->options & CLUOPT_CONCURRENT) != 0); + + /* + * The lock mode is AccessExclusiveLock for normal processing and + * ShareUpdateExclusiveLock for concurrent processing (so that SELECT, + * INSERT, UPDATE and DELETE commands work, but cluster_rel() cannot be + * called concurrently for the same relation). + */ + lmode = !concurrent ? AccessExclusiveLock : ShareUpdateExclusiveLock; - Assert(CheckRelationLockedByMe(OldHeap, AccessExclusiveLock, false)); + /* There are specific requirements on concurrent processing. */ + if (concurrent) + { + /* + * Make sure we have no XID assigned, otherwise call of + * setup_logical_decoding() can cause a deadlock. + * + * The existence of transaction block actually does not imply that XID + * was already assigned, but it very likely is. We might want to check + * the result of GetCurrentTransactionIdIfAny() instead, but that + * would be less clear from user's perspective. + */ + PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)"); + + check_repack_concurrently_requirements(OldHeap); + } /* Check for user-requested abort. */ CHECK_FOR_INTERRUPTS(); @@ -325,10 +511,13 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid, * If this is a single-transaction CLUSTER, we can skip these tests. We * *must* skip the one on indisclustered since it would reject an attempt * to cluster a not-previously-clustered index. + * + * XXX move [some of] these comments to where the RECHECK flag is + * determined? */ if (recheck && !cluster_rel_recheck(cmd, OldHeap, indexOid, save_userid, - params->options)) + lmode, params->options)) goto out; /* @@ -343,6 +532,12 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid, errmsg("cannot run %s on a shared catalog", RepackCommandAsString(cmd))); + /* + * The CONCURRENTLY case should have been rejected earlier because it does + * not support system catalogs. + */ + Assert(!(OldHeap->rd_rel->relisshared && concurrent)); + /* * Don't process temp tables of other backends ... their local buffer * manager is not going to cope. @@ -363,7 +558,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid, if (OidIsValid(indexOid)) { /* verify the index is good and lock it */ - check_index_is_clusterable(OldHeap, indexOid, AccessExclusiveLock); + check_index_is_clusterable(OldHeap, indexOid, lmode); /* also open it */ index = index_open(indexOid, NoLock); } @@ -398,7 +593,9 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid, if (OldHeap->rd_rel->relkind == RELKIND_MATVIEW && !RelationIsPopulated(OldHeap)) { - relation_close(OldHeap, AccessExclusiveLock); + if (index) + index_close(index, lmode); + relation_close(OldHeap, lmode); goto out; } @@ -411,11 +608,34 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid, * invalid, because we move tuples around. Promote them to relation * locks. Predicate locks on indexes will be promoted when they are * reindexed. + * + * During concurrent processing, the heap as well as its indexes stay in + * operation, so we postpone this step until they are locked using + * AccessExclusiveLock near the end of the processing. */ - TransferPredicateLocksToHeapRelation(OldHeap); + if (!concurrent) + TransferPredicateLocksToHeapRelation(OldHeap); /* rebuild_relation does all the dirty work */ - rebuild_relation(OldHeap, index, verbose); + PG_TRY(); + { + /* + * For concurrent processing, make sure that our logical decoding + * ignores data changes of other tables than the one we are + * processing. + */ + if (concurrent) + begin_concurrent_repack(OldHeap); + + rebuild_relation(OldHeap, index, verbose, concurrent); + } + PG_FINALLY(); + { + if (concurrent) + end_concurrent_repack(); + } + PG_END_TRY(); + /* rebuild_relation closes OldHeap, and index if valid */ out: @@ -434,14 +654,14 @@ out: */ static bool cluster_rel_recheck(RepackCommand cmd, Relation OldHeap, Oid indexOid, - Oid userid, int options) + Oid userid, LOCKMODE lmode, int options) { Oid tableOid = RelationGetRelid(OldHeap); /* Check that the user still has privileges for the relation */ if (!cluster_is_permitted_for_relation(cmd, tableOid, userid)) { - relation_close(OldHeap, AccessExclusiveLock); + relation_close(OldHeap, lmode); return false; } @@ -455,7 +675,7 @@ cluster_rel_recheck(RepackCommand cmd, Relation OldHeap, Oid indexOid, */ if (RELATION_IS_OTHER_TEMP(OldHeap)) { - relation_close(OldHeap, AccessExclusiveLock); + relation_close(OldHeap, lmode); return false; } @@ -466,7 +686,7 @@ cluster_rel_recheck(RepackCommand cmd, Relation OldHeap, Oid indexOid, */ if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(indexOid))) { - relation_close(OldHeap, AccessExclusiveLock); + relation_close(OldHeap, lmode); return false; } @@ -477,7 +697,7 @@ cluster_rel_recheck(RepackCommand cmd, Relation OldHeap, Oid indexOid, if ((options & CLUOPT_RECHECK_ISCLUSTERED) != 0 && !get_index_isclustered(indexOid)) { - relation_close(OldHeap, AccessExclusiveLock); + relation_close(OldHeap, lmode); return false; } } @@ -489,7 +709,7 @@ cluster_rel_recheck(RepackCommand cmd, Relation OldHeap, Oid indexOid, * Verify that the specified heap and index are valid to cluster on * * Side effect: obtains lock on the index. The caller may - * in some cases already have AccessExclusiveLock on the table, but + * in some cases already have a lock of the same strength on the table, but * not in all cases so we can't rely on the table-level lock for * protection here. */ @@ -618,18 +838,87 @@ mark_index_clustered(Relation rel, Oid indexOid, bool is_internal) table_close(pg_index, RowExclusiveLock); } +/* + * Check if the CONCURRENTLY option is legal for the relation. + */ +static void +check_repack_concurrently_requirements(Relation rel) +{ + char relpersistence, + replident; + Oid ident_idx; + + /* Data changes in system relations are not logically decoded. */ + if (IsCatalogRelation(rel)) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot repack relation \"%s\"", + RelationGetRelationName(rel)), + errhint("REPACK CONCURRENTLY is not supported for catalog relations."))); + + /* + * reorderbuffer.c does not seem to handle processing of TOAST relation + * alone. + */ + if (IsToastRelation(rel)) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("cannot repack relation \"%s\"", + RelationGetRelationName(rel)), + errhint("REPACK CONCURRENTLY is not supported for TOAST relations, unless the main relation is repacked too."))); + + relpersistence = rel->rd_rel->relpersistence; + if (relpersistence != RELPERSISTENCE_PERMANENT) + ereport(ERROR, + (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), + errmsg("cannot repack relation \"%s\"", + RelationGetRelationName(rel)), + errhint("REPACK CONCURRENTLY is only allowed for permanent relations."))); + + /* With NOTHING, WAL does not contain the old tuple. */ + replident = rel->rd_rel->relreplident; + if (replident == REPLICA_IDENTITY_NOTHING) + ereport(ERROR, + (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), + errmsg("cannot repack relation \"%s\"", + RelationGetRelationName(rel)), + errhint("Relation \"%s\" has insufficient replication identity.", + RelationGetRelationName(rel)))); + + /* + * Identity index is not set if the replica identity is FULL, but PK might + * exist in such a case. + */ + ident_idx = RelationGetReplicaIndex(rel); + if (!OidIsValid(ident_idx) && OidIsValid(rel->rd_pkindex)) + ident_idx = rel->rd_pkindex; + if (!OidIsValid(ident_idx)) + ereport(ERROR, + (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), + errmsg("cannot process relation \"%s\"", + RelationGetRelationName(rel)), + errhint("Relation \"%s\" has no identity index.", + RelationGetRelationName(rel)))); +} + + /* * rebuild_relation: rebuild an existing relation in index or physical order * - * OldHeap: table to rebuild. + * OldHeap: table to rebuild. See cluster_rel() for comments on the required + * lock strength. + * * index: index to cluster by, or NULL to rewrite in physical order. * - * On entry, heap and index (if one is given) must be open, and - * AccessExclusiveLock held on them. - * On exit, they are closed, but locks on them are not released. + * On entry, heap and index (if one is given) must be open, and the + * appropriate lock held on them -- AccessExclusiveLock for exclusive + * processing and ShareUpdateExclusiveLock for concurrent processing. + * + * On exit, they are closed, but still locked with AccessExclusiveLock. + * (The function handles the lock upgrade if 'concurrent' is true.) */ static void -rebuild_relation(Relation OldHeap, Relation index, bool verbose) +rebuild_relation(Relation OldHeap, Relation index, bool verbose, bool concurrent) { Oid tableOid = RelationGetRelid(OldHeap); Oid accessMethod = OldHeap->rd_rel->relam; @@ -637,13 +926,38 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose) Oid OIDNewHeap; Relation NewHeap; char relpersistence; - bool is_system_catalog; bool swap_toast_by_content; TransactionId frozenXid; MultiXactId cutoffMulti; + LogicalDecodingContext *decoding_ctx = NULL; + Snapshot snapshot = NULL; +#if USE_ASSERT_CHECKING + LOCKMODE lmode; + + lmode = concurrent ? ShareUpdateExclusiveLock : AccessExclusiveLock; + + Assert(CheckRelationLockedByMe(OldHeap, lmode, false)); + Assert(index == NULL || CheckRelationLockedByMe(index, lmode, false)); +#endif + + if (concurrent) + { + /* + * Prepare to capture the concurrent data changes. + * + * Note that this call waits for all transactions with XID already + * assigned to finish. If some of those transactions is waiting for a + * lock conflicting with ShareUpdateExclusiveLock on our table (e.g. + * it runs CREATE INDEX), we can end up in a deadlock. Not sure this + * risk is worth unlocking/locking the table (and its clustering + * index) and checking again if its still eligible for REPACK + * CONCURRENTLY. + */ + decoding_ctx = setup_logical_decoding(tableOid); - Assert(CheckRelationLockedByMe(OldHeap, AccessExclusiveLock, false) && - (index == NULL || CheckRelationLockedByMe(index, AccessExclusiveLock, false))); + snapshot = SnapBuildInitialSnapshotForRepack(decoding_ctx->snapshot_builder); + PushActiveSnapshot(snapshot); + } /* for CLUSTER or REPACK USING INDEX, mark the index as the one to use */ if (index != NULL) @@ -651,7 +965,6 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose) /* Remember info about rel before closing OldHeap */ relpersistence = OldHeap->rd_rel->relpersistence; - is_system_catalog = IsSystemRelation(OldHeap); /* * Create the transient table that will receive the re-ordered data. @@ -667,30 +980,61 @@ rebuild_relation(Relation OldHeap, Relation index, bool verbose) NewHeap = table_open(OIDNewHeap, NoLock); /* Copy the heap data into the new table in the desired order */ - copy_table_data(NewHeap, OldHeap, index, verbose, + copy_table_data(NewHeap, OldHeap, index, snapshot, decoding_ctx, verbose, &swap_toast_by_content, &frozenXid, &cutoffMulti); + /* The historic snapshot won't be needed anymore. */ + if (snapshot) + { + PopActiveSnapshot(); + UpdateActiveSnapshotCommandId(); + } - /* Close relcache entries, but keep lock until transaction commit */ - table_close(OldHeap, NoLock); - if (index) - index_close(index, NoLock); + if (concurrent) + { + Assert(!swap_toast_by_content); - /* - * Close the new relation so it can be dropped as soon as the storage is - * swapped. The relation is not visible to others, so no need to unlock it - * explicitly. - */ - table_close(NewHeap, NoLock); + /* + * Close the index, but keep the lock. Both heaps will be closed by + * the following call. + */ + if (index) + index_close(index, NoLock); - /* - * Swap the physical files of the target and transient tables, then - * rebuild the target's indexes and throw away the transient table. - */ - finish_heap_swap(tableOid, OIDNewHeap, is_system_catalog, - swap_toast_by_content, false, true, - frozenXid, cutoffMulti, - relpersistence); + rebuild_relation_finish_concurrent(NewHeap, OldHeap, decoding_ctx, + frozenXid, cutoffMulti); + + pgstat_progress_update_param(PROGRESS_REPACK_PHASE, + PROGRESS_REPACK_PHASE_FINAL_CLEANUP); + + /* Done with decoding. */ + cleanup_logical_decoding(decoding_ctx); + } + else + { + bool is_system_catalog = IsSystemRelation(OldHeap); + + /* Close relcache entries, but keep lock until transaction commit */ + table_close(OldHeap, NoLock); + if (index) + index_close(index, NoLock); + + /* + * Close the new relation so it can be dropped as soon as the storage + * is swapped. The relation is not visible to others, so no need to + * unlock it explicitly. + */ + table_close(NewHeap, NoLock); + + /* + * Swap the physical files of the target and transient tables, then + * rebuild the target's indexes and throw away the transient table. + */ + finish_heap_swap(tableOid, OIDNewHeap, is_system_catalog, + swap_toast_by_content, false, true, true, + frozenXid, cutoffMulti, + relpersistence); + } } @@ -825,15 +1169,19 @@ make_new_heap(Oid OIDOldHeap, Oid NewTableSpace, Oid NewAccessMethod, /* * Do the physical copying of table data. * + * 'snapshot' and 'decoding_ctx': see table_relation_copy_for_cluster(). Pass + * iff concurrent processing is required. + * * There are three output parameters: * *pSwapToastByContent is set true if toast tables must be swapped by content. * *pFreezeXid receives the TransactionId used as freeze cutoff point. * *pCutoffMulti receives the MultiXactId used as a cutoff point. */ static void -copy_table_data(Relation NewHeap, Relation OldHeap, Relation OldIndex, bool verbose, - bool *pSwapToastByContent, TransactionId *pFreezeXid, - MultiXactId *pCutoffMulti) +copy_table_data(Relation NewHeap, Relation OldHeap, Relation OldIndex, + Snapshot snapshot, LogicalDecodingContext *decoding_ctx, + bool verbose, bool *pSwapToastByContent, + TransactionId *pFreezeXid, MultiXactId *pCutoffMulti) { Relation relRelation; HeapTuple reltup; @@ -850,6 +1198,10 @@ copy_table_data(Relation NewHeap, Relation OldHeap, Relation OldIndex, bool verb int elevel = verbose ? INFO : DEBUG2; PGRUsage ru0; char *nspname; + bool concurrent = snapshot != NULL; + LOCKMODE lmode; + + lmode = concurrent ? ShareUpdateExclusiveLock : AccessExclusiveLock; pg_rusage_init(&ru0); @@ -878,7 +1230,7 @@ copy_table_data(Relation NewHeap, Relation OldHeap, Relation OldIndex, bool verb * will be held till end of transaction. */ if (OldHeap->rd_rel->reltoastrelid) - LockRelationOid(OldHeap->rd_rel->reltoastrelid, AccessExclusiveLock); + LockRelationOid(OldHeap->rd_rel->reltoastrelid, lmode); /* * If both tables have TOAST tables, perform toast swap by content. It is @@ -887,7 +1239,8 @@ copy_table_data(Relation NewHeap, Relation OldHeap, Relation OldIndex, bool verb * swap by links. This is okay because swap by content is only essential * for system catalogs, and we don't support schema changes for them. */ - if (OldHeap->rd_rel->reltoastrelid && NewHeap->rd_rel->reltoastrelid) + if (OldHeap->rd_rel->reltoastrelid && NewHeap->rd_rel->reltoastrelid && + !concurrent) { *pSwapToastByContent = true; @@ -908,6 +1261,10 @@ copy_table_data(Relation NewHeap, Relation OldHeap, Relation OldIndex, bool verb * follow the toast pointers to the wrong place. (It would actually * work for values copied over from the old toast table, but not for * any values that we toast which were previously not toasted.) + * + * This would not work with CONCURRENTLY because we may need to delete + * TOASTed tuples from the new heap. With this hack, we'd delete them + * from the old heap. */ NewHeap->rd_toastoid = OldHeap->rd_rel->reltoastrelid; } @@ -983,7 +1340,9 @@ copy_table_data(Relation NewHeap, Relation OldHeap, Relation OldIndex, bool verb * values (e.g. because the AM doesn't use freezing). */ table_relation_copy_for_cluster(OldHeap, NewHeap, OldIndex, use_sort, - cutoffs.OldestXmin, &cutoffs.FreezeLimit, + cutoffs.OldestXmin, snapshot, + decoding_ctx, + &cutoffs.FreezeLimit, &cutoffs.MultiXactCutoff, &num_tuples, &tups_vacuumed, &tups_recently_dead); @@ -992,7 +1351,11 @@ copy_table_data(Relation NewHeap, Relation OldHeap, Relation OldIndex, bool verb *pFreezeXid = cutoffs.FreezeLimit; *pCutoffMulti = cutoffs.MultiXactCutoff; - /* Reset rd_toastoid just to be tidy --- it shouldn't be looked at again */ + /* + * Reset rd_toastoid just to be tidy --- it shouldn't be looked at again. + * In the CONCURRENTLY case, we need to set it again before applying the + * concurrent changes. + */ NewHeap->rd_toastoid = InvalidOid; num_pages = RelationGetNumberOfBlocks(NewHeap); @@ -1450,14 +1813,13 @@ finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap, bool swap_toast_by_content, bool check_constraints, bool is_internal, + bool reindex, TransactionId frozenXid, MultiXactId cutoffMulti, char newrelpersistence) { ObjectAddress object; Oid mapped_tables[4]; - int reindex_flags; - ReindexParams reindex_params = {0}; int i; /* Report that we are now swapping relation files */ @@ -1483,39 +1845,47 @@ finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap, if (is_system_catalog) CacheInvalidateCatalog(OIDOldHeap); - /* - * Rebuild each index on the relation (but not the toast table, which is - * all-new at this point). It is important to do this before the DROP - * step because if we are processing a system catalog that will be used - * during DROP, we want to have its indexes available. There is no - * advantage to the other order anyway because this is all transactional, - * so no chance to reclaim disk space before commit. We do not need a - * final CommandCounterIncrement() because reindex_relation does it. - * - * Note: because index_build is called via reindex_relation, it will never - * set indcheckxmin true for the indexes. This is OK even though in some - * sense we are building new indexes rather than rebuilding existing ones, - * because the new heap won't contain any HOT chains at all, let alone - * broken ones, so it can't be necessary to set indcheckxmin. - */ - reindex_flags = REINDEX_REL_SUPPRESS_INDEX_USE; - if (check_constraints) - reindex_flags |= REINDEX_REL_CHECK_CONSTRAINTS; + if (reindex) + { + int reindex_flags; + ReindexParams reindex_params = {0}; - /* - * Ensure that the indexes have the same persistence as the parent - * relation. - */ - if (newrelpersistence == RELPERSISTENCE_UNLOGGED) - reindex_flags |= REINDEX_REL_FORCE_INDEXES_UNLOGGED; - else if (newrelpersistence == RELPERSISTENCE_PERMANENT) - reindex_flags |= REINDEX_REL_FORCE_INDEXES_PERMANENT; + /* + * Rebuild each index on the relation (but not the toast table, which + * is all-new at this point). It is important to do this before the + * DROP step because if we are processing a system catalog that will + * be used during DROP, we want to have its indexes available. There + * is no advantage to the other order anyway because this is all + * transactional, so no chance to reclaim disk space before commit. We + * do not need a final CommandCounterIncrement() because + * reindex_relation does it. + * + * Note: because index_build is called via reindex_relation, it will + * never set indcheckxmin true for the indexes. This is OK even + * though in some sense we are building new indexes rather than + * rebuilding existing ones, because the new heap won't contain any + * HOT chains at all, let alone broken ones, so it can't be necessary + * to set indcheckxmin. + */ + reindex_flags = REINDEX_REL_SUPPRESS_INDEX_USE; + if (check_constraints) + reindex_flags |= REINDEX_REL_CHECK_CONSTRAINTS; - /* Report that we are now reindexing relations */ - pgstat_progress_update_param(PROGRESS_REPACK_PHASE, - PROGRESS_REPACK_PHASE_REBUILD_INDEX); + /* + * Ensure that the indexes have the same persistence as the parent + * relation. + */ + if (newrelpersistence == RELPERSISTENCE_UNLOGGED) + reindex_flags |= REINDEX_REL_FORCE_INDEXES_UNLOGGED; + else if (newrelpersistence == RELPERSISTENCE_PERMANENT) + reindex_flags |= REINDEX_REL_FORCE_INDEXES_PERMANENT; + + /* Report that we are now reindexing relations */ + pgstat_progress_update_param(PROGRESS_REPACK_PHASE, + PROGRESS_REPACK_PHASE_REBUILD_INDEX); - reindex_relation(NULL, OIDOldHeap, reindex_flags, &reindex_params); + reindex_relation(NULL, OIDOldHeap, reindex_flags, &reindex_params); + } /* Report that we are now doing clean up */ pgstat_progress_update_param(PROGRESS_REPACK_PHASE, @@ -1559,6 +1929,17 @@ finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap, object.objectId = OIDNewHeap; object.objectSubId = 0; + if (!reindex) + { + /* + * Make sure the changes in pg_class are visible. This is especially + * important if !swap_toast_by_content, so that the correct TOAST + * relation is dropped. (reindex_relation() above did not help in this + * case)) + */ + CommandCounterIncrement(); + } + /* * The new relation is local to our transaction and we know nothing * depends on it, so DROP_RESTRICT should be OK. @@ -1598,7 +1979,7 @@ finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap, /* Get the associated valid index to be renamed */ toastidx = toast_get_valid_index(newrel->rd_rel->reltoastrelid, - NoLock); + AccessExclusiveLock); /* rename the toast table ... */ snprintf(NewToastName, NAMEDATALEN, "pg_toast_%u", @@ -1858,7 +2239,8 @@ cluster_is_permitted_for_relation(RepackCommand cmd, Oid relid, Oid userid) * case, if an index name is given, it's up to the caller to resolve it. */ static Relation -process_single_relation(RepackStmt *stmt, ClusterParams *params) +process_single_relation(RepackStmt *stmt, LOCKMODE lockmode, bool isTopLevel, + ClusterParams *params) { Relation rel; Oid tableOid; @@ -1867,13 +2249,9 @@ process_single_relation(RepackStmt *stmt, ClusterParams *params) Assert(stmt->command == REPACK_COMMAND_CLUSTER || stmt->command == REPACK_COMMAND_REPACK); - /* - * Find, lock, and check permissions on the table. We obtain - * AccessExclusiveLock right away to avoid lock-upgrade hazard in the - * single-transaction case. - */ + /* Find, lock, and check permissions on the table. */ tableOid = RangeVarGetRelidExtended(stmt->relation->relation, - AccessExclusiveLock, + lockmode, 0, RangeVarCallbackMaintainsTable, NULL); @@ -1905,13 +2283,14 @@ process_single_relation(RepackStmt *stmt, ClusterParams *params) return rel; else { - Oid indexOid; + Oid indexOid = InvalidOid; indexOid = determine_clustered_index(rel, stmt->usingindex, stmt->indexname); if (OidIsValid(indexOid)) - check_index_is_clusterable(rel, indexOid, AccessExclusiveLock); - cluster_rel(stmt->command, rel, indexOid, params); + check_index_is_clusterable(rel, indexOid, lockmode); + + cluster_rel(stmt->command, rel, indexOid, params, isTopLevel); /* Do an analyze, if requested */ if (params->options & CLUOPT_ANALYZE) @@ -1994,3 +2373,1047 @@ RepackCommandAsString(RepackCommand cmd) } return "???"; /* keep compiler quiet */ } + + +/* + * Call this function before REPACK CONCURRENTLY starts to setup logical + * decoding. It makes sure that other users of the table put enough + * information into WAL. + * + * The point is that at various places we expect that the table we're + * processing is treated like a system catalog. For example, we need to be + * able to scan it using a "historic snapshot" anytime during the processing + * (as opposed to scanning only at the start point of the decoding, as logical + * replication does during initial table synchronization), in order to apply + * concurrent UPDATE / DELETE commands. + * + * Note that TOAST table needs no attention here as it's not scanned using + * historic snapshot. + */ +static void +begin_concurrent_repack(Relation rel) +{ + Oid toastrelid; + + /* + * Avoid logical decoding of other relations by this backend. The lock we + * have guarantees that the actual locator cannot be changed concurrently: + * TRUNCATE needs AccessExclusiveLock. + */ + Assert(CheckRelationLockedByMe(rel, ShareUpdateExclusiveLock, false)); + repacked_rel_locator = rel->rd_locator; + toastrelid = rel->rd_rel->reltoastrelid; + if (OidIsValid(toastrelid)) + { + Relation toastrel; + + /* Avoid logical decoding of other TOAST relations. */ + toastrel = table_open(toastrelid, AccessShareLock); + repacked_rel_toast_locator = toastrel->rd_locator; + table_close(toastrel, AccessShareLock); + } +} + +/* + * Call this when done with REPACK CONCURRENTLY. + */ +static void +end_concurrent_repack(void) +{ + /* + * Restore normal function of (future) logical decoding for this backend. + */ + repacked_rel_locator.relNumber = InvalidOid; + repacked_rel_toast_locator.relNumber = InvalidOid; +} + +/* + * Is this backend performing logical decoding on behalf of REPACK + * (CONCURRENTLY) ? + */ +bool +am_decoding_for_repack(void) +{ + return OidIsValid(repacked_rel_locator.relNumber); +} + +/* + * Does the WAL record contain a data change that this backend does not need + * to decode on behalf of REPACK (CONCURRENTLY)? + */ +bool +change_useless_for_repack(XLogRecordBuffer *buf) +{ + XLogReaderState *r = buf->record; + RelFileLocator locator; + + /* TOAST locator should not be set unless the main is. */ + Assert(!OidIsValid(repacked_rel_toast_locator.relNumber) || + OidIsValid(repacked_rel_locator.relNumber)); + + /* + * Backends not involved in REPACK (CONCURRENTLY) should not do the + * filtering. + */ + if (!am_decoding_for_repack()) + return false; + + /* + * If the record does not contain the block 0, it's probably not INSERT / + * UPDATE / DELETE. In any case, we do not have enough information to + * filter the change out. + */ + if (!XLogRecGetBlockTagExtended(r, 0, &locator, NULL, NULL, NULL)) + return false; + + /* + * Decode the change if it belongs to the table we are repacking, or if it + * belongs to its TOAST relation. + */ + if (RelFileLocatorEquals(locator, repacked_rel_locator)) + return false; + if (OidIsValid(repacked_rel_toast_locator.relNumber) && + RelFileLocatorEquals(locator, repacked_rel_toast_locator)) + return false; + + /* Filter out changes of other tables. */ + return true; +} + +/* + * This function is much like pg_create_logical_replication_slot() except that + * the new slot is neither released (if anyone else could read changes from + * our slot, we could miss changes other backends do while we copy the + * existing data into temporary table), nor persisted (it's easier to handle + * crash by restarting all the work from scratch). + */ +static LogicalDecodingContext * +setup_logical_decoding(Oid relid) +{ + Relation rel; + TupleDesc tupdesc; + LogicalDecodingContext *ctx; + RepackDecodingState *dstate = palloc0_object(RepackDecodingState); + + /* + * REPACK CONCURRENTLY is not allowed in a transaction block, so this + * should never fire. + */ + Assert(!TransactionIdIsValid(GetTopTransactionIdIfAny())); + + /* + * A single backend should not execute multiple REPACK commands at a time, + * so use PID to make the slot unique. + */ + snprintf(NameStr(dstate->slotname), NAMEDATALEN, "repack_%d", MyProcPid); + + /* + * Check if we can use logical decoding. + */ + CheckSlotPermissions(); + CheckLogicalDecodingRequirements(); + + /* RS_TEMPORARY so that the slot gets cleaned up on ERROR. */ + ReplicationSlotCreate(NameStr(dstate->slotname), true, RS_TEMPORARY, + false, false, false); + + /* + * Neither prepare_write nor do_write callback nor update_progress is + * useful for us. + */ + ctx = CreateInitDecodingContext(REPL_PLUGIN_NAME, + NIL, + true, + InvalidXLogRecPtr, + XL_ROUTINE(.page_read = read_local_xlog_page, + .segment_open = wal_segment_open, + .segment_close = wal_segment_close), + NULL, NULL, NULL); + + /* + * We don't have control on setting fast_forward, so at least check it. + */ + Assert(!ctx->fast_forward); + + DecodingContextFindStartpoint(ctx); + + /* Some WAL records should have been read. */ + Assert(ctx->reader->EndRecPtr != InvalidXLogRecPtr); + + XLByteToSeg(ctx->reader->EndRecPtr, repack_current_segment, + wal_segment_size); + + /* + * Setup structures to store decoded changes. + */ + dstate->relid = relid; + dstate->tstore = tuplestore_begin_heap(false, false, + maintenance_work_mem); + + /* Caller should already have the table locked. */ + rel = table_open(relid, NoLock); + tupdesc = CreateTupleDescCopy(RelationGetDescr(rel)); + dstate->tupdesc = tupdesc; + table_close(rel, NoLock); + + /* Initialize the descriptor to store the changes ... */ + dstate->tupdesc_change = CreateTemplateTupleDesc(1); + + TupleDescInitEntry(dstate->tupdesc_change, 1, NULL, BYTEAOID, -1, 0); + /* ... as well as the corresponding slot. */ + dstate->tsslot = MakeSingleTupleTableSlot(dstate->tupdesc_change, + &TTSOpsMinimalTuple); + + dstate->resowner = ResourceOwnerCreate(CurrentResourceOwner, + "logical decoding"); + + ctx->output_writer_private = dstate; + return ctx; +} + +/* + * Retrieve tuple from ConcurrentChange structure. + * + * The input data starts with the structure but it might not be appropriately + * aligned. + */ +static HeapTuple +get_changed_tuple(char *change) +{ + HeapTupleData tup_data; + HeapTuple result; + char *src; + + /* + * Ensure alignment before accessing the fields. (This is why we can't use + * heap_copytuple() instead of this function.) + */ + src = change + offsetof(ConcurrentChange, tup_data); + memcpy(&tup_data, src, sizeof(HeapTupleData)); + + result = (HeapTuple) palloc(HEAPTUPLESIZE + tup_data.t_len); + memcpy(result, &tup_data, sizeof(HeapTupleData)); + result->t_data = (HeapTupleHeader) ((char *) result + HEAPTUPLESIZE); + src = change + SizeOfConcurrentChange; + memcpy(result->t_data, src, result->t_len); + + return result; +} + +/* + * Decode logical changes from the WAL sequence up to end_of_wal. + */ +void +repack_decode_concurrent_changes(LogicalDecodingContext *ctx, + XLogRecPtr end_of_wal) +{ + RepackDecodingState *dstate; + ResourceOwner resowner_old; + + dstate = (RepackDecodingState *) ctx->output_writer_private; + resowner_old = CurrentResourceOwner; + CurrentResourceOwner = dstate->resowner; + + PG_TRY(); + { + while (ctx->reader->EndRecPtr < end_of_wal) + { + XLogRecord *record; + XLogSegNo segno_new; + char *errm = NULL; + XLogRecPtr end_lsn; + + record = XLogReadRecord(ctx->reader, &errm); + if (errm) + elog(ERROR, "%s", errm); + + if (record != NULL) + LogicalDecodingProcessRecord(ctx, ctx->reader); + + /* + * If WAL segment boundary has been crossed, inform the decoding + * system that the catalog_xmin can advance. (We can confirm more + * often, but a filling a single WAL segment should not take much + * time.) + */ + end_lsn = ctx->reader->EndRecPtr; + XLByteToSeg(end_lsn, segno_new, wal_segment_size); + if (segno_new != repack_current_segment) + { + LogicalConfirmReceivedLocation(end_lsn); + elog(DEBUG1, "REPACK: confirmed receive location %X/%X", + (uint32) (end_lsn >> 32), (uint32) end_lsn); + repack_current_segment = segno_new; + } + + CHECK_FOR_INTERRUPTS(); + } + InvalidateSystemCaches(); + CurrentResourceOwner = resowner_old; + } + PG_CATCH(); + { + /* clear all timetravel entries */ + InvalidateSystemCaches(); + CurrentResourceOwner = resowner_old; + PG_RE_THROW(); + } + PG_END_TRY(); +} + +/* + * Apply changes stored in 'file'. + */ +static void +apply_concurrent_changes(RepackDecodingState *dstate, ChangeDest *dest) +{ + Relation rel = dest->rel; + TupleTableSlot *index_slot, + *ident_slot; + HeapTuple tup_old = NULL; + + if (dstate->nchanges == 0) + return; + + /* TupleTableSlot is needed to pass the tuple to ExecInsertIndexTuples(). */ + index_slot = MakeSingleTupleTableSlot(dstate->tupdesc, &TTSOpsHeapTuple); + + /* A slot to fetch tuples from identity index. */ + ident_slot = table_slot_create(rel, NULL); + + while (tuplestore_gettupleslot(dstate->tstore, true, false, + dstate->tsslot)) + { + bool shouldFree; + HeapTuple tup_change, + tup, + tup_exist; + char *change_raw, + *src; + ConcurrentChange change; + bool isnull[1]; + Datum values[1]; + + CHECK_FOR_INTERRUPTS(); + + /* Get the change from the single-column tuple. */ + tup_change = ExecFetchSlotHeapTuple(dstate->tsslot, false, &shouldFree); + heap_deform_tuple(tup_change, dstate->tupdesc_change, values, isnull); + Assert(!isnull[0]); + + /* Make sure we access aligned data. */ + change_raw = (char *) DatumGetByteaP(values[0]); + src = (char *) VARDATA(change_raw); + memcpy(&change, src, SizeOfConcurrentChange); + + /* + * Extract the tuple from the change. The tuple is copied here because + * it might be assigned to 'tup_old', in which case it needs to + * survive into the next iteration. + */ + tup = get_changed_tuple(src); + + if (change.kind == CHANGE_UPDATE_OLD) + { + Assert(tup_old == NULL); + tup_old = tup; + } + else if (change.kind == CHANGE_INSERT) + { + Assert(tup_old == NULL); + + apply_concurrent_insert(rel, tup, dest->iistate, index_slot); + + pfree(tup); + } + else if (change.kind == CHANGE_UPDATE_NEW || + change.kind == CHANGE_DELETE) + { + HeapTuple tup_key; + + if (change.kind == CHANGE_UPDATE_NEW) + { + tup_key = tup_old != NULL ? tup_old : tup; + } + else + { + Assert(tup_old == NULL); + tup_key = tup; + } + + /* + * Find the tuple to be updated or deleted. + */ + tup_exist = find_target_tuple(rel, dest, tup_key, ident_slot); + if (tup_exist == NULL) + elog(ERROR, "failed to find target tuple"); + + if (change.kind == CHANGE_UPDATE_NEW) + apply_concurrent_update(rel, tup, tup_exist, dest->iistate, + index_slot); + else + apply_concurrent_delete(rel, tup_exist); + + if (tup_old != NULL) + { + pfree(tup_old); + tup_old = NULL; + } + + pfree(tup); + } + else + elog(ERROR, "unrecognized kind of change: %d", change.kind); + + /* + * If a change was applied now, increment CID for next writes and + * update the snapshot so it sees the changes we've applied so far. + */ + if (change.kind != CHANGE_UPDATE_OLD) + { + CommandCounterIncrement(); + UpdateActiveSnapshotCommandId(); + } + + /* TTSOpsMinimalTuple has .get_heap_tuple==NULL. */ + Assert(shouldFree); + pfree(tup_change); + } + + tuplestore_clear(dstate->tstore); + dstate->nchanges = 0; + + /* Cleanup. */ + ExecDropSingleTupleTableSlot(index_slot); + ExecDropSingleTupleTableSlot(ident_slot); +} + +static void +apply_concurrent_insert(Relation rel, HeapTuple tup, IndexInsertState *iistate, + TupleTableSlot *index_slot) +{ + List *recheck; + + /* + * Like simple_heap_insert(), but make sure that the INSERT is not + * logically decoded - see reform_and_rewrite_tuple() for more + * information. + */ + heap_insert(rel, tup, GetCurrentCommandId(true), HEAP_INSERT_NO_LOGICAL, + NULL); + + /* + * Update indexes. + * + * In case functions in the index need the active snapshot and caller + * hasn't set one. + */ + ExecStoreHeapTuple(tup, index_slot, false); + recheck = ExecInsertIndexTuples(iistate->rri, + index_slot, + iistate->estate, + false, /* update */ + false, /* noDupErr */ + NULL, /* specConflict */ + NIL, /* arbiterIndexes */ + false /* onlySummarizing */ + ); + + /* + * If recheck is required, it must have been performed on the source + * relation by now. (All the logical changes we process here are already + * committed.) + */ + list_free(recheck); + + pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_INSERTED, 1); +} + +static void +apply_concurrent_update(Relation rel, HeapTuple tup, HeapTuple tup_target, + IndexInsertState *iistate, TupleTableSlot *index_slot) +{ + LockTupleMode lockmode; + TM_FailureData tmfd; + TU_UpdateIndexes update_indexes; + TM_Result res; + List *recheck; + + /* + * Write the new tuple into the new heap. ('tup' gets the TID assigned + * here.) + * + * Do it like in simple_heap_update(), except for 'wal_logical' (and + * except for 'wait'). + */ + res = heap_update(rel, &tup_target->t_self, tup, + GetCurrentCommandId(true), + InvalidSnapshot, + false, /* no wait - only we are doing changes */ + &tmfd, &lockmode, &update_indexes, + false /* wal_logical */ ); + if (res != TM_Ok) + ereport(ERROR, (errmsg("failed to apply concurrent UPDATE"))); + + ExecStoreHeapTuple(tup, index_slot, false); + + if (update_indexes != TU_None) + { + recheck = ExecInsertIndexTuples(iistate->rri, + index_slot, + iistate->estate, + true, /* update */ + false, /* noDupErr */ + NULL, /* specConflict */ + NIL, /* arbiterIndexes */ + /* onlySummarizing */ + update_indexes == TU_Summarizing); + list_free(recheck); + } + + pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_UPDATED, 1); +} + +static void +apply_concurrent_delete(Relation rel, HeapTuple tup_target) +{ + TM_Result res; + TM_FailureData tmfd; + + /* + * Delete tuple from the new heap. + * + * Do it like in simple_heap_delete(), except for 'wal_logical' (and + * except for 'wait'). + */ + res = heap_delete(rel, &tup_target->t_self, GetCurrentCommandId(true), + InvalidSnapshot, false, + &tmfd, + false, /* no wait - only we are doing changes */ + false /* wal_logical */ ); + + if (res != TM_Ok) + ereport(ERROR, (errmsg("failed to apply concurrent DELETE"))); + + pgstat_progress_incr_param(PROGRESS_REPACK_HEAP_TUPLES_DELETED, 1); +} + +/* + * Find the tuple to be updated or deleted. + * + * 'tup_key' is a tuple containing the key values for the scan. + */ +static HeapTuple +find_target_tuple(Relation rel, ChangeDest *dest, HeapTuple tup_key, + TupleTableSlot *ident_slot) +{ + Relation ident_index = dest->ident_index; + IndexScanDesc scan; + Form_pg_index ident_form; + int2vector *ident_indkey; + HeapTuple result = NULL; + + /* XXX no instrumentation for now */ + scan = index_beginscan(rel, ident_index, GetActiveSnapshot(), + NULL, dest->ident_key_nentries, 0); + + /* + * Scan key is passed by caller, so it does not have to be constructed + * multiple times. Key entries have all fields initialized, except for + * sk_argument. + */ + index_rescan(scan, dest->ident_key, dest->ident_key_nentries, NULL, 0); + + /* Info needed to retrieve key values from heap tuple. */ + ident_form = ident_index->rd_index; + ident_indkey = &ident_form->indkey; + + /* Use the incoming tuple to finalize the scan key. */ + for (int i = 0; i < scan->numberOfKeys; i++) + { + ScanKey entry; + bool isnull; + int16 attno_heap; + + entry = &scan->keyData[i]; + attno_heap = ident_indkey->values[i]; + entry->sk_argument = heap_getattr(tup_key, + attno_heap, + rel->rd_att, + &isnull); + Assert(!isnull); + } + if (index_getnext_slot(scan, ForwardScanDirection, ident_slot)) + { + bool shouldFree; + + result = ExecFetchSlotHeapTuple(ident_slot, false, &shouldFree); + /* TTSOpsBufferHeapTuple has .get_heap_tuple != NULL. */ + Assert(!shouldFree); + } + index_endscan(scan); + + return result; +} + +/* + * Decode and apply concurrent changes. + */ +static void +process_concurrent_changes(LogicalDecodingContext *decoding_ctx, + XLogRecPtr end_of_wal, ChangeDest *dest) +{ + RepackDecodingState *dstate; + + pgstat_progress_update_param(PROGRESS_REPACK_PHASE, + PROGRESS_REPACK_PHASE_CATCH_UP); + + dstate = (RepackDecodingState *) decoding_ctx->output_writer_private; + + repack_decode_concurrent_changes(decoding_ctx, end_of_wal); + + if (dstate->nchanges == 0) + return; + + apply_concurrent_changes(dstate, dest); +} + +/* + * Initialize IndexInsertState for index specified by ident_index_id. + * + * While doing that, also return the identity index in *ident_index_p. + */ +static IndexInsertState * +get_index_insert_state(Relation relation, Oid ident_index_id, + Relation *ident_index_p) +{ + EState *estate; + int i; + IndexInsertState *result; + Relation ident_index = NULL; + + result = (IndexInsertState *) palloc0(sizeof(IndexInsertState)); + estate = CreateExecutorState(); + + result->rri = (ResultRelInfo *) palloc(sizeof(ResultRelInfo)); + InitResultRelInfo(result->rri, relation, 0, 0, 0); + ExecOpenIndices(result->rri, false); + + /* + * Find the relcache entry of the identity index so that we spend no extra + * effort to open / close it. + */ + for (i = 0; i < result->rri->ri_NumIndices; i++) + { + Relation ind_rel; + + ind_rel = result->rri->ri_IndexRelationDescs[i]; + if (ind_rel->rd_id == ident_index_id) + ident_index = ind_rel; + } + if (ident_index == NULL) + elog(ERROR, "failed to open identity index"); + + /* Only initialize fields needed by ExecInsertIndexTuples(). */ + result->estate = estate; + + *ident_index_p = ident_index; + return result; +} + +/* + * Build scan key to process logical changes. + */ +static ScanKey +build_identity_key(Oid ident_idx_oid, Relation rel_src, int *nentries) +{ + Relation ident_idx_rel; + Form_pg_index ident_idx; + int n, + i; + ScanKey result; + + Assert(OidIsValid(ident_idx_oid)); + ident_idx_rel = index_open(ident_idx_oid, AccessShareLock); + ident_idx = ident_idx_rel->rd_index; + n = ident_idx->indnatts; + result = (ScanKey) palloc(sizeof(ScanKeyData) * n); + for (i = 0; i < n; i++) + { + ScanKey entry; + int16 relattno; + Form_pg_attribute att; + Oid opfamily, + opcintype, + opno, + opcode; + + entry = &result[i]; + relattno = ident_idx->indkey.values[i]; + if (relattno >= 1) + { + TupleDesc desc; + + desc = rel_src->rd_att; + att = TupleDescAttr(desc, relattno - 1); + } + else + elog(ERROR, "unexpected attribute number %d in index", relattno); + + opfamily = ident_idx_rel->rd_opfamily[i]; + opcintype = ident_idx_rel->rd_opcintype[i]; + opno = get_opfamily_member(opfamily, opcintype, opcintype, + BTEqualStrategyNumber); + + if (!OidIsValid(opno)) + elog(ERROR, "failed to find = operator for type %u", opcintype); + + opcode = get_opcode(opno); + if (!OidIsValid(opcode)) + elog(ERROR, "failed to find = operator for operator %u", opno); + + /* Initialize everything but argument. */ + ScanKeyInit(entry, + i + 1, + BTEqualStrategyNumber, opcode, + (Datum) NULL); + entry->sk_collation = att->attcollation; + } + index_close(ident_idx_rel, AccessShareLock); + + *nentries = n; + return result; +} + +static void +free_index_insert_state(IndexInsertState *iistate) +{ + ExecCloseIndices(iistate->rri); + FreeExecutorState(iistate->estate); + pfree(iistate->rri); + pfree(iistate); +} + +static void +cleanup_logical_decoding(LogicalDecodingContext *ctx) +{ + RepackDecodingState *dstate; + + dstate = (RepackDecodingState *) ctx->output_writer_private; + + ExecDropSingleTupleTableSlot(dstate->tsslot); + FreeTupleDesc(dstate->tupdesc_change); + FreeTupleDesc(dstate->tupdesc); + tuplestore_end(dstate->tstore); + + FreeDecodingContext(ctx); + + ReplicationSlotRelease(); + ReplicationSlotDrop(NameStr(dstate->slotname), false); + pfree(dstate); +} + +/* + * The final steps of rebuild_relation() for concurrent processing. + * + * On entry, NewHeap is locked in AccessExclusiveLock mode. OldHeap and its + * clustering index (if one is passed) are still locked in a mode that allows + * concurrent data changes. On exit, both tables and their indexes are closed, + * but locked in AccessExclusiveLock mode. + */ +static void +rebuild_relation_finish_concurrent(Relation NewHeap, Relation OldHeap, + LogicalDecodingContext *decoding_ctx, + TransactionId frozenXid, + MultiXactId cutoffMulti) +{ + LOCKMODE lockmode_old PG_USED_FOR_ASSERTS_ONLY; + List *ind_oids_new; + Oid old_table_oid = RelationGetRelid(OldHeap); + Oid new_table_oid = RelationGetRelid(NewHeap); + List *ind_oids_old = RelationGetIndexList(OldHeap); + ListCell *lc, + *lc2; + char relpersistence; + bool is_system_catalog; + Oid ident_idx_old, + ident_idx_new; + XLogRecPtr wal_insert_ptr, + end_of_wal; + char dummy_rec_data = '\0'; + Relation *ind_refs, + *ind_refs_p; + int nind; + ChangeDest chgdst; + + /* Like in cluster_rel(). */ + lockmode_old = ShareUpdateExclusiveLock; + Assert(CheckRelationLockedByMe(OldHeap, lockmode_old, false)); + /* This is expected from the caller. */ + Assert(CheckRelationLockedByMe(NewHeap, AccessExclusiveLock, false)); + + ident_idx_old = RelationGetReplicaIndex(OldHeap); + + /* + * Unlike the exclusive case, we build new indexes for the new relation + * rather than swapping the storage and reindexing the old relation. The + * point is that the index build can take some time, so we do it before we + * get AccessExclusiveLock on the old heap and therefore we cannot swap + * the heap storage yet. + * + * index_create() will lock the new indexes using AccessExclusiveLock - no + * need to change that. At the same time, we use ShareUpdateExclusiveLock + * to lock the existing indexes - that should be enough to prevent others + * from changing them while we're repacking the relation. The lock on + * table should prevent others from changing the index column list, but + * might not be enough for commands like ALTER INDEX ... SET ... (Those + * are not necessarily dangerous, but can make user confused if the + * changes they do get lost due to REPACK.) + */ + ind_oids_new = build_new_indexes(NewHeap, OldHeap, ind_oids_old); + + /* + * Processing shouldn't start w/o valid identity index. + */ + Assert(OidIsValid(ident_idx_old)); + + /* Find "identity index" on the new relation. */ + ident_idx_new = InvalidOid; + forboth(lc, ind_oids_old, lc2, ind_oids_new) + { + Oid ind_old = lfirst_oid(lc); + Oid ind_new = lfirst_oid(lc2); + + if (ident_idx_old == ind_old) + { + ident_idx_new = ind_new; + break; + } + } + if (!OidIsValid(ident_idx_new)) + + /* + * Should not happen, given our lock on the old relation. + */ + ereport(ERROR, + (errmsg("identity index missing on the new relation"))); + + /* Gather information to apply concurrent changes. */ + chgdst.rel = NewHeap; + chgdst.iistate = get_index_insert_state(NewHeap, ident_idx_new, + &chgdst.ident_index); + chgdst.ident_key = build_identity_key(ident_idx_new, OldHeap, + &chgdst.ident_key_nentries); + + /* + * During testing, wait for another backend to perform concurrent data + * changes which we will process below. + */ + INJECTION_POINT("repack-concurrently-before-lock", NULL); + + /* + * Flush all WAL records inserted so far (possibly except for the last + * incomplete page, see GetInsertRecPtr), to minimize the amount of data + * we need to flush while holding exclusive lock on the source table. + */ + wal_insert_ptr = GetInsertRecPtr(); + XLogFlush(wal_insert_ptr); + end_of_wal = GetFlushRecPtr(NULL); + + /* + * Apply concurrent changes first time, to minimize the time we need to + * hold AccessExclusiveLock. (Quite some amount of WAL could have been + * written during the data copying and index creation.) + */ + process_concurrent_changes(decoding_ctx, end_of_wal, &chgdst); + + /* + * Acquire AccessExclusiveLock on the table, its TOAST relation (if there + * is one), all its indexes, so that we can swap the files. + */ + LockRelationOid(old_table_oid, AccessExclusiveLock); + + /* + * Lock all indexes now, not only the clustering one: all indexes need to + * have their files swapped. While doing that, store their relation + * references in an array, to handle predicate locks below. + */ + ind_refs_p = ind_refs = palloc_array(Relation, list_length(ind_oids_old)); + nind = 0; + foreach_oid(ind_oid, ind_oids_old) + { + Relation index; + + index = index_open(ind_oid, AccessExclusiveLock); + + /* + * TODO 1) Do we need to check if ALTER INDEX was executed since the + * new index was created in build_new_indexes()? 2) Specifically for + * the clustering index, should check_index_is_clusterable() be called + * here? (Not sure about the latter: ShareUpdateExclusiveLock on the + * table probably blocks all commands that affect the result of + * check_index_is_clusterable().) + */ + *ind_refs_p = index; + ind_refs_p++; + nind++; + } + + /* + * Lock the OldHeap's TOAST relation exclusively - again, the lock is + * needed to swap the files. + */ + if (OidIsValid(OldHeap->rd_rel->reltoastrelid)) + LockRelationOid(OldHeap->rd_rel->reltoastrelid, AccessExclusiveLock); + + /* + * Tuples and pages of the old heap will be gone, but the heap will stay. + */ + TransferPredicateLocksToHeapRelation(OldHeap); + /* The same for indexes. */ + for (int i = 0; i < nind; i++) + { + Relation index = ind_refs[i]; + + TransferPredicateLocksToHeapRelation(index); + + /* + * References to indexes on the old relation are not needed anymore, + * however locks stay till the end of the transaction. + */ + index_close(index, NoLock); + } + pfree(ind_refs); + + /* + * Flush anything we see in WAL, to make sure that all changes committed + * while we were waiting for the exclusive lock are available for + * decoding. This should not be necessary if all backends had + * synchronous_commit set, but we can't rely on this setting. + * + * Unfortunately, GetInsertRecPtr() may lag behind the actual insert + * position, and GetLastImportantRecPtr() points at the start of the last + * record rather than at the end. Thus the simplest way to determine the + * insert position is to insert a dummy record and use its LSN. + * + * XXX Consider using GetLastImportantRecPtr() and adding the size of the + * last record (plus the total size of all the page headers the record + * spans)? + */ + XLogBeginInsert(); + XLogRegisterData(&dummy_rec_data, 1); + wal_insert_ptr = XLogInsert(RM_XLOG_ID, XLOG_NOOP); + XLogFlush(wal_insert_ptr); + end_of_wal = GetFlushRecPtr(NULL); + + /* Apply the concurrent changes again. */ + process_concurrent_changes(decoding_ctx, end_of_wal, &chgdst); + + /* Remember info about rel before closing OldHeap */ + relpersistence = OldHeap->rd_rel->relpersistence; + is_system_catalog = IsSystemRelation(OldHeap); + + pgstat_progress_update_param(PROGRESS_REPACK_PHASE, + PROGRESS_REPACK_PHASE_SWAP_REL_FILES); + + /* + * Even ShareUpdateExclusiveLock should have prevented others from + * creating / dropping indexes (even using the CONCURRENTLY option), so we + * do not need to check whether the lists match. + */ + forboth(lc, ind_oids_old, lc2, ind_oids_new) + { + Oid ind_old = lfirst_oid(lc); + Oid ind_new = lfirst_oid(lc2); + Oid mapped_tables[4]; + + /* Zero out possible results from swapped_relation_files */ + memset(mapped_tables, 0, sizeof(mapped_tables)); + + swap_relation_files(ind_old, ind_new, + (old_table_oid == RelationRelationId), + false, /* swap_toast_by_content */ + true, + InvalidTransactionId, + InvalidMultiXactId, + mapped_tables); + +#ifdef USE_ASSERT_CHECKING + + /* + * Concurrent processing is not supported for system relations, so + * there should be no mapped tables. + */ + for (int i = 0; i < 4; i++) + Assert(mapped_tables[i] == 0); +#endif + } + + /* The new indexes must be visible for deletion. */ + CommandCounterIncrement(); + + /* Close the old heap but keep lock until transaction commit. */ + table_close(OldHeap, NoLock); + /* Close the new heap. (We didn't have to open its indexes). */ + table_close(NewHeap, NoLock); + + /* Cleanup what we don't need anymore. (And close the identity index.) */ + pfree(chgdst.ident_key); + free_index_insert_state(chgdst.iistate); + + /* + * Swap the relations and their TOAST relations and TOAST indexes. This + * also drops the new relation and its indexes. + * + * (System catalogs are currently not supported.) + */ + Assert(!is_system_catalog); + finish_heap_swap(old_table_oid, new_table_oid, + is_system_catalog, + false, /* swap_toast_by_content */ + false, true, false, + frozenXid, cutoffMulti, + relpersistence); +} + +/* + * Build indexes on NewHeap according to those on OldHeap. + * + * OldIndexes is the list of index OIDs on OldHeap. The contained indexes end + * up locked using ShareUpdateExclusiveLock. + * + * A list of OIDs of the corresponding indexes created on NewHeap is + * returned. The order of items does match, so we can use these arrays to swap + * index storage. + */ +static List * +build_new_indexes(Relation NewHeap, Relation OldHeap, List *OldIndexes) +{ + List *result = NIL; + + pgstat_progress_update_param(PROGRESS_REPACK_PHASE, + PROGRESS_REPACK_PHASE_REBUILD_INDEX); + + foreach_oid(ind_oid, OldIndexes) + { + Oid ind_oid_new; + char *newName; + Relation ind; + + ind = index_open(ind_oid, ShareUpdateExclusiveLock); + + newName = ChooseRelationName(get_rel_name(ind_oid), + NULL, + "repacknew", + get_rel_namespace(ind->rd_index->indrelid), + false); + ind_oid_new = index_create_copy(NewHeap, ind_oid, + ind->rd_rel->reltablespace, newName, + false); + result = lappend_oid(result, ind_oid_new); + + index_close(ind, NoLock); + } + + return result; +} diff --git a/src/backend/commands/matview.c b/src/backend/commands/matview.c index 81a55a33ef2..ebc70f5bead 100644 --- a/src/backend/commands/matview.c +++ b/src/backend/commands/matview.c @@ -892,7 +892,7 @@ refresh_by_match_merge(Oid matviewOid, Oid tempOid, Oid relowner, static void refresh_by_heap_swap(Oid matviewOid, Oid OIDNewHeap, char relpersistence) { - finish_heap_swap(matviewOid, OIDNewHeap, false, false, true, true, + finish_heap_swap(matviewOid, OIDNewHeap, false, false, true, true, true, RecentXmin, ReadNextMultiXactId(), relpersistence); } diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index f976c0e5c7e..296387c7889 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -6025,6 +6025,7 @@ ATRewriteTables(AlterTableStmt *parsetree, List **wqueue, LOCKMODE lockmode, finish_heap_swap(tab->relid, OIDNewHeap, false, false, true, !OidIsValid(tab->newTableSpace), + true, RecentXmin, ReadNextMultiXactId(), persistence); diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index c363467a9cc..30a2b2578f7 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -126,7 +126,7 @@ static void vac_truncate_clog(TransactionId frozenXID, TransactionId lastSaneFrozenXid, MultiXactId lastSaneMinMulti); static bool vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, - BufferAccessStrategy bstrategy); + BufferAccessStrategy bstrategy, bool isTopLevel); static double compute_parallel_delay(void); static VacOptValue get_vacoptval_from_boolean(DefElem *def); static bool vac_tid_reaped(ItemPointer itemptr, void *state); @@ -629,7 +629,8 @@ vacuum(List *relations, const VacuumParams params, BufferAccessStrategy bstrateg if (params.options & VACOPT_VACUUM) { - if (!vacuum_rel(vrel->oid, vrel->relation, params, bstrategy)) + if (!vacuum_rel(vrel->oid, vrel->relation, params, bstrategy, + isTopLevel)) continue; } @@ -1999,7 +2000,7 @@ vac_truncate_clog(TransactionId frozenXID, */ static bool vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, - BufferAccessStrategy bstrategy) + BufferAccessStrategy bstrategy, bool isTopLevel) { LOCKMODE lmode; Relation rel; @@ -2290,7 +2291,7 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, /* VACUUM FULL is a variant of REPACK; see cluster.c */ cluster_rel(REPACK_COMMAND_VACUUMFULL, rel, InvalidOid, - &cluster_params); + &cluster_params, isTopLevel); /* cluster_rel closes the relation, but keeps lock */ rel = NULL; @@ -2333,7 +2334,8 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params, toast_vacuum_params.options |= VACOPT_PROCESS_MAIN; toast_vacuum_params.toast_parent = relid; - vacuum_rel(toast_relid, NULL, toast_vacuum_params, bstrategy); + vacuum_rel(toast_relid, NULL, toast_vacuum_params, bstrategy, + isTopLevel); } /* diff --git a/src/backend/meson.build b/src/backend/meson.build index 712a857cdb4..3e43edf48a0 100644 --- a/src/backend/meson.build +++ b/src/backend/meson.build @@ -194,5 +194,6 @@ pg_test_mod_args = pg_mod_args + { subdir('jit/llvm') subdir('replication/libpqwalreceiver') subdir('replication/pgoutput') +subdir('replication/pgoutput_repack') subdir('snowball') subdir('utils/mb/conversion_procs') diff --git a/src/backend/replication/logical/decode.c b/src/backend/replication/logical/decode.c index e25dd6bc366..6a17e101f6a 100644 --- a/src/backend/replication/logical/decode.c +++ b/src/backend/replication/logical/decode.c @@ -33,6 +33,7 @@ #include "access/xlogreader.h" #include "access/xlogrecord.h" #include "catalog/pg_control.h" +#include "commands/cluster.h" #include "replication/decode.h" #include "replication/logical.h" #include "replication/message.h" @@ -420,7 +421,8 @@ heap2_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf) { case XLOG_HEAP2_MULTI_INSERT: if (SnapBuildProcessChange(builder, xid, buf->origptr) && - !ctx->fast_forward) + !ctx->fast_forward && + !change_useless_for_repack(buf)) DecodeMultiInsert(ctx, buf); break; case XLOG_HEAP2_NEW_CID: @@ -467,6 +469,15 @@ heap_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf) TransactionId xid = XLogRecGetXid(buf->record); SnapBuild *builder = ctx->snapshot_builder; + /* + * XXX Should we return here if change_useless_for_repack() returns true, + * instead of calling the function below? Unlike the fast-forward case, we + * shouldn't need the base snapshot for the containing transaction until + * we receive a change that belongs to the table being REPACKed. Thus it + * should be fine to skip SnapBuildProcessChange(), and therefore + * reorderbuffer.c can create the transaction later. + */ + ReorderBufferProcessXid(ctx->reorder, xid, buf->origptr); /* @@ -484,7 +495,8 @@ heap_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf) { case XLOG_HEAP_INSERT: if (SnapBuildProcessChange(builder, xid, buf->origptr) && - !ctx->fast_forward) + !ctx->fast_forward && + !change_useless_for_repack(buf)) DecodeInsert(ctx, buf); break; @@ -496,19 +508,22 @@ heap_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf) case XLOG_HEAP_HOT_UPDATE: case XLOG_HEAP_UPDATE: if (SnapBuildProcessChange(builder, xid, buf->origptr) && - !ctx->fast_forward) + !ctx->fast_forward && + !change_useless_for_repack(buf)) DecodeUpdate(ctx, buf); break; case XLOG_HEAP_DELETE: if (SnapBuildProcessChange(builder, xid, buf->origptr) && - !ctx->fast_forward) + !ctx->fast_forward && + !change_useless_for_repack(buf)) DecodeDelete(ctx, buf); break; case XLOG_HEAP_TRUNCATE: if (SnapBuildProcessChange(builder, xid, buf->origptr) && - !ctx->fast_forward) + !ctx->fast_forward && + !change_useless_for_repack(buf)) DecodeTruncate(ctx, buf); break; @@ -524,7 +539,8 @@ heap_decode(LogicalDecodingContext *ctx, XLogRecordBuffer *buf) case XLOG_HEAP_CONFIRM: if (SnapBuildProcessChange(builder, xid, buf->origptr) && - !ctx->fast_forward) + !ctx->fast_forward && + !change_useless_for_repack(buf)) DecodeSpecConfirm(ctx, buf); break; @@ -1021,6 +1037,15 @@ DecodeDelete(LogicalDecodingContext *ctx, XLogRecordBuffer *buf) xlrec = (xl_heap_delete *) XLogRecGetData(r); + /* + * Ignore changes which are considered useless for logical + * decoding. Currently such changes are created by REPACK (CONCURRENTLY) + * when replays DELETE commands on the new table (which is not yet visible + * to other transactions). + */ + if (xlrec->flags & XLH_DELETE_NO_LOGICAL) + return; + /* only interested in our database */ XLogRecGetBlockTag(r, 0, &target_locator, NULL, NULL); if (target_locator.dbOid != ctx->slot->data.database) diff --git a/src/backend/replication/logical/snapbuild.c b/src/backend/replication/logical/snapbuild.c index a738ad8a864..ffe6aa7f7bb 100644 --- a/src/backend/replication/logical/snapbuild.c +++ b/src/backend/replication/logical/snapbuild.c @@ -486,6 +486,27 @@ SnapBuildInitialSnapshot(SnapBuild *builder) return SnapBuildMVCCFromHistoric(snap, true); } +/* + * Build an MVCC snapshot for the initial data load performed by REPACK + * CONCURRENTLY command. + * + * The snapshot will only be used to scan one particular relation, which is + * treated like a catalog (therefore ->building_full_snapshot is not + * important), and the caller should already have a replication slot setup (so + * we do not set MyProc->xmin). XXX Do we yet need to add some restrictions? + */ +Snapshot +SnapBuildInitialSnapshotForRepack(SnapBuild *builder) +{ + Snapshot snap; + + Assert(builder->state == SNAPBUILD_CONSISTENT); + Assert(builder->building_full_snapshot); + + snap = SnapBuildBuildSnapshot(builder); + return SnapBuildMVCCFromHistoric(snap, false); +} + /* * Turn a historic MVCC snapshot into an ordinary MVCC snapshot. * diff --git a/src/backend/replication/pgoutput_repack/Makefile b/src/backend/replication/pgoutput_repack/Makefile new file mode 100644 index 00000000000..4efeb713b70 --- /dev/null +++ b/src/backend/replication/pgoutput_repack/Makefile @@ -0,0 +1,32 @@ +#------------------------------------------------------------------------- +# +# Makefile-- +# Makefile for src/backend/replication/pgoutput_repack +# +# IDENTIFICATION +# src/backend/replication/pgoutput_repack +# +#------------------------------------------------------------------------- + +subdir = src/backend/replication/pgoutput_repack +top_builddir = ../../../.. +include $(top_builddir)/src/Makefile.global + +OBJS = \ + $(WIN32RES) \ + pgoutput_repack.o +PGFILEDESC = "pgoutput_repack - logical replication output plugin for REPACK command" +NAME = pgoutput_repack + +all: all-shared-lib + +include $(top_srcdir)/src/Makefile.shlib + +install: all installdirs install-lib + +installdirs: installdirs-lib + +uninstall: uninstall-lib + +clean distclean: clean-lib + rm -f $(OBJS) diff --git a/src/backend/replication/pgoutput_repack/meson.build b/src/backend/replication/pgoutput_repack/meson.build new file mode 100644 index 00000000000..133e865a4a0 --- /dev/null +++ b/src/backend/replication/pgoutput_repack/meson.build @@ -0,0 +1,18 @@ +# Copyright (c) 2022-2024, PostgreSQL Global Development Group + +pgoutput_repack_sources = files( + 'pgoutput_repack.c', +) + +if host_system == 'windows' + pgoutput_repack_sources += rc_lib_gen.process(win32ver_rc, extra_args: [ + '--NAME', 'pgoutput_repack', + '--FILEDESC', 'pgoutput_repack - logical replication output plugin for REPACK command',]) +endif + +pgoutput_repack = shared_module('pgoutput_repack', + pgoutput_repack_sources, + kwargs: pg_mod_args, +) + +backend_targets += pgoutput_repack diff --git a/src/backend/replication/pgoutput_repack/pgoutput_repack.c b/src/backend/replication/pgoutput_repack/pgoutput_repack.c new file mode 100644 index 00000000000..a074eed393d --- /dev/null +++ b/src/backend/replication/pgoutput_repack/pgoutput_repack.c @@ -0,0 +1,241 @@ +/*------------------------------------------------------------------------- + * + * pgoutput_repack.c + * Logical Replication output plugin for REPACK command + * + * Copyright (c) 2012-2026, PostgreSQL Global Development Group + * + * IDENTIFICATION + * src/backend/replication/pgoutput_repack/pgoutput_repack.c + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include "access/heaptoast.h" +#include "commands/cluster.h" +#include "replication/snapbuild.h" +#include "utils/memutils.h" + +PG_MODULE_MAGIC; + +static void plugin_startup(LogicalDecodingContext *ctx, + OutputPluginOptions *opt, bool is_init); +static void plugin_shutdown(LogicalDecodingContext *ctx); +static void plugin_begin_txn(LogicalDecodingContext *ctx, + ReorderBufferTXN *txn); +static void plugin_commit_txn(LogicalDecodingContext *ctx, + ReorderBufferTXN *txn, XLogRecPtr commit_lsn); +static void plugin_change(LogicalDecodingContext *ctx, ReorderBufferTXN *txn, + Relation rel, ReorderBufferChange *change); +static void store_change(LogicalDecodingContext *ctx, + ConcurrentChangeKind kind, HeapTuple tuple); + +void +_PG_output_plugin_init(OutputPluginCallbacks *cb) +{ + AssertVariableIsOfType(&_PG_output_plugin_init, LogicalOutputPluginInit); + + cb->startup_cb = plugin_startup; + cb->begin_cb = plugin_begin_txn; + cb->change_cb = plugin_change; + cb->commit_cb = plugin_commit_txn; + cb->shutdown_cb = plugin_shutdown; +} + + +/* initialize this plugin */ +static void +plugin_startup(LogicalDecodingContext *ctx, OutputPluginOptions *opt, + bool is_init) +{ + ctx->output_plugin_private = NULL; + + /* Probably unnecessary, as we don't use the SQL interface ... */ + opt->output_type = OUTPUT_PLUGIN_BINARY_OUTPUT; + + if (ctx->output_plugin_options != NIL) + { + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("This plugin does not expect any options"))); + } +} + +static void +plugin_shutdown(LogicalDecodingContext *ctx) +{ +} + +/* + * As we don't release the slot during processing of particular table, there's + * no room for SQL interface, even for debugging purposes. Therefore we need + * neither OutputPluginPrepareWrite() nor OutputPluginWrite() in the plugin + * callbacks. (Although we might want to write custom callbacks, this API + * seems to be unnecessarily generic for our purposes.) + */ + +/* BEGIN callback */ +static void +plugin_begin_txn(LogicalDecodingContext *ctx, ReorderBufferTXN *txn) +{ +} + +/* COMMIT callback */ +static void +plugin_commit_txn(LogicalDecodingContext *ctx, ReorderBufferTXN *txn, + XLogRecPtr commit_lsn) +{ +} + +/* + * Callback for individual changed tuples + */ +static void +plugin_change(LogicalDecodingContext *ctx, ReorderBufferTXN *txn, + Relation relation, ReorderBufferChange *change) +{ + RepackDecodingState *dstate; + + dstate = (RepackDecodingState *) ctx->output_writer_private; + + /* Only interested in one particular relation. */ + if (relation->rd_id != dstate->relid) + return; + + /* Decode entry depending on its type */ + switch (change->action) + { + case REORDER_BUFFER_CHANGE_INSERT: + { + HeapTuple newtuple; + + newtuple = change->data.tp.newtuple; + + /* + * Identity checks in the main function should have made this + * impossible. + */ + if (newtuple == NULL) + elog(ERROR, "Incomplete insert info."); + + store_change(ctx, CHANGE_INSERT, newtuple); + } + break; + case REORDER_BUFFER_CHANGE_UPDATE: + { + HeapTuple oldtuple, + newtuple; + + oldtuple = change->data.tp.oldtuple; + newtuple = change->data.tp.newtuple; + + if (newtuple == NULL) + elog(ERROR, "Incomplete update info."); + + if (oldtuple != NULL) + store_change(ctx, CHANGE_UPDATE_OLD, oldtuple); + + store_change(ctx, CHANGE_UPDATE_NEW, newtuple); + } + break; + case REORDER_BUFFER_CHANGE_DELETE: + { + HeapTuple oldtuple; + + oldtuple = change->data.tp.oldtuple; + + if (oldtuple == NULL) + elog(ERROR, "Incomplete delete info."); + + store_change(ctx, CHANGE_DELETE, oldtuple); + } + break; + default: + /* + * Should not come here. This includes TRUNCATE of the table being + * processed. heap_decode() cannot check the file locator easily, + * but we assume that TRUNCATE uses AccessExclusiveLock on the + * table so it should not occur during REPACK (CONCURRENTLY). + */ + Assert(false); + break; + } +} + +/* Store concurrent data change. */ +static void +store_change(LogicalDecodingContext *ctx, ConcurrentChangeKind kind, + HeapTuple tuple) +{ + RepackDecodingState *dstate; + char *change_raw; + ConcurrentChange change; + bool flattened = false; + Size size; + Datum values[1]; + bool isnull[1]; + char *dst; + + dstate = (RepackDecodingState *) ctx->output_writer_private; + + size = VARHDRSZ + SizeOfConcurrentChange; + + /* + * ReorderBufferCommit() stores the TOAST chunks in its private memory + * context and frees them after having called apply_change(). Therefore + * we need flat copy (including TOAST) that we eventually copy into the + * memory context which is available to decode_concurrent_changes(). + */ + if (HeapTupleHasExternal(tuple)) + { + /* + * toast_flatten_tuple_to_datum() might be more convenient but we + * don't want the decompression it does. + */ + tuple = toast_flatten_tuple(tuple, dstate->tupdesc); + flattened = true; + } + + size += tuple->t_len; + if (size >= MaxAllocSize) + elog(ERROR, "Change is too big."); + + /* Construct the change. */ + change_raw = (char *) palloc0(size); + SET_VARSIZE(change_raw, size); + + /* + * Since the varlena alignment might not be sufficient for the structure, + * set the fields in a local instance and remember where it should + * eventually be copied. + */ + change.kind = kind; + dst = (char *) VARDATA(change_raw); + + /* + * Copy the tuple. + * + * Note: change->tup_data.t_data must be fixed on retrieval! + */ + memcpy(&change.tup_data, tuple, sizeof(HeapTupleData)); + memcpy(dst, &change, SizeOfConcurrentChange); + dst += SizeOfConcurrentChange; + memcpy(dst, tuple->t_data, tuple->t_len); + + /* The data has been copied. */ + if (flattened) + pfree(tuple); + + /* Store as tuple of 1 bytea column. */ + values[0] = PointerGetDatum(change_raw); + isnull[0] = false; + tuplestore_putvalues(dstate->tstore, dstate->tupdesc_change, + values, isnull); + + /* Accounting. */ + dstate->nchanges++; + + /* Cleanup. */ + pfree(change_raw); +} diff --git a/src/backend/storage/lmgr/generate-lwlocknames.pl b/src/backend/storage/lmgr/generate-lwlocknames.pl index b49007167b0..2e7f1054e62 100644 --- a/src/backend/storage/lmgr/generate-lwlocknames.pl +++ b/src/backend/storage/lmgr/generate-lwlocknames.pl @@ -162,7 +162,7 @@ while (<$lwlocklist>) die "$wait_event_lwlocks[$lwlock_count] defined in wait_event_names.txt but " - . " missing from lwlocklist.h" + . "missing from lwlocklist.h" if $lwlock_count < scalar @wait_event_lwlocks; die diff --git a/src/backend/utils/time/snapmgr.c b/src/backend/utils/time/snapmgr.c index 3af1b366adf..fdf3427b43f 100644 --- a/src/backend/utils/time/snapmgr.c +++ b/src/backend/utils/time/snapmgr.c @@ -214,7 +214,6 @@ static List *exportedSnapshots = NIL; /* Prototypes for local functions */ static void UnregisterSnapshotNoOwner(Snapshot snapshot); -static void FreeSnapshot(Snapshot snapshot); static void SnapshotResetXmin(void); /* ResourceOwner callbacks to track snapshot references */ @@ -659,7 +658,7 @@ CopySnapshot(Snapshot snapshot) * FreeSnapshot * Free the memory associated with a snapshot. */ -static void +void FreeSnapshot(Snapshot snapshot) { Assert(snapshot->regd_count == 0); diff --git a/src/bin/psql/tab-complete.in.c b/src/bin/psql/tab-complete.in.c index 2a1bb47ff03..0ec0f4c4790 100644 --- a/src/bin/psql/tab-complete.in.c +++ b/src/bin/psql/tab-complete.in.c @@ -5121,8 +5121,8 @@ match_previous_words(int pattern_id, * one word, so the above test is correct. */ if (ends_with(prev_wd, '(') || ends_with(prev_wd, ',')) - COMPLETE_WITH("ANALYZE", "VERBOSE"); - else if (TailMatches("ANALYZE", "VERBOSE")) + COMPLETE_WITH("ANALYZE", "CONCURRENTLY", "VERBOSE"); + else if (TailMatches("ANALYZE", "CONCURRENTLY", "VERBOSE")) COMPLETE_WITH("ON", "OFF"); } diff --git a/src/include/access/heapam.h b/src/include/access/heapam.h index ce48fac42ba..90cccea5bb5 100644 --- a/src/include/access/heapam.h +++ b/src/include/access/heapam.h @@ -361,14 +361,15 @@ extern void heap_multi_insert(Relation relation, TupleTableSlot **slots, BulkInsertState bistate); extern TM_Result heap_delete(Relation relation, const ItemPointerData *tid, CommandId cid, Snapshot crosscheck, bool wait, - TM_FailureData *tmfd, bool changingPart); + TM_FailureData *tmfd, bool changingPart, + bool wal_logical); extern void heap_finish_speculative(Relation relation, const ItemPointerData *tid); extern void heap_abort_speculative(Relation relation, const ItemPointerData *tid); extern TM_Result heap_update(Relation relation, const ItemPointerData *otid, HeapTuple newtup, CommandId cid, Snapshot crosscheck, bool wait, TM_FailureData *tmfd, LockTupleMode *lockmode, - TU_UpdateIndexes *update_indexes); + TU_UpdateIndexes *update_indexes, bool wal_logical); extern TM_Result heap_lock_tuple(Relation relation, HeapTuple tuple, CommandId cid, LockTupleMode mode, LockWaitPolicy wait_policy, bool follow_updates, diff --git a/src/include/access/heapam_xlog.h b/src/include/access/heapam_xlog.h index ce3566ba949..f1f5495556b 100644 --- a/src/include/access/heapam_xlog.h +++ b/src/include/access/heapam_xlog.h @@ -104,6 +104,8 @@ #define XLH_DELETE_CONTAINS_OLD_KEY (1<<2) #define XLH_DELETE_IS_SUPER (1<<3) #define XLH_DELETE_IS_PARTITION_MOVE (1<<4) +/* See heap_delete() */ +#define XLH_DELETE_NO_LOGICAL (1<<5) /* convenience macro for checking whether any form of old tuple was logged */ #define XLH_DELETE_CONTAINS_OLD \ diff --git a/src/include/access/tableam.h b/src/include/access/tableam.h index e2ec5289d4d..76aa993009a 100644 --- a/src/include/access/tableam.h +++ b/src/include/access/tableam.h @@ -22,6 +22,7 @@ #include "access/xact.h" #include "commands/vacuum.h" #include "executor/tuptable.h" +#include "replication/logical.h" #include "storage/read_stream.h" #include "utils/rel.h" #include "utils/snapshot.h" @@ -629,6 +630,8 @@ typedef struct TableAmRoutine Relation OldIndex, bool use_sort, TransactionId OldestXmin, + Snapshot snapshot, + LogicalDecodingContext *decoding_ctx, TransactionId *xid_cutoff, MultiXactId *multi_cutoff, double *num_tuples, @@ -1646,6 +1649,10 @@ table_relation_copy_data(Relation rel, const RelFileLocator *newrlocator) * not needed for the relation's AM * - *xid_cutoff - ditto * - *multi_cutoff - ditto + * - snapshot - if != NULL, ignore data changes done by transactions that this + * (MVCC) snapshot considers still in-progress or in the future. + * - decoding_ctx - logical decoding context, to capture concurrent data + * changes. * * Output parameters: * - *xid_cutoff - rel's new relfrozenxid value, may be invalid @@ -1658,6 +1665,8 @@ table_relation_copy_for_cluster(Relation OldTable, Relation NewTable, Relation OldIndex, bool use_sort, TransactionId OldestXmin, + Snapshot snapshot, + LogicalDecodingContext *decoding_ctx, TransactionId *xid_cutoff, MultiXactId *multi_cutoff, double *num_tuples, @@ -1666,6 +1675,7 @@ table_relation_copy_for_cluster(Relation OldTable, Relation NewTable, { OldTable->rd_tableam->relation_copy_for_cluster(OldTable, NewTable, OldIndex, use_sort, OldestXmin, + snapshot, decoding_ctx, xid_cutoff, multi_cutoff, num_tuples, tups_vacuumed, tups_recently_dead); diff --git a/src/include/commands/cluster.h b/src/include/commands/cluster.h index 28741988478..6a5c476294a 100644 --- a/src/include/commands/cluster.h +++ b/src/include/commands/cluster.h @@ -13,10 +13,15 @@ #ifndef CLUSTER_H #define CLUSTER_H +#include "nodes/execnodes.h" #include "nodes/parsenodes.h" #include "parser/parse_node.h" +#include "replication/decode.h" +#include "replication/logical.h" #include "storage/lock.h" #include "utils/relcache.h" +#include "utils/resowner.h" +#include "utils/tuplestore.h" /* flag bits for ClusterParams->options */ @@ -25,6 +30,8 @@ #define CLUOPT_RECHECK_ISCLUSTERED 0x04 /* recheck relation state for * indisclustered */ #define CLUOPT_ANALYZE 0x08 /* do an ANALYZE */ +#define CLUOPT_CONCURRENT 0x10 /* allow concurrent data changes */ + /* options for CLUSTER */ typedef struct ClusterParams @@ -33,10 +40,84 @@ typedef struct ClusterParams } ClusterParams; +/* + * The following definitions are used by REPACK CONCURRENTLY. + */ + +typedef enum +{ + CHANGE_INSERT, + CHANGE_UPDATE_OLD, + CHANGE_UPDATE_NEW, + CHANGE_DELETE +} ConcurrentChangeKind; + +typedef struct ConcurrentChange +{ + /* See the enum above. */ + ConcurrentChangeKind kind; + + /* + * The actual tuple. + * + * The tuple data follows the ConcurrentChange structure. Before use make + * sure the tuple is correctly aligned (ConcurrentChange can be stored as + * bytea) and that tuple->t_data is fixed. + */ + HeapTupleData tup_data; +} ConcurrentChange; + +#define SizeOfConcurrentChange (offsetof(ConcurrentChange, tup_data) + \ + sizeof(HeapTupleData)) + +/* + * Logical decoding state. + * + * Here we store the data changes that we decode from WAL while the table + * contents is being copied to a new storage. Also the necessary metadata + * needed to apply these changes to the table is stored here. + */ +typedef struct RepackDecodingState +{ + /* The relation whose changes we're decoding. */ + Oid relid; + + /* Replication slot name. */ + NameData slotname; + + /* + * Decoded changes are stored here. Although we try to avoid excessive + * batches, it can happen that the changes need to be stored to disk. The + * tuplestore does this transparently. + */ + Tuplestorestate *tstore; + + /* The current number of changes in tstore. */ + double nchanges; + + /* + * Descriptor to store the ConcurrentChange structure serialized (bytea). + * We can't store the tuple directly because tuplestore only supports + * minimum tuple and we may need to transfer OID system column from the + * output plugin. Also we need to transfer the change kind, so it's better + * to put everything in the structure than to use 2 tuplestores "in + * parallel". + */ + TupleDesc tupdesc_change; + + /* Tuple descriptor needed to update indexes. */ + TupleDesc tupdesc; + + /* Slot to retrieve data from tstore. */ + TupleTableSlot *tsslot; + + ResourceOwner resowner; +} RepackDecodingState; + extern void ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel); extern void cluster_rel(RepackCommand command, Relation OldHeap, Oid indexOid, - ClusterParams *params); + ClusterParams *params, bool isTopLevel); extern void check_index_is_clusterable(Relation OldHeap, Oid indexOid, LOCKMODE lockmode); extern void mark_index_clustered(Relation rel, Oid indexOid, bool is_internal); @@ -48,8 +129,13 @@ extern void finish_heap_swap(Oid OIDOldHeap, Oid OIDNewHeap, bool swap_toast_by_content, bool check_constraints, bool is_internal, + bool reindex, TransactionId frozenXid, MultiXactId cutoffMulti, char newrelpersistence); +extern bool am_decoding_for_repack(void); +extern bool change_useless_for_repack(XLogRecordBuffer *buf); +extern void repack_decode_concurrent_changes(LogicalDecodingContext *ctx, + XLogRecPtr end_of_wal); #endif /* CLUSTER_H */ diff --git a/src/include/commands/progress.h b/src/include/commands/progress.h index f00e39b937d..4445724a463 100644 --- a/src/include/commands/progress.h +++ b/src/include/commands/progress.h @@ -86,10 +86,12 @@ #define PROGRESS_REPACK_PHASE 1 #define PROGRESS_REPACK_INDEX_RELID 2 #define PROGRESS_REPACK_HEAP_TUPLES_SCANNED 3 -#define PROGRESS_REPACK_HEAP_TUPLES_WRITTEN 4 -#define PROGRESS_REPACK_TOTAL_HEAP_BLKS 5 -#define PROGRESS_REPACK_HEAP_BLKS_SCANNED 6 -#define PROGRESS_REPACK_INDEX_REBUILD_COUNT 7 +#define PROGRESS_REPACK_HEAP_TUPLES_INSERTED 4 +#define PROGRESS_REPACK_HEAP_TUPLES_UPDATED 5 +#define PROGRESS_REPACK_HEAP_TUPLES_DELETED 6 +#define PROGRESS_REPACK_TOTAL_HEAP_BLKS 7 +#define PROGRESS_REPACK_HEAP_BLKS_SCANNED 8 +#define PROGRESS_REPACK_INDEX_REBUILD_COUNT 9 /* * Phases of repack (as advertised via PROGRESS_REPACK_PHASE). @@ -98,9 +100,10 @@ #define PROGRESS_REPACK_PHASE_INDEX_SCAN_HEAP 2 #define PROGRESS_REPACK_PHASE_SORT_TUPLES 3 #define PROGRESS_REPACK_PHASE_WRITE_NEW_HEAP 4 -#define PROGRESS_REPACK_PHASE_SWAP_REL_FILES 5 -#define PROGRESS_REPACK_PHASE_REBUILD_INDEX 6 -#define PROGRESS_REPACK_PHASE_FINAL_CLEANUP 7 +#define PROGRESS_REPACK_PHASE_CATCH_UP 5 +#define PROGRESS_REPACK_PHASE_SWAP_REL_FILES 6 +#define PROGRESS_REPACK_PHASE_REBUILD_INDEX 7 +#define PROGRESS_REPACK_PHASE_FINAL_CLEANUP 8 /* Progress parameters for CREATE INDEX */ /* 3, 4 and 5 reserved for "waitfor" metrics */ diff --git a/src/include/replication/snapbuild.h b/src/include/replication/snapbuild.h index 34383dea776..5ee267d1c90 100644 --- a/src/include/replication/snapbuild.h +++ b/src/include/replication/snapbuild.h @@ -73,6 +73,7 @@ extern void FreeSnapshotBuilder(SnapBuild *builder); extern void SnapBuildSnapDecRefcount(Snapshot snap); extern Snapshot SnapBuildInitialSnapshot(SnapBuild *builder); +extern Snapshot SnapBuildInitialSnapshotForRepack(SnapBuild *builder); extern Snapshot SnapBuildMVCCFromHistoric(Snapshot snapshot, bool in_place); extern const char *SnapBuildExportSnapshot(SnapBuild *builder); extern void SnapBuildClearExportedSnapshot(void); diff --git a/src/include/storage/lockdefs.h b/src/include/storage/lockdefs.h index b73bb5618e6..3785b009808 100644 --- a/src/include/storage/lockdefs.h +++ b/src/include/storage/lockdefs.h @@ -36,8 +36,8 @@ typedef int LOCKMODE; #define AccessShareLock 1 /* SELECT */ #define RowShareLock 2 /* SELECT FOR UPDATE/FOR SHARE */ #define RowExclusiveLock 3 /* INSERT, UPDATE, DELETE */ -#define ShareUpdateExclusiveLock 4 /* VACUUM (non-FULL), ANALYZE, CREATE - * INDEX CONCURRENTLY */ +#define ShareUpdateExclusiveLock 4 /* VACUUM (non-exclusive), ANALYZE, CREATE + * INDEX CONCURRENTLY, REPACK CONCURRENTLY */ #define ShareLock 5 /* CREATE INDEX (WITHOUT CONCURRENTLY) */ #define ShareRowExclusiveLock 6 /* like EXCLUSIVE MODE, but allows ROW * SHARE */ diff --git a/src/include/utils/snapmgr.h b/src/include/utils/snapmgr.h index de824945f0b..0eb8ced76d3 100644 --- a/src/include/utils/snapmgr.h +++ b/src/include/utils/snapmgr.h @@ -64,6 +64,8 @@ extern Snapshot GetLatestSnapshot(void); extern void SnapshotSetCommandId(CommandId curcid); extern Snapshot CopySnapshot(Snapshot snapshot); +extern void FreeSnapshot(Snapshot snapshot); + extern Snapshot GetCatalogSnapshot(Oid relid); extern Snapshot GetNonHistoricCatalogSnapshot(Oid relid); extern void InvalidateCatalogSnapshot(void); diff --git a/src/test/modules/injection_points/Makefile b/src/test/modules/injection_points/Makefile index a41d781f8c9..6b6a3b84a57 100644 --- a/src/test/modules/injection_points/Makefile +++ b/src/test/modules/injection_points/Makefile @@ -14,8 +14,11 @@ REGRESS_OPTS = --dlpath=$(top_builddir)/src/test/regress ISOLATION = basic \ inplace \ + repack \ + repack_toast \ syscache-update-pruned \ heap_lock_update +ISOLATION_OPTS = --temp-config $(top_srcdir)/src/test/modules/injection_points/logical.conf # The injection points are cluster-wide, so disable installcheck NO_INSTALLCHECK = 1 diff --git a/src/test/modules/injection_points/expected/repack.out b/src/test/modules/injection_points/expected/repack.out new file mode 100644 index 00000000000..b575e9052ee --- /dev/null +++ b/src/test/modules/injection_points/expected/repack.out @@ -0,0 +1,113 @@ +Parsed test spec with 2 sessions + +starting permutation: wait_before_lock change_existing change_new change_subxact1 change_subxact2 check2 wakeup_before_lock check1 +injection_points_attach +----------------------- + +(1 row) + +step wait_before_lock: + REPACK (CONCURRENTLY) repack_test USING INDEX repack_test_pkey; + <waiting ...> +step change_existing: + UPDATE repack_test SET i=10 where i=1; + UPDATE repack_test SET j=20 where i=2; + UPDATE repack_test SET i=30 where i=3; + UPDATE repack_test SET i=40 where i=30; + DELETE FROM repack_test WHERE i=4; + +step change_new: + INSERT INTO repack_test(i, j) VALUES (5, 5), (6, 6), (7, 7), (8, 8); + UPDATE repack_test SET i=50 where i=5; + UPDATE repack_test SET j=60 where i=6; + DELETE FROM repack_test WHERE i=7; + +step change_subxact1: + BEGIN; + INSERT INTO repack_test(i, j) VALUES (100, 100); + SAVEPOINT s1; + UPDATE repack_test SET i=101 where i=100; + SAVEPOINT s2; + UPDATE repack_test SET i=102 where i=101; + COMMIT; + +step change_subxact2: + BEGIN; + SAVEPOINT s1; + INSERT INTO repack_test(i, j) VALUES (110, 110); + ROLLBACK TO SAVEPOINT s1; + INSERT INTO repack_test(i, j) VALUES (110, 111); + COMMIT; + +step check2: + INSERT INTO relfilenodes(node) + SELECT relfilenode FROM pg_class WHERE relname='repack_test'; + + SELECT i, j FROM repack_test ORDER BY i, j; + + INSERT INTO data_s2(i, j) + SELECT i, j FROM repack_test; + + i| j +---+--- + 2| 20 + 6| 60 + 8| 8 + 10| 1 + 40| 3 + 50| 5 +102|100 +110|111 +(8 rows) + +step wakeup_before_lock: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step wait_before_lock: <... completed> +step check1: + INSERT INTO relfilenodes(node) + SELECT relfilenode FROM pg_class WHERE relname='repack_test'; + + SELECT count(DISTINCT node) FROM relfilenodes; + + SELECT i, j FROM repack_test ORDER BY i, j; + + INSERT INTO data_s1(i, j) + SELECT i, j FROM repack_test; + + SELECT count(*) + FROM data_s1 d1 FULL JOIN data_s2 d2 USING (i, j) + WHERE d1.i ISNULL OR d2.i ISNULL; + +count +----- + 2 +(1 row) + + i| j +---+--- + 2| 20 + 6| 60 + 8| 8 + 10| 1 + 40| 3 + 50| 5 +102|100 +110|111 +(8 rows) + +count +----- + 0 +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/src/test/modules/injection_points/expected/repack_toast.out b/src/test/modules/injection_points/expected/repack_toast.out new file mode 100644 index 00000000000..4f866a74e32 --- /dev/null +++ b/src/test/modules/injection_points/expected/repack_toast.out @@ -0,0 +1,64 @@ +Parsed test spec with 2 sessions + +starting permutation: wait_before_lock change check2 wakeup_before_lock check1 +injection_points_attach +----------------------- + +(1 row) + +step wait_before_lock: + REPACK (CONCURRENTLY) repack_test; + <waiting ...> +step change: + UPDATE repack_test SET j=get_long_string() where i=2; + DELETE FROM repack_test WHERE i=3; + INSERT INTO repack_test(i, j) VALUES (4, get_long_string()); + +step check2: + INSERT INTO relfilenodes(node) + SELECT c2.relfilenode + FROM pg_class c1 JOIN pg_class c2 ON c2.oid = c1.oid OR c2.oid = c1.reltoastrelid + WHERE c1.relname='repack_test'; + + INSERT INTO data_s2(i, j) + SELECT i, j FROM repack_test; + +step wakeup_before_lock: + SELECT injection_points_wakeup('repack-concurrently-before-lock'); + +injection_points_wakeup +----------------------- + +(1 row) + +step wait_before_lock: <... completed> +step check1: + INSERT INTO relfilenodes(node) + SELECT c2.relfilenode + FROM pg_class c1 JOIN pg_class c2 ON c2.oid = c1.oid OR c2.oid = c1.reltoastrelid + WHERE c1.relname='repack_test'; + + SELECT count(DISTINCT node) FROM relfilenodes; + + INSERT INTO data_s1(i, j) + SELECT i, j FROM repack_test; + + SELECT count(*) + FROM data_s1 d1 FULL JOIN data_s2 d2 USING (i, j) + WHERE d1.i ISNULL OR d2.i ISNULL; + +count +----- + 4 +(1 row) + +count +----- + 0 +(1 row) + +injection_points_detach +----------------------- + +(1 row) + diff --git a/src/test/modules/injection_points/logical.conf b/src/test/modules/injection_points/logical.conf new file mode 100644 index 00000000000..e3d257315fa --- /dev/null +++ b/src/test/modules/injection_points/logical.conf @@ -0,0 +1 @@ +wal_level = logical diff --git a/src/test/modules/injection_points/meson.build b/src/test/modules/injection_points/meson.build index fcc85414515..bbf1f6e665c 100644 --- a/src/test/modules/injection_points/meson.build +++ b/src/test/modules/injection_points/meson.build @@ -45,11 +45,15 @@ tests += { 'specs': [ 'basic', 'inplace', + 'repack', + 'repack_toast', 'syscache-update-pruned', 'heap_lock_update', ], 'runningcheck': false, # see syscache-update-pruned # Some tests wait for all snapshots, so avoid parallel execution 'runningcheck-parallel': false, + # 'repack' requires wal_level = 'logical'. + 'regress_args': ['--temp-config', files('logical.conf')], }, } diff --git a/src/test/modules/injection_points/specs/repack.spec b/src/test/modules/injection_points/specs/repack.spec new file mode 100644 index 00000000000..d727a9b056b --- /dev/null +++ b/src/test/modules/injection_points/specs/repack.spec @@ -0,0 +1,142 @@ +# REPACK (CONCURRENTLY) ... USING INDEX ...; +setup +{ + CREATE EXTENSION injection_points; + + CREATE TABLE repack_test(i int PRIMARY KEY, j int); + INSERT INTO repack_test(i, j) VALUES (1, 1), (2, 2), (3, 3), (4, 4); + + CREATE TABLE relfilenodes(node oid); + + CREATE TABLE data_s1(i int, j int); + CREATE TABLE data_s2(i int, j int); +} + +teardown +{ + DROP TABLE repack_test; + DROP EXTENSION injection_points; + + DROP TABLE relfilenodes; + DROP TABLE data_s1; + DROP TABLE data_s2; +} + +session s1 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +# Perform the initial load and wait for s2 to do some data changes. +step wait_before_lock +{ + REPACK (CONCURRENTLY) repack_test USING INDEX repack_test_pkey; +} +# Check the table from the perspective of s1. +# +# Besides the contents, we also check that relfilenode has changed. + +# Have each session write the contents into a table and use FULL JOIN to check +# if the outputs are identical. +step check1 +{ + INSERT INTO relfilenodes(node) + SELECT relfilenode FROM pg_class WHERE relname='repack_test'; + + SELECT count(DISTINCT node) FROM relfilenodes; + + SELECT i, j FROM repack_test ORDER BY i, j; + + INSERT INTO data_s1(i, j) + SELECT i, j FROM repack_test; + + SELECT count(*) + FROM data_s1 d1 FULL JOIN data_s2 d2 USING (i, j) + WHERE d1.i ISNULL OR d2.i ISNULL; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); +} + +session s2 +# Change the existing data. UPDATE changes both key and non-key columns. Also +# update one row twice to test whether tuple version generated by this session +# can be found. +step change_existing +{ + UPDATE repack_test SET i=10 where i=1; + UPDATE repack_test SET j=20 where i=2; + UPDATE repack_test SET i=30 where i=3; + UPDATE repack_test SET i=40 where i=30; + DELETE FROM repack_test WHERE i=4; +} +# Insert new rows and UPDATE / DELETE some of them. Again, update both key and +# non-key column. +step change_new +{ + INSERT INTO repack_test(i, j) VALUES (5, 5), (6, 6), (7, 7), (8, 8); + UPDATE repack_test SET i=50 where i=5; + UPDATE repack_test SET j=60 where i=6; + DELETE FROM repack_test WHERE i=7; +} + +# When applying concurrent data changes, we should see the effects of an +# in-progress subtransaction. +# +# XXX Not sure this test is useful now - it was designed for the patch that +# preserves tuple visibility and which therefore modifies +# TransactionIdIsCurrentTransactionId(). +step change_subxact1 +{ + BEGIN; + INSERT INTO repack_test(i, j) VALUES (100, 100); + SAVEPOINT s1; + UPDATE repack_test SET i=101 where i=100; + SAVEPOINT s2; + UPDATE repack_test SET i=102 where i=101; + COMMIT; +} + +# When applying concurrent data changes, we should not see the effects of a +# rolled back subtransaction. +# +# XXX Is this test useful? See above. +step change_subxact2 +{ + BEGIN; + SAVEPOINT s1; + INSERT INTO repack_test(i, j) VALUES (110, 110); + ROLLBACK TO SAVEPOINT s1; + INSERT INTO repack_test(i, j) VALUES (110, 111); + COMMIT; +} + +# Check the table from the perspective of s2. +step check2 +{ + INSERT INTO relfilenodes(node) + SELECT relfilenode FROM pg_class WHERE relname='repack_test'; + + SELECT i, j FROM repack_test ORDER BY i, j; + + INSERT INTO data_s2(i, j) + SELECT i, j FROM repack_test; +} +step wakeup_before_lock +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +# Test if data changes introduced while one session is performing REPACK +# CONCURRENTLY find their way into the table. +permutation + wait_before_lock + change_existing + change_new + change_subxact1 + change_subxact2 + check2 + wakeup_before_lock + check1 diff --git a/src/test/modules/injection_points/specs/repack_toast.spec b/src/test/modules/injection_points/specs/repack_toast.spec new file mode 100644 index 00000000000..b48abf21450 --- /dev/null +++ b/src/test/modules/injection_points/specs/repack_toast.spec @@ -0,0 +1,105 @@ +# REPACK (CONCURRENTLY); +# +# Test handling of TOAST. At the same time, no tuplesort. +setup +{ + CREATE EXTENSION injection_points; + + -- Return a string that needs to be TOASTed. + CREATE FUNCTION get_long_string() + RETURNS text + LANGUAGE sql as $$ + SELECT string_agg(chr(65 + trunc(25 * random())::int), '') + FROM generate_series(1, 2048) s(x); + $$; + + CREATE TABLE repack_test(i int PRIMARY KEY, j text); + INSERT INTO repack_test(i, j) VALUES (1, get_long_string()), + (2, get_long_string()), (3, get_long_string()); + + CREATE TABLE relfilenodes(node oid); + + CREATE TABLE data_s1(i int, j text); + CREATE TABLE data_s2(i int, j text); +} + +teardown +{ + DROP TABLE repack_test; + DROP EXTENSION injection_points; + DROP FUNCTION get_long_string(); + + DROP TABLE relfilenodes; + DROP TABLE data_s1; + DROP TABLE data_s2; +} + +session s1 +setup +{ + SELECT injection_points_set_local(); + SELECT injection_points_attach('repack-concurrently-before-lock', 'wait'); +} +# Perform the initial load and wait for s2 to do some data changes. +step wait_before_lock +{ + REPACK (CONCURRENTLY) repack_test; +} +# Check the table from the perspective of s1. +# +# Besides the contents, we also check that relfilenode has changed. + +# Have each session write the contents into a table and use FULL JOIN to check +# if the outputs are identical. +step check1 +{ + INSERT INTO relfilenodes(node) + SELECT c2.relfilenode + FROM pg_class c1 JOIN pg_class c2 ON c2.oid = c1.oid OR c2.oid = c1.reltoastrelid + WHERE c1.relname='repack_test'; + + SELECT count(DISTINCT node) FROM relfilenodes; + + INSERT INTO data_s1(i, j) + SELECT i, j FROM repack_test; + + SELECT count(*) + FROM data_s1 d1 FULL JOIN data_s2 d2 USING (i, j) + WHERE d1.i ISNULL OR d2.i ISNULL; +} +teardown +{ + SELECT injection_points_detach('repack-concurrently-before-lock'); +} + +session s2 +step change +{ + UPDATE repack_test SET j=get_long_string() where i=2; + DELETE FROM repack_test WHERE i=3; + INSERT INTO repack_test(i, j) VALUES (4, get_long_string()); +} +# Check the table from the perspective of s2. +step check2 +{ + INSERT INTO relfilenodes(node) + SELECT c2.relfilenode + FROM pg_class c1 JOIN pg_class c2 ON c2.oid = c1.oid OR c2.oid = c1.reltoastrelid + WHERE c1.relname='repack_test'; + + INSERT INTO data_s2(i, j) + SELECT i, j FROM repack_test; +} +step wakeup_before_lock +{ + SELECT injection_points_wakeup('repack-concurrently-before-lock'); +} + +# Test if data changes introduced while one session is performing REPACK +# CONCURRENTLY find their way into the table. +permutation + wait_before_lock + change + check2 + wakeup_before_lock + check1 diff --git a/src/test/regress/expected/rules.out b/src/test/regress/expected/rules.out index 48461550636..470920f0d16 100644 --- a/src/test/regress/expected/rules.out +++ b/src/test/regress/expected/rules.out @@ -2014,7 +2014,7 @@ pg_stat_progress_cluster| SELECT pid, phase, repack_index_relid AS cluster_index_relid, heap_tuples_scanned, - heap_tuples_written, + (heap_tuples_inserted + heap_tuples_updated) AS heap_tuples_written, heap_blks_total, heap_blks_scanned, index_rebuild_count @@ -2094,17 +2094,20 @@ pg_stat_progress_repack| SELECT s.pid, WHEN 2 THEN 'index scanning heap'::text WHEN 3 THEN 'sorting tuples'::text WHEN 4 THEN 'writing new heap'::text - WHEN 5 THEN 'swapping relation files'::text - WHEN 6 THEN 'rebuilding index'::text - WHEN 7 THEN 'performing final cleanup'::text + WHEN 5 THEN 'catch-up'::text + WHEN 6 THEN 'swapping relation files'::text + WHEN 7 THEN 'rebuilding index'::text + WHEN 8 THEN 'performing final cleanup'::text ELSE NULL::text END AS phase, (s.param3)::oid AS repack_index_relid, s.param4 AS heap_tuples_scanned, - s.param5 AS heap_tuples_written, - s.param6 AS heap_blks_total, - s.param7 AS heap_blks_scanned, - s.param8 AS index_rebuild_count + s.param5 AS heap_tuples_inserted, + s.param6 AS heap_tuples_updated, + s.param7 AS heap_tuples_deleted, + s.param8 AS heap_blks_total, + s.param9 AS heap_blks_scanned, + s.param10 AS index_rebuild_count FROM (pg_stat_get_progress_info('REPACK'::text) s(pid, datid, relid, param1, param2, param3, param4, param5, param6, param7, param8, param9, param10, param11, param12, param13, param14, param15, param16, param17, param18, param19, param20) LEFT JOIN pg_database d ON ((s.datid = d.oid))); pg_stat_progress_vacuum| SELECT s.pid, diff --git a/src/tools/pgindent/typedefs.list b/src/tools/pgindent/typedefs.list index c81d93d0e5a..a0b7b38a5e2 100644 --- a/src/tools/pgindent/typedefs.list +++ b/src/tools/pgindent/typedefs.list @@ -419,6 +419,7 @@ CatCacheHeader CatalogId CatalogIdMapEntry CatalogIndexState +ChangeDest ChangeVarNodes_callback ChangeVarNodes_context CheckPoint @@ -495,6 +496,8 @@ CompressFileHandle CompressionLocation CompressorState ComputeXidHorizonsResult +ConcurrentChange +ConcurrentChangeKind ConditionVariable ConditionVariableMinimallyPadded ConditionalStack @@ -1274,6 +1277,7 @@ IndexElem IndexFetchHeapData IndexFetchTableData IndexInfo +IndexInsertState IndexList IndexOnlyScan IndexOnlyScanState @@ -2570,6 +2574,7 @@ ReorderBufferUpdateProgressTxnCB ReorderTuple RepOriginId RepackCommand +RepackDecodingState RepackStmt ReparameterizeForeignPathByChild_function ReplaceVarsFromTargetList_context -- 2.47.3 --=-=-= Content-Type: text/x-diff Content-Disposition: attachment; filename=v30-0005-Use-background-worker-to-do-logical-decoding.patch ^ permalink raw reply [nested|flat] 114+ messages in thread
* [PATCH v2 1/4] Make stack depth check work with asan's use-after-return @ 2026-02-07 19:04 Andres Freund <[email protected]> 0 siblings, 0 replies; 114+ messages in thread From: Andres Freund @ 2026-02-07 19:04 UTC (permalink / raw) With address sanitizer's stack-use-after-return check, stack variables are moved to heap allocations, to allow to detect references to the memory at a later time. That broke our stack-depth check, which is why we had to disable detect_stack_use_after_return in CI. Luckily __builtin_frame_address() works correctly, even under asan, so use that. Author: Reviewed-by: Discussion: https://postgr.es/m/ Backpatch: --- src/backend/utils/misc/stack_depth.c | 17 ++++++++++++++++- .cirrus.tasks.yml | 2 +- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/src/backend/utils/misc/stack_depth.c b/src/backend/utils/misc/stack_depth.c index 61a07cf824e..9955e28c176 100644 --- a/src/backend/utils/misc/stack_depth.c +++ b/src/backend/utils/misc/stack_depth.c @@ -108,13 +108,28 @@ check_stack_depth(void) bool stack_is_too_deep(void) { +#ifndef HAVE__BUILTIN_FRAME_ADDRESS char stack_top_loc; +#endif ssize_t stack_depth; + char *stack_address; + + /* + * With address sanitizer's stack-use-after-return check, stack variables + * are moved to heap allocations, to allow to detect references to the + * memory at a later time. That would break our stack-depth check. Luckily + * __builtin_frame_address() works correctly, even under asan. + */ +#ifndef HAVE__BUILTIN_FRAME_ADDRESS + stack_address = &stack_top_loc; +#else + stack_address = (char *) __builtin_frame_address(0); +#endif /* * Compute distance from reference point to my local variables */ - stack_depth = (ssize_t) (stack_base_ptr - &stack_top_loc); + stack_depth = (ssize_t) (stack_base_ptr - stack_address); /* * Take abs value, since stacks grow up on some machines, down on others diff --git a/.cirrus.tasks.yml b/.cirrus.tasks.yml index a22cef063f3..8683d1ae9c7 100644 --- a/.cirrus.tasks.yml +++ b/.cirrus.tasks.yml @@ -447,7 +447,7 @@ task: # print_stacktraces=1,verbosity=2, duh # detect_leaks=0: too many uninteresting leak errors in short-lived binaries UBSAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:verbosity=2 - ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0:detect_stack_use_after_return=0 + ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0 # SANITIZER_FLAGS is set in the tasks below CFLAGS: -Og -ggdb -fno-sanitize-recover=all $SANITIZER_FLAGS -- 2.53.0.1.gb2826b52eb --7e2sypi5gxvkgog6-- ^ permalink raw reply [nested|flat] 114+ messages in thread
* [PATCH v2 1/4] Make stack depth check work with asan's use-after-return @ 2026-02-07 19:04 Andres Freund <[email protected]> 0 siblings, 0 replies; 114+ messages in thread From: Andres Freund @ 2026-02-07 19:04 UTC (permalink / raw) With address sanitizer's stack-use-after-return check, stack variables are moved to heap allocations, to allow to detect references to the memory at a later time. That broke our stack-depth check, which is why we had to disable detect_stack_use_after_return in CI. Luckily __builtin_frame_address() works correctly, even under asan, so use that. Author: Reviewed-by: Discussion: https://postgr.es/m/ Backpatch: --- src/backend/utils/misc/stack_depth.c | 17 ++++++++++++++++- .cirrus.tasks.yml | 2 +- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/src/backend/utils/misc/stack_depth.c b/src/backend/utils/misc/stack_depth.c index 61a07cf824e..9955e28c176 100644 --- a/src/backend/utils/misc/stack_depth.c +++ b/src/backend/utils/misc/stack_depth.c @@ -108,13 +108,28 @@ check_stack_depth(void) bool stack_is_too_deep(void) { +#ifndef HAVE__BUILTIN_FRAME_ADDRESS char stack_top_loc; +#endif ssize_t stack_depth; + char *stack_address; + + /* + * With address sanitizer's stack-use-after-return check, stack variables + * are moved to heap allocations, to allow to detect references to the + * memory at a later time. That would break our stack-depth check. Luckily + * __builtin_frame_address() works correctly, even under asan. + */ +#ifndef HAVE__BUILTIN_FRAME_ADDRESS + stack_address = &stack_top_loc; +#else + stack_address = (char *) __builtin_frame_address(0); +#endif /* * Compute distance from reference point to my local variables */ - stack_depth = (ssize_t) (stack_base_ptr - &stack_top_loc); + stack_depth = (ssize_t) (stack_base_ptr - stack_address); /* * Take abs value, since stacks grow up on some machines, down on others diff --git a/.cirrus.tasks.yml b/.cirrus.tasks.yml index a22cef063f3..8683d1ae9c7 100644 --- a/.cirrus.tasks.yml +++ b/.cirrus.tasks.yml @@ -447,7 +447,7 @@ task: # print_stacktraces=1,verbosity=2, duh # detect_leaks=0: too many uninteresting leak errors in short-lived binaries UBSAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:verbosity=2 - ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0:detect_stack_use_after_return=0 + ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0 # SANITIZER_FLAGS is set in the tasks below CFLAGS: -Og -ggdb -fno-sanitize-recover=all $SANITIZER_FLAGS -- 2.53.0.1.gb2826b52eb --7e2sypi5gxvkgog6-- ^ permalink raw reply [nested|flat] 114+ messages in thread
* [PATCH v2 1/4] Make stack depth check work with asan's use-after-return @ 2026-02-07 19:04 Andres Freund <[email protected]> 0 siblings, 0 replies; 114+ messages in thread From: Andres Freund @ 2026-02-07 19:04 UTC (permalink / raw) With address sanitizer's stack-use-after-return check, stack variables are moved to heap allocations, to allow to detect references to the memory at a later time. That broke our stack-depth check, which is why we had to disable detect_stack_use_after_return in CI. Luckily __builtin_frame_address() works correctly, even under asan, so use that. Author: Reviewed-by: Discussion: https://postgr.es/m/ Backpatch: --- src/backend/utils/misc/stack_depth.c | 17 ++++++++++++++++- .cirrus.tasks.yml | 2 +- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/src/backend/utils/misc/stack_depth.c b/src/backend/utils/misc/stack_depth.c index 61a07cf824e..9955e28c176 100644 --- a/src/backend/utils/misc/stack_depth.c +++ b/src/backend/utils/misc/stack_depth.c @@ -108,13 +108,28 @@ check_stack_depth(void) bool stack_is_too_deep(void) { +#ifndef HAVE__BUILTIN_FRAME_ADDRESS char stack_top_loc; +#endif ssize_t stack_depth; + char *stack_address; + + /* + * With address sanitizer's stack-use-after-return check, stack variables + * are moved to heap allocations, to allow to detect references to the + * memory at a later time. That would break our stack-depth check. Luckily + * __builtin_frame_address() works correctly, even under asan. + */ +#ifndef HAVE__BUILTIN_FRAME_ADDRESS + stack_address = &stack_top_loc; +#else + stack_address = (char *) __builtin_frame_address(0); +#endif /* * Compute distance from reference point to my local variables */ - stack_depth = (ssize_t) (stack_base_ptr - &stack_top_loc); + stack_depth = (ssize_t) (stack_base_ptr - stack_address); /* * Take abs value, since stacks grow up on some machines, down on others diff --git a/.cirrus.tasks.yml b/.cirrus.tasks.yml index a22cef063f3..8683d1ae9c7 100644 --- a/.cirrus.tasks.yml +++ b/.cirrus.tasks.yml @@ -447,7 +447,7 @@ task: # print_stacktraces=1,verbosity=2, duh # detect_leaks=0: too many uninteresting leak errors in short-lived binaries UBSAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:verbosity=2 - ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0:detect_stack_use_after_return=0 + ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0 # SANITIZER_FLAGS is set in the tasks below CFLAGS: -Og -ggdb -fno-sanitize-recover=all $SANITIZER_FLAGS -- 2.53.0.1.gb2826b52eb --7e2sypi5gxvkgog6-- ^ permalink raw reply [nested|flat] 114+ messages in thread
* [PATCH v2 1/4] Make stack depth check work with asan's use-after-return @ 2026-02-07 19:04 Andres Freund <[email protected]> 0 siblings, 0 replies; 114+ messages in thread From: Andres Freund @ 2026-02-07 19:04 UTC (permalink / raw) With address sanitizer's stack-use-after-return check, stack variables are moved to heap allocations, to allow to detect references to the memory at a later time. That broke our stack-depth check, which is why we had to disable detect_stack_use_after_return in CI. Luckily __builtin_frame_address() works correctly, even under asan, so use that. Author: Reviewed-by: Discussion: https://postgr.es/m/ Backpatch: --- src/backend/utils/misc/stack_depth.c | 17 ++++++++++++++++- .cirrus.tasks.yml | 2 +- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/src/backend/utils/misc/stack_depth.c b/src/backend/utils/misc/stack_depth.c index 61a07cf824e..9955e28c176 100644 --- a/src/backend/utils/misc/stack_depth.c +++ b/src/backend/utils/misc/stack_depth.c @@ -108,13 +108,28 @@ check_stack_depth(void) bool stack_is_too_deep(void) { +#ifndef HAVE__BUILTIN_FRAME_ADDRESS char stack_top_loc; +#endif ssize_t stack_depth; + char *stack_address; + + /* + * With address sanitizer's stack-use-after-return check, stack variables + * are moved to heap allocations, to allow to detect references to the + * memory at a later time. That would break our stack-depth check. Luckily + * __builtin_frame_address() works correctly, even under asan. + */ +#ifndef HAVE__BUILTIN_FRAME_ADDRESS + stack_address = &stack_top_loc; +#else + stack_address = (char *) __builtin_frame_address(0); +#endif /* * Compute distance from reference point to my local variables */ - stack_depth = (ssize_t) (stack_base_ptr - &stack_top_loc); + stack_depth = (ssize_t) (stack_base_ptr - stack_address); /* * Take abs value, since stacks grow up on some machines, down on others diff --git a/.cirrus.tasks.yml b/.cirrus.tasks.yml index a22cef063f3..8683d1ae9c7 100644 --- a/.cirrus.tasks.yml +++ b/.cirrus.tasks.yml @@ -447,7 +447,7 @@ task: # print_stacktraces=1,verbosity=2, duh # detect_leaks=0: too many uninteresting leak errors in short-lived binaries UBSAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:verbosity=2 - ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0:detect_stack_use_after_return=0 + ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0 # SANITIZER_FLAGS is set in the tasks below CFLAGS: -Og -ggdb -fno-sanitize-recover=all $SANITIZER_FLAGS -- 2.53.0.1.gb2826b52eb --7e2sypi5gxvkgog6-- ^ permalink raw reply [nested|flat] 114+ messages in thread
* [PATCH v2 1/4] Make stack depth check work with asan's use-after-return @ 2026-02-07 19:04 Andres Freund <[email protected]> 0 siblings, 0 replies; 114+ messages in thread From: Andres Freund @ 2026-02-07 19:04 UTC (permalink / raw) With address sanitizer's stack-use-after-return check, stack variables are moved to heap allocations, to allow to detect references to the memory at a later time. That broke our stack-depth check, which is why we had to disable detect_stack_use_after_return in CI. Luckily __builtin_frame_address() works correctly, even under asan, so use that. Author: Reviewed-by: Discussion: https://postgr.es/m/ Backpatch: --- src/backend/utils/misc/stack_depth.c | 17 ++++++++++++++++- .cirrus.tasks.yml | 2 +- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/src/backend/utils/misc/stack_depth.c b/src/backend/utils/misc/stack_depth.c index 61a07cf824e..9955e28c176 100644 --- a/src/backend/utils/misc/stack_depth.c +++ b/src/backend/utils/misc/stack_depth.c @@ -108,13 +108,28 @@ check_stack_depth(void) bool stack_is_too_deep(void) { +#ifndef HAVE__BUILTIN_FRAME_ADDRESS char stack_top_loc; +#endif ssize_t stack_depth; + char *stack_address; + + /* + * With address sanitizer's stack-use-after-return check, stack variables + * are moved to heap allocations, to allow to detect references to the + * memory at a later time. That would break our stack-depth check. Luckily + * __builtin_frame_address() works correctly, even under asan. + */ +#ifndef HAVE__BUILTIN_FRAME_ADDRESS + stack_address = &stack_top_loc; +#else + stack_address = (char *) __builtin_frame_address(0); +#endif /* * Compute distance from reference point to my local variables */ - stack_depth = (ssize_t) (stack_base_ptr - &stack_top_loc); + stack_depth = (ssize_t) (stack_base_ptr - stack_address); /* * Take abs value, since stacks grow up on some machines, down on others diff --git a/.cirrus.tasks.yml b/.cirrus.tasks.yml index a22cef063f3..8683d1ae9c7 100644 --- a/.cirrus.tasks.yml +++ b/.cirrus.tasks.yml @@ -447,7 +447,7 @@ task: # print_stacktraces=1,verbosity=2, duh # detect_leaks=0: too many uninteresting leak errors in short-lived binaries UBSAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:verbosity=2 - ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0:detect_stack_use_after_return=0 + ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0 # SANITIZER_FLAGS is set in the tasks below CFLAGS: -Og -ggdb -fno-sanitize-recover=all $SANITIZER_FLAGS -- 2.53.0.1.gb2826b52eb --7e2sypi5gxvkgog6-- ^ permalink raw reply [nested|flat] 114+ messages in thread
* [PATCH v2 1/4] Make stack depth check work with asan's use-after-return @ 2026-02-07 19:04 Andres Freund <[email protected]> 0 siblings, 0 replies; 114+ messages in thread From: Andres Freund @ 2026-02-07 19:04 UTC (permalink / raw) With address sanitizer's stack-use-after-return check, stack variables are moved to heap allocations, to allow to detect references to the memory at a later time. That broke our stack-depth check, which is why we had to disable detect_stack_use_after_return in CI. Luckily __builtin_frame_address() works correctly, even under asan, so use that. Author: Reviewed-by: Discussion: https://postgr.es/m/ Backpatch: --- src/backend/utils/misc/stack_depth.c | 17 ++++++++++++++++- .cirrus.tasks.yml | 2 +- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/src/backend/utils/misc/stack_depth.c b/src/backend/utils/misc/stack_depth.c index 61a07cf824e..9955e28c176 100644 --- a/src/backend/utils/misc/stack_depth.c +++ b/src/backend/utils/misc/stack_depth.c @@ -108,13 +108,28 @@ check_stack_depth(void) bool stack_is_too_deep(void) { +#ifndef HAVE__BUILTIN_FRAME_ADDRESS char stack_top_loc; +#endif ssize_t stack_depth; + char *stack_address; + + /* + * With address sanitizer's stack-use-after-return check, stack variables + * are moved to heap allocations, to allow to detect references to the + * memory at a later time. That would break our stack-depth check. Luckily + * __builtin_frame_address() works correctly, even under asan. + */ +#ifndef HAVE__BUILTIN_FRAME_ADDRESS + stack_address = &stack_top_loc; +#else + stack_address = (char *) __builtin_frame_address(0); +#endif /* * Compute distance from reference point to my local variables */ - stack_depth = (ssize_t) (stack_base_ptr - &stack_top_loc); + stack_depth = (ssize_t) (stack_base_ptr - stack_address); /* * Take abs value, since stacks grow up on some machines, down on others diff --git a/.cirrus.tasks.yml b/.cirrus.tasks.yml index a22cef063f3..8683d1ae9c7 100644 --- a/.cirrus.tasks.yml +++ b/.cirrus.tasks.yml @@ -447,7 +447,7 @@ task: # print_stacktraces=1,verbosity=2, duh # detect_leaks=0: too many uninteresting leak errors in short-lived binaries UBSAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:verbosity=2 - ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0:detect_stack_use_after_return=0 + ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0 # SANITIZER_FLAGS is set in the tasks below CFLAGS: -Og -ggdb -fno-sanitize-recover=all $SANITIZER_FLAGS -- 2.53.0.1.gb2826b52eb --7e2sypi5gxvkgog6-- ^ permalink raw reply [nested|flat] 114+ messages in thread
* [PATCH v2 1/4] Make stack depth check work with asan's use-after-return @ 2026-02-07 19:04 Andres Freund <[email protected]> 0 siblings, 0 replies; 114+ messages in thread From: Andres Freund @ 2026-02-07 19:04 UTC (permalink / raw) With address sanitizer's stack-use-after-return check, stack variables are moved to heap allocations, to allow to detect references to the memory at a later time. That broke our stack-depth check, which is why we had to disable detect_stack_use_after_return in CI. Luckily __builtin_frame_address() works correctly, even under asan, so use that. Author: Reviewed-by: Discussion: https://postgr.es/m/ Backpatch: --- src/backend/utils/misc/stack_depth.c | 17 ++++++++++++++++- .cirrus.tasks.yml | 2 +- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/src/backend/utils/misc/stack_depth.c b/src/backend/utils/misc/stack_depth.c index 61a07cf824e..9955e28c176 100644 --- a/src/backend/utils/misc/stack_depth.c +++ b/src/backend/utils/misc/stack_depth.c @@ -108,13 +108,28 @@ check_stack_depth(void) bool stack_is_too_deep(void) { +#ifndef HAVE__BUILTIN_FRAME_ADDRESS char stack_top_loc; +#endif ssize_t stack_depth; + char *stack_address; + + /* + * With address sanitizer's stack-use-after-return check, stack variables + * are moved to heap allocations, to allow to detect references to the + * memory at a later time. That would break our stack-depth check. Luckily + * __builtin_frame_address() works correctly, even under asan. + */ +#ifndef HAVE__BUILTIN_FRAME_ADDRESS + stack_address = &stack_top_loc; +#else + stack_address = (char *) __builtin_frame_address(0); +#endif /* * Compute distance from reference point to my local variables */ - stack_depth = (ssize_t) (stack_base_ptr - &stack_top_loc); + stack_depth = (ssize_t) (stack_base_ptr - stack_address); /* * Take abs value, since stacks grow up on some machines, down on others diff --git a/.cirrus.tasks.yml b/.cirrus.tasks.yml index a22cef063f3..8683d1ae9c7 100644 --- a/.cirrus.tasks.yml +++ b/.cirrus.tasks.yml @@ -447,7 +447,7 @@ task: # print_stacktraces=1,verbosity=2, duh # detect_leaks=0: too many uninteresting leak errors in short-lived binaries UBSAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:verbosity=2 - ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0:detect_stack_use_after_return=0 + ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0 # SANITIZER_FLAGS is set in the tasks below CFLAGS: -Og -ggdb -fno-sanitize-recover=all $SANITIZER_FLAGS -- 2.53.0.1.gb2826b52eb --7e2sypi5gxvkgog6-- ^ permalink raw reply [nested|flat] 114+ messages in thread
* [PATCH v2 1/4] Make stack depth check work with asan's use-after-return @ 2026-02-07 19:04 Andres Freund <[email protected]> 0 siblings, 0 replies; 114+ messages in thread From: Andres Freund @ 2026-02-07 19:04 UTC (permalink / raw) With address sanitizer's stack-use-after-return check, stack variables are moved to heap allocations, to allow to detect references to the memory at a later time. That broke our stack-depth check, which is why we had to disable detect_stack_use_after_return in CI. Luckily __builtin_frame_address() works correctly, even under asan, so use that. Author: Reviewed-by: Discussion: https://postgr.es/m/ Backpatch: --- src/backend/utils/misc/stack_depth.c | 17 ++++++++++++++++- .cirrus.tasks.yml | 2 +- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/src/backend/utils/misc/stack_depth.c b/src/backend/utils/misc/stack_depth.c index 61a07cf824e..9955e28c176 100644 --- a/src/backend/utils/misc/stack_depth.c +++ b/src/backend/utils/misc/stack_depth.c @@ -108,13 +108,28 @@ check_stack_depth(void) bool stack_is_too_deep(void) { +#ifndef HAVE__BUILTIN_FRAME_ADDRESS char stack_top_loc; +#endif ssize_t stack_depth; + char *stack_address; + + /* + * With address sanitizer's stack-use-after-return check, stack variables + * are moved to heap allocations, to allow to detect references to the + * memory at a later time. That would break our stack-depth check. Luckily + * __builtin_frame_address() works correctly, even under asan. + */ +#ifndef HAVE__BUILTIN_FRAME_ADDRESS + stack_address = &stack_top_loc; +#else + stack_address = (char *) __builtin_frame_address(0); +#endif /* * Compute distance from reference point to my local variables */ - stack_depth = (ssize_t) (stack_base_ptr - &stack_top_loc); + stack_depth = (ssize_t) (stack_base_ptr - stack_address); /* * Take abs value, since stacks grow up on some machines, down on others diff --git a/.cirrus.tasks.yml b/.cirrus.tasks.yml index a22cef063f3..8683d1ae9c7 100644 --- a/.cirrus.tasks.yml +++ b/.cirrus.tasks.yml @@ -447,7 +447,7 @@ task: # print_stacktraces=1,verbosity=2, duh # detect_leaks=0: too many uninteresting leak errors in short-lived binaries UBSAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:verbosity=2 - ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0:detect_stack_use_after_return=0 + ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0 # SANITIZER_FLAGS is set in the tasks below CFLAGS: -Og -ggdb -fno-sanitize-recover=all $SANITIZER_FLAGS -- 2.53.0.1.gb2826b52eb --7e2sypi5gxvkgog6-- ^ permalink raw reply [nested|flat] 114+ messages in thread
* [PATCH v2 1/4] Make stack depth check work with asan's use-after-return @ 2026-02-07 19:04 Andres Freund <[email protected]> 0 siblings, 0 replies; 114+ messages in thread From: Andres Freund @ 2026-02-07 19:04 UTC (permalink / raw) With address sanitizer's stack-use-after-return check, stack variables are moved to heap allocations, to allow to detect references to the memory at a later time. That broke our stack-depth check, which is why we had to disable detect_stack_use_after_return in CI. Luckily __builtin_frame_address() works correctly, even under asan, so use that. Author: Reviewed-by: Discussion: https://postgr.es/m/ Backpatch: --- src/backend/utils/misc/stack_depth.c | 17 ++++++++++++++++- .cirrus.tasks.yml | 2 +- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/src/backend/utils/misc/stack_depth.c b/src/backend/utils/misc/stack_depth.c index 61a07cf824e..9955e28c176 100644 --- a/src/backend/utils/misc/stack_depth.c +++ b/src/backend/utils/misc/stack_depth.c @@ -108,13 +108,28 @@ check_stack_depth(void) bool stack_is_too_deep(void) { +#ifndef HAVE__BUILTIN_FRAME_ADDRESS char stack_top_loc; +#endif ssize_t stack_depth; + char *stack_address; + + /* + * With address sanitizer's stack-use-after-return check, stack variables + * are moved to heap allocations, to allow to detect references to the + * memory at a later time. That would break our stack-depth check. Luckily + * __builtin_frame_address() works correctly, even under asan. + */ +#ifndef HAVE__BUILTIN_FRAME_ADDRESS + stack_address = &stack_top_loc; +#else + stack_address = (char *) __builtin_frame_address(0); +#endif /* * Compute distance from reference point to my local variables */ - stack_depth = (ssize_t) (stack_base_ptr - &stack_top_loc); + stack_depth = (ssize_t) (stack_base_ptr - stack_address); /* * Take abs value, since stacks grow up on some machines, down on others diff --git a/.cirrus.tasks.yml b/.cirrus.tasks.yml index a22cef063f3..8683d1ae9c7 100644 --- a/.cirrus.tasks.yml +++ b/.cirrus.tasks.yml @@ -447,7 +447,7 @@ task: # print_stacktraces=1,verbosity=2, duh # detect_leaks=0: too many uninteresting leak errors in short-lived binaries UBSAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:verbosity=2 - ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0:detect_stack_use_after_return=0 + ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0 # SANITIZER_FLAGS is set in the tasks below CFLAGS: -Og -ggdb -fno-sanitize-recover=all $SANITIZER_FLAGS -- 2.53.0.1.gb2826b52eb --7e2sypi5gxvkgog6-- ^ permalink raw reply [nested|flat] 114+ messages in thread
* [PATCH v2 1/4] Make stack depth check work with asan's use-after-return @ 2026-02-07 19:04 Andres Freund <[email protected]> 0 siblings, 0 replies; 114+ messages in thread From: Andres Freund @ 2026-02-07 19:04 UTC (permalink / raw) With address sanitizer's stack-use-after-return check, stack variables are moved to heap allocations, to allow to detect references to the memory at a later time. That broke our stack-depth check, which is why we had to disable detect_stack_use_after_return in CI. Luckily __builtin_frame_address() works correctly, even under asan, so use that. Author: Reviewed-by: Discussion: https://postgr.es/m/ Backpatch: --- src/backend/utils/misc/stack_depth.c | 17 ++++++++++++++++- .cirrus.tasks.yml | 2 +- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/src/backend/utils/misc/stack_depth.c b/src/backend/utils/misc/stack_depth.c index 61a07cf824e..9955e28c176 100644 --- a/src/backend/utils/misc/stack_depth.c +++ b/src/backend/utils/misc/stack_depth.c @@ -108,13 +108,28 @@ check_stack_depth(void) bool stack_is_too_deep(void) { +#ifndef HAVE__BUILTIN_FRAME_ADDRESS char stack_top_loc; +#endif ssize_t stack_depth; + char *stack_address; + + /* + * With address sanitizer's stack-use-after-return check, stack variables + * are moved to heap allocations, to allow to detect references to the + * memory at a later time. That would break our stack-depth check. Luckily + * __builtin_frame_address() works correctly, even under asan. + */ +#ifndef HAVE__BUILTIN_FRAME_ADDRESS + stack_address = &stack_top_loc; +#else + stack_address = (char *) __builtin_frame_address(0); +#endif /* * Compute distance from reference point to my local variables */ - stack_depth = (ssize_t) (stack_base_ptr - &stack_top_loc); + stack_depth = (ssize_t) (stack_base_ptr - stack_address); /* * Take abs value, since stacks grow up on some machines, down on others diff --git a/.cirrus.tasks.yml b/.cirrus.tasks.yml index a22cef063f3..8683d1ae9c7 100644 --- a/.cirrus.tasks.yml +++ b/.cirrus.tasks.yml @@ -447,7 +447,7 @@ task: # print_stacktraces=1,verbosity=2, duh # detect_leaks=0: too many uninteresting leak errors in short-lived binaries UBSAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:verbosity=2 - ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0:detect_stack_use_after_return=0 + ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0 # SANITIZER_FLAGS is set in the tasks below CFLAGS: -Og -ggdb -fno-sanitize-recover=all $SANITIZER_FLAGS -- 2.53.0.1.gb2826b52eb --7e2sypi5gxvkgog6-- ^ permalink raw reply [nested|flat] 114+ messages in thread
* [PATCH v2 1/4] Make stack depth check work with asan's use-after-return @ 2026-02-07 19:04 Andres Freund <[email protected]> 0 siblings, 0 replies; 114+ messages in thread From: Andres Freund @ 2026-02-07 19:04 UTC (permalink / raw) With address sanitizer's stack-use-after-return check, stack variables are moved to heap allocations, to allow to detect references to the memory at a later time. That broke our stack-depth check, which is why we had to disable detect_stack_use_after_return in CI. Luckily __builtin_frame_address() works correctly, even under asan, so use that. Author: Reviewed-by: Discussion: https://postgr.es/m/ Backpatch: --- src/backend/utils/misc/stack_depth.c | 17 ++++++++++++++++- .cirrus.tasks.yml | 2 +- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/src/backend/utils/misc/stack_depth.c b/src/backend/utils/misc/stack_depth.c index 61a07cf824e..9955e28c176 100644 --- a/src/backend/utils/misc/stack_depth.c +++ b/src/backend/utils/misc/stack_depth.c @@ -108,13 +108,28 @@ check_stack_depth(void) bool stack_is_too_deep(void) { +#ifndef HAVE__BUILTIN_FRAME_ADDRESS char stack_top_loc; +#endif ssize_t stack_depth; + char *stack_address; + + /* + * With address sanitizer's stack-use-after-return check, stack variables + * are moved to heap allocations, to allow to detect references to the + * memory at a later time. That would break our stack-depth check. Luckily + * __builtin_frame_address() works correctly, even under asan. + */ +#ifndef HAVE__BUILTIN_FRAME_ADDRESS + stack_address = &stack_top_loc; +#else + stack_address = (char *) __builtin_frame_address(0); +#endif /* * Compute distance from reference point to my local variables */ - stack_depth = (ssize_t) (stack_base_ptr - &stack_top_loc); + stack_depth = (ssize_t) (stack_base_ptr - stack_address); /* * Take abs value, since stacks grow up on some machines, down on others diff --git a/.cirrus.tasks.yml b/.cirrus.tasks.yml index a22cef063f3..8683d1ae9c7 100644 --- a/.cirrus.tasks.yml +++ b/.cirrus.tasks.yml @@ -447,7 +447,7 @@ task: # print_stacktraces=1,verbosity=2, duh # detect_leaks=0: too many uninteresting leak errors in short-lived binaries UBSAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:verbosity=2 - ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0:detect_stack_use_after_return=0 + ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0 # SANITIZER_FLAGS is set in the tasks below CFLAGS: -Og -ggdb -fno-sanitize-recover=all $SANITIZER_FLAGS -- 2.53.0.1.gb2826b52eb --7e2sypi5gxvkgog6-- ^ permalink raw reply [nested|flat] 114+ messages in thread
* [PATCH v2 1/4] Make stack depth check work with asan's use-after-return @ 2026-02-07 19:04 Andres Freund <[email protected]> 0 siblings, 0 replies; 114+ messages in thread From: Andres Freund @ 2026-02-07 19:04 UTC (permalink / raw) With address sanitizer's stack-use-after-return check, stack variables are moved to heap allocations, to allow to detect references to the memory at a later time. That broke our stack-depth check, which is why we had to disable detect_stack_use_after_return in CI. Luckily __builtin_frame_address() works correctly, even under asan, so use that. Author: Reviewed-by: Discussion: https://postgr.es/m/ Backpatch: --- src/backend/utils/misc/stack_depth.c | 17 ++++++++++++++++- .cirrus.tasks.yml | 2 +- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/src/backend/utils/misc/stack_depth.c b/src/backend/utils/misc/stack_depth.c index 61a07cf824e..9955e28c176 100644 --- a/src/backend/utils/misc/stack_depth.c +++ b/src/backend/utils/misc/stack_depth.c @@ -108,13 +108,28 @@ check_stack_depth(void) bool stack_is_too_deep(void) { +#ifndef HAVE__BUILTIN_FRAME_ADDRESS char stack_top_loc; +#endif ssize_t stack_depth; + char *stack_address; + + /* + * With address sanitizer's stack-use-after-return check, stack variables + * are moved to heap allocations, to allow to detect references to the + * memory at a later time. That would break our stack-depth check. Luckily + * __builtin_frame_address() works correctly, even under asan. + */ +#ifndef HAVE__BUILTIN_FRAME_ADDRESS + stack_address = &stack_top_loc; +#else + stack_address = (char *) __builtin_frame_address(0); +#endif /* * Compute distance from reference point to my local variables */ - stack_depth = (ssize_t) (stack_base_ptr - &stack_top_loc); + stack_depth = (ssize_t) (stack_base_ptr - stack_address); /* * Take abs value, since stacks grow up on some machines, down on others diff --git a/.cirrus.tasks.yml b/.cirrus.tasks.yml index a22cef063f3..8683d1ae9c7 100644 --- a/.cirrus.tasks.yml +++ b/.cirrus.tasks.yml @@ -447,7 +447,7 @@ task: # print_stacktraces=1,verbosity=2, duh # detect_leaks=0: too many uninteresting leak errors in short-lived binaries UBSAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:verbosity=2 - ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0:detect_stack_use_after_return=0 + ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0 # SANITIZER_FLAGS is set in the tasks below CFLAGS: -Og -ggdb -fno-sanitize-recover=all $SANITIZER_FLAGS -- 2.53.0.1.gb2826b52eb --7e2sypi5gxvkgog6-- ^ permalink raw reply [nested|flat] 114+ messages in thread
* [PATCH v2 1/4] Make stack depth check work with asan's use-after-return @ 2026-02-07 19:04 Andres Freund <[email protected]> 0 siblings, 0 replies; 114+ messages in thread From: Andres Freund @ 2026-02-07 19:04 UTC (permalink / raw) With address sanitizer's stack-use-after-return check, stack variables are moved to heap allocations, to allow to detect references to the memory at a later time. That broke our stack-depth check, which is why we had to disable detect_stack_use_after_return in CI. Luckily __builtin_frame_address() works correctly, even under asan, so use that. Author: Reviewed-by: Discussion: https://postgr.es/m/ Backpatch: --- src/backend/utils/misc/stack_depth.c | 17 ++++++++++++++++- .cirrus.tasks.yml | 2 +- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/src/backend/utils/misc/stack_depth.c b/src/backend/utils/misc/stack_depth.c index 61a07cf824e..9955e28c176 100644 --- a/src/backend/utils/misc/stack_depth.c +++ b/src/backend/utils/misc/stack_depth.c @@ -108,13 +108,28 @@ check_stack_depth(void) bool stack_is_too_deep(void) { +#ifndef HAVE__BUILTIN_FRAME_ADDRESS char stack_top_loc; +#endif ssize_t stack_depth; + char *stack_address; + + /* + * With address sanitizer's stack-use-after-return check, stack variables + * are moved to heap allocations, to allow to detect references to the + * memory at a later time. That would break our stack-depth check. Luckily + * __builtin_frame_address() works correctly, even under asan. + */ +#ifndef HAVE__BUILTIN_FRAME_ADDRESS + stack_address = &stack_top_loc; +#else + stack_address = (char *) __builtin_frame_address(0); +#endif /* * Compute distance from reference point to my local variables */ - stack_depth = (ssize_t) (stack_base_ptr - &stack_top_loc); + stack_depth = (ssize_t) (stack_base_ptr - stack_address); /* * Take abs value, since stacks grow up on some machines, down on others diff --git a/.cirrus.tasks.yml b/.cirrus.tasks.yml index a22cef063f3..8683d1ae9c7 100644 --- a/.cirrus.tasks.yml +++ b/.cirrus.tasks.yml @@ -447,7 +447,7 @@ task: # print_stacktraces=1,verbosity=2, duh # detect_leaks=0: too many uninteresting leak errors in short-lived binaries UBSAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:verbosity=2 - ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0:detect_stack_use_after_return=0 + ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0 # SANITIZER_FLAGS is set in the tasks below CFLAGS: -Og -ggdb -fno-sanitize-recover=all $SANITIZER_FLAGS -- 2.53.0.1.gb2826b52eb --7e2sypi5gxvkgog6-- ^ permalink raw reply [nested|flat] 114+ messages in thread
* [PATCH v2 1/4] Make stack depth check work with asan's use-after-return @ 2026-02-07 19:04 Andres Freund <[email protected]> 0 siblings, 0 replies; 114+ messages in thread From: Andres Freund @ 2026-02-07 19:04 UTC (permalink / raw) With address sanitizer's stack-use-after-return check, stack variables are moved to heap allocations, to allow to detect references to the memory at a later time. That broke our stack-depth check, which is why we had to disable detect_stack_use_after_return in CI. Luckily __builtin_frame_address() works correctly, even under asan, so use that. Author: Reviewed-by: Discussion: https://postgr.es/m/ Backpatch: --- src/backend/utils/misc/stack_depth.c | 17 ++++++++++++++++- .cirrus.tasks.yml | 2 +- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/src/backend/utils/misc/stack_depth.c b/src/backend/utils/misc/stack_depth.c index 61a07cf824e..9955e28c176 100644 --- a/src/backend/utils/misc/stack_depth.c +++ b/src/backend/utils/misc/stack_depth.c @@ -108,13 +108,28 @@ check_stack_depth(void) bool stack_is_too_deep(void) { +#ifndef HAVE__BUILTIN_FRAME_ADDRESS char stack_top_loc; +#endif ssize_t stack_depth; + char *stack_address; + + /* + * With address sanitizer's stack-use-after-return check, stack variables + * are moved to heap allocations, to allow to detect references to the + * memory at a later time. That would break our stack-depth check. Luckily + * __builtin_frame_address() works correctly, even under asan. + */ +#ifndef HAVE__BUILTIN_FRAME_ADDRESS + stack_address = &stack_top_loc; +#else + stack_address = (char *) __builtin_frame_address(0); +#endif /* * Compute distance from reference point to my local variables */ - stack_depth = (ssize_t) (stack_base_ptr - &stack_top_loc); + stack_depth = (ssize_t) (stack_base_ptr - stack_address); /* * Take abs value, since stacks grow up on some machines, down on others diff --git a/.cirrus.tasks.yml b/.cirrus.tasks.yml index a22cef063f3..8683d1ae9c7 100644 --- a/.cirrus.tasks.yml +++ b/.cirrus.tasks.yml @@ -447,7 +447,7 @@ task: # print_stacktraces=1,verbosity=2, duh # detect_leaks=0: too many uninteresting leak errors in short-lived binaries UBSAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:verbosity=2 - ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0:detect_stack_use_after_return=0 + ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0 # SANITIZER_FLAGS is set in the tasks below CFLAGS: -Og -ggdb -fno-sanitize-recover=all $SANITIZER_FLAGS -- 2.53.0.1.gb2826b52eb --7e2sypi5gxvkgog6-- ^ permalink raw reply [nested|flat] 114+ messages in thread
* [PATCH v2 1/4] Make stack depth check work with asan's use-after-return @ 2026-02-07 19:04 Andres Freund <[email protected]> 0 siblings, 0 replies; 114+ messages in thread From: Andres Freund @ 2026-02-07 19:04 UTC (permalink / raw) With address sanitizer's stack-use-after-return check, stack variables are moved to heap allocations, to allow to detect references to the memory at a later time. That broke our stack-depth check, which is why we had to disable detect_stack_use_after_return in CI. Luckily __builtin_frame_address() works correctly, even under asan, so use that. Author: Reviewed-by: Discussion: https://postgr.es/m/ Backpatch: --- src/backend/utils/misc/stack_depth.c | 17 ++++++++++++++++- .cirrus.tasks.yml | 2 +- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/src/backend/utils/misc/stack_depth.c b/src/backend/utils/misc/stack_depth.c index 61a07cf824e..9955e28c176 100644 --- a/src/backend/utils/misc/stack_depth.c +++ b/src/backend/utils/misc/stack_depth.c @@ -108,13 +108,28 @@ check_stack_depth(void) bool stack_is_too_deep(void) { +#ifndef HAVE__BUILTIN_FRAME_ADDRESS char stack_top_loc; +#endif ssize_t stack_depth; + char *stack_address; + + /* + * With address sanitizer's stack-use-after-return check, stack variables + * are moved to heap allocations, to allow to detect references to the + * memory at a later time. That would break our stack-depth check. Luckily + * __builtin_frame_address() works correctly, even under asan. + */ +#ifndef HAVE__BUILTIN_FRAME_ADDRESS + stack_address = &stack_top_loc; +#else + stack_address = (char *) __builtin_frame_address(0); +#endif /* * Compute distance from reference point to my local variables */ - stack_depth = (ssize_t) (stack_base_ptr - &stack_top_loc); + stack_depth = (ssize_t) (stack_base_ptr - stack_address); /* * Take abs value, since stacks grow up on some machines, down on others diff --git a/.cirrus.tasks.yml b/.cirrus.tasks.yml index a22cef063f3..8683d1ae9c7 100644 --- a/.cirrus.tasks.yml +++ b/.cirrus.tasks.yml @@ -447,7 +447,7 @@ task: # print_stacktraces=1,verbosity=2, duh # detect_leaks=0: too many uninteresting leak errors in short-lived binaries UBSAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:verbosity=2 - ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0:detect_stack_use_after_return=0 + ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0 # SANITIZER_FLAGS is set in the tasks below CFLAGS: -Og -ggdb -fno-sanitize-recover=all $SANITIZER_FLAGS -- 2.53.0.1.gb2826b52eb --7e2sypi5gxvkgog6-- ^ permalink raw reply [nested|flat] 114+ messages in thread
* [PATCH v2 1/4] Make stack depth check work with asan's use-after-return @ 2026-02-07 19:04 Andres Freund <[email protected]> 0 siblings, 0 replies; 114+ messages in thread From: Andres Freund @ 2026-02-07 19:04 UTC (permalink / raw) With address sanitizer's stack-use-after-return check, stack variables are moved to heap allocations, to allow to detect references to the memory at a later time. That broke our stack-depth check, which is why we had to disable detect_stack_use_after_return in CI. Luckily __builtin_frame_address() works correctly, even under asan, so use that. Author: Reviewed-by: Discussion: https://postgr.es/m/ Backpatch: --- src/backend/utils/misc/stack_depth.c | 17 ++++++++++++++++- .cirrus.tasks.yml | 2 +- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/src/backend/utils/misc/stack_depth.c b/src/backend/utils/misc/stack_depth.c index 61a07cf824e..9955e28c176 100644 --- a/src/backend/utils/misc/stack_depth.c +++ b/src/backend/utils/misc/stack_depth.c @@ -108,13 +108,28 @@ check_stack_depth(void) bool stack_is_too_deep(void) { +#ifndef HAVE__BUILTIN_FRAME_ADDRESS char stack_top_loc; +#endif ssize_t stack_depth; + char *stack_address; + + /* + * With address sanitizer's stack-use-after-return check, stack variables + * are moved to heap allocations, to allow to detect references to the + * memory at a later time. That would break our stack-depth check. Luckily + * __builtin_frame_address() works correctly, even under asan. + */ +#ifndef HAVE__BUILTIN_FRAME_ADDRESS + stack_address = &stack_top_loc; +#else + stack_address = (char *) __builtin_frame_address(0); +#endif /* * Compute distance from reference point to my local variables */ - stack_depth = (ssize_t) (stack_base_ptr - &stack_top_loc); + stack_depth = (ssize_t) (stack_base_ptr - stack_address); /* * Take abs value, since stacks grow up on some machines, down on others diff --git a/.cirrus.tasks.yml b/.cirrus.tasks.yml index a22cef063f3..8683d1ae9c7 100644 --- a/.cirrus.tasks.yml +++ b/.cirrus.tasks.yml @@ -447,7 +447,7 @@ task: # print_stacktraces=1,verbosity=2, duh # detect_leaks=0: too many uninteresting leak errors in short-lived binaries UBSAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:verbosity=2 - ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0:detect_stack_use_after_return=0 + ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0 # SANITIZER_FLAGS is set in the tasks below CFLAGS: -Og -ggdb -fno-sanitize-recover=all $SANITIZER_FLAGS -- 2.53.0.1.gb2826b52eb --7e2sypi5gxvkgog6-- ^ permalink raw reply [nested|flat] 114+ messages in thread
* [PATCH v2 1/4] Make stack depth check work with asan's use-after-return @ 2026-02-07 19:04 Andres Freund <[email protected]> 0 siblings, 0 replies; 114+ messages in thread From: Andres Freund @ 2026-02-07 19:04 UTC (permalink / raw) With address sanitizer's stack-use-after-return check, stack variables are moved to heap allocations, to allow to detect references to the memory at a later time. That broke our stack-depth check, which is why we had to disable detect_stack_use_after_return in CI. Luckily __builtin_frame_address() works correctly, even under asan, so use that. Author: Reviewed-by: Discussion: https://postgr.es/m/ Backpatch: --- src/backend/utils/misc/stack_depth.c | 17 ++++++++++++++++- .cirrus.tasks.yml | 2 +- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/src/backend/utils/misc/stack_depth.c b/src/backend/utils/misc/stack_depth.c index 61a07cf824e..9955e28c176 100644 --- a/src/backend/utils/misc/stack_depth.c +++ b/src/backend/utils/misc/stack_depth.c @@ -108,13 +108,28 @@ check_stack_depth(void) bool stack_is_too_deep(void) { +#ifndef HAVE__BUILTIN_FRAME_ADDRESS char stack_top_loc; +#endif ssize_t stack_depth; + char *stack_address; + + /* + * With address sanitizer's stack-use-after-return check, stack variables + * are moved to heap allocations, to allow to detect references to the + * memory at a later time. That would break our stack-depth check. Luckily + * __builtin_frame_address() works correctly, even under asan. + */ +#ifndef HAVE__BUILTIN_FRAME_ADDRESS + stack_address = &stack_top_loc; +#else + stack_address = (char *) __builtin_frame_address(0); +#endif /* * Compute distance from reference point to my local variables */ - stack_depth = (ssize_t) (stack_base_ptr - &stack_top_loc); + stack_depth = (ssize_t) (stack_base_ptr - stack_address); /* * Take abs value, since stacks grow up on some machines, down on others diff --git a/.cirrus.tasks.yml b/.cirrus.tasks.yml index a22cef063f3..8683d1ae9c7 100644 --- a/.cirrus.tasks.yml +++ b/.cirrus.tasks.yml @@ -447,7 +447,7 @@ task: # print_stacktraces=1,verbosity=2, duh # detect_leaks=0: too many uninteresting leak errors in short-lived binaries UBSAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:verbosity=2 - ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0:detect_stack_use_after_return=0 + ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0 # SANITIZER_FLAGS is set in the tasks below CFLAGS: -Og -ggdb -fno-sanitize-recover=all $SANITIZER_FLAGS -- 2.53.0.1.gb2826b52eb --7e2sypi5gxvkgog6-- ^ permalink raw reply [nested|flat] 114+ messages in thread
* [PATCH v2 1/4] Make stack depth check work with asan's use-after-return @ 2026-02-07 19:04 Andres Freund <[email protected]> 0 siblings, 0 replies; 114+ messages in thread From: Andres Freund @ 2026-02-07 19:04 UTC (permalink / raw) With address sanitizer's stack-use-after-return check, stack variables are moved to heap allocations, to allow to detect references to the memory at a later time. That broke our stack-depth check, which is why we had to disable detect_stack_use_after_return in CI. Luckily __builtin_frame_address() works correctly, even under asan, so use that. Author: Reviewed-by: Discussion: https://postgr.es/m/ Backpatch: --- src/backend/utils/misc/stack_depth.c | 17 ++++++++++++++++- .cirrus.tasks.yml | 2 +- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/src/backend/utils/misc/stack_depth.c b/src/backend/utils/misc/stack_depth.c index 61a07cf824e..9955e28c176 100644 --- a/src/backend/utils/misc/stack_depth.c +++ b/src/backend/utils/misc/stack_depth.c @@ -108,13 +108,28 @@ check_stack_depth(void) bool stack_is_too_deep(void) { +#ifndef HAVE__BUILTIN_FRAME_ADDRESS char stack_top_loc; +#endif ssize_t stack_depth; + char *stack_address; + + /* + * With address sanitizer's stack-use-after-return check, stack variables + * are moved to heap allocations, to allow to detect references to the + * memory at a later time. That would break our stack-depth check. Luckily + * __builtin_frame_address() works correctly, even under asan. + */ +#ifndef HAVE__BUILTIN_FRAME_ADDRESS + stack_address = &stack_top_loc; +#else + stack_address = (char *) __builtin_frame_address(0); +#endif /* * Compute distance from reference point to my local variables */ - stack_depth = (ssize_t) (stack_base_ptr - &stack_top_loc); + stack_depth = (ssize_t) (stack_base_ptr - stack_address); /* * Take abs value, since stacks grow up on some machines, down on others diff --git a/.cirrus.tasks.yml b/.cirrus.tasks.yml index a22cef063f3..8683d1ae9c7 100644 --- a/.cirrus.tasks.yml +++ b/.cirrus.tasks.yml @@ -447,7 +447,7 @@ task: # print_stacktraces=1,verbosity=2, duh # detect_leaks=0: too many uninteresting leak errors in short-lived binaries UBSAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:verbosity=2 - ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0:detect_stack_use_after_return=0 + ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0 # SANITIZER_FLAGS is set in the tasks below CFLAGS: -Og -ggdb -fno-sanitize-recover=all $SANITIZER_FLAGS -- 2.53.0.1.gb2826b52eb --7e2sypi5gxvkgog6-- ^ permalink raw reply [nested|flat] 114+ messages in thread
* [PATCH v2 1/4] Make stack depth check work with asan's use-after-return @ 2026-02-07 19:04 Andres Freund <[email protected]> 0 siblings, 0 replies; 114+ messages in thread From: Andres Freund @ 2026-02-07 19:04 UTC (permalink / raw) With address sanitizer's stack-use-after-return check, stack variables are moved to heap allocations, to allow to detect references to the memory at a later time. That broke our stack-depth check, which is why we had to disable detect_stack_use_after_return in CI. Luckily __builtin_frame_address() works correctly, even under asan, so use that. Author: Reviewed-by: Discussion: https://postgr.es/m/ Backpatch: --- src/backend/utils/misc/stack_depth.c | 17 ++++++++++++++++- .cirrus.tasks.yml | 2 +- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/src/backend/utils/misc/stack_depth.c b/src/backend/utils/misc/stack_depth.c index 61a07cf824e..9955e28c176 100644 --- a/src/backend/utils/misc/stack_depth.c +++ b/src/backend/utils/misc/stack_depth.c @@ -108,13 +108,28 @@ check_stack_depth(void) bool stack_is_too_deep(void) { +#ifndef HAVE__BUILTIN_FRAME_ADDRESS char stack_top_loc; +#endif ssize_t stack_depth; + char *stack_address; + + /* + * With address sanitizer's stack-use-after-return check, stack variables + * are moved to heap allocations, to allow to detect references to the + * memory at a later time. That would break our stack-depth check. Luckily + * __builtin_frame_address() works correctly, even under asan. + */ +#ifndef HAVE__BUILTIN_FRAME_ADDRESS + stack_address = &stack_top_loc; +#else + stack_address = (char *) __builtin_frame_address(0); +#endif /* * Compute distance from reference point to my local variables */ - stack_depth = (ssize_t) (stack_base_ptr - &stack_top_loc); + stack_depth = (ssize_t) (stack_base_ptr - stack_address); /* * Take abs value, since stacks grow up on some machines, down on others diff --git a/.cirrus.tasks.yml b/.cirrus.tasks.yml index a22cef063f3..8683d1ae9c7 100644 --- a/.cirrus.tasks.yml +++ b/.cirrus.tasks.yml @@ -447,7 +447,7 @@ task: # print_stacktraces=1,verbosity=2, duh # detect_leaks=0: too many uninteresting leak errors in short-lived binaries UBSAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:verbosity=2 - ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0:detect_stack_use_after_return=0 + ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0 # SANITIZER_FLAGS is set in the tasks below CFLAGS: -Og -ggdb -fno-sanitize-recover=all $SANITIZER_FLAGS -- 2.53.0.1.gb2826b52eb --7e2sypi5gxvkgog6-- ^ permalink raw reply [nested|flat] 114+ messages in thread
* [PATCH v2 1/4] Make stack depth check work with asan's use-after-return @ 2026-02-07 19:04 Andres Freund <[email protected]> 0 siblings, 0 replies; 114+ messages in thread From: Andres Freund @ 2026-02-07 19:04 UTC (permalink / raw) With address sanitizer's stack-use-after-return check, stack variables are moved to heap allocations, to allow to detect references to the memory at a later time. That broke our stack-depth check, which is why we had to disable detect_stack_use_after_return in CI. Luckily __builtin_frame_address() works correctly, even under asan, so use that. Author: Reviewed-by: Discussion: https://postgr.es/m/ Backpatch: --- src/backend/utils/misc/stack_depth.c | 17 ++++++++++++++++- .cirrus.tasks.yml | 2 +- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/src/backend/utils/misc/stack_depth.c b/src/backend/utils/misc/stack_depth.c index 61a07cf824e..9955e28c176 100644 --- a/src/backend/utils/misc/stack_depth.c +++ b/src/backend/utils/misc/stack_depth.c @@ -108,13 +108,28 @@ check_stack_depth(void) bool stack_is_too_deep(void) { +#ifndef HAVE__BUILTIN_FRAME_ADDRESS char stack_top_loc; +#endif ssize_t stack_depth; + char *stack_address; + + /* + * With address sanitizer's stack-use-after-return check, stack variables + * are moved to heap allocations, to allow to detect references to the + * memory at a later time. That would break our stack-depth check. Luckily + * __builtin_frame_address() works correctly, even under asan. + */ +#ifndef HAVE__BUILTIN_FRAME_ADDRESS + stack_address = &stack_top_loc; +#else + stack_address = (char *) __builtin_frame_address(0); +#endif /* * Compute distance from reference point to my local variables */ - stack_depth = (ssize_t) (stack_base_ptr - &stack_top_loc); + stack_depth = (ssize_t) (stack_base_ptr - stack_address); /* * Take abs value, since stacks grow up on some machines, down on others diff --git a/.cirrus.tasks.yml b/.cirrus.tasks.yml index a22cef063f3..8683d1ae9c7 100644 --- a/.cirrus.tasks.yml +++ b/.cirrus.tasks.yml @@ -447,7 +447,7 @@ task: # print_stacktraces=1,verbosity=2, duh # detect_leaks=0: too many uninteresting leak errors in short-lived binaries UBSAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:verbosity=2 - ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0:detect_stack_use_after_return=0 + ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0 # SANITIZER_FLAGS is set in the tasks below CFLAGS: -Og -ggdb -fno-sanitize-recover=all $SANITIZER_FLAGS -- 2.53.0.1.gb2826b52eb --7e2sypi5gxvkgog6-- ^ permalink raw reply [nested|flat] 114+ messages in thread
* [PATCH v2 1/4] Make stack depth check work with asan's use-after-return @ 2026-02-07 19:04 Andres Freund <[email protected]> 0 siblings, 0 replies; 114+ messages in thread From: Andres Freund @ 2026-02-07 19:04 UTC (permalink / raw) With address sanitizer's stack-use-after-return check, stack variables are moved to heap allocations, to allow to detect references to the memory at a later time. That broke our stack-depth check, which is why we had to disable detect_stack_use_after_return in CI. Luckily __builtin_frame_address() works correctly, even under asan, so use that. Author: Reviewed-by: Discussion: https://postgr.es/m/ Backpatch: --- src/backend/utils/misc/stack_depth.c | 17 ++++++++++++++++- .cirrus.tasks.yml | 2 +- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/src/backend/utils/misc/stack_depth.c b/src/backend/utils/misc/stack_depth.c index 61a07cf824e..9955e28c176 100644 --- a/src/backend/utils/misc/stack_depth.c +++ b/src/backend/utils/misc/stack_depth.c @@ -108,13 +108,28 @@ check_stack_depth(void) bool stack_is_too_deep(void) { +#ifndef HAVE__BUILTIN_FRAME_ADDRESS char stack_top_loc; +#endif ssize_t stack_depth; + char *stack_address; + + /* + * With address sanitizer's stack-use-after-return check, stack variables + * are moved to heap allocations, to allow to detect references to the + * memory at a later time. That would break our stack-depth check. Luckily + * __builtin_frame_address() works correctly, even under asan. + */ +#ifndef HAVE__BUILTIN_FRAME_ADDRESS + stack_address = &stack_top_loc; +#else + stack_address = (char *) __builtin_frame_address(0); +#endif /* * Compute distance from reference point to my local variables */ - stack_depth = (ssize_t) (stack_base_ptr - &stack_top_loc); + stack_depth = (ssize_t) (stack_base_ptr - stack_address); /* * Take abs value, since stacks grow up on some machines, down on others diff --git a/.cirrus.tasks.yml b/.cirrus.tasks.yml index a22cef063f3..8683d1ae9c7 100644 --- a/.cirrus.tasks.yml +++ b/.cirrus.tasks.yml @@ -447,7 +447,7 @@ task: # print_stacktraces=1,verbosity=2, duh # detect_leaks=0: too many uninteresting leak errors in short-lived binaries UBSAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:verbosity=2 - ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0:detect_stack_use_after_return=0 + ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0 # SANITIZER_FLAGS is set in the tasks below CFLAGS: -Og -ggdb -fno-sanitize-recover=all $SANITIZER_FLAGS -- 2.53.0.1.gb2826b52eb --7e2sypi5gxvkgog6-- ^ permalink raw reply [nested|flat] 114+ messages in thread
* [PATCH v2 1/4] Make stack depth check work with asan's use-after-return @ 2026-02-07 19:04 Andres Freund <[email protected]> 0 siblings, 0 replies; 114+ messages in thread From: Andres Freund @ 2026-02-07 19:04 UTC (permalink / raw) With address sanitizer's stack-use-after-return check, stack variables are moved to heap allocations, to allow to detect references to the memory at a later time. That broke our stack-depth check, which is why we had to disable detect_stack_use_after_return in CI. Luckily __builtin_frame_address() works correctly, even under asan, so use that. Author: Reviewed-by: Discussion: https://postgr.es/m/ Backpatch: --- src/backend/utils/misc/stack_depth.c | 17 ++++++++++++++++- .cirrus.tasks.yml | 2 +- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/src/backend/utils/misc/stack_depth.c b/src/backend/utils/misc/stack_depth.c index 61a07cf824e..9955e28c176 100644 --- a/src/backend/utils/misc/stack_depth.c +++ b/src/backend/utils/misc/stack_depth.c @@ -108,13 +108,28 @@ check_stack_depth(void) bool stack_is_too_deep(void) { +#ifndef HAVE__BUILTIN_FRAME_ADDRESS char stack_top_loc; +#endif ssize_t stack_depth; + char *stack_address; + + /* + * With address sanitizer's stack-use-after-return check, stack variables + * are moved to heap allocations, to allow to detect references to the + * memory at a later time. That would break our stack-depth check. Luckily + * __builtin_frame_address() works correctly, even under asan. + */ +#ifndef HAVE__BUILTIN_FRAME_ADDRESS + stack_address = &stack_top_loc; +#else + stack_address = (char *) __builtin_frame_address(0); +#endif /* * Compute distance from reference point to my local variables */ - stack_depth = (ssize_t) (stack_base_ptr - &stack_top_loc); + stack_depth = (ssize_t) (stack_base_ptr - stack_address); /* * Take abs value, since stacks grow up on some machines, down on others diff --git a/.cirrus.tasks.yml b/.cirrus.tasks.yml index a22cef063f3..8683d1ae9c7 100644 --- a/.cirrus.tasks.yml +++ b/.cirrus.tasks.yml @@ -447,7 +447,7 @@ task: # print_stacktraces=1,verbosity=2, duh # detect_leaks=0: too many uninteresting leak errors in short-lived binaries UBSAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:verbosity=2 - ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0:detect_stack_use_after_return=0 + ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0 # SANITIZER_FLAGS is set in the tasks below CFLAGS: -Og -ggdb -fno-sanitize-recover=all $SANITIZER_FLAGS -- 2.53.0.1.gb2826b52eb --7e2sypi5gxvkgog6-- ^ permalink raw reply [nested|flat] 114+ messages in thread
* [PATCH v2 1/4] Make stack depth check work with asan's use-after-return @ 2026-02-07 19:04 Andres Freund <[email protected]> 0 siblings, 0 replies; 114+ messages in thread From: Andres Freund @ 2026-02-07 19:04 UTC (permalink / raw) With address sanitizer's stack-use-after-return check, stack variables are moved to heap allocations, to allow to detect references to the memory at a later time. That broke our stack-depth check, which is why we had to disable detect_stack_use_after_return in CI. Luckily __builtin_frame_address() works correctly, even under asan, so use that. Author: Reviewed-by: Discussion: https://postgr.es/m/ Backpatch: --- src/backend/utils/misc/stack_depth.c | 17 ++++++++++++++++- .cirrus.tasks.yml | 2 +- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/src/backend/utils/misc/stack_depth.c b/src/backend/utils/misc/stack_depth.c index 61a07cf824e..9955e28c176 100644 --- a/src/backend/utils/misc/stack_depth.c +++ b/src/backend/utils/misc/stack_depth.c @@ -108,13 +108,28 @@ check_stack_depth(void) bool stack_is_too_deep(void) { +#ifndef HAVE__BUILTIN_FRAME_ADDRESS char stack_top_loc; +#endif ssize_t stack_depth; + char *stack_address; + + /* + * With address sanitizer's stack-use-after-return check, stack variables + * are moved to heap allocations, to allow to detect references to the + * memory at a later time. That would break our stack-depth check. Luckily + * __builtin_frame_address() works correctly, even under asan. + */ +#ifndef HAVE__BUILTIN_FRAME_ADDRESS + stack_address = &stack_top_loc; +#else + stack_address = (char *) __builtin_frame_address(0); +#endif /* * Compute distance from reference point to my local variables */ - stack_depth = (ssize_t) (stack_base_ptr - &stack_top_loc); + stack_depth = (ssize_t) (stack_base_ptr - stack_address); /* * Take abs value, since stacks grow up on some machines, down on others diff --git a/.cirrus.tasks.yml b/.cirrus.tasks.yml index a22cef063f3..8683d1ae9c7 100644 --- a/.cirrus.tasks.yml +++ b/.cirrus.tasks.yml @@ -447,7 +447,7 @@ task: # print_stacktraces=1,verbosity=2, duh # detect_leaks=0: too many uninteresting leak errors in short-lived binaries UBSAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:verbosity=2 - ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0:detect_stack_use_after_return=0 + ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0 # SANITIZER_FLAGS is set in the tasks below CFLAGS: -Og -ggdb -fno-sanitize-recover=all $SANITIZER_FLAGS -- 2.53.0.1.gb2826b52eb --7e2sypi5gxvkgog6-- ^ permalink raw reply [nested|flat] 114+ messages in thread
* [PATCH v2 1/4] Make stack depth check work with asan's use-after-return @ 2026-02-07 19:04 Andres Freund <[email protected]> 0 siblings, 0 replies; 114+ messages in thread From: Andres Freund @ 2026-02-07 19:04 UTC (permalink / raw) With address sanitizer's stack-use-after-return check, stack variables are moved to heap allocations, to allow to detect references to the memory at a later time. That broke our stack-depth check, which is why we had to disable detect_stack_use_after_return in CI. Luckily __builtin_frame_address() works correctly, even under asan, so use that. Author: Reviewed-by: Discussion: https://postgr.es/m/ Backpatch: --- src/backend/utils/misc/stack_depth.c | 17 ++++++++++++++++- .cirrus.tasks.yml | 2 +- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/src/backend/utils/misc/stack_depth.c b/src/backend/utils/misc/stack_depth.c index 61a07cf824e..9955e28c176 100644 --- a/src/backend/utils/misc/stack_depth.c +++ b/src/backend/utils/misc/stack_depth.c @@ -108,13 +108,28 @@ check_stack_depth(void) bool stack_is_too_deep(void) { +#ifndef HAVE__BUILTIN_FRAME_ADDRESS char stack_top_loc; +#endif ssize_t stack_depth; + char *stack_address; + + /* + * With address sanitizer's stack-use-after-return check, stack variables + * are moved to heap allocations, to allow to detect references to the + * memory at a later time. That would break our stack-depth check. Luckily + * __builtin_frame_address() works correctly, even under asan. + */ +#ifndef HAVE__BUILTIN_FRAME_ADDRESS + stack_address = &stack_top_loc; +#else + stack_address = (char *) __builtin_frame_address(0); +#endif /* * Compute distance from reference point to my local variables */ - stack_depth = (ssize_t) (stack_base_ptr - &stack_top_loc); + stack_depth = (ssize_t) (stack_base_ptr - stack_address); /* * Take abs value, since stacks grow up on some machines, down on others diff --git a/.cirrus.tasks.yml b/.cirrus.tasks.yml index a22cef063f3..8683d1ae9c7 100644 --- a/.cirrus.tasks.yml +++ b/.cirrus.tasks.yml @@ -447,7 +447,7 @@ task: # print_stacktraces=1,verbosity=2, duh # detect_leaks=0: too many uninteresting leak errors in short-lived binaries UBSAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:verbosity=2 - ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0:detect_stack_use_after_return=0 + ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0 # SANITIZER_FLAGS is set in the tasks below CFLAGS: -Og -ggdb -fno-sanitize-recover=all $SANITIZER_FLAGS -- 2.53.0.1.gb2826b52eb --7e2sypi5gxvkgog6-- ^ permalink raw reply [nested|flat] 114+ messages in thread
* [PATCH v2 1/4] Make stack depth check work with asan's use-after-return @ 2026-02-07 19:04 Andres Freund <[email protected]> 0 siblings, 0 replies; 114+ messages in thread From: Andres Freund @ 2026-02-07 19:04 UTC (permalink / raw) With address sanitizer's stack-use-after-return check, stack variables are moved to heap allocations, to allow to detect references to the memory at a later time. That broke our stack-depth check, which is why we had to disable detect_stack_use_after_return in CI. Luckily __builtin_frame_address() works correctly, even under asan, so use that. Author: Reviewed-by: Discussion: https://postgr.es/m/ Backpatch: --- src/backend/utils/misc/stack_depth.c | 17 ++++++++++++++++- .cirrus.tasks.yml | 2 +- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/src/backend/utils/misc/stack_depth.c b/src/backend/utils/misc/stack_depth.c index 61a07cf824e..9955e28c176 100644 --- a/src/backend/utils/misc/stack_depth.c +++ b/src/backend/utils/misc/stack_depth.c @@ -108,13 +108,28 @@ check_stack_depth(void) bool stack_is_too_deep(void) { +#ifndef HAVE__BUILTIN_FRAME_ADDRESS char stack_top_loc; +#endif ssize_t stack_depth; + char *stack_address; + + /* + * With address sanitizer's stack-use-after-return check, stack variables + * are moved to heap allocations, to allow to detect references to the + * memory at a later time. That would break our stack-depth check. Luckily + * __builtin_frame_address() works correctly, even under asan. + */ +#ifndef HAVE__BUILTIN_FRAME_ADDRESS + stack_address = &stack_top_loc; +#else + stack_address = (char *) __builtin_frame_address(0); +#endif /* * Compute distance from reference point to my local variables */ - stack_depth = (ssize_t) (stack_base_ptr - &stack_top_loc); + stack_depth = (ssize_t) (stack_base_ptr - stack_address); /* * Take abs value, since stacks grow up on some machines, down on others diff --git a/.cirrus.tasks.yml b/.cirrus.tasks.yml index a22cef063f3..8683d1ae9c7 100644 --- a/.cirrus.tasks.yml +++ b/.cirrus.tasks.yml @@ -447,7 +447,7 @@ task: # print_stacktraces=1,verbosity=2, duh # detect_leaks=0: too many uninteresting leak errors in short-lived binaries UBSAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:verbosity=2 - ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0:detect_stack_use_after_return=0 + ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0 # SANITIZER_FLAGS is set in the tasks below CFLAGS: -Og -ggdb -fno-sanitize-recover=all $SANITIZER_FLAGS -- 2.53.0.1.gb2826b52eb --7e2sypi5gxvkgog6-- ^ permalink raw reply [nested|flat] 114+ messages in thread
* [PATCH v2 1/4] Make stack depth check work with asan's use-after-return @ 2026-02-07 19:04 Andres Freund <[email protected]> 0 siblings, 0 replies; 114+ messages in thread From: Andres Freund @ 2026-02-07 19:04 UTC (permalink / raw) With address sanitizer's stack-use-after-return check, stack variables are moved to heap allocations, to allow to detect references to the memory at a later time. That broke our stack-depth check, which is why we had to disable detect_stack_use_after_return in CI. Luckily __builtin_frame_address() works correctly, even under asan, so use that. Author: Reviewed-by: Discussion: https://postgr.es/m/ Backpatch: --- src/backend/utils/misc/stack_depth.c | 17 ++++++++++++++++- .cirrus.tasks.yml | 2 +- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/src/backend/utils/misc/stack_depth.c b/src/backend/utils/misc/stack_depth.c index 61a07cf824e..9955e28c176 100644 --- a/src/backend/utils/misc/stack_depth.c +++ b/src/backend/utils/misc/stack_depth.c @@ -108,13 +108,28 @@ check_stack_depth(void) bool stack_is_too_deep(void) { +#ifndef HAVE__BUILTIN_FRAME_ADDRESS char stack_top_loc; +#endif ssize_t stack_depth; + char *stack_address; + + /* + * With address sanitizer's stack-use-after-return check, stack variables + * are moved to heap allocations, to allow to detect references to the + * memory at a later time. That would break our stack-depth check. Luckily + * __builtin_frame_address() works correctly, even under asan. + */ +#ifndef HAVE__BUILTIN_FRAME_ADDRESS + stack_address = &stack_top_loc; +#else + stack_address = (char *) __builtin_frame_address(0); +#endif /* * Compute distance from reference point to my local variables */ - stack_depth = (ssize_t) (stack_base_ptr - &stack_top_loc); + stack_depth = (ssize_t) (stack_base_ptr - stack_address); /* * Take abs value, since stacks grow up on some machines, down on others diff --git a/.cirrus.tasks.yml b/.cirrus.tasks.yml index a22cef063f3..8683d1ae9c7 100644 --- a/.cirrus.tasks.yml +++ b/.cirrus.tasks.yml @@ -447,7 +447,7 @@ task: # print_stacktraces=1,verbosity=2, duh # detect_leaks=0: too many uninteresting leak errors in short-lived binaries UBSAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:verbosity=2 - ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0:detect_stack_use_after_return=0 + ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0 # SANITIZER_FLAGS is set in the tasks below CFLAGS: -Og -ggdb -fno-sanitize-recover=all $SANITIZER_FLAGS -- 2.53.0.1.gb2826b52eb --7e2sypi5gxvkgog6-- ^ permalink raw reply [nested|flat] 114+ messages in thread
* [PATCH v2 1/4] Make stack depth check work with asan's use-after-return @ 2026-02-07 19:04 Andres Freund <[email protected]> 0 siblings, 0 replies; 114+ messages in thread From: Andres Freund @ 2026-02-07 19:04 UTC (permalink / raw) With address sanitizer's stack-use-after-return check, stack variables are moved to heap allocations, to allow to detect references to the memory at a later time. That broke our stack-depth check, which is why we had to disable detect_stack_use_after_return in CI. Luckily __builtin_frame_address() works correctly, even under asan, so use that. Author: Reviewed-by: Discussion: https://postgr.es/m/ Backpatch: --- src/backend/utils/misc/stack_depth.c | 17 ++++++++++++++++- .cirrus.tasks.yml | 2 +- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/src/backend/utils/misc/stack_depth.c b/src/backend/utils/misc/stack_depth.c index 61a07cf824e..9955e28c176 100644 --- a/src/backend/utils/misc/stack_depth.c +++ b/src/backend/utils/misc/stack_depth.c @@ -108,13 +108,28 @@ check_stack_depth(void) bool stack_is_too_deep(void) { +#ifndef HAVE__BUILTIN_FRAME_ADDRESS char stack_top_loc; +#endif ssize_t stack_depth; + char *stack_address; + + /* + * With address sanitizer's stack-use-after-return check, stack variables + * are moved to heap allocations, to allow to detect references to the + * memory at a later time. That would break our stack-depth check. Luckily + * __builtin_frame_address() works correctly, even under asan. + */ +#ifndef HAVE__BUILTIN_FRAME_ADDRESS + stack_address = &stack_top_loc; +#else + stack_address = (char *) __builtin_frame_address(0); +#endif /* * Compute distance from reference point to my local variables */ - stack_depth = (ssize_t) (stack_base_ptr - &stack_top_loc); + stack_depth = (ssize_t) (stack_base_ptr - stack_address); /* * Take abs value, since stacks grow up on some machines, down on others diff --git a/.cirrus.tasks.yml b/.cirrus.tasks.yml index a22cef063f3..8683d1ae9c7 100644 --- a/.cirrus.tasks.yml +++ b/.cirrus.tasks.yml @@ -447,7 +447,7 @@ task: # print_stacktraces=1,verbosity=2, duh # detect_leaks=0: too many uninteresting leak errors in short-lived binaries UBSAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:verbosity=2 - ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0:detect_stack_use_after_return=0 + ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0 # SANITIZER_FLAGS is set in the tasks below CFLAGS: -Og -ggdb -fno-sanitize-recover=all $SANITIZER_FLAGS -- 2.53.0.1.gb2826b52eb --7e2sypi5gxvkgog6-- ^ permalink raw reply [nested|flat] 114+ messages in thread
* [PATCH v2 1/4] Make stack depth check work with asan's use-after-return @ 2026-02-07 19:04 Andres Freund <[email protected]> 0 siblings, 0 replies; 114+ messages in thread From: Andres Freund @ 2026-02-07 19:04 UTC (permalink / raw) With address sanitizer's stack-use-after-return check, stack variables are moved to heap allocations, to allow to detect references to the memory at a later time. That broke our stack-depth check, which is why we had to disable detect_stack_use_after_return in CI. Luckily __builtin_frame_address() works correctly, even under asan, so use that. Author: Reviewed-by: Discussion: https://postgr.es/m/ Backpatch: --- src/backend/utils/misc/stack_depth.c | 17 ++++++++++++++++- .cirrus.tasks.yml | 2 +- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/src/backend/utils/misc/stack_depth.c b/src/backend/utils/misc/stack_depth.c index 61a07cf824e..9955e28c176 100644 --- a/src/backend/utils/misc/stack_depth.c +++ b/src/backend/utils/misc/stack_depth.c @@ -108,13 +108,28 @@ check_stack_depth(void) bool stack_is_too_deep(void) { +#ifndef HAVE__BUILTIN_FRAME_ADDRESS char stack_top_loc; +#endif ssize_t stack_depth; + char *stack_address; + + /* + * With address sanitizer's stack-use-after-return check, stack variables + * are moved to heap allocations, to allow to detect references to the + * memory at a later time. That would break our stack-depth check. Luckily + * __builtin_frame_address() works correctly, even under asan. + */ +#ifndef HAVE__BUILTIN_FRAME_ADDRESS + stack_address = &stack_top_loc; +#else + stack_address = (char *) __builtin_frame_address(0); +#endif /* * Compute distance from reference point to my local variables */ - stack_depth = (ssize_t) (stack_base_ptr - &stack_top_loc); + stack_depth = (ssize_t) (stack_base_ptr - stack_address); /* * Take abs value, since stacks grow up on some machines, down on others diff --git a/.cirrus.tasks.yml b/.cirrus.tasks.yml index a22cef063f3..8683d1ae9c7 100644 --- a/.cirrus.tasks.yml +++ b/.cirrus.tasks.yml @@ -447,7 +447,7 @@ task: # print_stacktraces=1,verbosity=2, duh # detect_leaks=0: too many uninteresting leak errors in short-lived binaries UBSAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:verbosity=2 - ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0:detect_stack_use_after_return=0 + ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0 # SANITIZER_FLAGS is set in the tasks below CFLAGS: -Og -ggdb -fno-sanitize-recover=all $SANITIZER_FLAGS -- 2.53.0.1.gb2826b52eb --7e2sypi5gxvkgog6-- ^ permalink raw reply [nested|flat] 114+ messages in thread
* [PATCH v2 1/4] Make stack depth check work with asan's use-after-return @ 2026-02-07 19:04 Andres Freund <[email protected]> 0 siblings, 0 replies; 114+ messages in thread From: Andres Freund @ 2026-02-07 19:04 UTC (permalink / raw) With address sanitizer's stack-use-after-return check, stack variables are moved to heap allocations, to allow to detect references to the memory at a later time. That broke our stack-depth check, which is why we had to disable detect_stack_use_after_return in CI. Luckily __builtin_frame_address() works correctly, even under asan, so use that. Author: Reviewed-by: Discussion: https://postgr.es/m/ Backpatch: --- src/backend/utils/misc/stack_depth.c | 17 ++++++++++++++++- .cirrus.tasks.yml | 2 +- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/src/backend/utils/misc/stack_depth.c b/src/backend/utils/misc/stack_depth.c index 61a07cf824e..9955e28c176 100644 --- a/src/backend/utils/misc/stack_depth.c +++ b/src/backend/utils/misc/stack_depth.c @@ -108,13 +108,28 @@ check_stack_depth(void) bool stack_is_too_deep(void) { +#ifndef HAVE__BUILTIN_FRAME_ADDRESS char stack_top_loc; +#endif ssize_t stack_depth; + char *stack_address; + + /* + * With address sanitizer's stack-use-after-return check, stack variables + * are moved to heap allocations, to allow to detect references to the + * memory at a later time. That would break our stack-depth check. Luckily + * __builtin_frame_address() works correctly, even under asan. + */ +#ifndef HAVE__BUILTIN_FRAME_ADDRESS + stack_address = &stack_top_loc; +#else + stack_address = (char *) __builtin_frame_address(0); +#endif /* * Compute distance from reference point to my local variables */ - stack_depth = (ssize_t) (stack_base_ptr - &stack_top_loc); + stack_depth = (ssize_t) (stack_base_ptr - stack_address); /* * Take abs value, since stacks grow up on some machines, down on others diff --git a/.cirrus.tasks.yml b/.cirrus.tasks.yml index a22cef063f3..8683d1ae9c7 100644 --- a/.cirrus.tasks.yml +++ b/.cirrus.tasks.yml @@ -447,7 +447,7 @@ task: # print_stacktraces=1,verbosity=2, duh # detect_leaks=0: too many uninteresting leak errors in short-lived binaries UBSAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:verbosity=2 - ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0:detect_stack_use_after_return=0 + ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0 # SANITIZER_FLAGS is set in the tasks below CFLAGS: -Og -ggdb -fno-sanitize-recover=all $SANITIZER_FLAGS -- 2.53.0.1.gb2826b52eb --7e2sypi5gxvkgog6-- ^ permalink raw reply [nested|flat] 114+ messages in thread
* [PATCH v2 1/4] Make stack depth check work with asan's use-after-return @ 2026-02-07 19:04 Andres Freund <[email protected]> 0 siblings, 0 replies; 114+ messages in thread From: Andres Freund @ 2026-02-07 19:04 UTC (permalink / raw) With address sanitizer's stack-use-after-return check, stack variables are moved to heap allocations, to allow to detect references to the memory at a later time. That broke our stack-depth check, which is why we had to disable detect_stack_use_after_return in CI. Luckily __builtin_frame_address() works correctly, even under asan, so use that. Author: Reviewed-by: Discussion: https://postgr.es/m/ Backpatch: --- src/backend/utils/misc/stack_depth.c | 17 ++++++++++++++++- .cirrus.tasks.yml | 2 +- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/src/backend/utils/misc/stack_depth.c b/src/backend/utils/misc/stack_depth.c index 61a07cf824e..9955e28c176 100644 --- a/src/backend/utils/misc/stack_depth.c +++ b/src/backend/utils/misc/stack_depth.c @@ -108,13 +108,28 @@ check_stack_depth(void) bool stack_is_too_deep(void) { +#ifndef HAVE__BUILTIN_FRAME_ADDRESS char stack_top_loc; +#endif ssize_t stack_depth; + char *stack_address; + + /* + * With address sanitizer's stack-use-after-return check, stack variables + * are moved to heap allocations, to allow to detect references to the + * memory at a later time. That would break our stack-depth check. Luckily + * __builtin_frame_address() works correctly, even under asan. + */ +#ifndef HAVE__BUILTIN_FRAME_ADDRESS + stack_address = &stack_top_loc; +#else + stack_address = (char *) __builtin_frame_address(0); +#endif /* * Compute distance from reference point to my local variables */ - stack_depth = (ssize_t) (stack_base_ptr - &stack_top_loc); + stack_depth = (ssize_t) (stack_base_ptr - stack_address); /* * Take abs value, since stacks grow up on some machines, down on others diff --git a/.cirrus.tasks.yml b/.cirrus.tasks.yml index a22cef063f3..8683d1ae9c7 100644 --- a/.cirrus.tasks.yml +++ b/.cirrus.tasks.yml @@ -447,7 +447,7 @@ task: # print_stacktraces=1,verbosity=2, duh # detect_leaks=0: too many uninteresting leak errors in short-lived binaries UBSAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:verbosity=2 - ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0:detect_stack_use_after_return=0 + ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0 # SANITIZER_FLAGS is set in the tasks below CFLAGS: -Og -ggdb -fno-sanitize-recover=all $SANITIZER_FLAGS -- 2.53.0.1.gb2826b52eb --7e2sypi5gxvkgog6-- ^ permalink raw reply [nested|flat] 114+ messages in thread
* [PATCH v2 1/4] Make stack depth check work with asan's use-after-return @ 2026-02-07 19:04 Andres Freund <[email protected]> 0 siblings, 0 replies; 114+ messages in thread From: Andres Freund @ 2026-02-07 19:04 UTC (permalink / raw) With address sanitizer's stack-use-after-return check, stack variables are moved to heap allocations, to allow to detect references to the memory at a later time. That broke our stack-depth check, which is why we had to disable detect_stack_use_after_return in CI. Luckily __builtin_frame_address() works correctly, even under asan, so use that. Author: Reviewed-by: Discussion: https://postgr.es/m/ Backpatch: --- src/backend/utils/misc/stack_depth.c | 17 ++++++++++++++++- .cirrus.tasks.yml | 2 +- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/src/backend/utils/misc/stack_depth.c b/src/backend/utils/misc/stack_depth.c index 61a07cf824e..9955e28c176 100644 --- a/src/backend/utils/misc/stack_depth.c +++ b/src/backend/utils/misc/stack_depth.c @@ -108,13 +108,28 @@ check_stack_depth(void) bool stack_is_too_deep(void) { +#ifndef HAVE__BUILTIN_FRAME_ADDRESS char stack_top_loc; +#endif ssize_t stack_depth; + char *stack_address; + + /* + * With address sanitizer's stack-use-after-return check, stack variables + * are moved to heap allocations, to allow to detect references to the + * memory at a later time. That would break our stack-depth check. Luckily + * __builtin_frame_address() works correctly, even under asan. + */ +#ifndef HAVE__BUILTIN_FRAME_ADDRESS + stack_address = &stack_top_loc; +#else + stack_address = (char *) __builtin_frame_address(0); +#endif /* * Compute distance from reference point to my local variables */ - stack_depth = (ssize_t) (stack_base_ptr - &stack_top_loc); + stack_depth = (ssize_t) (stack_base_ptr - stack_address); /* * Take abs value, since stacks grow up on some machines, down on others diff --git a/.cirrus.tasks.yml b/.cirrus.tasks.yml index a22cef063f3..8683d1ae9c7 100644 --- a/.cirrus.tasks.yml +++ b/.cirrus.tasks.yml @@ -447,7 +447,7 @@ task: # print_stacktraces=1,verbosity=2, duh # detect_leaks=0: too many uninteresting leak errors in short-lived binaries UBSAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:verbosity=2 - ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0:detect_stack_use_after_return=0 + ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0 # SANITIZER_FLAGS is set in the tasks below CFLAGS: -Og -ggdb -fno-sanitize-recover=all $SANITIZER_FLAGS -- 2.53.0.1.gb2826b52eb --7e2sypi5gxvkgog6-- ^ permalink raw reply [nested|flat] 114+ messages in thread
* [PATCH v2 1/4] Make stack depth check work with asan's use-after-return @ 2026-02-07 19:04 Andres Freund <[email protected]> 0 siblings, 0 replies; 114+ messages in thread From: Andres Freund @ 2026-02-07 19:04 UTC (permalink / raw) With address sanitizer's stack-use-after-return check, stack variables are moved to heap allocations, to allow to detect references to the memory at a later time. That broke our stack-depth check, which is why we had to disable detect_stack_use_after_return in CI. Luckily __builtin_frame_address() works correctly, even under asan, so use that. Author: Reviewed-by: Discussion: https://postgr.es/m/ Backpatch: --- src/backend/utils/misc/stack_depth.c | 17 ++++++++++++++++- .cirrus.tasks.yml | 2 +- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/src/backend/utils/misc/stack_depth.c b/src/backend/utils/misc/stack_depth.c index 61a07cf824e..9955e28c176 100644 --- a/src/backend/utils/misc/stack_depth.c +++ b/src/backend/utils/misc/stack_depth.c @@ -108,13 +108,28 @@ check_stack_depth(void) bool stack_is_too_deep(void) { +#ifndef HAVE__BUILTIN_FRAME_ADDRESS char stack_top_loc; +#endif ssize_t stack_depth; + char *stack_address; + + /* + * With address sanitizer's stack-use-after-return check, stack variables + * are moved to heap allocations, to allow to detect references to the + * memory at a later time. That would break our stack-depth check. Luckily + * __builtin_frame_address() works correctly, even under asan. + */ +#ifndef HAVE__BUILTIN_FRAME_ADDRESS + stack_address = &stack_top_loc; +#else + stack_address = (char *) __builtin_frame_address(0); +#endif /* * Compute distance from reference point to my local variables */ - stack_depth = (ssize_t) (stack_base_ptr - &stack_top_loc); + stack_depth = (ssize_t) (stack_base_ptr - stack_address); /* * Take abs value, since stacks grow up on some machines, down on others diff --git a/.cirrus.tasks.yml b/.cirrus.tasks.yml index a22cef063f3..8683d1ae9c7 100644 --- a/.cirrus.tasks.yml +++ b/.cirrus.tasks.yml @@ -447,7 +447,7 @@ task: # print_stacktraces=1,verbosity=2, duh # detect_leaks=0: too many uninteresting leak errors in short-lived binaries UBSAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:verbosity=2 - ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0:detect_stack_use_after_return=0 + ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0 # SANITIZER_FLAGS is set in the tasks below CFLAGS: -Og -ggdb -fno-sanitize-recover=all $SANITIZER_FLAGS -- 2.53.0.1.gb2826b52eb --7e2sypi5gxvkgog6-- ^ permalink raw reply [nested|flat] 114+ messages in thread
* [PATCH v2 1/4] Make stack depth check work with asan's use-after-return @ 2026-02-07 19:04 Andres Freund <[email protected]> 0 siblings, 0 replies; 114+ messages in thread From: Andres Freund @ 2026-02-07 19:04 UTC (permalink / raw) With address sanitizer's stack-use-after-return check, stack variables are moved to heap allocations, to allow to detect references to the memory at a later time. That broke our stack-depth check, which is why we had to disable detect_stack_use_after_return in CI. Luckily __builtin_frame_address() works correctly, even under asan, so use that. Author: Reviewed-by: Discussion: https://postgr.es/m/ Backpatch: --- src/backend/utils/misc/stack_depth.c | 17 ++++++++++++++++- .cirrus.tasks.yml | 2 +- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/src/backend/utils/misc/stack_depth.c b/src/backend/utils/misc/stack_depth.c index 61a07cf824e..9955e28c176 100644 --- a/src/backend/utils/misc/stack_depth.c +++ b/src/backend/utils/misc/stack_depth.c @@ -108,13 +108,28 @@ check_stack_depth(void) bool stack_is_too_deep(void) { +#ifndef HAVE__BUILTIN_FRAME_ADDRESS char stack_top_loc; +#endif ssize_t stack_depth; + char *stack_address; + + /* + * With address sanitizer's stack-use-after-return check, stack variables + * are moved to heap allocations, to allow to detect references to the + * memory at a later time. That would break our stack-depth check. Luckily + * __builtin_frame_address() works correctly, even under asan. + */ +#ifndef HAVE__BUILTIN_FRAME_ADDRESS + stack_address = &stack_top_loc; +#else + stack_address = (char *) __builtin_frame_address(0); +#endif /* * Compute distance from reference point to my local variables */ - stack_depth = (ssize_t) (stack_base_ptr - &stack_top_loc); + stack_depth = (ssize_t) (stack_base_ptr - stack_address); /* * Take abs value, since stacks grow up on some machines, down on others diff --git a/.cirrus.tasks.yml b/.cirrus.tasks.yml index a22cef063f3..8683d1ae9c7 100644 --- a/.cirrus.tasks.yml +++ b/.cirrus.tasks.yml @@ -447,7 +447,7 @@ task: # print_stacktraces=1,verbosity=2, duh # detect_leaks=0: too many uninteresting leak errors in short-lived binaries UBSAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:verbosity=2 - ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0:detect_stack_use_after_return=0 + ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0 # SANITIZER_FLAGS is set in the tasks below CFLAGS: -Og -ggdb -fno-sanitize-recover=all $SANITIZER_FLAGS -- 2.53.0.1.gb2826b52eb --7e2sypi5gxvkgog6-- ^ permalink raw reply [nested|flat] 114+ messages in thread
* [PATCH v2 1/4] Make stack depth check work with asan's use-after-return @ 2026-02-07 19:04 Andres Freund <[email protected]> 0 siblings, 0 replies; 114+ messages in thread From: Andres Freund @ 2026-02-07 19:04 UTC (permalink / raw) With address sanitizer's stack-use-after-return check, stack variables are moved to heap allocations, to allow to detect references to the memory at a later time. That broke our stack-depth check, which is why we had to disable detect_stack_use_after_return in CI. Luckily __builtin_frame_address() works correctly, even under asan, so use that. Author: Reviewed-by: Discussion: https://postgr.es/m/ Backpatch: --- src/backend/utils/misc/stack_depth.c | 17 ++++++++++++++++- .cirrus.tasks.yml | 2 +- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/src/backend/utils/misc/stack_depth.c b/src/backend/utils/misc/stack_depth.c index 61a07cf824e..9955e28c176 100644 --- a/src/backend/utils/misc/stack_depth.c +++ b/src/backend/utils/misc/stack_depth.c @@ -108,13 +108,28 @@ check_stack_depth(void) bool stack_is_too_deep(void) { +#ifndef HAVE__BUILTIN_FRAME_ADDRESS char stack_top_loc; +#endif ssize_t stack_depth; + char *stack_address; + + /* + * With address sanitizer's stack-use-after-return check, stack variables + * are moved to heap allocations, to allow to detect references to the + * memory at a later time. That would break our stack-depth check. Luckily + * __builtin_frame_address() works correctly, even under asan. + */ +#ifndef HAVE__BUILTIN_FRAME_ADDRESS + stack_address = &stack_top_loc; +#else + stack_address = (char *) __builtin_frame_address(0); +#endif /* * Compute distance from reference point to my local variables */ - stack_depth = (ssize_t) (stack_base_ptr - &stack_top_loc); + stack_depth = (ssize_t) (stack_base_ptr - stack_address); /* * Take abs value, since stacks grow up on some machines, down on others diff --git a/.cirrus.tasks.yml b/.cirrus.tasks.yml index a22cef063f3..8683d1ae9c7 100644 --- a/.cirrus.tasks.yml +++ b/.cirrus.tasks.yml @@ -447,7 +447,7 @@ task: # print_stacktraces=1,verbosity=2, duh # detect_leaks=0: too many uninteresting leak errors in short-lived binaries UBSAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:verbosity=2 - ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0:detect_stack_use_after_return=0 + ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0 # SANITIZER_FLAGS is set in the tasks below CFLAGS: -Og -ggdb -fno-sanitize-recover=all $SANITIZER_FLAGS -- 2.53.0.1.gb2826b52eb --7e2sypi5gxvkgog6-- ^ permalink raw reply [nested|flat] 114+ messages in thread
* [PATCH v2 1/4] Make stack depth check work with asan's use-after-return @ 2026-02-07 19:04 Andres Freund <[email protected]> 0 siblings, 0 replies; 114+ messages in thread From: Andres Freund @ 2026-02-07 19:04 UTC (permalink / raw) With address sanitizer's stack-use-after-return check, stack variables are moved to heap allocations, to allow to detect references to the memory at a later time. That broke our stack-depth check, which is why we had to disable detect_stack_use_after_return in CI. Luckily __builtin_frame_address() works correctly, even under asan, so use that. Author: Reviewed-by: Discussion: https://postgr.es/m/ Backpatch: --- src/backend/utils/misc/stack_depth.c | 17 ++++++++++++++++- .cirrus.tasks.yml | 2 +- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/src/backend/utils/misc/stack_depth.c b/src/backend/utils/misc/stack_depth.c index 61a07cf824e..9955e28c176 100644 --- a/src/backend/utils/misc/stack_depth.c +++ b/src/backend/utils/misc/stack_depth.c @@ -108,13 +108,28 @@ check_stack_depth(void) bool stack_is_too_deep(void) { +#ifndef HAVE__BUILTIN_FRAME_ADDRESS char stack_top_loc; +#endif ssize_t stack_depth; + char *stack_address; + + /* + * With address sanitizer's stack-use-after-return check, stack variables + * are moved to heap allocations, to allow to detect references to the + * memory at a later time. That would break our stack-depth check. Luckily + * __builtin_frame_address() works correctly, even under asan. + */ +#ifndef HAVE__BUILTIN_FRAME_ADDRESS + stack_address = &stack_top_loc; +#else + stack_address = (char *) __builtin_frame_address(0); +#endif /* * Compute distance from reference point to my local variables */ - stack_depth = (ssize_t) (stack_base_ptr - &stack_top_loc); + stack_depth = (ssize_t) (stack_base_ptr - stack_address); /* * Take abs value, since stacks grow up on some machines, down on others diff --git a/.cirrus.tasks.yml b/.cirrus.tasks.yml index a22cef063f3..8683d1ae9c7 100644 --- a/.cirrus.tasks.yml +++ b/.cirrus.tasks.yml @@ -447,7 +447,7 @@ task: # print_stacktraces=1,verbosity=2, duh # detect_leaks=0: too many uninteresting leak errors in short-lived binaries UBSAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:verbosity=2 - ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0:detect_stack_use_after_return=0 + ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0 # SANITIZER_FLAGS is set in the tasks below CFLAGS: -Og -ggdb -fno-sanitize-recover=all $SANITIZER_FLAGS -- 2.53.0.1.gb2826b52eb --7e2sypi5gxvkgog6-- ^ permalink raw reply [nested|flat] 114+ messages in thread
* [PATCH v2 1/4] Make stack depth check work with asan's use-after-return @ 2026-02-07 19:04 Andres Freund <[email protected]> 0 siblings, 0 replies; 114+ messages in thread From: Andres Freund @ 2026-02-07 19:04 UTC (permalink / raw) With address sanitizer's stack-use-after-return check, stack variables are moved to heap allocations, to allow to detect references to the memory at a later time. That broke our stack-depth check, which is why we had to disable detect_stack_use_after_return in CI. Luckily __builtin_frame_address() works correctly, even under asan, so use that. Author: Reviewed-by: Discussion: https://postgr.es/m/ Backpatch: --- src/backend/utils/misc/stack_depth.c | 17 ++++++++++++++++- .cirrus.tasks.yml | 2 +- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/src/backend/utils/misc/stack_depth.c b/src/backend/utils/misc/stack_depth.c index 61a07cf824e..9955e28c176 100644 --- a/src/backend/utils/misc/stack_depth.c +++ b/src/backend/utils/misc/stack_depth.c @@ -108,13 +108,28 @@ check_stack_depth(void) bool stack_is_too_deep(void) { +#ifndef HAVE__BUILTIN_FRAME_ADDRESS char stack_top_loc; +#endif ssize_t stack_depth; + char *stack_address; + + /* + * With address sanitizer's stack-use-after-return check, stack variables + * are moved to heap allocations, to allow to detect references to the + * memory at a later time. That would break our stack-depth check. Luckily + * __builtin_frame_address() works correctly, even under asan. + */ +#ifndef HAVE__BUILTIN_FRAME_ADDRESS + stack_address = &stack_top_loc; +#else + stack_address = (char *) __builtin_frame_address(0); +#endif /* * Compute distance from reference point to my local variables */ - stack_depth = (ssize_t) (stack_base_ptr - &stack_top_loc); + stack_depth = (ssize_t) (stack_base_ptr - stack_address); /* * Take abs value, since stacks grow up on some machines, down on others diff --git a/.cirrus.tasks.yml b/.cirrus.tasks.yml index a22cef063f3..8683d1ae9c7 100644 --- a/.cirrus.tasks.yml +++ b/.cirrus.tasks.yml @@ -447,7 +447,7 @@ task: # print_stacktraces=1,verbosity=2, duh # detect_leaks=0: too many uninteresting leak errors in short-lived binaries UBSAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:verbosity=2 - ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0:detect_stack_use_after_return=0 + ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0 # SANITIZER_FLAGS is set in the tasks below CFLAGS: -Og -ggdb -fno-sanitize-recover=all $SANITIZER_FLAGS -- 2.53.0.1.gb2826b52eb --7e2sypi5gxvkgog6-- ^ permalink raw reply [nested|flat] 114+ messages in thread
* [PATCH v2 1/4] Make stack depth check work with asan's use-after-return @ 2026-02-07 19:04 Andres Freund <[email protected]> 0 siblings, 0 replies; 114+ messages in thread From: Andres Freund @ 2026-02-07 19:04 UTC (permalink / raw) With address sanitizer's stack-use-after-return check, stack variables are moved to heap allocations, to allow to detect references to the memory at a later time. That broke our stack-depth check, which is why we had to disable detect_stack_use_after_return in CI. Luckily __builtin_frame_address() works correctly, even under asan, so use that. Author: Reviewed-by: Discussion: https://postgr.es/m/ Backpatch: --- src/backend/utils/misc/stack_depth.c | 17 ++++++++++++++++- .cirrus.tasks.yml | 2 +- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/src/backend/utils/misc/stack_depth.c b/src/backend/utils/misc/stack_depth.c index 61a07cf824e..9955e28c176 100644 --- a/src/backend/utils/misc/stack_depth.c +++ b/src/backend/utils/misc/stack_depth.c @@ -108,13 +108,28 @@ check_stack_depth(void) bool stack_is_too_deep(void) { +#ifndef HAVE__BUILTIN_FRAME_ADDRESS char stack_top_loc; +#endif ssize_t stack_depth; + char *stack_address; + + /* + * With address sanitizer's stack-use-after-return check, stack variables + * are moved to heap allocations, to allow to detect references to the + * memory at a later time. That would break our stack-depth check. Luckily + * __builtin_frame_address() works correctly, even under asan. + */ +#ifndef HAVE__BUILTIN_FRAME_ADDRESS + stack_address = &stack_top_loc; +#else + stack_address = (char *) __builtin_frame_address(0); +#endif /* * Compute distance from reference point to my local variables */ - stack_depth = (ssize_t) (stack_base_ptr - &stack_top_loc); + stack_depth = (ssize_t) (stack_base_ptr - stack_address); /* * Take abs value, since stacks grow up on some machines, down on others diff --git a/.cirrus.tasks.yml b/.cirrus.tasks.yml index a22cef063f3..8683d1ae9c7 100644 --- a/.cirrus.tasks.yml +++ b/.cirrus.tasks.yml @@ -447,7 +447,7 @@ task: # print_stacktraces=1,verbosity=2, duh # detect_leaks=0: too many uninteresting leak errors in short-lived binaries UBSAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:verbosity=2 - ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0:detect_stack_use_after_return=0 + ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0 # SANITIZER_FLAGS is set in the tasks below CFLAGS: -Og -ggdb -fno-sanitize-recover=all $SANITIZER_FLAGS -- 2.53.0.1.gb2826b52eb --7e2sypi5gxvkgog6-- ^ permalink raw reply [nested|flat] 114+ messages in thread
* [PATCH v2 1/4] Make stack depth check work with asan's use-after-return @ 2026-02-07 19:04 Andres Freund <[email protected]> 0 siblings, 0 replies; 114+ messages in thread From: Andres Freund @ 2026-02-07 19:04 UTC (permalink / raw) With address sanitizer's stack-use-after-return check, stack variables are moved to heap allocations, to allow to detect references to the memory at a later time. That broke our stack-depth check, which is why we had to disable detect_stack_use_after_return in CI. Luckily __builtin_frame_address() works correctly, even under asan, so use that. Author: Reviewed-by: Discussion: https://postgr.es/m/ Backpatch: --- src/backend/utils/misc/stack_depth.c | 17 ++++++++++++++++- .cirrus.tasks.yml | 2 +- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/src/backend/utils/misc/stack_depth.c b/src/backend/utils/misc/stack_depth.c index 61a07cf824e..9955e28c176 100644 --- a/src/backend/utils/misc/stack_depth.c +++ b/src/backend/utils/misc/stack_depth.c @@ -108,13 +108,28 @@ check_stack_depth(void) bool stack_is_too_deep(void) { +#ifndef HAVE__BUILTIN_FRAME_ADDRESS char stack_top_loc; +#endif ssize_t stack_depth; + char *stack_address; + + /* + * With address sanitizer's stack-use-after-return check, stack variables + * are moved to heap allocations, to allow to detect references to the + * memory at a later time. That would break our stack-depth check. Luckily + * __builtin_frame_address() works correctly, even under asan. + */ +#ifndef HAVE__BUILTIN_FRAME_ADDRESS + stack_address = &stack_top_loc; +#else + stack_address = (char *) __builtin_frame_address(0); +#endif /* * Compute distance from reference point to my local variables */ - stack_depth = (ssize_t) (stack_base_ptr - &stack_top_loc); + stack_depth = (ssize_t) (stack_base_ptr - stack_address); /* * Take abs value, since stacks grow up on some machines, down on others diff --git a/.cirrus.tasks.yml b/.cirrus.tasks.yml index a22cef063f3..8683d1ae9c7 100644 --- a/.cirrus.tasks.yml +++ b/.cirrus.tasks.yml @@ -447,7 +447,7 @@ task: # print_stacktraces=1,verbosity=2, duh # detect_leaks=0: too many uninteresting leak errors in short-lived binaries UBSAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:verbosity=2 - ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0:detect_stack_use_after_return=0 + ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0 # SANITIZER_FLAGS is set in the tasks below CFLAGS: -Og -ggdb -fno-sanitize-recover=all $SANITIZER_FLAGS -- 2.53.0.1.gb2826b52eb --7e2sypi5gxvkgog6-- ^ permalink raw reply [nested|flat] 114+ messages in thread
* [PATCH v2 1/4] Make stack depth check work with asan's use-after-return @ 2026-02-07 19:04 Andres Freund <[email protected]> 0 siblings, 0 replies; 114+ messages in thread From: Andres Freund @ 2026-02-07 19:04 UTC (permalink / raw) With address sanitizer's stack-use-after-return check, stack variables are moved to heap allocations, to allow to detect references to the memory at a later time. That broke our stack-depth check, which is why we had to disable detect_stack_use_after_return in CI. Luckily __builtin_frame_address() works correctly, even under asan, so use that. Author: Reviewed-by: Discussion: https://postgr.es/m/ Backpatch: --- src/backend/utils/misc/stack_depth.c | 17 ++++++++++++++++- .cirrus.tasks.yml | 2 +- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/src/backend/utils/misc/stack_depth.c b/src/backend/utils/misc/stack_depth.c index 61a07cf824e..9955e28c176 100644 --- a/src/backend/utils/misc/stack_depth.c +++ b/src/backend/utils/misc/stack_depth.c @@ -108,13 +108,28 @@ check_stack_depth(void) bool stack_is_too_deep(void) { +#ifndef HAVE__BUILTIN_FRAME_ADDRESS char stack_top_loc; +#endif ssize_t stack_depth; + char *stack_address; + + /* + * With address sanitizer's stack-use-after-return check, stack variables + * are moved to heap allocations, to allow to detect references to the + * memory at a later time. That would break our stack-depth check. Luckily + * __builtin_frame_address() works correctly, even under asan. + */ +#ifndef HAVE__BUILTIN_FRAME_ADDRESS + stack_address = &stack_top_loc; +#else + stack_address = (char *) __builtin_frame_address(0); +#endif /* * Compute distance from reference point to my local variables */ - stack_depth = (ssize_t) (stack_base_ptr - &stack_top_loc); + stack_depth = (ssize_t) (stack_base_ptr - stack_address); /* * Take abs value, since stacks grow up on some machines, down on others diff --git a/.cirrus.tasks.yml b/.cirrus.tasks.yml index a22cef063f3..8683d1ae9c7 100644 --- a/.cirrus.tasks.yml +++ b/.cirrus.tasks.yml @@ -447,7 +447,7 @@ task: # print_stacktraces=1,verbosity=2, duh # detect_leaks=0: too many uninteresting leak errors in short-lived binaries UBSAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:verbosity=2 - ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0:detect_stack_use_after_return=0 + ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0 # SANITIZER_FLAGS is set in the tasks below CFLAGS: -Og -ggdb -fno-sanitize-recover=all $SANITIZER_FLAGS -- 2.53.0.1.gb2826b52eb --7e2sypi5gxvkgog6-- ^ permalink raw reply [nested|flat] 114+ messages in thread
* [PATCH v2 1/4] Make stack depth check work with asan's use-after-return @ 2026-02-07 19:04 Andres Freund <[email protected]> 0 siblings, 0 replies; 114+ messages in thread From: Andres Freund @ 2026-02-07 19:04 UTC (permalink / raw) With address sanitizer's stack-use-after-return check, stack variables are moved to heap allocations, to allow to detect references to the memory at a later time. That broke our stack-depth check, which is why we had to disable detect_stack_use_after_return in CI. Luckily __builtin_frame_address() works correctly, even under asan, so use that. Author: Reviewed-by: Discussion: https://postgr.es/m/ Backpatch: --- src/backend/utils/misc/stack_depth.c | 17 ++++++++++++++++- .cirrus.tasks.yml | 2 +- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/src/backend/utils/misc/stack_depth.c b/src/backend/utils/misc/stack_depth.c index 61a07cf824e..9955e28c176 100644 --- a/src/backend/utils/misc/stack_depth.c +++ b/src/backend/utils/misc/stack_depth.c @@ -108,13 +108,28 @@ check_stack_depth(void) bool stack_is_too_deep(void) { +#ifndef HAVE__BUILTIN_FRAME_ADDRESS char stack_top_loc; +#endif ssize_t stack_depth; + char *stack_address; + + /* + * With address sanitizer's stack-use-after-return check, stack variables + * are moved to heap allocations, to allow to detect references to the + * memory at a later time. That would break our stack-depth check. Luckily + * __builtin_frame_address() works correctly, even under asan. + */ +#ifndef HAVE__BUILTIN_FRAME_ADDRESS + stack_address = &stack_top_loc; +#else + stack_address = (char *) __builtin_frame_address(0); +#endif /* * Compute distance from reference point to my local variables */ - stack_depth = (ssize_t) (stack_base_ptr - &stack_top_loc); + stack_depth = (ssize_t) (stack_base_ptr - stack_address); /* * Take abs value, since stacks grow up on some machines, down on others diff --git a/.cirrus.tasks.yml b/.cirrus.tasks.yml index a22cef063f3..8683d1ae9c7 100644 --- a/.cirrus.tasks.yml +++ b/.cirrus.tasks.yml @@ -447,7 +447,7 @@ task: # print_stacktraces=1,verbosity=2, duh # detect_leaks=0: too many uninteresting leak errors in short-lived binaries UBSAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:verbosity=2 - ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0:detect_stack_use_after_return=0 + ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0 # SANITIZER_FLAGS is set in the tasks below CFLAGS: -Og -ggdb -fno-sanitize-recover=all $SANITIZER_FLAGS -- 2.53.0.1.gb2826b52eb --7e2sypi5gxvkgog6-- ^ permalink raw reply [nested|flat] 114+ messages in thread
* [PATCH v2 1/4] Make stack depth check work with asan's use-after-return @ 2026-02-07 19:04 Andres Freund <[email protected]> 0 siblings, 0 replies; 114+ messages in thread From: Andres Freund @ 2026-02-07 19:04 UTC (permalink / raw) With address sanitizer's stack-use-after-return check, stack variables are moved to heap allocations, to allow to detect references to the memory at a later time. That broke our stack-depth check, which is why we had to disable detect_stack_use_after_return in CI. Luckily __builtin_frame_address() works correctly, even under asan, so use that. Author: Reviewed-by: Discussion: https://postgr.es/m/ Backpatch: --- src/backend/utils/misc/stack_depth.c | 17 ++++++++++++++++- .cirrus.tasks.yml | 2 +- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/src/backend/utils/misc/stack_depth.c b/src/backend/utils/misc/stack_depth.c index 61a07cf824e..9955e28c176 100644 --- a/src/backend/utils/misc/stack_depth.c +++ b/src/backend/utils/misc/stack_depth.c @@ -108,13 +108,28 @@ check_stack_depth(void) bool stack_is_too_deep(void) { +#ifndef HAVE__BUILTIN_FRAME_ADDRESS char stack_top_loc; +#endif ssize_t stack_depth; + char *stack_address; + + /* + * With address sanitizer's stack-use-after-return check, stack variables + * are moved to heap allocations, to allow to detect references to the + * memory at a later time. That would break our stack-depth check. Luckily + * __builtin_frame_address() works correctly, even under asan. + */ +#ifndef HAVE__BUILTIN_FRAME_ADDRESS + stack_address = &stack_top_loc; +#else + stack_address = (char *) __builtin_frame_address(0); +#endif /* * Compute distance from reference point to my local variables */ - stack_depth = (ssize_t) (stack_base_ptr - &stack_top_loc); + stack_depth = (ssize_t) (stack_base_ptr - stack_address); /* * Take abs value, since stacks grow up on some machines, down on others diff --git a/.cirrus.tasks.yml b/.cirrus.tasks.yml index a22cef063f3..8683d1ae9c7 100644 --- a/.cirrus.tasks.yml +++ b/.cirrus.tasks.yml @@ -447,7 +447,7 @@ task: # print_stacktraces=1,verbosity=2, duh # detect_leaks=0: too many uninteresting leak errors in short-lived binaries UBSAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:verbosity=2 - ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0:detect_stack_use_after_return=0 + ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0 # SANITIZER_FLAGS is set in the tasks below CFLAGS: -Og -ggdb -fno-sanitize-recover=all $SANITIZER_FLAGS -- 2.53.0.1.gb2826b52eb --7e2sypi5gxvkgog6-- ^ permalink raw reply [nested|flat] 114+ messages in thread
* [PATCH v2 1/4] Make stack depth check work with asan's use-after-return @ 2026-02-07 19:04 Andres Freund <[email protected]> 0 siblings, 0 replies; 114+ messages in thread From: Andres Freund @ 2026-02-07 19:04 UTC (permalink / raw) With address sanitizer's stack-use-after-return check, stack variables are moved to heap allocations, to allow to detect references to the memory at a later time. That broke our stack-depth check, which is why we had to disable detect_stack_use_after_return in CI. Luckily __builtin_frame_address() works correctly, even under asan, so use that. Author: Reviewed-by: Discussion: https://postgr.es/m/ Backpatch: --- src/backend/utils/misc/stack_depth.c | 17 ++++++++++++++++- .cirrus.tasks.yml | 2 +- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/src/backend/utils/misc/stack_depth.c b/src/backend/utils/misc/stack_depth.c index 61a07cf824e..9955e28c176 100644 --- a/src/backend/utils/misc/stack_depth.c +++ b/src/backend/utils/misc/stack_depth.c @@ -108,13 +108,28 @@ check_stack_depth(void) bool stack_is_too_deep(void) { +#ifndef HAVE__BUILTIN_FRAME_ADDRESS char stack_top_loc; +#endif ssize_t stack_depth; + char *stack_address; + + /* + * With address sanitizer's stack-use-after-return check, stack variables + * are moved to heap allocations, to allow to detect references to the + * memory at a later time. That would break our stack-depth check. Luckily + * __builtin_frame_address() works correctly, even under asan. + */ +#ifndef HAVE__BUILTIN_FRAME_ADDRESS + stack_address = &stack_top_loc; +#else + stack_address = (char *) __builtin_frame_address(0); +#endif /* * Compute distance from reference point to my local variables */ - stack_depth = (ssize_t) (stack_base_ptr - &stack_top_loc); + stack_depth = (ssize_t) (stack_base_ptr - stack_address); /* * Take abs value, since stacks grow up on some machines, down on others diff --git a/.cirrus.tasks.yml b/.cirrus.tasks.yml index a22cef063f3..8683d1ae9c7 100644 --- a/.cirrus.tasks.yml +++ b/.cirrus.tasks.yml @@ -447,7 +447,7 @@ task: # print_stacktraces=1,verbosity=2, duh # detect_leaks=0: too many uninteresting leak errors in short-lived binaries UBSAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:verbosity=2 - ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0:detect_stack_use_after_return=0 + ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0 # SANITIZER_FLAGS is set in the tasks below CFLAGS: -Og -ggdb -fno-sanitize-recover=all $SANITIZER_FLAGS -- 2.53.0.1.gb2826b52eb --7e2sypi5gxvkgog6-- ^ permalink raw reply [nested|flat] 114+ messages in thread
* [PATCH v2 1/4] Make stack depth check work with asan's use-after-return @ 2026-02-07 19:04 Andres Freund <[email protected]> 0 siblings, 0 replies; 114+ messages in thread From: Andres Freund @ 2026-02-07 19:04 UTC (permalink / raw) With address sanitizer's stack-use-after-return check, stack variables are moved to heap allocations, to allow to detect references to the memory at a later time. That broke our stack-depth check, which is why we had to disable detect_stack_use_after_return in CI. Luckily __builtin_frame_address() works correctly, even under asan, so use that. Author: Reviewed-by: Discussion: https://postgr.es/m/ Backpatch: --- src/backend/utils/misc/stack_depth.c | 17 ++++++++++++++++- .cirrus.tasks.yml | 2 +- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/src/backend/utils/misc/stack_depth.c b/src/backend/utils/misc/stack_depth.c index 61a07cf824e..9955e28c176 100644 --- a/src/backend/utils/misc/stack_depth.c +++ b/src/backend/utils/misc/stack_depth.c @@ -108,13 +108,28 @@ check_stack_depth(void) bool stack_is_too_deep(void) { +#ifndef HAVE__BUILTIN_FRAME_ADDRESS char stack_top_loc; +#endif ssize_t stack_depth; + char *stack_address; + + /* + * With address sanitizer's stack-use-after-return check, stack variables + * are moved to heap allocations, to allow to detect references to the + * memory at a later time. That would break our stack-depth check. Luckily + * __builtin_frame_address() works correctly, even under asan. + */ +#ifndef HAVE__BUILTIN_FRAME_ADDRESS + stack_address = &stack_top_loc; +#else + stack_address = (char *) __builtin_frame_address(0); +#endif /* * Compute distance from reference point to my local variables */ - stack_depth = (ssize_t) (stack_base_ptr - &stack_top_loc); + stack_depth = (ssize_t) (stack_base_ptr - stack_address); /* * Take abs value, since stacks grow up on some machines, down on others diff --git a/.cirrus.tasks.yml b/.cirrus.tasks.yml index a22cef063f3..8683d1ae9c7 100644 --- a/.cirrus.tasks.yml +++ b/.cirrus.tasks.yml @@ -447,7 +447,7 @@ task: # print_stacktraces=1,verbosity=2, duh # detect_leaks=0: too many uninteresting leak errors in short-lived binaries UBSAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:verbosity=2 - ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0:detect_stack_use_after_return=0 + ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0 # SANITIZER_FLAGS is set in the tasks below CFLAGS: -Og -ggdb -fno-sanitize-recover=all $SANITIZER_FLAGS -- 2.53.0.1.gb2826b52eb --7e2sypi5gxvkgog6-- ^ permalink raw reply [nested|flat] 114+ messages in thread
* [PATCH v2 1/4] Make stack depth check work with asan's use-after-return @ 2026-02-07 19:04 Andres Freund <[email protected]> 0 siblings, 0 replies; 114+ messages in thread From: Andres Freund @ 2026-02-07 19:04 UTC (permalink / raw) With address sanitizer's stack-use-after-return check, stack variables are moved to heap allocations, to allow to detect references to the memory at a later time. That broke our stack-depth check, which is why we had to disable detect_stack_use_after_return in CI. Luckily __builtin_frame_address() works correctly, even under asan, so use that. Author: Reviewed-by: Discussion: https://postgr.es/m/ Backpatch: --- src/backend/utils/misc/stack_depth.c | 17 ++++++++++++++++- .cirrus.tasks.yml | 2 +- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/src/backend/utils/misc/stack_depth.c b/src/backend/utils/misc/stack_depth.c index 61a07cf824e..9955e28c176 100644 --- a/src/backend/utils/misc/stack_depth.c +++ b/src/backend/utils/misc/stack_depth.c @@ -108,13 +108,28 @@ check_stack_depth(void) bool stack_is_too_deep(void) { +#ifndef HAVE__BUILTIN_FRAME_ADDRESS char stack_top_loc; +#endif ssize_t stack_depth; + char *stack_address; + + /* + * With address sanitizer's stack-use-after-return check, stack variables + * are moved to heap allocations, to allow to detect references to the + * memory at a later time. That would break our stack-depth check. Luckily + * __builtin_frame_address() works correctly, even under asan. + */ +#ifndef HAVE__BUILTIN_FRAME_ADDRESS + stack_address = &stack_top_loc; +#else + stack_address = (char *) __builtin_frame_address(0); +#endif /* * Compute distance from reference point to my local variables */ - stack_depth = (ssize_t) (stack_base_ptr - &stack_top_loc); + stack_depth = (ssize_t) (stack_base_ptr - stack_address); /* * Take abs value, since stacks grow up on some machines, down on others diff --git a/.cirrus.tasks.yml b/.cirrus.tasks.yml index a22cef063f3..8683d1ae9c7 100644 --- a/.cirrus.tasks.yml +++ b/.cirrus.tasks.yml @@ -447,7 +447,7 @@ task: # print_stacktraces=1,verbosity=2, duh # detect_leaks=0: too many uninteresting leak errors in short-lived binaries UBSAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:verbosity=2 - ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0:detect_stack_use_after_return=0 + ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0 # SANITIZER_FLAGS is set in the tasks below CFLAGS: -Og -ggdb -fno-sanitize-recover=all $SANITIZER_FLAGS -- 2.53.0.1.gb2826b52eb --7e2sypi5gxvkgog6-- ^ permalink raw reply [nested|flat] 114+ messages in thread
* [PATCH v2 1/4] Make stack depth check work with asan's use-after-return @ 2026-02-07 19:04 Andres Freund <[email protected]> 0 siblings, 0 replies; 114+ messages in thread From: Andres Freund @ 2026-02-07 19:04 UTC (permalink / raw) With address sanitizer's stack-use-after-return check, stack variables are moved to heap allocations, to allow to detect references to the memory at a later time. That broke our stack-depth check, which is why we had to disable detect_stack_use_after_return in CI. Luckily __builtin_frame_address() works correctly, even under asan, so use that. Author: Reviewed-by: Discussion: https://postgr.es/m/ Backpatch: --- src/backend/utils/misc/stack_depth.c | 17 ++++++++++++++++- .cirrus.tasks.yml | 2 +- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/src/backend/utils/misc/stack_depth.c b/src/backend/utils/misc/stack_depth.c index 61a07cf824e..9955e28c176 100644 --- a/src/backend/utils/misc/stack_depth.c +++ b/src/backend/utils/misc/stack_depth.c @@ -108,13 +108,28 @@ check_stack_depth(void) bool stack_is_too_deep(void) { +#ifndef HAVE__BUILTIN_FRAME_ADDRESS char stack_top_loc; +#endif ssize_t stack_depth; + char *stack_address; + + /* + * With address sanitizer's stack-use-after-return check, stack variables + * are moved to heap allocations, to allow to detect references to the + * memory at a later time. That would break our stack-depth check. Luckily + * __builtin_frame_address() works correctly, even under asan. + */ +#ifndef HAVE__BUILTIN_FRAME_ADDRESS + stack_address = &stack_top_loc; +#else + stack_address = (char *) __builtin_frame_address(0); +#endif /* * Compute distance from reference point to my local variables */ - stack_depth = (ssize_t) (stack_base_ptr - &stack_top_loc); + stack_depth = (ssize_t) (stack_base_ptr - stack_address); /* * Take abs value, since stacks grow up on some machines, down on others diff --git a/.cirrus.tasks.yml b/.cirrus.tasks.yml index a22cef063f3..8683d1ae9c7 100644 --- a/.cirrus.tasks.yml +++ b/.cirrus.tasks.yml @@ -447,7 +447,7 @@ task: # print_stacktraces=1,verbosity=2, duh # detect_leaks=0: too many uninteresting leak errors in short-lived binaries UBSAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:verbosity=2 - ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0:detect_stack_use_after_return=0 + ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0 # SANITIZER_FLAGS is set in the tasks below CFLAGS: -Og -ggdb -fno-sanitize-recover=all $SANITIZER_FLAGS -- 2.53.0.1.gb2826b52eb --7e2sypi5gxvkgog6-- ^ permalink raw reply [nested|flat] 114+ messages in thread
* [PATCH v2 1/4] Make stack depth check work with asan's use-after-return @ 2026-02-07 19:04 Andres Freund <[email protected]> 0 siblings, 0 replies; 114+ messages in thread From: Andres Freund @ 2026-02-07 19:04 UTC (permalink / raw) With address sanitizer's stack-use-after-return check, stack variables are moved to heap allocations, to allow to detect references to the memory at a later time. That broke our stack-depth check, which is why we had to disable detect_stack_use_after_return in CI. Luckily __builtin_frame_address() works correctly, even under asan, so use that. Author: Reviewed-by: Discussion: https://postgr.es/m/ Backpatch: --- src/backend/utils/misc/stack_depth.c | 17 ++++++++++++++++- .cirrus.tasks.yml | 2 +- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/src/backend/utils/misc/stack_depth.c b/src/backend/utils/misc/stack_depth.c index 61a07cf824e..9955e28c176 100644 --- a/src/backend/utils/misc/stack_depth.c +++ b/src/backend/utils/misc/stack_depth.c @@ -108,13 +108,28 @@ check_stack_depth(void) bool stack_is_too_deep(void) { +#ifndef HAVE__BUILTIN_FRAME_ADDRESS char stack_top_loc; +#endif ssize_t stack_depth; + char *stack_address; + + /* + * With address sanitizer's stack-use-after-return check, stack variables + * are moved to heap allocations, to allow to detect references to the + * memory at a later time. That would break our stack-depth check. Luckily + * __builtin_frame_address() works correctly, even under asan. + */ +#ifndef HAVE__BUILTIN_FRAME_ADDRESS + stack_address = &stack_top_loc; +#else + stack_address = (char *) __builtin_frame_address(0); +#endif /* * Compute distance from reference point to my local variables */ - stack_depth = (ssize_t) (stack_base_ptr - &stack_top_loc); + stack_depth = (ssize_t) (stack_base_ptr - stack_address); /* * Take abs value, since stacks grow up on some machines, down on others diff --git a/.cirrus.tasks.yml b/.cirrus.tasks.yml index a22cef063f3..8683d1ae9c7 100644 --- a/.cirrus.tasks.yml +++ b/.cirrus.tasks.yml @@ -447,7 +447,7 @@ task: # print_stacktraces=1,verbosity=2, duh # detect_leaks=0: too many uninteresting leak errors in short-lived binaries UBSAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:verbosity=2 - ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0:detect_stack_use_after_return=0 + ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0 # SANITIZER_FLAGS is set in the tasks below CFLAGS: -Og -ggdb -fno-sanitize-recover=all $SANITIZER_FLAGS -- 2.53.0.1.gb2826b52eb --7e2sypi5gxvkgog6-- ^ permalink raw reply [nested|flat] 114+ messages in thread
* [PATCH v2 1/4] Make stack depth check work with asan's use-after-return @ 2026-02-07 19:04 Andres Freund <[email protected]> 0 siblings, 0 replies; 114+ messages in thread From: Andres Freund @ 2026-02-07 19:04 UTC (permalink / raw) With address sanitizer's stack-use-after-return check, stack variables are moved to heap allocations, to allow to detect references to the memory at a later time. That broke our stack-depth check, which is why we had to disable detect_stack_use_after_return in CI. Luckily __builtin_frame_address() works correctly, even under asan, so use that. Author: Reviewed-by: Discussion: https://postgr.es/m/ Backpatch: --- src/backend/utils/misc/stack_depth.c | 17 ++++++++++++++++- .cirrus.tasks.yml | 2 +- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/src/backend/utils/misc/stack_depth.c b/src/backend/utils/misc/stack_depth.c index 61a07cf824e..9955e28c176 100644 --- a/src/backend/utils/misc/stack_depth.c +++ b/src/backend/utils/misc/stack_depth.c @@ -108,13 +108,28 @@ check_stack_depth(void) bool stack_is_too_deep(void) { +#ifndef HAVE__BUILTIN_FRAME_ADDRESS char stack_top_loc; +#endif ssize_t stack_depth; + char *stack_address; + + /* + * With address sanitizer's stack-use-after-return check, stack variables + * are moved to heap allocations, to allow to detect references to the + * memory at a later time. That would break our stack-depth check. Luckily + * __builtin_frame_address() works correctly, even under asan. + */ +#ifndef HAVE__BUILTIN_FRAME_ADDRESS + stack_address = &stack_top_loc; +#else + stack_address = (char *) __builtin_frame_address(0); +#endif /* * Compute distance from reference point to my local variables */ - stack_depth = (ssize_t) (stack_base_ptr - &stack_top_loc); + stack_depth = (ssize_t) (stack_base_ptr - stack_address); /* * Take abs value, since stacks grow up on some machines, down on others diff --git a/.cirrus.tasks.yml b/.cirrus.tasks.yml index a22cef063f3..8683d1ae9c7 100644 --- a/.cirrus.tasks.yml +++ b/.cirrus.tasks.yml @@ -447,7 +447,7 @@ task: # print_stacktraces=1,verbosity=2, duh # detect_leaks=0: too many uninteresting leak errors in short-lived binaries UBSAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:verbosity=2 - ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0:detect_stack_use_after_return=0 + ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0 # SANITIZER_FLAGS is set in the tasks below CFLAGS: -Og -ggdb -fno-sanitize-recover=all $SANITIZER_FLAGS -- 2.53.0.1.gb2826b52eb --7e2sypi5gxvkgog6-- ^ permalink raw reply [nested|flat] 114+ messages in thread
* [PATCH v2 1/4] Make stack depth check work with asan's use-after-return @ 2026-02-07 19:04 Andres Freund <[email protected]> 0 siblings, 0 replies; 114+ messages in thread From: Andres Freund @ 2026-02-07 19:04 UTC (permalink / raw) With address sanitizer's stack-use-after-return check, stack variables are moved to heap allocations, to allow to detect references to the memory at a later time. That broke our stack-depth check, which is why we had to disable detect_stack_use_after_return in CI. Luckily __builtin_frame_address() works correctly, even under asan, so use that. Author: Reviewed-by: Discussion: https://postgr.es/m/ Backpatch: --- src/backend/utils/misc/stack_depth.c | 17 ++++++++++++++++- .cirrus.tasks.yml | 2 +- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/src/backend/utils/misc/stack_depth.c b/src/backend/utils/misc/stack_depth.c index 61a07cf824e..9955e28c176 100644 --- a/src/backend/utils/misc/stack_depth.c +++ b/src/backend/utils/misc/stack_depth.c @@ -108,13 +108,28 @@ check_stack_depth(void) bool stack_is_too_deep(void) { +#ifndef HAVE__BUILTIN_FRAME_ADDRESS char stack_top_loc; +#endif ssize_t stack_depth; + char *stack_address; + + /* + * With address sanitizer's stack-use-after-return check, stack variables + * are moved to heap allocations, to allow to detect references to the + * memory at a later time. That would break our stack-depth check. Luckily + * __builtin_frame_address() works correctly, even under asan. + */ +#ifndef HAVE__BUILTIN_FRAME_ADDRESS + stack_address = &stack_top_loc; +#else + stack_address = (char *) __builtin_frame_address(0); +#endif /* * Compute distance from reference point to my local variables */ - stack_depth = (ssize_t) (stack_base_ptr - &stack_top_loc); + stack_depth = (ssize_t) (stack_base_ptr - stack_address); /* * Take abs value, since stacks grow up on some machines, down on others diff --git a/.cirrus.tasks.yml b/.cirrus.tasks.yml index a22cef063f3..8683d1ae9c7 100644 --- a/.cirrus.tasks.yml +++ b/.cirrus.tasks.yml @@ -447,7 +447,7 @@ task: # print_stacktraces=1,verbosity=2, duh # detect_leaks=0: too many uninteresting leak errors in short-lived binaries UBSAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:verbosity=2 - ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0:detect_stack_use_after_return=0 + ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0 # SANITIZER_FLAGS is set in the tasks below CFLAGS: -Og -ggdb -fno-sanitize-recover=all $SANITIZER_FLAGS -- 2.53.0.1.gb2826b52eb --7e2sypi5gxvkgog6-- ^ permalink raw reply [nested|flat] 114+ messages in thread
* [PATCH v2 1/4] Make stack depth check work with asan's use-after-return @ 2026-02-07 19:04 Andres Freund <[email protected]> 0 siblings, 0 replies; 114+ messages in thread From: Andres Freund @ 2026-02-07 19:04 UTC (permalink / raw) With address sanitizer's stack-use-after-return check, stack variables are moved to heap allocations, to allow to detect references to the memory at a later time. That broke our stack-depth check, which is why we had to disable detect_stack_use_after_return in CI. Luckily __builtin_frame_address() works correctly, even under asan, so use that. Author: Reviewed-by: Discussion: https://postgr.es/m/ Backpatch: --- src/backend/utils/misc/stack_depth.c | 17 ++++++++++++++++- .cirrus.tasks.yml | 2 +- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/src/backend/utils/misc/stack_depth.c b/src/backend/utils/misc/stack_depth.c index 61a07cf824e..9955e28c176 100644 --- a/src/backend/utils/misc/stack_depth.c +++ b/src/backend/utils/misc/stack_depth.c @@ -108,13 +108,28 @@ check_stack_depth(void) bool stack_is_too_deep(void) { +#ifndef HAVE__BUILTIN_FRAME_ADDRESS char stack_top_loc; +#endif ssize_t stack_depth; + char *stack_address; + + /* + * With address sanitizer's stack-use-after-return check, stack variables + * are moved to heap allocations, to allow to detect references to the + * memory at a later time. That would break our stack-depth check. Luckily + * __builtin_frame_address() works correctly, even under asan. + */ +#ifndef HAVE__BUILTIN_FRAME_ADDRESS + stack_address = &stack_top_loc; +#else + stack_address = (char *) __builtin_frame_address(0); +#endif /* * Compute distance from reference point to my local variables */ - stack_depth = (ssize_t) (stack_base_ptr - &stack_top_loc); + stack_depth = (ssize_t) (stack_base_ptr - stack_address); /* * Take abs value, since stacks grow up on some machines, down on others diff --git a/.cirrus.tasks.yml b/.cirrus.tasks.yml index a22cef063f3..8683d1ae9c7 100644 --- a/.cirrus.tasks.yml +++ b/.cirrus.tasks.yml @@ -447,7 +447,7 @@ task: # print_stacktraces=1,verbosity=2, duh # detect_leaks=0: too many uninteresting leak errors in short-lived binaries UBSAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:verbosity=2 - ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0:detect_stack_use_after_return=0 + ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0 # SANITIZER_FLAGS is set in the tasks below CFLAGS: -Og -ggdb -fno-sanitize-recover=all $SANITIZER_FLAGS -- 2.53.0.1.gb2826b52eb --7e2sypi5gxvkgog6-- ^ permalink raw reply [nested|flat] 114+ messages in thread
* [PATCH v2 1/4] Make stack depth check work with asan's use-after-return @ 2026-02-07 19:04 Andres Freund <[email protected]> 0 siblings, 0 replies; 114+ messages in thread From: Andres Freund @ 2026-02-07 19:04 UTC (permalink / raw) With address sanitizer's stack-use-after-return check, stack variables are moved to heap allocations, to allow to detect references to the memory at a later time. That broke our stack-depth check, which is why we had to disable detect_stack_use_after_return in CI. Luckily __builtin_frame_address() works correctly, even under asan, so use that. Author: Reviewed-by: Discussion: https://postgr.es/m/ Backpatch: --- src/backend/utils/misc/stack_depth.c | 17 ++++++++++++++++- .cirrus.tasks.yml | 2 +- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/src/backend/utils/misc/stack_depth.c b/src/backend/utils/misc/stack_depth.c index 61a07cf824e..9955e28c176 100644 --- a/src/backend/utils/misc/stack_depth.c +++ b/src/backend/utils/misc/stack_depth.c @@ -108,13 +108,28 @@ check_stack_depth(void) bool stack_is_too_deep(void) { +#ifndef HAVE__BUILTIN_FRAME_ADDRESS char stack_top_loc; +#endif ssize_t stack_depth; + char *stack_address; + + /* + * With address sanitizer's stack-use-after-return check, stack variables + * are moved to heap allocations, to allow to detect references to the + * memory at a later time. That would break our stack-depth check. Luckily + * __builtin_frame_address() works correctly, even under asan. + */ +#ifndef HAVE__BUILTIN_FRAME_ADDRESS + stack_address = &stack_top_loc; +#else + stack_address = (char *) __builtin_frame_address(0); +#endif /* * Compute distance from reference point to my local variables */ - stack_depth = (ssize_t) (stack_base_ptr - &stack_top_loc); + stack_depth = (ssize_t) (stack_base_ptr - stack_address); /* * Take abs value, since stacks grow up on some machines, down on others diff --git a/.cirrus.tasks.yml b/.cirrus.tasks.yml index a22cef063f3..8683d1ae9c7 100644 --- a/.cirrus.tasks.yml +++ b/.cirrus.tasks.yml @@ -447,7 +447,7 @@ task: # print_stacktraces=1,verbosity=2, duh # detect_leaks=0: too many uninteresting leak errors in short-lived binaries UBSAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:verbosity=2 - ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0:detect_stack_use_after_return=0 + ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0 # SANITIZER_FLAGS is set in the tasks below CFLAGS: -Og -ggdb -fno-sanitize-recover=all $SANITIZER_FLAGS -- 2.53.0.1.gb2826b52eb --7e2sypi5gxvkgog6-- ^ permalink raw reply [nested|flat] 114+ messages in thread
* [PATCH v2 1/4] Make stack depth check work with asan's use-after-return @ 2026-02-07 19:04 Andres Freund <[email protected]> 0 siblings, 0 replies; 114+ messages in thread From: Andres Freund @ 2026-02-07 19:04 UTC (permalink / raw) With address sanitizer's stack-use-after-return check, stack variables are moved to heap allocations, to allow to detect references to the memory at a later time. That broke our stack-depth check, which is why we had to disable detect_stack_use_after_return in CI. Luckily __builtin_frame_address() works correctly, even under asan, so use that. Author: Reviewed-by: Discussion: https://postgr.es/m/ Backpatch: --- src/backend/utils/misc/stack_depth.c | 17 ++++++++++++++++- .cirrus.tasks.yml | 2 +- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/src/backend/utils/misc/stack_depth.c b/src/backend/utils/misc/stack_depth.c index 61a07cf824e..9955e28c176 100644 --- a/src/backend/utils/misc/stack_depth.c +++ b/src/backend/utils/misc/stack_depth.c @@ -108,13 +108,28 @@ check_stack_depth(void) bool stack_is_too_deep(void) { +#ifndef HAVE__BUILTIN_FRAME_ADDRESS char stack_top_loc; +#endif ssize_t stack_depth; + char *stack_address; + + /* + * With address sanitizer's stack-use-after-return check, stack variables + * are moved to heap allocations, to allow to detect references to the + * memory at a later time. That would break our stack-depth check. Luckily + * __builtin_frame_address() works correctly, even under asan. + */ +#ifndef HAVE__BUILTIN_FRAME_ADDRESS + stack_address = &stack_top_loc; +#else + stack_address = (char *) __builtin_frame_address(0); +#endif /* * Compute distance from reference point to my local variables */ - stack_depth = (ssize_t) (stack_base_ptr - &stack_top_loc); + stack_depth = (ssize_t) (stack_base_ptr - stack_address); /* * Take abs value, since stacks grow up on some machines, down on others diff --git a/.cirrus.tasks.yml b/.cirrus.tasks.yml index a22cef063f3..8683d1ae9c7 100644 --- a/.cirrus.tasks.yml +++ b/.cirrus.tasks.yml @@ -447,7 +447,7 @@ task: # print_stacktraces=1,verbosity=2, duh # detect_leaks=0: too many uninteresting leak errors in short-lived binaries UBSAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:verbosity=2 - ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0:detect_stack_use_after_return=0 + ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0 # SANITIZER_FLAGS is set in the tasks below CFLAGS: -Og -ggdb -fno-sanitize-recover=all $SANITIZER_FLAGS -- 2.53.0.1.gb2826b52eb --7e2sypi5gxvkgog6-- ^ permalink raw reply [nested|flat] 114+ messages in thread
* [PATCH v2 1/4] Make stack depth check work with asan's use-after-return @ 2026-02-07 19:04 Andres Freund <[email protected]> 0 siblings, 0 replies; 114+ messages in thread From: Andres Freund @ 2026-02-07 19:04 UTC (permalink / raw) With address sanitizer's stack-use-after-return check, stack variables are moved to heap allocations, to allow to detect references to the memory at a later time. That broke our stack-depth check, which is why we had to disable detect_stack_use_after_return in CI. Luckily __builtin_frame_address() works correctly, even under asan, so use that. Author: Reviewed-by: Discussion: https://postgr.es/m/ Backpatch: --- src/backend/utils/misc/stack_depth.c | 17 ++++++++++++++++- .cirrus.tasks.yml | 2 +- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/src/backend/utils/misc/stack_depth.c b/src/backend/utils/misc/stack_depth.c index 61a07cf824e..9955e28c176 100644 --- a/src/backend/utils/misc/stack_depth.c +++ b/src/backend/utils/misc/stack_depth.c @@ -108,13 +108,28 @@ check_stack_depth(void) bool stack_is_too_deep(void) { +#ifndef HAVE__BUILTIN_FRAME_ADDRESS char stack_top_loc; +#endif ssize_t stack_depth; + char *stack_address; + + /* + * With address sanitizer's stack-use-after-return check, stack variables + * are moved to heap allocations, to allow to detect references to the + * memory at a later time. That would break our stack-depth check. Luckily + * __builtin_frame_address() works correctly, even under asan. + */ +#ifndef HAVE__BUILTIN_FRAME_ADDRESS + stack_address = &stack_top_loc; +#else + stack_address = (char *) __builtin_frame_address(0); +#endif /* * Compute distance from reference point to my local variables */ - stack_depth = (ssize_t) (stack_base_ptr - &stack_top_loc); + stack_depth = (ssize_t) (stack_base_ptr - stack_address); /* * Take abs value, since stacks grow up on some machines, down on others diff --git a/.cirrus.tasks.yml b/.cirrus.tasks.yml index a22cef063f3..8683d1ae9c7 100644 --- a/.cirrus.tasks.yml +++ b/.cirrus.tasks.yml @@ -447,7 +447,7 @@ task: # print_stacktraces=1,verbosity=2, duh # detect_leaks=0: too many uninteresting leak errors in short-lived binaries UBSAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:verbosity=2 - ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0:detect_stack_use_after_return=0 + ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0 # SANITIZER_FLAGS is set in the tasks below CFLAGS: -Og -ggdb -fno-sanitize-recover=all $SANITIZER_FLAGS -- 2.53.0.1.gb2826b52eb --7e2sypi5gxvkgog6-- ^ permalink raw reply [nested|flat] 114+ messages in thread
* [PATCH v2 1/4] Make stack depth check work with asan's use-after-return @ 2026-02-07 19:04 Andres Freund <[email protected]> 0 siblings, 0 replies; 114+ messages in thread From: Andres Freund @ 2026-02-07 19:04 UTC (permalink / raw) With address sanitizer's stack-use-after-return check, stack variables are moved to heap allocations, to allow to detect references to the memory at a later time. That broke our stack-depth check, which is why we had to disable detect_stack_use_after_return in CI. Luckily __builtin_frame_address() works correctly, even under asan, so use that. Author: Reviewed-by: Discussion: https://postgr.es/m/ Backpatch: --- src/backend/utils/misc/stack_depth.c | 17 ++++++++++++++++- .cirrus.tasks.yml | 2 +- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/src/backend/utils/misc/stack_depth.c b/src/backend/utils/misc/stack_depth.c index 61a07cf824e..9955e28c176 100644 --- a/src/backend/utils/misc/stack_depth.c +++ b/src/backend/utils/misc/stack_depth.c @@ -108,13 +108,28 @@ check_stack_depth(void) bool stack_is_too_deep(void) { +#ifndef HAVE__BUILTIN_FRAME_ADDRESS char stack_top_loc; +#endif ssize_t stack_depth; + char *stack_address; + + /* + * With address sanitizer's stack-use-after-return check, stack variables + * are moved to heap allocations, to allow to detect references to the + * memory at a later time. That would break our stack-depth check. Luckily + * __builtin_frame_address() works correctly, even under asan. + */ +#ifndef HAVE__BUILTIN_FRAME_ADDRESS + stack_address = &stack_top_loc; +#else + stack_address = (char *) __builtin_frame_address(0); +#endif /* * Compute distance from reference point to my local variables */ - stack_depth = (ssize_t) (stack_base_ptr - &stack_top_loc); + stack_depth = (ssize_t) (stack_base_ptr - stack_address); /* * Take abs value, since stacks grow up on some machines, down on others diff --git a/.cirrus.tasks.yml b/.cirrus.tasks.yml index a22cef063f3..8683d1ae9c7 100644 --- a/.cirrus.tasks.yml +++ b/.cirrus.tasks.yml @@ -447,7 +447,7 @@ task: # print_stacktraces=1,verbosity=2, duh # detect_leaks=0: too many uninteresting leak errors in short-lived binaries UBSAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:verbosity=2 - ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0:detect_stack_use_after_return=0 + ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0 # SANITIZER_FLAGS is set in the tasks below CFLAGS: -Og -ggdb -fno-sanitize-recover=all $SANITIZER_FLAGS -- 2.53.0.1.gb2826b52eb --7e2sypi5gxvkgog6-- ^ permalink raw reply [nested|flat] 114+ messages in thread
* [PATCH v2 1/4] Make stack depth check work with asan's use-after-return @ 2026-02-07 19:04 Andres Freund <[email protected]> 0 siblings, 0 replies; 114+ messages in thread From: Andres Freund @ 2026-02-07 19:04 UTC (permalink / raw) With address sanitizer's stack-use-after-return check, stack variables are moved to heap allocations, to allow to detect references to the memory at a later time. That broke our stack-depth check, which is why we had to disable detect_stack_use_after_return in CI. Luckily __builtin_frame_address() works correctly, even under asan, so use that. Author: Reviewed-by: Discussion: https://postgr.es/m/ Backpatch: --- src/backend/utils/misc/stack_depth.c | 17 ++++++++++++++++- .cirrus.tasks.yml | 2 +- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/src/backend/utils/misc/stack_depth.c b/src/backend/utils/misc/stack_depth.c index 61a07cf824e..9955e28c176 100644 --- a/src/backend/utils/misc/stack_depth.c +++ b/src/backend/utils/misc/stack_depth.c @@ -108,13 +108,28 @@ check_stack_depth(void) bool stack_is_too_deep(void) { +#ifndef HAVE__BUILTIN_FRAME_ADDRESS char stack_top_loc; +#endif ssize_t stack_depth; + char *stack_address; + + /* + * With address sanitizer's stack-use-after-return check, stack variables + * are moved to heap allocations, to allow to detect references to the + * memory at a later time. That would break our stack-depth check. Luckily + * __builtin_frame_address() works correctly, even under asan. + */ +#ifndef HAVE__BUILTIN_FRAME_ADDRESS + stack_address = &stack_top_loc; +#else + stack_address = (char *) __builtin_frame_address(0); +#endif /* * Compute distance from reference point to my local variables */ - stack_depth = (ssize_t) (stack_base_ptr - &stack_top_loc); + stack_depth = (ssize_t) (stack_base_ptr - stack_address); /* * Take abs value, since stacks grow up on some machines, down on others diff --git a/.cirrus.tasks.yml b/.cirrus.tasks.yml index a22cef063f3..8683d1ae9c7 100644 --- a/.cirrus.tasks.yml +++ b/.cirrus.tasks.yml @@ -447,7 +447,7 @@ task: # print_stacktraces=1,verbosity=2, duh # detect_leaks=0: too many uninteresting leak errors in short-lived binaries UBSAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:verbosity=2 - ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0:detect_stack_use_after_return=0 + ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0 # SANITIZER_FLAGS is set in the tasks below CFLAGS: -Og -ggdb -fno-sanitize-recover=all $SANITIZER_FLAGS -- 2.53.0.1.gb2826b52eb --7e2sypi5gxvkgog6-- ^ permalink raw reply [nested|flat] 114+ messages in thread
* [PATCH v2 1/4] Make stack depth check work with asan's use-after-return @ 2026-02-07 19:04 Andres Freund <[email protected]> 0 siblings, 0 replies; 114+ messages in thread From: Andres Freund @ 2026-02-07 19:04 UTC (permalink / raw) With address sanitizer's stack-use-after-return check, stack variables are moved to heap allocations, to allow to detect references to the memory at a later time. That broke our stack-depth check, which is why we had to disable detect_stack_use_after_return in CI. Luckily __builtin_frame_address() works correctly, even under asan, so use that. Author: Reviewed-by: Discussion: https://postgr.es/m/ Backpatch: --- src/backend/utils/misc/stack_depth.c | 17 ++++++++++++++++- .cirrus.tasks.yml | 2 +- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/src/backend/utils/misc/stack_depth.c b/src/backend/utils/misc/stack_depth.c index 61a07cf824e..9955e28c176 100644 --- a/src/backend/utils/misc/stack_depth.c +++ b/src/backend/utils/misc/stack_depth.c @@ -108,13 +108,28 @@ check_stack_depth(void) bool stack_is_too_deep(void) { +#ifndef HAVE__BUILTIN_FRAME_ADDRESS char stack_top_loc; +#endif ssize_t stack_depth; + char *stack_address; + + /* + * With address sanitizer's stack-use-after-return check, stack variables + * are moved to heap allocations, to allow to detect references to the + * memory at a later time. That would break our stack-depth check. Luckily + * __builtin_frame_address() works correctly, even under asan. + */ +#ifndef HAVE__BUILTIN_FRAME_ADDRESS + stack_address = &stack_top_loc; +#else + stack_address = (char *) __builtin_frame_address(0); +#endif /* * Compute distance from reference point to my local variables */ - stack_depth = (ssize_t) (stack_base_ptr - &stack_top_loc); + stack_depth = (ssize_t) (stack_base_ptr - stack_address); /* * Take abs value, since stacks grow up on some machines, down on others diff --git a/.cirrus.tasks.yml b/.cirrus.tasks.yml index a22cef063f3..8683d1ae9c7 100644 --- a/.cirrus.tasks.yml +++ b/.cirrus.tasks.yml @@ -447,7 +447,7 @@ task: # print_stacktraces=1,verbosity=2, duh # detect_leaks=0: too many uninteresting leak errors in short-lived binaries UBSAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:verbosity=2 - ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0:detect_stack_use_after_return=0 + ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0 # SANITIZER_FLAGS is set in the tasks below CFLAGS: -Og -ggdb -fno-sanitize-recover=all $SANITIZER_FLAGS -- 2.53.0.1.gb2826b52eb --7e2sypi5gxvkgog6-- ^ permalink raw reply [nested|flat] 114+ messages in thread
* [PATCH v2 1/4] Make stack depth check work with asan's use-after-return @ 2026-02-07 19:04 Andres Freund <[email protected]> 0 siblings, 0 replies; 114+ messages in thread From: Andres Freund @ 2026-02-07 19:04 UTC (permalink / raw) With address sanitizer's stack-use-after-return check, stack variables are moved to heap allocations, to allow to detect references to the memory at a later time. That broke our stack-depth check, which is why we had to disable detect_stack_use_after_return in CI. Luckily __builtin_frame_address() works correctly, even under asan, so use that. Author: Reviewed-by: Discussion: https://postgr.es/m/ Backpatch: --- src/backend/utils/misc/stack_depth.c | 17 ++++++++++++++++- .cirrus.tasks.yml | 2 +- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/src/backend/utils/misc/stack_depth.c b/src/backend/utils/misc/stack_depth.c index 61a07cf824e..9955e28c176 100644 --- a/src/backend/utils/misc/stack_depth.c +++ b/src/backend/utils/misc/stack_depth.c @@ -108,13 +108,28 @@ check_stack_depth(void) bool stack_is_too_deep(void) { +#ifndef HAVE__BUILTIN_FRAME_ADDRESS char stack_top_loc; +#endif ssize_t stack_depth; + char *stack_address; + + /* + * With address sanitizer's stack-use-after-return check, stack variables + * are moved to heap allocations, to allow to detect references to the + * memory at a later time. That would break our stack-depth check. Luckily + * __builtin_frame_address() works correctly, even under asan. + */ +#ifndef HAVE__BUILTIN_FRAME_ADDRESS + stack_address = &stack_top_loc; +#else + stack_address = (char *) __builtin_frame_address(0); +#endif /* * Compute distance from reference point to my local variables */ - stack_depth = (ssize_t) (stack_base_ptr - &stack_top_loc); + stack_depth = (ssize_t) (stack_base_ptr - stack_address); /* * Take abs value, since stacks grow up on some machines, down on others diff --git a/.cirrus.tasks.yml b/.cirrus.tasks.yml index a22cef063f3..8683d1ae9c7 100644 --- a/.cirrus.tasks.yml +++ b/.cirrus.tasks.yml @@ -447,7 +447,7 @@ task: # print_stacktraces=1,verbosity=2, duh # detect_leaks=0: too many uninteresting leak errors in short-lived binaries UBSAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:verbosity=2 - ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0:detect_stack_use_after_return=0 + ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0 # SANITIZER_FLAGS is set in the tasks below CFLAGS: -Og -ggdb -fno-sanitize-recover=all $SANITIZER_FLAGS -- 2.53.0.1.gb2826b52eb --7e2sypi5gxvkgog6-- ^ permalink raw reply [nested|flat] 114+ messages in thread
* [PATCH v2 1/4] Make stack depth check work with asan's use-after-return @ 2026-02-07 19:04 Andres Freund <[email protected]> 0 siblings, 0 replies; 114+ messages in thread From: Andres Freund @ 2026-02-07 19:04 UTC (permalink / raw) With address sanitizer's stack-use-after-return check, stack variables are moved to heap allocations, to allow to detect references to the memory at a later time. That broke our stack-depth check, which is why we had to disable detect_stack_use_after_return in CI. Luckily __builtin_frame_address() works correctly, even under asan, so use that. Author: Reviewed-by: Discussion: https://postgr.es/m/ Backpatch: --- src/backend/utils/misc/stack_depth.c | 17 ++++++++++++++++- .cirrus.tasks.yml | 2 +- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/src/backend/utils/misc/stack_depth.c b/src/backend/utils/misc/stack_depth.c index 61a07cf824e..9955e28c176 100644 --- a/src/backend/utils/misc/stack_depth.c +++ b/src/backend/utils/misc/stack_depth.c @@ -108,13 +108,28 @@ check_stack_depth(void) bool stack_is_too_deep(void) { +#ifndef HAVE__BUILTIN_FRAME_ADDRESS char stack_top_loc; +#endif ssize_t stack_depth; + char *stack_address; + + /* + * With address sanitizer's stack-use-after-return check, stack variables + * are moved to heap allocations, to allow to detect references to the + * memory at a later time. That would break our stack-depth check. Luckily + * __builtin_frame_address() works correctly, even under asan. + */ +#ifndef HAVE__BUILTIN_FRAME_ADDRESS + stack_address = &stack_top_loc; +#else + stack_address = (char *) __builtin_frame_address(0); +#endif /* * Compute distance from reference point to my local variables */ - stack_depth = (ssize_t) (stack_base_ptr - &stack_top_loc); + stack_depth = (ssize_t) (stack_base_ptr - stack_address); /* * Take abs value, since stacks grow up on some machines, down on others diff --git a/.cirrus.tasks.yml b/.cirrus.tasks.yml index a22cef063f3..8683d1ae9c7 100644 --- a/.cirrus.tasks.yml +++ b/.cirrus.tasks.yml @@ -447,7 +447,7 @@ task: # print_stacktraces=1,verbosity=2, duh # detect_leaks=0: too many uninteresting leak errors in short-lived binaries UBSAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:verbosity=2 - ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0:detect_stack_use_after_return=0 + ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0 # SANITIZER_FLAGS is set in the tasks below CFLAGS: -Og -ggdb -fno-sanitize-recover=all $SANITIZER_FLAGS -- 2.53.0.1.gb2826b52eb --7e2sypi5gxvkgog6-- ^ permalink raw reply [nested|flat] 114+ messages in thread
* [PATCH v2 1/4] Make stack depth check work with asan's use-after-return @ 2026-02-07 19:04 Andres Freund <[email protected]> 0 siblings, 0 replies; 114+ messages in thread From: Andres Freund @ 2026-02-07 19:04 UTC (permalink / raw) With address sanitizer's stack-use-after-return check, stack variables are moved to heap allocations, to allow to detect references to the memory at a later time. That broke our stack-depth check, which is why we had to disable detect_stack_use_after_return in CI. Luckily __builtin_frame_address() works correctly, even under asan, so use that. Author: Reviewed-by: Discussion: https://postgr.es/m/ Backpatch: --- src/backend/utils/misc/stack_depth.c | 17 ++++++++++++++++- .cirrus.tasks.yml | 2 +- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/src/backend/utils/misc/stack_depth.c b/src/backend/utils/misc/stack_depth.c index 61a07cf824e..9955e28c176 100644 --- a/src/backend/utils/misc/stack_depth.c +++ b/src/backend/utils/misc/stack_depth.c @@ -108,13 +108,28 @@ check_stack_depth(void) bool stack_is_too_deep(void) { +#ifndef HAVE__BUILTIN_FRAME_ADDRESS char stack_top_loc; +#endif ssize_t stack_depth; + char *stack_address; + + /* + * With address sanitizer's stack-use-after-return check, stack variables + * are moved to heap allocations, to allow to detect references to the + * memory at a later time. That would break our stack-depth check. Luckily + * __builtin_frame_address() works correctly, even under asan. + */ +#ifndef HAVE__BUILTIN_FRAME_ADDRESS + stack_address = &stack_top_loc; +#else + stack_address = (char *) __builtin_frame_address(0); +#endif /* * Compute distance from reference point to my local variables */ - stack_depth = (ssize_t) (stack_base_ptr - &stack_top_loc); + stack_depth = (ssize_t) (stack_base_ptr - stack_address); /* * Take abs value, since stacks grow up on some machines, down on others diff --git a/.cirrus.tasks.yml b/.cirrus.tasks.yml index a22cef063f3..8683d1ae9c7 100644 --- a/.cirrus.tasks.yml +++ b/.cirrus.tasks.yml @@ -447,7 +447,7 @@ task: # print_stacktraces=1,verbosity=2, duh # detect_leaks=0: too many uninteresting leak errors in short-lived binaries UBSAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:verbosity=2 - ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0:detect_stack_use_after_return=0 + ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0 # SANITIZER_FLAGS is set in the tasks below CFLAGS: -Og -ggdb -fno-sanitize-recover=all $SANITIZER_FLAGS -- 2.53.0.1.gb2826b52eb --7e2sypi5gxvkgog6-- ^ permalink raw reply [nested|flat] 114+ messages in thread
* [PATCH v2 1/4] Make stack depth check work with asan's use-after-return @ 2026-02-07 19:04 Andres Freund <[email protected]> 0 siblings, 0 replies; 114+ messages in thread From: Andres Freund @ 2026-02-07 19:04 UTC (permalink / raw) With address sanitizer's stack-use-after-return check, stack variables are moved to heap allocations, to allow to detect references to the memory at a later time. That broke our stack-depth check, which is why we had to disable detect_stack_use_after_return in CI. Luckily __builtin_frame_address() works correctly, even under asan, so use that. Author: Reviewed-by: Discussion: https://postgr.es/m/ Backpatch: --- src/backend/utils/misc/stack_depth.c | 17 ++++++++++++++++- .cirrus.tasks.yml | 2 +- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/src/backend/utils/misc/stack_depth.c b/src/backend/utils/misc/stack_depth.c index 61a07cf824e..9955e28c176 100644 --- a/src/backend/utils/misc/stack_depth.c +++ b/src/backend/utils/misc/stack_depth.c @@ -108,13 +108,28 @@ check_stack_depth(void) bool stack_is_too_deep(void) { +#ifndef HAVE__BUILTIN_FRAME_ADDRESS char stack_top_loc; +#endif ssize_t stack_depth; + char *stack_address; + + /* + * With address sanitizer's stack-use-after-return check, stack variables + * are moved to heap allocations, to allow to detect references to the + * memory at a later time. That would break our stack-depth check. Luckily + * __builtin_frame_address() works correctly, even under asan. + */ +#ifndef HAVE__BUILTIN_FRAME_ADDRESS + stack_address = &stack_top_loc; +#else + stack_address = (char *) __builtin_frame_address(0); +#endif /* * Compute distance from reference point to my local variables */ - stack_depth = (ssize_t) (stack_base_ptr - &stack_top_loc); + stack_depth = (ssize_t) (stack_base_ptr - stack_address); /* * Take abs value, since stacks grow up on some machines, down on others diff --git a/.cirrus.tasks.yml b/.cirrus.tasks.yml index a22cef063f3..8683d1ae9c7 100644 --- a/.cirrus.tasks.yml +++ b/.cirrus.tasks.yml @@ -447,7 +447,7 @@ task: # print_stacktraces=1,verbosity=2, duh # detect_leaks=0: too many uninteresting leak errors in short-lived binaries UBSAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:verbosity=2 - ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0:detect_stack_use_after_return=0 + ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0 # SANITIZER_FLAGS is set in the tasks below CFLAGS: -Og -ggdb -fno-sanitize-recover=all $SANITIZER_FLAGS -- 2.53.0.1.gb2826b52eb --7e2sypi5gxvkgog6-- ^ permalink raw reply [nested|flat] 114+ messages in thread
* [PATCH v2 1/4] Make stack depth check work with asan's use-after-return @ 2026-02-07 19:04 Andres Freund <[email protected]> 0 siblings, 0 replies; 114+ messages in thread From: Andres Freund @ 2026-02-07 19:04 UTC (permalink / raw) With address sanitizer's stack-use-after-return check, stack variables are moved to heap allocations, to allow to detect references to the memory at a later time. That broke our stack-depth check, which is why we had to disable detect_stack_use_after_return in CI. Luckily __builtin_frame_address() works correctly, even under asan, so use that. Author: Reviewed-by: Discussion: https://postgr.es/m/ Backpatch: --- src/backend/utils/misc/stack_depth.c | 17 ++++++++++++++++- .cirrus.tasks.yml | 2 +- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/src/backend/utils/misc/stack_depth.c b/src/backend/utils/misc/stack_depth.c index 61a07cf824e..9955e28c176 100644 --- a/src/backend/utils/misc/stack_depth.c +++ b/src/backend/utils/misc/stack_depth.c @@ -108,13 +108,28 @@ check_stack_depth(void) bool stack_is_too_deep(void) { +#ifndef HAVE__BUILTIN_FRAME_ADDRESS char stack_top_loc; +#endif ssize_t stack_depth; + char *stack_address; + + /* + * With address sanitizer's stack-use-after-return check, stack variables + * are moved to heap allocations, to allow to detect references to the + * memory at a later time. That would break our stack-depth check. Luckily + * __builtin_frame_address() works correctly, even under asan. + */ +#ifndef HAVE__BUILTIN_FRAME_ADDRESS + stack_address = &stack_top_loc; +#else + stack_address = (char *) __builtin_frame_address(0); +#endif /* * Compute distance from reference point to my local variables */ - stack_depth = (ssize_t) (stack_base_ptr - &stack_top_loc); + stack_depth = (ssize_t) (stack_base_ptr - stack_address); /* * Take abs value, since stacks grow up on some machines, down on others diff --git a/.cirrus.tasks.yml b/.cirrus.tasks.yml index a22cef063f3..8683d1ae9c7 100644 --- a/.cirrus.tasks.yml +++ b/.cirrus.tasks.yml @@ -447,7 +447,7 @@ task: # print_stacktraces=1,verbosity=2, duh # detect_leaks=0: too many uninteresting leak errors in short-lived binaries UBSAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:verbosity=2 - ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0:detect_stack_use_after_return=0 + ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0 # SANITIZER_FLAGS is set in the tasks below CFLAGS: -Og -ggdb -fno-sanitize-recover=all $SANITIZER_FLAGS -- 2.53.0.1.gb2826b52eb --7e2sypi5gxvkgog6-- ^ permalink raw reply [nested|flat] 114+ messages in thread
* [PATCH v2 1/4] Make stack depth check work with asan's use-after-return @ 2026-02-07 19:04 Andres Freund <[email protected]> 0 siblings, 0 replies; 114+ messages in thread From: Andres Freund @ 2026-02-07 19:04 UTC (permalink / raw) With address sanitizer's stack-use-after-return check, stack variables are moved to heap allocations, to allow to detect references to the memory at a later time. That broke our stack-depth check, which is why we had to disable detect_stack_use_after_return in CI. Luckily __builtin_frame_address() works correctly, even under asan, so use that. Author: Reviewed-by: Discussion: https://postgr.es/m/ Backpatch: --- src/backend/utils/misc/stack_depth.c | 17 ++++++++++++++++- .cirrus.tasks.yml | 2 +- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/src/backend/utils/misc/stack_depth.c b/src/backend/utils/misc/stack_depth.c index 61a07cf824e..9955e28c176 100644 --- a/src/backend/utils/misc/stack_depth.c +++ b/src/backend/utils/misc/stack_depth.c @@ -108,13 +108,28 @@ check_stack_depth(void) bool stack_is_too_deep(void) { +#ifndef HAVE__BUILTIN_FRAME_ADDRESS char stack_top_loc; +#endif ssize_t stack_depth; + char *stack_address; + + /* + * With address sanitizer's stack-use-after-return check, stack variables + * are moved to heap allocations, to allow to detect references to the + * memory at a later time. That would break our stack-depth check. Luckily + * __builtin_frame_address() works correctly, even under asan. + */ +#ifndef HAVE__BUILTIN_FRAME_ADDRESS + stack_address = &stack_top_loc; +#else + stack_address = (char *) __builtin_frame_address(0); +#endif /* * Compute distance from reference point to my local variables */ - stack_depth = (ssize_t) (stack_base_ptr - &stack_top_loc); + stack_depth = (ssize_t) (stack_base_ptr - stack_address); /* * Take abs value, since stacks grow up on some machines, down on others diff --git a/.cirrus.tasks.yml b/.cirrus.tasks.yml index a22cef063f3..8683d1ae9c7 100644 --- a/.cirrus.tasks.yml +++ b/.cirrus.tasks.yml @@ -447,7 +447,7 @@ task: # print_stacktraces=1,verbosity=2, duh # detect_leaks=0: too many uninteresting leak errors in short-lived binaries UBSAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:verbosity=2 - ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0:detect_stack_use_after_return=0 + ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0 # SANITIZER_FLAGS is set in the tasks below CFLAGS: -Og -ggdb -fno-sanitize-recover=all $SANITIZER_FLAGS -- 2.53.0.1.gb2826b52eb --7e2sypi5gxvkgog6-- ^ permalink raw reply [nested|flat] 114+ messages in thread
* [PATCH v2 1/4] Make stack depth check work with asan's use-after-return @ 2026-02-07 19:04 Andres Freund <[email protected]> 0 siblings, 0 replies; 114+ messages in thread From: Andres Freund @ 2026-02-07 19:04 UTC (permalink / raw) With address sanitizer's stack-use-after-return check, stack variables are moved to heap allocations, to allow to detect references to the memory at a later time. That broke our stack-depth check, which is why we had to disable detect_stack_use_after_return in CI. Luckily __builtin_frame_address() works correctly, even under asan, so use that. Author: Reviewed-by: Discussion: https://postgr.es/m/ Backpatch: --- src/backend/utils/misc/stack_depth.c | 17 ++++++++++++++++- .cirrus.tasks.yml | 2 +- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/src/backend/utils/misc/stack_depth.c b/src/backend/utils/misc/stack_depth.c index 61a07cf824e..9955e28c176 100644 --- a/src/backend/utils/misc/stack_depth.c +++ b/src/backend/utils/misc/stack_depth.c @@ -108,13 +108,28 @@ check_stack_depth(void) bool stack_is_too_deep(void) { +#ifndef HAVE__BUILTIN_FRAME_ADDRESS char stack_top_loc; +#endif ssize_t stack_depth; + char *stack_address; + + /* + * With address sanitizer's stack-use-after-return check, stack variables + * are moved to heap allocations, to allow to detect references to the + * memory at a later time. That would break our stack-depth check. Luckily + * __builtin_frame_address() works correctly, even under asan. + */ +#ifndef HAVE__BUILTIN_FRAME_ADDRESS + stack_address = &stack_top_loc; +#else + stack_address = (char *) __builtin_frame_address(0); +#endif /* * Compute distance from reference point to my local variables */ - stack_depth = (ssize_t) (stack_base_ptr - &stack_top_loc); + stack_depth = (ssize_t) (stack_base_ptr - stack_address); /* * Take abs value, since stacks grow up on some machines, down on others diff --git a/.cirrus.tasks.yml b/.cirrus.tasks.yml index a22cef063f3..8683d1ae9c7 100644 --- a/.cirrus.tasks.yml +++ b/.cirrus.tasks.yml @@ -447,7 +447,7 @@ task: # print_stacktraces=1,verbosity=2, duh # detect_leaks=0: too many uninteresting leak errors in short-lived binaries UBSAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:verbosity=2 - ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0:detect_stack_use_after_return=0 + ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0 # SANITIZER_FLAGS is set in the tasks below CFLAGS: -Og -ggdb -fno-sanitize-recover=all $SANITIZER_FLAGS -- 2.53.0.1.gb2826b52eb --7e2sypi5gxvkgog6-- ^ permalink raw reply [nested|flat] 114+ messages in thread
* [PATCH v2 1/4] Make stack depth check work with asan's use-after-return @ 2026-02-07 19:04 Andres Freund <[email protected]> 0 siblings, 0 replies; 114+ messages in thread From: Andres Freund @ 2026-02-07 19:04 UTC (permalink / raw) With address sanitizer's stack-use-after-return check, stack variables are moved to heap allocations, to allow to detect references to the memory at a later time. That broke our stack-depth check, which is why we had to disable detect_stack_use_after_return in CI. Luckily __builtin_frame_address() works correctly, even under asan, so use that. Author: Reviewed-by: Discussion: https://postgr.es/m/ Backpatch: --- src/backend/utils/misc/stack_depth.c | 17 ++++++++++++++++- .cirrus.tasks.yml | 2 +- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/src/backend/utils/misc/stack_depth.c b/src/backend/utils/misc/stack_depth.c index 61a07cf824e..9955e28c176 100644 --- a/src/backend/utils/misc/stack_depth.c +++ b/src/backend/utils/misc/stack_depth.c @@ -108,13 +108,28 @@ check_stack_depth(void) bool stack_is_too_deep(void) { +#ifndef HAVE__BUILTIN_FRAME_ADDRESS char stack_top_loc; +#endif ssize_t stack_depth; + char *stack_address; + + /* + * With address sanitizer's stack-use-after-return check, stack variables + * are moved to heap allocations, to allow to detect references to the + * memory at a later time. That would break our stack-depth check. Luckily + * __builtin_frame_address() works correctly, even under asan. + */ +#ifndef HAVE__BUILTIN_FRAME_ADDRESS + stack_address = &stack_top_loc; +#else + stack_address = (char *) __builtin_frame_address(0); +#endif /* * Compute distance from reference point to my local variables */ - stack_depth = (ssize_t) (stack_base_ptr - &stack_top_loc); + stack_depth = (ssize_t) (stack_base_ptr - stack_address); /* * Take abs value, since stacks grow up on some machines, down on others diff --git a/.cirrus.tasks.yml b/.cirrus.tasks.yml index a22cef063f3..8683d1ae9c7 100644 --- a/.cirrus.tasks.yml +++ b/.cirrus.tasks.yml @@ -447,7 +447,7 @@ task: # print_stacktraces=1,verbosity=2, duh # detect_leaks=0: too many uninteresting leak errors in short-lived binaries UBSAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:verbosity=2 - ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0:detect_stack_use_after_return=0 + ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0 # SANITIZER_FLAGS is set in the tasks below CFLAGS: -Og -ggdb -fno-sanitize-recover=all $SANITIZER_FLAGS -- 2.53.0.1.gb2826b52eb --7e2sypi5gxvkgog6-- ^ permalink raw reply [nested|flat] 114+ messages in thread
* [PATCH v2 1/4] Make stack depth check work with asan's use-after-return @ 2026-02-07 19:04 Andres Freund <[email protected]> 0 siblings, 0 replies; 114+ messages in thread From: Andres Freund @ 2026-02-07 19:04 UTC (permalink / raw) With address sanitizer's stack-use-after-return check, stack variables are moved to heap allocations, to allow to detect references to the memory at a later time. That broke our stack-depth check, which is why we had to disable detect_stack_use_after_return in CI. Luckily __builtin_frame_address() works correctly, even under asan, so use that. Author: Reviewed-by: Discussion: https://postgr.es/m/ Backpatch: --- src/backend/utils/misc/stack_depth.c | 17 ++++++++++++++++- .cirrus.tasks.yml | 2 +- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/src/backend/utils/misc/stack_depth.c b/src/backend/utils/misc/stack_depth.c index 61a07cf824e..9955e28c176 100644 --- a/src/backend/utils/misc/stack_depth.c +++ b/src/backend/utils/misc/stack_depth.c @@ -108,13 +108,28 @@ check_stack_depth(void) bool stack_is_too_deep(void) { +#ifndef HAVE__BUILTIN_FRAME_ADDRESS char stack_top_loc; +#endif ssize_t stack_depth; + char *stack_address; + + /* + * With address sanitizer's stack-use-after-return check, stack variables + * are moved to heap allocations, to allow to detect references to the + * memory at a later time. That would break our stack-depth check. Luckily + * __builtin_frame_address() works correctly, even under asan. + */ +#ifndef HAVE__BUILTIN_FRAME_ADDRESS + stack_address = &stack_top_loc; +#else + stack_address = (char *) __builtin_frame_address(0); +#endif /* * Compute distance from reference point to my local variables */ - stack_depth = (ssize_t) (stack_base_ptr - &stack_top_loc); + stack_depth = (ssize_t) (stack_base_ptr - stack_address); /* * Take abs value, since stacks grow up on some machines, down on others diff --git a/.cirrus.tasks.yml b/.cirrus.tasks.yml index a22cef063f3..8683d1ae9c7 100644 --- a/.cirrus.tasks.yml +++ b/.cirrus.tasks.yml @@ -447,7 +447,7 @@ task: # print_stacktraces=1,verbosity=2, duh # detect_leaks=0: too many uninteresting leak errors in short-lived binaries UBSAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:verbosity=2 - ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0:detect_stack_use_after_return=0 + ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0 # SANITIZER_FLAGS is set in the tasks below CFLAGS: -Og -ggdb -fno-sanitize-recover=all $SANITIZER_FLAGS -- 2.53.0.1.gb2826b52eb --7e2sypi5gxvkgog6-- ^ permalink raw reply [nested|flat] 114+ messages in thread
* [PATCH v2 1/4] Make stack depth check work with asan's use-after-return @ 2026-02-07 19:04 Andres Freund <[email protected]> 0 siblings, 0 replies; 114+ messages in thread From: Andres Freund @ 2026-02-07 19:04 UTC (permalink / raw) With address sanitizer's stack-use-after-return check, stack variables are moved to heap allocations, to allow to detect references to the memory at a later time. That broke our stack-depth check, which is why we had to disable detect_stack_use_after_return in CI. Luckily __builtin_frame_address() works correctly, even under asan, so use that. Author: Reviewed-by: Discussion: https://postgr.es/m/ Backpatch: --- src/backend/utils/misc/stack_depth.c | 17 ++++++++++++++++- .cirrus.tasks.yml | 2 +- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/src/backend/utils/misc/stack_depth.c b/src/backend/utils/misc/stack_depth.c index 61a07cf824e..9955e28c176 100644 --- a/src/backend/utils/misc/stack_depth.c +++ b/src/backend/utils/misc/stack_depth.c @@ -108,13 +108,28 @@ check_stack_depth(void) bool stack_is_too_deep(void) { +#ifndef HAVE__BUILTIN_FRAME_ADDRESS char stack_top_loc; +#endif ssize_t stack_depth; + char *stack_address; + + /* + * With address sanitizer's stack-use-after-return check, stack variables + * are moved to heap allocations, to allow to detect references to the + * memory at a later time. That would break our stack-depth check. Luckily + * __builtin_frame_address() works correctly, even under asan. + */ +#ifndef HAVE__BUILTIN_FRAME_ADDRESS + stack_address = &stack_top_loc; +#else + stack_address = (char *) __builtin_frame_address(0); +#endif /* * Compute distance from reference point to my local variables */ - stack_depth = (ssize_t) (stack_base_ptr - &stack_top_loc); + stack_depth = (ssize_t) (stack_base_ptr - stack_address); /* * Take abs value, since stacks grow up on some machines, down on others diff --git a/.cirrus.tasks.yml b/.cirrus.tasks.yml index a22cef063f3..8683d1ae9c7 100644 --- a/.cirrus.tasks.yml +++ b/.cirrus.tasks.yml @@ -447,7 +447,7 @@ task: # print_stacktraces=1,verbosity=2, duh # detect_leaks=0: too many uninteresting leak errors in short-lived binaries UBSAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:verbosity=2 - ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0:detect_stack_use_after_return=0 + ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0 # SANITIZER_FLAGS is set in the tasks below CFLAGS: -Og -ggdb -fno-sanitize-recover=all $SANITIZER_FLAGS -- 2.53.0.1.gb2826b52eb --7e2sypi5gxvkgog6-- ^ permalink raw reply [nested|flat] 114+ messages in thread
* [PATCH v2 1/4] Make stack depth check work with asan's use-after-return @ 2026-02-07 19:04 Andres Freund <[email protected]> 0 siblings, 0 replies; 114+ messages in thread From: Andres Freund @ 2026-02-07 19:04 UTC (permalink / raw) With address sanitizer's stack-use-after-return check, stack variables are moved to heap allocations, to allow to detect references to the memory at a later time. That broke our stack-depth check, which is why we had to disable detect_stack_use_after_return in CI. Luckily __builtin_frame_address() works correctly, even under asan, so use that. Author: Reviewed-by: Discussion: https://postgr.es/m/ Backpatch: --- src/backend/utils/misc/stack_depth.c | 17 ++++++++++++++++- .cirrus.tasks.yml | 2 +- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/src/backend/utils/misc/stack_depth.c b/src/backend/utils/misc/stack_depth.c index 61a07cf824e..9955e28c176 100644 --- a/src/backend/utils/misc/stack_depth.c +++ b/src/backend/utils/misc/stack_depth.c @@ -108,13 +108,28 @@ check_stack_depth(void) bool stack_is_too_deep(void) { +#ifndef HAVE__BUILTIN_FRAME_ADDRESS char stack_top_loc; +#endif ssize_t stack_depth; + char *stack_address; + + /* + * With address sanitizer's stack-use-after-return check, stack variables + * are moved to heap allocations, to allow to detect references to the + * memory at a later time. That would break our stack-depth check. Luckily + * __builtin_frame_address() works correctly, even under asan. + */ +#ifndef HAVE__BUILTIN_FRAME_ADDRESS + stack_address = &stack_top_loc; +#else + stack_address = (char *) __builtin_frame_address(0); +#endif /* * Compute distance from reference point to my local variables */ - stack_depth = (ssize_t) (stack_base_ptr - &stack_top_loc); + stack_depth = (ssize_t) (stack_base_ptr - stack_address); /* * Take abs value, since stacks grow up on some machines, down on others diff --git a/.cirrus.tasks.yml b/.cirrus.tasks.yml index a22cef063f3..8683d1ae9c7 100644 --- a/.cirrus.tasks.yml +++ b/.cirrus.tasks.yml @@ -447,7 +447,7 @@ task: # print_stacktraces=1,verbosity=2, duh # detect_leaks=0: too many uninteresting leak errors in short-lived binaries UBSAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:verbosity=2 - ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0:detect_stack_use_after_return=0 + ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0 # SANITIZER_FLAGS is set in the tasks below CFLAGS: -Og -ggdb -fno-sanitize-recover=all $SANITIZER_FLAGS -- 2.53.0.1.gb2826b52eb --7e2sypi5gxvkgog6-- ^ permalink raw reply [nested|flat] 114+ messages in thread
* [PATCH v2 1/4] Make stack depth check work with asan's use-after-return @ 2026-02-07 19:04 Andres Freund <[email protected]> 0 siblings, 0 replies; 114+ messages in thread From: Andres Freund @ 2026-02-07 19:04 UTC (permalink / raw) With address sanitizer's stack-use-after-return check, stack variables are moved to heap allocations, to allow to detect references to the memory at a later time. That broke our stack-depth check, which is why we had to disable detect_stack_use_after_return in CI. Luckily __builtin_frame_address() works correctly, even under asan, so use that. Author: Reviewed-by: Discussion: https://postgr.es/m/ Backpatch: --- src/backend/utils/misc/stack_depth.c | 17 ++++++++++++++++- .cirrus.tasks.yml | 2 +- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/src/backend/utils/misc/stack_depth.c b/src/backend/utils/misc/stack_depth.c index 61a07cf824e..9955e28c176 100644 --- a/src/backend/utils/misc/stack_depth.c +++ b/src/backend/utils/misc/stack_depth.c @@ -108,13 +108,28 @@ check_stack_depth(void) bool stack_is_too_deep(void) { +#ifndef HAVE__BUILTIN_FRAME_ADDRESS char stack_top_loc; +#endif ssize_t stack_depth; + char *stack_address; + + /* + * With address sanitizer's stack-use-after-return check, stack variables + * are moved to heap allocations, to allow to detect references to the + * memory at a later time. That would break our stack-depth check. Luckily + * __builtin_frame_address() works correctly, even under asan. + */ +#ifndef HAVE__BUILTIN_FRAME_ADDRESS + stack_address = &stack_top_loc; +#else + stack_address = (char *) __builtin_frame_address(0); +#endif /* * Compute distance from reference point to my local variables */ - stack_depth = (ssize_t) (stack_base_ptr - &stack_top_loc); + stack_depth = (ssize_t) (stack_base_ptr - stack_address); /* * Take abs value, since stacks grow up on some machines, down on others diff --git a/.cirrus.tasks.yml b/.cirrus.tasks.yml index a22cef063f3..8683d1ae9c7 100644 --- a/.cirrus.tasks.yml +++ b/.cirrus.tasks.yml @@ -447,7 +447,7 @@ task: # print_stacktraces=1,verbosity=2, duh # detect_leaks=0: too many uninteresting leak errors in short-lived binaries UBSAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:verbosity=2 - ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0:detect_stack_use_after_return=0 + ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0 # SANITIZER_FLAGS is set in the tasks below CFLAGS: -Og -ggdb -fno-sanitize-recover=all $SANITIZER_FLAGS -- 2.53.0.1.gb2826b52eb --7e2sypi5gxvkgog6-- ^ permalink raw reply [nested|flat] 114+ messages in thread
* [PATCH v2 1/4] Make stack depth check work with asan's use-after-return @ 2026-02-07 19:04 Andres Freund <[email protected]> 0 siblings, 0 replies; 114+ messages in thread From: Andres Freund @ 2026-02-07 19:04 UTC (permalink / raw) With address sanitizer's stack-use-after-return check, stack variables are moved to heap allocations, to allow to detect references to the memory at a later time. That broke our stack-depth check, which is why we had to disable detect_stack_use_after_return in CI. Luckily __builtin_frame_address() works correctly, even under asan, so use that. Author: Reviewed-by: Discussion: https://postgr.es/m/ Backpatch: --- src/backend/utils/misc/stack_depth.c | 17 ++++++++++++++++- .cirrus.tasks.yml | 2 +- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/src/backend/utils/misc/stack_depth.c b/src/backend/utils/misc/stack_depth.c index 61a07cf824e..9955e28c176 100644 --- a/src/backend/utils/misc/stack_depth.c +++ b/src/backend/utils/misc/stack_depth.c @@ -108,13 +108,28 @@ check_stack_depth(void) bool stack_is_too_deep(void) { +#ifndef HAVE__BUILTIN_FRAME_ADDRESS char stack_top_loc; +#endif ssize_t stack_depth; + char *stack_address; + + /* + * With address sanitizer's stack-use-after-return check, stack variables + * are moved to heap allocations, to allow to detect references to the + * memory at a later time. That would break our stack-depth check. Luckily + * __builtin_frame_address() works correctly, even under asan. + */ +#ifndef HAVE__BUILTIN_FRAME_ADDRESS + stack_address = &stack_top_loc; +#else + stack_address = (char *) __builtin_frame_address(0); +#endif /* * Compute distance from reference point to my local variables */ - stack_depth = (ssize_t) (stack_base_ptr - &stack_top_loc); + stack_depth = (ssize_t) (stack_base_ptr - stack_address); /* * Take abs value, since stacks grow up on some machines, down on others diff --git a/.cirrus.tasks.yml b/.cirrus.tasks.yml index a22cef063f3..8683d1ae9c7 100644 --- a/.cirrus.tasks.yml +++ b/.cirrus.tasks.yml @@ -447,7 +447,7 @@ task: # print_stacktraces=1,verbosity=2, duh # detect_leaks=0: too many uninteresting leak errors in short-lived binaries UBSAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:verbosity=2 - ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0:detect_stack_use_after_return=0 + ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0 # SANITIZER_FLAGS is set in the tasks below CFLAGS: -Og -ggdb -fno-sanitize-recover=all $SANITIZER_FLAGS -- 2.53.0.1.gb2826b52eb --7e2sypi5gxvkgog6-- ^ permalink raw reply [nested|flat] 114+ messages in thread
* [PATCH v2 1/4] Make stack depth check work with asan's use-after-return @ 2026-02-07 19:04 Andres Freund <[email protected]> 0 siblings, 0 replies; 114+ messages in thread From: Andres Freund @ 2026-02-07 19:04 UTC (permalink / raw) With address sanitizer's stack-use-after-return check, stack variables are moved to heap allocations, to allow to detect references to the memory at a later time. That broke our stack-depth check, which is why we had to disable detect_stack_use_after_return in CI. Luckily __builtin_frame_address() works correctly, even under asan, so use that. Author: Reviewed-by: Discussion: https://postgr.es/m/ Backpatch: --- src/backend/utils/misc/stack_depth.c | 17 ++++++++++++++++- .cirrus.tasks.yml | 2 +- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/src/backend/utils/misc/stack_depth.c b/src/backend/utils/misc/stack_depth.c index 61a07cf824e..9955e28c176 100644 --- a/src/backend/utils/misc/stack_depth.c +++ b/src/backend/utils/misc/stack_depth.c @@ -108,13 +108,28 @@ check_stack_depth(void) bool stack_is_too_deep(void) { +#ifndef HAVE__BUILTIN_FRAME_ADDRESS char stack_top_loc; +#endif ssize_t stack_depth; + char *stack_address; + + /* + * With address sanitizer's stack-use-after-return check, stack variables + * are moved to heap allocations, to allow to detect references to the + * memory at a later time. That would break our stack-depth check. Luckily + * __builtin_frame_address() works correctly, even under asan. + */ +#ifndef HAVE__BUILTIN_FRAME_ADDRESS + stack_address = &stack_top_loc; +#else + stack_address = (char *) __builtin_frame_address(0); +#endif /* * Compute distance from reference point to my local variables */ - stack_depth = (ssize_t) (stack_base_ptr - &stack_top_loc); + stack_depth = (ssize_t) (stack_base_ptr - stack_address); /* * Take abs value, since stacks grow up on some machines, down on others diff --git a/.cirrus.tasks.yml b/.cirrus.tasks.yml index a22cef063f3..8683d1ae9c7 100644 --- a/.cirrus.tasks.yml +++ b/.cirrus.tasks.yml @@ -447,7 +447,7 @@ task: # print_stacktraces=1,verbosity=2, duh # detect_leaks=0: too many uninteresting leak errors in short-lived binaries UBSAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:verbosity=2 - ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0:detect_stack_use_after_return=0 + ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0 # SANITIZER_FLAGS is set in the tasks below CFLAGS: -Og -ggdb -fno-sanitize-recover=all $SANITIZER_FLAGS -- 2.53.0.1.gb2826b52eb --7e2sypi5gxvkgog6-- ^ permalink raw reply [nested|flat] 114+ messages in thread
* [PATCH v2 1/4] Make stack depth check work with asan's use-after-return @ 2026-02-07 19:04 Andres Freund <[email protected]> 0 siblings, 0 replies; 114+ messages in thread From: Andres Freund @ 2026-02-07 19:04 UTC (permalink / raw) With address sanitizer's stack-use-after-return check, stack variables are moved to heap allocations, to allow to detect references to the memory at a later time. That broke our stack-depth check, which is why we had to disable detect_stack_use_after_return in CI. Luckily __builtin_frame_address() works correctly, even under asan, so use that. Author: Reviewed-by: Discussion: https://postgr.es/m/ Backpatch: --- src/backend/utils/misc/stack_depth.c | 17 ++++++++++++++++- .cirrus.tasks.yml | 2 +- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/src/backend/utils/misc/stack_depth.c b/src/backend/utils/misc/stack_depth.c index 61a07cf824e..9955e28c176 100644 --- a/src/backend/utils/misc/stack_depth.c +++ b/src/backend/utils/misc/stack_depth.c @@ -108,13 +108,28 @@ check_stack_depth(void) bool stack_is_too_deep(void) { +#ifndef HAVE__BUILTIN_FRAME_ADDRESS char stack_top_loc; +#endif ssize_t stack_depth; + char *stack_address; + + /* + * With address sanitizer's stack-use-after-return check, stack variables + * are moved to heap allocations, to allow to detect references to the + * memory at a later time. That would break our stack-depth check. Luckily + * __builtin_frame_address() works correctly, even under asan. + */ +#ifndef HAVE__BUILTIN_FRAME_ADDRESS + stack_address = &stack_top_loc; +#else + stack_address = (char *) __builtin_frame_address(0); +#endif /* * Compute distance from reference point to my local variables */ - stack_depth = (ssize_t) (stack_base_ptr - &stack_top_loc); + stack_depth = (ssize_t) (stack_base_ptr - stack_address); /* * Take abs value, since stacks grow up on some machines, down on others diff --git a/.cirrus.tasks.yml b/.cirrus.tasks.yml index a22cef063f3..8683d1ae9c7 100644 --- a/.cirrus.tasks.yml +++ b/.cirrus.tasks.yml @@ -447,7 +447,7 @@ task: # print_stacktraces=1,verbosity=2, duh # detect_leaks=0: too many uninteresting leak errors in short-lived binaries UBSAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:verbosity=2 - ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0:detect_stack_use_after_return=0 + ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0 # SANITIZER_FLAGS is set in the tasks below CFLAGS: -Og -ggdb -fno-sanitize-recover=all $SANITIZER_FLAGS -- 2.53.0.1.gb2826b52eb --7e2sypi5gxvkgog6-- ^ permalink raw reply [nested|flat] 114+ messages in thread
* [PATCH v2 1/4] Make stack depth check work with asan's use-after-return @ 2026-02-07 19:04 Andres Freund <[email protected]> 0 siblings, 0 replies; 114+ messages in thread From: Andres Freund @ 2026-02-07 19:04 UTC (permalink / raw) With address sanitizer's stack-use-after-return check, stack variables are moved to heap allocations, to allow to detect references to the memory at a later time. That broke our stack-depth check, which is why we had to disable detect_stack_use_after_return in CI. Luckily __builtin_frame_address() works correctly, even under asan, so use that. Author: Reviewed-by: Discussion: https://postgr.es/m/ Backpatch: --- src/backend/utils/misc/stack_depth.c | 17 ++++++++++++++++- .cirrus.tasks.yml | 2 +- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/src/backend/utils/misc/stack_depth.c b/src/backend/utils/misc/stack_depth.c index 61a07cf824e..9955e28c176 100644 --- a/src/backend/utils/misc/stack_depth.c +++ b/src/backend/utils/misc/stack_depth.c @@ -108,13 +108,28 @@ check_stack_depth(void) bool stack_is_too_deep(void) { +#ifndef HAVE__BUILTIN_FRAME_ADDRESS char stack_top_loc; +#endif ssize_t stack_depth; + char *stack_address; + + /* + * With address sanitizer's stack-use-after-return check, stack variables + * are moved to heap allocations, to allow to detect references to the + * memory at a later time. That would break our stack-depth check. Luckily + * __builtin_frame_address() works correctly, even under asan. + */ +#ifndef HAVE__BUILTIN_FRAME_ADDRESS + stack_address = &stack_top_loc; +#else + stack_address = (char *) __builtin_frame_address(0); +#endif /* * Compute distance from reference point to my local variables */ - stack_depth = (ssize_t) (stack_base_ptr - &stack_top_loc); + stack_depth = (ssize_t) (stack_base_ptr - stack_address); /* * Take abs value, since stacks grow up on some machines, down on others diff --git a/.cirrus.tasks.yml b/.cirrus.tasks.yml index a22cef063f3..8683d1ae9c7 100644 --- a/.cirrus.tasks.yml +++ b/.cirrus.tasks.yml @@ -447,7 +447,7 @@ task: # print_stacktraces=1,verbosity=2, duh # detect_leaks=0: too many uninteresting leak errors in short-lived binaries UBSAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:verbosity=2 - ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0:detect_stack_use_after_return=0 + ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0 # SANITIZER_FLAGS is set in the tasks below CFLAGS: -Og -ggdb -fno-sanitize-recover=all $SANITIZER_FLAGS -- 2.53.0.1.gb2826b52eb --7e2sypi5gxvkgog6-- ^ permalink raw reply [nested|flat] 114+ messages in thread
* [PATCH v2 1/4] Make stack depth check work with asan's use-after-return @ 2026-02-07 19:04 Andres Freund <[email protected]> 0 siblings, 0 replies; 114+ messages in thread From: Andres Freund @ 2026-02-07 19:04 UTC (permalink / raw) With address sanitizer's stack-use-after-return check, stack variables are moved to heap allocations, to allow to detect references to the memory at a later time. That broke our stack-depth check, which is why we had to disable detect_stack_use_after_return in CI. Luckily __builtin_frame_address() works correctly, even under asan, so use that. Author: Reviewed-by: Discussion: https://postgr.es/m/ Backpatch: --- src/backend/utils/misc/stack_depth.c | 17 ++++++++++++++++- .cirrus.tasks.yml | 2 +- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/src/backend/utils/misc/stack_depth.c b/src/backend/utils/misc/stack_depth.c index 61a07cf824e..9955e28c176 100644 --- a/src/backend/utils/misc/stack_depth.c +++ b/src/backend/utils/misc/stack_depth.c @@ -108,13 +108,28 @@ check_stack_depth(void) bool stack_is_too_deep(void) { +#ifndef HAVE__BUILTIN_FRAME_ADDRESS char stack_top_loc; +#endif ssize_t stack_depth; + char *stack_address; + + /* + * With address sanitizer's stack-use-after-return check, stack variables + * are moved to heap allocations, to allow to detect references to the + * memory at a later time. That would break our stack-depth check. Luckily + * __builtin_frame_address() works correctly, even under asan. + */ +#ifndef HAVE__BUILTIN_FRAME_ADDRESS + stack_address = &stack_top_loc; +#else + stack_address = (char *) __builtin_frame_address(0); +#endif /* * Compute distance from reference point to my local variables */ - stack_depth = (ssize_t) (stack_base_ptr - &stack_top_loc); + stack_depth = (ssize_t) (stack_base_ptr - stack_address); /* * Take abs value, since stacks grow up on some machines, down on others diff --git a/.cirrus.tasks.yml b/.cirrus.tasks.yml index a22cef063f3..8683d1ae9c7 100644 --- a/.cirrus.tasks.yml +++ b/.cirrus.tasks.yml @@ -447,7 +447,7 @@ task: # print_stacktraces=1,verbosity=2, duh # detect_leaks=0: too many uninteresting leak errors in short-lived binaries UBSAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:verbosity=2 - ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0:detect_stack_use_after_return=0 + ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0 # SANITIZER_FLAGS is set in the tasks below CFLAGS: -Og -ggdb -fno-sanitize-recover=all $SANITIZER_FLAGS -- 2.53.0.1.gb2826b52eb --7e2sypi5gxvkgog6-- ^ permalink raw reply [nested|flat] 114+ messages in thread
* [PATCH v2 1/4] Make stack depth check work with asan's use-after-return @ 2026-02-07 19:04 Andres Freund <[email protected]> 0 siblings, 0 replies; 114+ messages in thread From: Andres Freund @ 2026-02-07 19:04 UTC (permalink / raw) With address sanitizer's stack-use-after-return check, stack variables are moved to heap allocations, to allow to detect references to the memory at a later time. That broke our stack-depth check, which is why we had to disable detect_stack_use_after_return in CI. Luckily __builtin_frame_address() works correctly, even under asan, so use that. Author: Reviewed-by: Discussion: https://postgr.es/m/ Backpatch: --- src/backend/utils/misc/stack_depth.c | 17 ++++++++++++++++- .cirrus.tasks.yml | 2 +- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/src/backend/utils/misc/stack_depth.c b/src/backend/utils/misc/stack_depth.c index 61a07cf824e..9955e28c176 100644 --- a/src/backend/utils/misc/stack_depth.c +++ b/src/backend/utils/misc/stack_depth.c @@ -108,13 +108,28 @@ check_stack_depth(void) bool stack_is_too_deep(void) { +#ifndef HAVE__BUILTIN_FRAME_ADDRESS char stack_top_loc; +#endif ssize_t stack_depth; + char *stack_address; + + /* + * With address sanitizer's stack-use-after-return check, stack variables + * are moved to heap allocations, to allow to detect references to the + * memory at a later time. That would break our stack-depth check. Luckily + * __builtin_frame_address() works correctly, even under asan. + */ +#ifndef HAVE__BUILTIN_FRAME_ADDRESS + stack_address = &stack_top_loc; +#else + stack_address = (char *) __builtin_frame_address(0); +#endif /* * Compute distance from reference point to my local variables */ - stack_depth = (ssize_t) (stack_base_ptr - &stack_top_loc); + stack_depth = (ssize_t) (stack_base_ptr - stack_address); /* * Take abs value, since stacks grow up on some machines, down on others diff --git a/.cirrus.tasks.yml b/.cirrus.tasks.yml index a22cef063f3..8683d1ae9c7 100644 --- a/.cirrus.tasks.yml +++ b/.cirrus.tasks.yml @@ -447,7 +447,7 @@ task: # print_stacktraces=1,verbosity=2, duh # detect_leaks=0: too many uninteresting leak errors in short-lived binaries UBSAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:verbosity=2 - ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0:detect_stack_use_after_return=0 + ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0 # SANITIZER_FLAGS is set in the tasks below CFLAGS: -Og -ggdb -fno-sanitize-recover=all $SANITIZER_FLAGS -- 2.53.0.1.gb2826b52eb --7e2sypi5gxvkgog6-- ^ permalink raw reply [nested|flat] 114+ messages in thread
* [PATCH v2 1/4] Make stack depth check work with asan's use-after-return @ 2026-02-07 19:04 Andres Freund <[email protected]> 0 siblings, 0 replies; 114+ messages in thread From: Andres Freund @ 2026-02-07 19:04 UTC (permalink / raw) With address sanitizer's stack-use-after-return check, stack variables are moved to heap allocations, to allow to detect references to the memory at a later time. That broke our stack-depth check, which is why we had to disable detect_stack_use_after_return in CI. Luckily __builtin_frame_address() works correctly, even under asan, so use that. Author: Reviewed-by: Discussion: https://postgr.es/m/ Backpatch: --- src/backend/utils/misc/stack_depth.c | 17 ++++++++++++++++- .cirrus.tasks.yml | 2 +- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/src/backend/utils/misc/stack_depth.c b/src/backend/utils/misc/stack_depth.c index 61a07cf824e..9955e28c176 100644 --- a/src/backend/utils/misc/stack_depth.c +++ b/src/backend/utils/misc/stack_depth.c @@ -108,13 +108,28 @@ check_stack_depth(void) bool stack_is_too_deep(void) { +#ifndef HAVE__BUILTIN_FRAME_ADDRESS char stack_top_loc; +#endif ssize_t stack_depth; + char *stack_address; + + /* + * With address sanitizer's stack-use-after-return check, stack variables + * are moved to heap allocations, to allow to detect references to the + * memory at a later time. That would break our stack-depth check. Luckily + * __builtin_frame_address() works correctly, even under asan. + */ +#ifndef HAVE__BUILTIN_FRAME_ADDRESS + stack_address = &stack_top_loc; +#else + stack_address = (char *) __builtin_frame_address(0); +#endif /* * Compute distance from reference point to my local variables */ - stack_depth = (ssize_t) (stack_base_ptr - &stack_top_loc); + stack_depth = (ssize_t) (stack_base_ptr - stack_address); /* * Take abs value, since stacks grow up on some machines, down on others diff --git a/.cirrus.tasks.yml b/.cirrus.tasks.yml index a22cef063f3..8683d1ae9c7 100644 --- a/.cirrus.tasks.yml +++ b/.cirrus.tasks.yml @@ -447,7 +447,7 @@ task: # print_stacktraces=1,verbosity=2, duh # detect_leaks=0: too many uninteresting leak errors in short-lived binaries UBSAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:verbosity=2 - ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0:detect_stack_use_after_return=0 + ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0 # SANITIZER_FLAGS is set in the tasks below CFLAGS: -Og -ggdb -fno-sanitize-recover=all $SANITIZER_FLAGS -- 2.53.0.1.gb2826b52eb --7e2sypi5gxvkgog6-- ^ permalink raw reply [nested|flat] 114+ messages in thread
* [PATCH v2 1/4] Make stack depth check work with asan's use-after-return @ 2026-02-07 19:04 Andres Freund <[email protected]> 0 siblings, 0 replies; 114+ messages in thread From: Andres Freund @ 2026-02-07 19:04 UTC (permalink / raw) With address sanitizer's stack-use-after-return check, stack variables are moved to heap allocations, to allow to detect references to the memory at a later time. That broke our stack-depth check, which is why we had to disable detect_stack_use_after_return in CI. Luckily __builtin_frame_address() works correctly, even under asan, so use that. Author: Reviewed-by: Discussion: https://postgr.es/m/ Backpatch: --- src/backend/utils/misc/stack_depth.c | 17 ++++++++++++++++- .cirrus.tasks.yml | 2 +- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/src/backend/utils/misc/stack_depth.c b/src/backend/utils/misc/stack_depth.c index 61a07cf824e..9955e28c176 100644 --- a/src/backend/utils/misc/stack_depth.c +++ b/src/backend/utils/misc/stack_depth.c @@ -108,13 +108,28 @@ check_stack_depth(void) bool stack_is_too_deep(void) { +#ifndef HAVE__BUILTIN_FRAME_ADDRESS char stack_top_loc; +#endif ssize_t stack_depth; + char *stack_address; + + /* + * With address sanitizer's stack-use-after-return check, stack variables + * are moved to heap allocations, to allow to detect references to the + * memory at a later time. That would break our stack-depth check. Luckily + * __builtin_frame_address() works correctly, even under asan. + */ +#ifndef HAVE__BUILTIN_FRAME_ADDRESS + stack_address = &stack_top_loc; +#else + stack_address = (char *) __builtin_frame_address(0); +#endif /* * Compute distance from reference point to my local variables */ - stack_depth = (ssize_t) (stack_base_ptr - &stack_top_loc); + stack_depth = (ssize_t) (stack_base_ptr - stack_address); /* * Take abs value, since stacks grow up on some machines, down on others diff --git a/.cirrus.tasks.yml b/.cirrus.tasks.yml index a22cef063f3..8683d1ae9c7 100644 --- a/.cirrus.tasks.yml +++ b/.cirrus.tasks.yml @@ -447,7 +447,7 @@ task: # print_stacktraces=1,verbosity=2, duh # detect_leaks=0: too many uninteresting leak errors in short-lived binaries UBSAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:verbosity=2 - ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0:detect_stack_use_after_return=0 + ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0 # SANITIZER_FLAGS is set in the tasks below CFLAGS: -Og -ggdb -fno-sanitize-recover=all $SANITIZER_FLAGS -- 2.53.0.1.gb2826b52eb --7e2sypi5gxvkgog6-- ^ permalink raw reply [nested|flat] 114+ messages in thread
* [PATCH v2 1/4] Make stack depth check work with asan's use-after-return @ 2026-02-07 19:04 Andres Freund <[email protected]> 0 siblings, 0 replies; 114+ messages in thread From: Andres Freund @ 2026-02-07 19:04 UTC (permalink / raw) With address sanitizer's stack-use-after-return check, stack variables are moved to heap allocations, to allow to detect references to the memory at a later time. That broke our stack-depth check, which is why we had to disable detect_stack_use_after_return in CI. Luckily __builtin_frame_address() works correctly, even under asan, so use that. Author: Reviewed-by: Discussion: https://postgr.es/m/ Backpatch: --- src/backend/utils/misc/stack_depth.c | 17 ++++++++++++++++- .cirrus.tasks.yml | 2 +- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/src/backend/utils/misc/stack_depth.c b/src/backend/utils/misc/stack_depth.c index 61a07cf824e..9955e28c176 100644 --- a/src/backend/utils/misc/stack_depth.c +++ b/src/backend/utils/misc/stack_depth.c @@ -108,13 +108,28 @@ check_stack_depth(void) bool stack_is_too_deep(void) { +#ifndef HAVE__BUILTIN_FRAME_ADDRESS char stack_top_loc; +#endif ssize_t stack_depth; + char *stack_address; + + /* + * With address sanitizer's stack-use-after-return check, stack variables + * are moved to heap allocations, to allow to detect references to the + * memory at a later time. That would break our stack-depth check. Luckily + * __builtin_frame_address() works correctly, even under asan. + */ +#ifndef HAVE__BUILTIN_FRAME_ADDRESS + stack_address = &stack_top_loc; +#else + stack_address = (char *) __builtin_frame_address(0); +#endif /* * Compute distance from reference point to my local variables */ - stack_depth = (ssize_t) (stack_base_ptr - &stack_top_loc); + stack_depth = (ssize_t) (stack_base_ptr - stack_address); /* * Take abs value, since stacks grow up on some machines, down on others diff --git a/.cirrus.tasks.yml b/.cirrus.tasks.yml index a22cef063f3..8683d1ae9c7 100644 --- a/.cirrus.tasks.yml +++ b/.cirrus.tasks.yml @@ -447,7 +447,7 @@ task: # print_stacktraces=1,verbosity=2, duh # detect_leaks=0: too many uninteresting leak errors in short-lived binaries UBSAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:verbosity=2 - ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0:detect_stack_use_after_return=0 + ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0 # SANITIZER_FLAGS is set in the tasks below CFLAGS: -Og -ggdb -fno-sanitize-recover=all $SANITIZER_FLAGS -- 2.53.0.1.gb2826b52eb --7e2sypi5gxvkgog6-- ^ permalink raw reply [nested|flat] 114+ messages in thread
* [PATCH v2 1/4] Make stack depth check work with asan's use-after-return @ 2026-02-07 19:04 Andres Freund <[email protected]> 0 siblings, 0 replies; 114+ messages in thread From: Andres Freund @ 2026-02-07 19:04 UTC (permalink / raw) With address sanitizer's stack-use-after-return check, stack variables are moved to heap allocations, to allow to detect references to the memory at a later time. That broke our stack-depth check, which is why we had to disable detect_stack_use_after_return in CI. Luckily __builtin_frame_address() works correctly, even under asan, so use that. Author: Reviewed-by: Discussion: https://postgr.es/m/ Backpatch: --- src/backend/utils/misc/stack_depth.c | 17 ++++++++++++++++- .cirrus.tasks.yml | 2 +- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/src/backend/utils/misc/stack_depth.c b/src/backend/utils/misc/stack_depth.c index 61a07cf824e..9955e28c176 100644 --- a/src/backend/utils/misc/stack_depth.c +++ b/src/backend/utils/misc/stack_depth.c @@ -108,13 +108,28 @@ check_stack_depth(void) bool stack_is_too_deep(void) { +#ifndef HAVE__BUILTIN_FRAME_ADDRESS char stack_top_loc; +#endif ssize_t stack_depth; + char *stack_address; + + /* + * With address sanitizer's stack-use-after-return check, stack variables + * are moved to heap allocations, to allow to detect references to the + * memory at a later time. That would break our stack-depth check. Luckily + * __builtin_frame_address() works correctly, even under asan. + */ +#ifndef HAVE__BUILTIN_FRAME_ADDRESS + stack_address = &stack_top_loc; +#else + stack_address = (char *) __builtin_frame_address(0); +#endif /* * Compute distance from reference point to my local variables */ - stack_depth = (ssize_t) (stack_base_ptr - &stack_top_loc); + stack_depth = (ssize_t) (stack_base_ptr - stack_address); /* * Take abs value, since stacks grow up on some machines, down on others diff --git a/.cirrus.tasks.yml b/.cirrus.tasks.yml index a22cef063f3..8683d1ae9c7 100644 --- a/.cirrus.tasks.yml +++ b/.cirrus.tasks.yml @@ -447,7 +447,7 @@ task: # print_stacktraces=1,verbosity=2, duh # detect_leaks=0: too many uninteresting leak errors in short-lived binaries UBSAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:verbosity=2 - ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0:detect_stack_use_after_return=0 + ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0 # SANITIZER_FLAGS is set in the tasks below CFLAGS: -Og -ggdb -fno-sanitize-recover=all $SANITIZER_FLAGS -- 2.53.0.1.gb2826b52eb --7e2sypi5gxvkgog6-- ^ permalink raw reply [nested|flat] 114+ messages in thread
* [PATCH v2 1/4] Make stack depth check work with asan's use-after-return @ 2026-02-07 19:04 Andres Freund <[email protected]> 0 siblings, 0 replies; 114+ messages in thread From: Andres Freund @ 2026-02-07 19:04 UTC (permalink / raw) With address sanitizer's stack-use-after-return check, stack variables are moved to heap allocations, to allow to detect references to the memory at a later time. That broke our stack-depth check, which is why we had to disable detect_stack_use_after_return in CI. Luckily __builtin_frame_address() works correctly, even under asan, so use that. Author: Reviewed-by: Discussion: https://postgr.es/m/ Backpatch: --- src/backend/utils/misc/stack_depth.c | 17 ++++++++++++++++- .cirrus.tasks.yml | 2 +- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/src/backend/utils/misc/stack_depth.c b/src/backend/utils/misc/stack_depth.c index 61a07cf824e..9955e28c176 100644 --- a/src/backend/utils/misc/stack_depth.c +++ b/src/backend/utils/misc/stack_depth.c @@ -108,13 +108,28 @@ check_stack_depth(void) bool stack_is_too_deep(void) { +#ifndef HAVE__BUILTIN_FRAME_ADDRESS char stack_top_loc; +#endif ssize_t stack_depth; + char *stack_address; + + /* + * With address sanitizer's stack-use-after-return check, stack variables + * are moved to heap allocations, to allow to detect references to the + * memory at a later time. That would break our stack-depth check. Luckily + * __builtin_frame_address() works correctly, even under asan. + */ +#ifndef HAVE__BUILTIN_FRAME_ADDRESS + stack_address = &stack_top_loc; +#else + stack_address = (char *) __builtin_frame_address(0); +#endif /* * Compute distance from reference point to my local variables */ - stack_depth = (ssize_t) (stack_base_ptr - &stack_top_loc); + stack_depth = (ssize_t) (stack_base_ptr - stack_address); /* * Take abs value, since stacks grow up on some machines, down on others diff --git a/.cirrus.tasks.yml b/.cirrus.tasks.yml index a22cef063f3..8683d1ae9c7 100644 --- a/.cirrus.tasks.yml +++ b/.cirrus.tasks.yml @@ -447,7 +447,7 @@ task: # print_stacktraces=1,verbosity=2, duh # detect_leaks=0: too many uninteresting leak errors in short-lived binaries UBSAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:verbosity=2 - ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0:detect_stack_use_after_return=0 + ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0 # SANITIZER_FLAGS is set in the tasks below CFLAGS: -Og -ggdb -fno-sanitize-recover=all $SANITIZER_FLAGS -- 2.53.0.1.gb2826b52eb --7e2sypi5gxvkgog6-- ^ permalink raw reply [nested|flat] 114+ messages in thread
* [PATCH v2 1/4] Make stack depth check work with asan's use-after-return @ 2026-02-07 19:04 Andres Freund <[email protected]> 0 siblings, 0 replies; 114+ messages in thread From: Andres Freund @ 2026-02-07 19:04 UTC (permalink / raw) With address sanitizer's stack-use-after-return check, stack variables are moved to heap allocations, to allow to detect references to the memory at a later time. That broke our stack-depth check, which is why we had to disable detect_stack_use_after_return in CI. Luckily __builtin_frame_address() works correctly, even under asan, so use that. Author: Reviewed-by: Discussion: https://postgr.es/m/ Backpatch: --- src/backend/utils/misc/stack_depth.c | 17 ++++++++++++++++- .cirrus.tasks.yml | 2 +- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/src/backend/utils/misc/stack_depth.c b/src/backend/utils/misc/stack_depth.c index 61a07cf824e..9955e28c176 100644 --- a/src/backend/utils/misc/stack_depth.c +++ b/src/backend/utils/misc/stack_depth.c @@ -108,13 +108,28 @@ check_stack_depth(void) bool stack_is_too_deep(void) { +#ifndef HAVE__BUILTIN_FRAME_ADDRESS char stack_top_loc; +#endif ssize_t stack_depth; + char *stack_address; + + /* + * With address sanitizer's stack-use-after-return check, stack variables + * are moved to heap allocations, to allow to detect references to the + * memory at a later time. That would break our stack-depth check. Luckily + * __builtin_frame_address() works correctly, even under asan. + */ +#ifndef HAVE__BUILTIN_FRAME_ADDRESS + stack_address = &stack_top_loc; +#else + stack_address = (char *) __builtin_frame_address(0); +#endif /* * Compute distance from reference point to my local variables */ - stack_depth = (ssize_t) (stack_base_ptr - &stack_top_loc); + stack_depth = (ssize_t) (stack_base_ptr - stack_address); /* * Take abs value, since stacks grow up on some machines, down on others diff --git a/.cirrus.tasks.yml b/.cirrus.tasks.yml index a22cef063f3..8683d1ae9c7 100644 --- a/.cirrus.tasks.yml +++ b/.cirrus.tasks.yml @@ -447,7 +447,7 @@ task: # print_stacktraces=1,verbosity=2, duh # detect_leaks=0: too many uninteresting leak errors in short-lived binaries UBSAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:verbosity=2 - ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0:detect_stack_use_after_return=0 + ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0 # SANITIZER_FLAGS is set in the tasks below CFLAGS: -Og -ggdb -fno-sanitize-recover=all $SANITIZER_FLAGS -- 2.53.0.1.gb2826b52eb --7e2sypi5gxvkgog6-- ^ permalink raw reply [nested|flat] 114+ messages in thread
* [PATCH v2 1/4] Make stack depth check work with asan's use-after-return @ 2026-02-07 19:04 Andres Freund <[email protected]> 0 siblings, 0 replies; 114+ messages in thread From: Andres Freund @ 2026-02-07 19:04 UTC (permalink / raw) With address sanitizer's stack-use-after-return check, stack variables are moved to heap allocations, to allow to detect references to the memory at a later time. That broke our stack-depth check, which is why we had to disable detect_stack_use_after_return in CI. Luckily __builtin_frame_address() works correctly, even under asan, so use that. Author: Reviewed-by: Discussion: https://postgr.es/m/ Backpatch: --- src/backend/utils/misc/stack_depth.c | 17 ++++++++++++++++- .cirrus.tasks.yml | 2 +- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/src/backend/utils/misc/stack_depth.c b/src/backend/utils/misc/stack_depth.c index 61a07cf824e..9955e28c176 100644 --- a/src/backend/utils/misc/stack_depth.c +++ b/src/backend/utils/misc/stack_depth.c @@ -108,13 +108,28 @@ check_stack_depth(void) bool stack_is_too_deep(void) { +#ifndef HAVE__BUILTIN_FRAME_ADDRESS char stack_top_loc; +#endif ssize_t stack_depth; + char *stack_address; + + /* + * With address sanitizer's stack-use-after-return check, stack variables + * are moved to heap allocations, to allow to detect references to the + * memory at a later time. That would break our stack-depth check. Luckily + * __builtin_frame_address() works correctly, even under asan. + */ +#ifndef HAVE__BUILTIN_FRAME_ADDRESS + stack_address = &stack_top_loc; +#else + stack_address = (char *) __builtin_frame_address(0); +#endif /* * Compute distance from reference point to my local variables */ - stack_depth = (ssize_t) (stack_base_ptr - &stack_top_loc); + stack_depth = (ssize_t) (stack_base_ptr - stack_address); /* * Take abs value, since stacks grow up on some machines, down on others diff --git a/.cirrus.tasks.yml b/.cirrus.tasks.yml index a22cef063f3..8683d1ae9c7 100644 --- a/.cirrus.tasks.yml +++ b/.cirrus.tasks.yml @@ -447,7 +447,7 @@ task: # print_stacktraces=1,verbosity=2, duh # detect_leaks=0: too many uninteresting leak errors in short-lived binaries UBSAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:verbosity=2 - ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0:detect_stack_use_after_return=0 + ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0 # SANITIZER_FLAGS is set in the tasks below CFLAGS: -Og -ggdb -fno-sanitize-recover=all $SANITIZER_FLAGS -- 2.53.0.1.gb2826b52eb --7e2sypi5gxvkgog6-- ^ permalink raw reply [nested|flat] 114+ messages in thread
* [PATCH v2 1/4] Make stack depth check work with asan's use-after-return @ 2026-02-07 19:04 Andres Freund <[email protected]> 0 siblings, 0 replies; 114+ messages in thread From: Andres Freund @ 2026-02-07 19:04 UTC (permalink / raw) With address sanitizer's stack-use-after-return check, stack variables are moved to heap allocations, to allow to detect references to the memory at a later time. That broke our stack-depth check, which is why we had to disable detect_stack_use_after_return in CI. Luckily __builtin_frame_address() works correctly, even under asan, so use that. Author: Reviewed-by: Discussion: https://postgr.es/m/ Backpatch: --- src/backend/utils/misc/stack_depth.c | 17 ++++++++++++++++- .cirrus.tasks.yml | 2 +- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/src/backend/utils/misc/stack_depth.c b/src/backend/utils/misc/stack_depth.c index 61a07cf824e..9955e28c176 100644 --- a/src/backend/utils/misc/stack_depth.c +++ b/src/backend/utils/misc/stack_depth.c @@ -108,13 +108,28 @@ check_stack_depth(void) bool stack_is_too_deep(void) { +#ifndef HAVE__BUILTIN_FRAME_ADDRESS char stack_top_loc; +#endif ssize_t stack_depth; + char *stack_address; + + /* + * With address sanitizer's stack-use-after-return check, stack variables + * are moved to heap allocations, to allow to detect references to the + * memory at a later time. That would break our stack-depth check. Luckily + * __builtin_frame_address() works correctly, even under asan. + */ +#ifndef HAVE__BUILTIN_FRAME_ADDRESS + stack_address = &stack_top_loc; +#else + stack_address = (char *) __builtin_frame_address(0); +#endif /* * Compute distance from reference point to my local variables */ - stack_depth = (ssize_t) (stack_base_ptr - &stack_top_loc); + stack_depth = (ssize_t) (stack_base_ptr - stack_address); /* * Take abs value, since stacks grow up on some machines, down on others diff --git a/.cirrus.tasks.yml b/.cirrus.tasks.yml index a22cef063f3..8683d1ae9c7 100644 --- a/.cirrus.tasks.yml +++ b/.cirrus.tasks.yml @@ -447,7 +447,7 @@ task: # print_stacktraces=1,verbosity=2, duh # detect_leaks=0: too many uninteresting leak errors in short-lived binaries UBSAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:verbosity=2 - ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0:detect_stack_use_after_return=0 + ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0 # SANITIZER_FLAGS is set in the tasks below CFLAGS: -Og -ggdb -fno-sanitize-recover=all $SANITIZER_FLAGS -- 2.53.0.1.gb2826b52eb --7e2sypi5gxvkgog6-- ^ permalink raw reply [nested|flat] 114+ messages in thread
* [PATCH v2 1/4] Make stack depth check work with asan's use-after-return @ 2026-02-07 19:04 Andres Freund <[email protected]> 0 siblings, 0 replies; 114+ messages in thread From: Andres Freund @ 2026-02-07 19:04 UTC (permalink / raw) With address sanitizer's stack-use-after-return check, stack variables are moved to heap allocations, to allow to detect references to the memory at a later time. That broke our stack-depth check, which is why we had to disable detect_stack_use_after_return in CI. Luckily __builtin_frame_address() works correctly, even under asan, so use that. Author: Reviewed-by: Discussion: https://postgr.es/m/ Backpatch: --- src/backend/utils/misc/stack_depth.c | 17 ++++++++++++++++- .cirrus.tasks.yml | 2 +- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/src/backend/utils/misc/stack_depth.c b/src/backend/utils/misc/stack_depth.c index 61a07cf824e..9955e28c176 100644 --- a/src/backend/utils/misc/stack_depth.c +++ b/src/backend/utils/misc/stack_depth.c @@ -108,13 +108,28 @@ check_stack_depth(void) bool stack_is_too_deep(void) { +#ifndef HAVE__BUILTIN_FRAME_ADDRESS char stack_top_loc; +#endif ssize_t stack_depth; + char *stack_address; + + /* + * With address sanitizer's stack-use-after-return check, stack variables + * are moved to heap allocations, to allow to detect references to the + * memory at a later time. That would break our stack-depth check. Luckily + * __builtin_frame_address() works correctly, even under asan. + */ +#ifndef HAVE__BUILTIN_FRAME_ADDRESS + stack_address = &stack_top_loc; +#else + stack_address = (char *) __builtin_frame_address(0); +#endif /* * Compute distance from reference point to my local variables */ - stack_depth = (ssize_t) (stack_base_ptr - &stack_top_loc); + stack_depth = (ssize_t) (stack_base_ptr - stack_address); /* * Take abs value, since stacks grow up on some machines, down on others diff --git a/.cirrus.tasks.yml b/.cirrus.tasks.yml index a22cef063f3..8683d1ae9c7 100644 --- a/.cirrus.tasks.yml +++ b/.cirrus.tasks.yml @@ -447,7 +447,7 @@ task: # print_stacktraces=1,verbosity=2, duh # detect_leaks=0: too many uninteresting leak errors in short-lived binaries UBSAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:verbosity=2 - ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0:detect_stack_use_after_return=0 + ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0 # SANITIZER_FLAGS is set in the tasks below CFLAGS: -Og -ggdb -fno-sanitize-recover=all $SANITIZER_FLAGS -- 2.53.0.1.gb2826b52eb --7e2sypi5gxvkgog6-- ^ permalink raw reply [nested|flat] 114+ messages in thread
* [PATCH v2 1/4] Make stack depth check work with asan's use-after-return @ 2026-02-07 19:04 Andres Freund <[email protected]> 0 siblings, 0 replies; 114+ messages in thread From: Andres Freund @ 2026-02-07 19:04 UTC (permalink / raw) With address sanitizer's stack-use-after-return check, stack variables are moved to heap allocations, to allow to detect references to the memory at a later time. That broke our stack-depth check, which is why we had to disable detect_stack_use_after_return in CI. Luckily __builtin_frame_address() works correctly, even under asan, so use that. Author: Reviewed-by: Discussion: https://postgr.es/m/ Backpatch: --- src/backend/utils/misc/stack_depth.c | 17 ++++++++++++++++- .cirrus.tasks.yml | 2 +- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/src/backend/utils/misc/stack_depth.c b/src/backend/utils/misc/stack_depth.c index 61a07cf824e..9955e28c176 100644 --- a/src/backend/utils/misc/stack_depth.c +++ b/src/backend/utils/misc/stack_depth.c @@ -108,13 +108,28 @@ check_stack_depth(void) bool stack_is_too_deep(void) { +#ifndef HAVE__BUILTIN_FRAME_ADDRESS char stack_top_loc; +#endif ssize_t stack_depth; + char *stack_address; + + /* + * With address sanitizer's stack-use-after-return check, stack variables + * are moved to heap allocations, to allow to detect references to the + * memory at a later time. That would break our stack-depth check. Luckily + * __builtin_frame_address() works correctly, even under asan. + */ +#ifndef HAVE__BUILTIN_FRAME_ADDRESS + stack_address = &stack_top_loc; +#else + stack_address = (char *) __builtin_frame_address(0); +#endif /* * Compute distance from reference point to my local variables */ - stack_depth = (ssize_t) (stack_base_ptr - &stack_top_loc); + stack_depth = (ssize_t) (stack_base_ptr - stack_address); /* * Take abs value, since stacks grow up on some machines, down on others diff --git a/.cirrus.tasks.yml b/.cirrus.tasks.yml index a22cef063f3..8683d1ae9c7 100644 --- a/.cirrus.tasks.yml +++ b/.cirrus.tasks.yml @@ -447,7 +447,7 @@ task: # print_stacktraces=1,verbosity=2, duh # detect_leaks=0: too many uninteresting leak errors in short-lived binaries UBSAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:verbosity=2 - ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0:detect_stack_use_after_return=0 + ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0 # SANITIZER_FLAGS is set in the tasks below CFLAGS: -Og -ggdb -fno-sanitize-recover=all $SANITIZER_FLAGS -- 2.53.0.1.gb2826b52eb --7e2sypi5gxvkgog6-- ^ permalink raw reply [nested|flat] 114+ messages in thread
* [PATCH v2 1/4] Make stack depth check work with asan's use-after-return @ 2026-02-07 19:04 Andres Freund <[email protected]> 0 siblings, 0 replies; 114+ messages in thread From: Andres Freund @ 2026-02-07 19:04 UTC (permalink / raw) With address sanitizer's stack-use-after-return check, stack variables are moved to heap allocations, to allow to detect references to the memory at a later time. That broke our stack-depth check, which is why we had to disable detect_stack_use_after_return in CI. Luckily __builtin_frame_address() works correctly, even under asan, so use that. Author: Reviewed-by: Discussion: https://postgr.es/m/ Backpatch: --- src/backend/utils/misc/stack_depth.c | 17 ++++++++++++++++- .cirrus.tasks.yml | 2 +- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/src/backend/utils/misc/stack_depth.c b/src/backend/utils/misc/stack_depth.c index 61a07cf824e..9955e28c176 100644 --- a/src/backend/utils/misc/stack_depth.c +++ b/src/backend/utils/misc/stack_depth.c @@ -108,13 +108,28 @@ check_stack_depth(void) bool stack_is_too_deep(void) { +#ifndef HAVE__BUILTIN_FRAME_ADDRESS char stack_top_loc; +#endif ssize_t stack_depth; + char *stack_address; + + /* + * With address sanitizer's stack-use-after-return check, stack variables + * are moved to heap allocations, to allow to detect references to the + * memory at a later time. That would break our stack-depth check. Luckily + * __builtin_frame_address() works correctly, even under asan. + */ +#ifndef HAVE__BUILTIN_FRAME_ADDRESS + stack_address = &stack_top_loc; +#else + stack_address = (char *) __builtin_frame_address(0); +#endif /* * Compute distance from reference point to my local variables */ - stack_depth = (ssize_t) (stack_base_ptr - &stack_top_loc); + stack_depth = (ssize_t) (stack_base_ptr - stack_address); /* * Take abs value, since stacks grow up on some machines, down on others diff --git a/.cirrus.tasks.yml b/.cirrus.tasks.yml index a22cef063f3..8683d1ae9c7 100644 --- a/.cirrus.tasks.yml +++ b/.cirrus.tasks.yml @@ -447,7 +447,7 @@ task: # print_stacktraces=1,verbosity=2, duh # detect_leaks=0: too many uninteresting leak errors in short-lived binaries UBSAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:verbosity=2 - ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0:detect_stack_use_after_return=0 + ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0 # SANITIZER_FLAGS is set in the tasks below CFLAGS: -Og -ggdb -fno-sanitize-recover=all $SANITIZER_FLAGS -- 2.53.0.1.gb2826b52eb --7e2sypi5gxvkgog6-- ^ permalink raw reply [nested|flat] 114+ messages in thread
* [PATCH v2 1/4] Make stack depth check work with asan's use-after-return @ 2026-02-07 19:04 Andres Freund <[email protected]> 0 siblings, 0 replies; 114+ messages in thread From: Andres Freund @ 2026-02-07 19:04 UTC (permalink / raw) With address sanitizer's stack-use-after-return check, stack variables are moved to heap allocations, to allow to detect references to the memory at a later time. That broke our stack-depth check, which is why we had to disable detect_stack_use_after_return in CI. Luckily __builtin_frame_address() works correctly, even under asan, so use that. Author: Reviewed-by: Discussion: https://postgr.es/m/ Backpatch: --- src/backend/utils/misc/stack_depth.c | 17 ++++++++++++++++- .cirrus.tasks.yml | 2 +- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/src/backend/utils/misc/stack_depth.c b/src/backend/utils/misc/stack_depth.c index 61a07cf824e..9955e28c176 100644 --- a/src/backend/utils/misc/stack_depth.c +++ b/src/backend/utils/misc/stack_depth.c @@ -108,13 +108,28 @@ check_stack_depth(void) bool stack_is_too_deep(void) { +#ifndef HAVE__BUILTIN_FRAME_ADDRESS char stack_top_loc; +#endif ssize_t stack_depth; + char *stack_address; + + /* + * With address sanitizer's stack-use-after-return check, stack variables + * are moved to heap allocations, to allow to detect references to the + * memory at a later time. That would break our stack-depth check. Luckily + * __builtin_frame_address() works correctly, even under asan. + */ +#ifndef HAVE__BUILTIN_FRAME_ADDRESS + stack_address = &stack_top_loc; +#else + stack_address = (char *) __builtin_frame_address(0); +#endif /* * Compute distance from reference point to my local variables */ - stack_depth = (ssize_t) (stack_base_ptr - &stack_top_loc); + stack_depth = (ssize_t) (stack_base_ptr - stack_address); /* * Take abs value, since stacks grow up on some machines, down on others diff --git a/.cirrus.tasks.yml b/.cirrus.tasks.yml index a22cef063f3..8683d1ae9c7 100644 --- a/.cirrus.tasks.yml +++ b/.cirrus.tasks.yml @@ -447,7 +447,7 @@ task: # print_stacktraces=1,verbosity=2, duh # detect_leaks=0: too many uninteresting leak errors in short-lived binaries UBSAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:verbosity=2 - ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0:detect_stack_use_after_return=0 + ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0 # SANITIZER_FLAGS is set in the tasks below CFLAGS: -Og -ggdb -fno-sanitize-recover=all $SANITIZER_FLAGS -- 2.53.0.1.gb2826b52eb --7e2sypi5gxvkgog6-- ^ permalink raw reply [nested|flat] 114+ messages in thread
* [PATCH v2 1/4] Make stack depth check work with asan's use-after-return @ 2026-02-07 19:04 Andres Freund <[email protected]> 0 siblings, 0 replies; 114+ messages in thread From: Andres Freund @ 2026-02-07 19:04 UTC (permalink / raw) With address sanitizer's stack-use-after-return check, stack variables are moved to heap allocations, to allow to detect references to the memory at a later time. That broke our stack-depth check, which is why we had to disable detect_stack_use_after_return in CI. Luckily __builtin_frame_address() works correctly, even under asan, so use that. Author: Reviewed-by: Discussion: https://postgr.es/m/ Backpatch: --- src/backend/utils/misc/stack_depth.c | 17 ++++++++++++++++- .cirrus.tasks.yml | 2 +- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/src/backend/utils/misc/stack_depth.c b/src/backend/utils/misc/stack_depth.c index 61a07cf824e..9955e28c176 100644 --- a/src/backend/utils/misc/stack_depth.c +++ b/src/backend/utils/misc/stack_depth.c @@ -108,13 +108,28 @@ check_stack_depth(void) bool stack_is_too_deep(void) { +#ifndef HAVE__BUILTIN_FRAME_ADDRESS char stack_top_loc; +#endif ssize_t stack_depth; + char *stack_address; + + /* + * With address sanitizer's stack-use-after-return check, stack variables + * are moved to heap allocations, to allow to detect references to the + * memory at a later time. That would break our stack-depth check. Luckily + * __builtin_frame_address() works correctly, even under asan. + */ +#ifndef HAVE__BUILTIN_FRAME_ADDRESS + stack_address = &stack_top_loc; +#else + stack_address = (char *) __builtin_frame_address(0); +#endif /* * Compute distance from reference point to my local variables */ - stack_depth = (ssize_t) (stack_base_ptr - &stack_top_loc); + stack_depth = (ssize_t) (stack_base_ptr - stack_address); /* * Take abs value, since stacks grow up on some machines, down on others diff --git a/.cirrus.tasks.yml b/.cirrus.tasks.yml index a22cef063f3..8683d1ae9c7 100644 --- a/.cirrus.tasks.yml +++ b/.cirrus.tasks.yml @@ -447,7 +447,7 @@ task: # print_stacktraces=1,verbosity=2, duh # detect_leaks=0: too many uninteresting leak errors in short-lived binaries UBSAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:verbosity=2 - ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0:detect_stack_use_after_return=0 + ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0 # SANITIZER_FLAGS is set in the tasks below CFLAGS: -Og -ggdb -fno-sanitize-recover=all $SANITIZER_FLAGS -- 2.53.0.1.gb2826b52eb --7e2sypi5gxvkgog6-- ^ permalink raw reply [nested|flat] 114+ messages in thread
* [PATCH v2 1/4] Make stack depth check work with asan's use-after-return @ 2026-02-07 19:04 Andres Freund <[email protected]> 0 siblings, 0 replies; 114+ messages in thread From: Andres Freund @ 2026-02-07 19:04 UTC (permalink / raw) With address sanitizer's stack-use-after-return check, stack variables are moved to heap allocations, to allow to detect references to the memory at a later time. That broke our stack-depth check, which is why we had to disable detect_stack_use_after_return in CI. Luckily __builtin_frame_address() works correctly, even under asan, so use that. Author: Reviewed-by: Discussion: https://postgr.es/m/ Backpatch: --- src/backend/utils/misc/stack_depth.c | 17 ++++++++++++++++- .cirrus.tasks.yml | 2 +- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/src/backend/utils/misc/stack_depth.c b/src/backend/utils/misc/stack_depth.c index 61a07cf824e..9955e28c176 100644 --- a/src/backend/utils/misc/stack_depth.c +++ b/src/backend/utils/misc/stack_depth.c @@ -108,13 +108,28 @@ check_stack_depth(void) bool stack_is_too_deep(void) { +#ifndef HAVE__BUILTIN_FRAME_ADDRESS char stack_top_loc; +#endif ssize_t stack_depth; + char *stack_address; + + /* + * With address sanitizer's stack-use-after-return check, stack variables + * are moved to heap allocations, to allow to detect references to the + * memory at a later time. That would break our stack-depth check. Luckily + * __builtin_frame_address() works correctly, even under asan. + */ +#ifndef HAVE__BUILTIN_FRAME_ADDRESS + stack_address = &stack_top_loc; +#else + stack_address = (char *) __builtin_frame_address(0); +#endif /* * Compute distance from reference point to my local variables */ - stack_depth = (ssize_t) (stack_base_ptr - &stack_top_loc); + stack_depth = (ssize_t) (stack_base_ptr - stack_address); /* * Take abs value, since stacks grow up on some machines, down on others diff --git a/.cirrus.tasks.yml b/.cirrus.tasks.yml index a22cef063f3..8683d1ae9c7 100644 --- a/.cirrus.tasks.yml +++ b/.cirrus.tasks.yml @@ -447,7 +447,7 @@ task: # print_stacktraces=1,verbosity=2, duh # detect_leaks=0: too many uninteresting leak errors in short-lived binaries UBSAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:verbosity=2 - ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0:detect_stack_use_after_return=0 + ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0 # SANITIZER_FLAGS is set in the tasks below CFLAGS: -Og -ggdb -fno-sanitize-recover=all $SANITIZER_FLAGS -- 2.53.0.1.gb2826b52eb --7e2sypi5gxvkgog6-- ^ permalink raw reply [nested|flat] 114+ messages in thread
* [PATCH v2 1/4] Make stack depth check work with asan's use-after-return @ 2026-02-07 19:04 Andres Freund <[email protected]> 0 siblings, 0 replies; 114+ messages in thread From: Andres Freund @ 2026-02-07 19:04 UTC (permalink / raw) With address sanitizer's stack-use-after-return check, stack variables are moved to heap allocations, to allow to detect references to the memory at a later time. That broke our stack-depth check, which is why we had to disable detect_stack_use_after_return in CI. Luckily __builtin_frame_address() works correctly, even under asan, so use that. Author: Reviewed-by: Discussion: https://postgr.es/m/ Backpatch: --- src/backend/utils/misc/stack_depth.c | 17 ++++++++++++++++- .cirrus.tasks.yml | 2 +- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/src/backend/utils/misc/stack_depth.c b/src/backend/utils/misc/stack_depth.c index 61a07cf824e..9955e28c176 100644 --- a/src/backend/utils/misc/stack_depth.c +++ b/src/backend/utils/misc/stack_depth.c @@ -108,13 +108,28 @@ check_stack_depth(void) bool stack_is_too_deep(void) { +#ifndef HAVE__BUILTIN_FRAME_ADDRESS char stack_top_loc; +#endif ssize_t stack_depth; + char *stack_address; + + /* + * With address sanitizer's stack-use-after-return check, stack variables + * are moved to heap allocations, to allow to detect references to the + * memory at a later time. That would break our stack-depth check. Luckily + * __builtin_frame_address() works correctly, even under asan. + */ +#ifndef HAVE__BUILTIN_FRAME_ADDRESS + stack_address = &stack_top_loc; +#else + stack_address = (char *) __builtin_frame_address(0); +#endif /* * Compute distance from reference point to my local variables */ - stack_depth = (ssize_t) (stack_base_ptr - &stack_top_loc); + stack_depth = (ssize_t) (stack_base_ptr - stack_address); /* * Take abs value, since stacks grow up on some machines, down on others diff --git a/.cirrus.tasks.yml b/.cirrus.tasks.yml index a22cef063f3..8683d1ae9c7 100644 --- a/.cirrus.tasks.yml +++ b/.cirrus.tasks.yml @@ -447,7 +447,7 @@ task: # print_stacktraces=1,verbosity=2, duh # detect_leaks=0: too many uninteresting leak errors in short-lived binaries UBSAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:verbosity=2 - ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0:detect_stack_use_after_return=0 + ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0 # SANITIZER_FLAGS is set in the tasks below CFLAGS: -Og -ggdb -fno-sanitize-recover=all $SANITIZER_FLAGS -- 2.53.0.1.gb2826b52eb --7e2sypi5gxvkgog6-- ^ permalink raw reply [nested|flat] 114+ messages in thread
* [PATCH v2 1/4] Make stack depth check work with asan's use-after-return @ 2026-02-07 19:04 Andres Freund <[email protected]> 0 siblings, 0 replies; 114+ messages in thread From: Andres Freund @ 2026-02-07 19:04 UTC (permalink / raw) With address sanitizer's stack-use-after-return check, stack variables are moved to heap allocations, to allow to detect references to the memory at a later time. That broke our stack-depth check, which is why we had to disable detect_stack_use_after_return in CI. Luckily __builtin_frame_address() works correctly, even under asan, so use that. Author: Reviewed-by: Discussion: https://postgr.es/m/ Backpatch: --- src/backend/utils/misc/stack_depth.c | 17 ++++++++++++++++- .cirrus.tasks.yml | 2 +- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/src/backend/utils/misc/stack_depth.c b/src/backend/utils/misc/stack_depth.c index 61a07cf824e..9955e28c176 100644 --- a/src/backend/utils/misc/stack_depth.c +++ b/src/backend/utils/misc/stack_depth.c @@ -108,13 +108,28 @@ check_stack_depth(void) bool stack_is_too_deep(void) { +#ifndef HAVE__BUILTIN_FRAME_ADDRESS char stack_top_loc; +#endif ssize_t stack_depth; + char *stack_address; + + /* + * With address sanitizer's stack-use-after-return check, stack variables + * are moved to heap allocations, to allow to detect references to the + * memory at a later time. That would break our stack-depth check. Luckily + * __builtin_frame_address() works correctly, even under asan. + */ +#ifndef HAVE__BUILTIN_FRAME_ADDRESS + stack_address = &stack_top_loc; +#else + stack_address = (char *) __builtin_frame_address(0); +#endif /* * Compute distance from reference point to my local variables */ - stack_depth = (ssize_t) (stack_base_ptr - &stack_top_loc); + stack_depth = (ssize_t) (stack_base_ptr - stack_address); /* * Take abs value, since stacks grow up on some machines, down on others diff --git a/.cirrus.tasks.yml b/.cirrus.tasks.yml index a22cef063f3..8683d1ae9c7 100644 --- a/.cirrus.tasks.yml +++ b/.cirrus.tasks.yml @@ -447,7 +447,7 @@ task: # print_stacktraces=1,verbosity=2, duh # detect_leaks=0: too many uninteresting leak errors in short-lived binaries UBSAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:verbosity=2 - ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0:detect_stack_use_after_return=0 + ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0 # SANITIZER_FLAGS is set in the tasks below CFLAGS: -Og -ggdb -fno-sanitize-recover=all $SANITIZER_FLAGS -- 2.53.0.1.gb2826b52eb --7e2sypi5gxvkgog6-- ^ permalink raw reply [nested|flat] 114+ messages in thread
* [PATCH v2 1/4] Make stack depth check work with asan's use-after-return @ 2026-02-07 19:04 Andres Freund <[email protected]> 0 siblings, 0 replies; 114+ messages in thread From: Andres Freund @ 2026-02-07 19:04 UTC (permalink / raw) With address sanitizer's stack-use-after-return check, stack variables are moved to heap allocations, to allow to detect references to the memory at a later time. That broke our stack-depth check, which is why we had to disable detect_stack_use_after_return in CI. Luckily __builtin_frame_address() works correctly, even under asan, so use that. Author: Reviewed-by: Discussion: https://postgr.es/m/ Backpatch: --- src/backend/utils/misc/stack_depth.c | 17 ++++++++++++++++- .cirrus.tasks.yml | 2 +- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/src/backend/utils/misc/stack_depth.c b/src/backend/utils/misc/stack_depth.c index 61a07cf824e..9955e28c176 100644 --- a/src/backend/utils/misc/stack_depth.c +++ b/src/backend/utils/misc/stack_depth.c @@ -108,13 +108,28 @@ check_stack_depth(void) bool stack_is_too_deep(void) { +#ifndef HAVE__BUILTIN_FRAME_ADDRESS char stack_top_loc; +#endif ssize_t stack_depth; + char *stack_address; + + /* + * With address sanitizer's stack-use-after-return check, stack variables + * are moved to heap allocations, to allow to detect references to the + * memory at a later time. That would break our stack-depth check. Luckily + * __builtin_frame_address() works correctly, even under asan. + */ +#ifndef HAVE__BUILTIN_FRAME_ADDRESS + stack_address = &stack_top_loc; +#else + stack_address = (char *) __builtin_frame_address(0); +#endif /* * Compute distance from reference point to my local variables */ - stack_depth = (ssize_t) (stack_base_ptr - &stack_top_loc); + stack_depth = (ssize_t) (stack_base_ptr - stack_address); /* * Take abs value, since stacks grow up on some machines, down on others diff --git a/.cirrus.tasks.yml b/.cirrus.tasks.yml index a22cef063f3..8683d1ae9c7 100644 --- a/.cirrus.tasks.yml +++ b/.cirrus.tasks.yml @@ -447,7 +447,7 @@ task: # print_stacktraces=1,verbosity=2, duh # detect_leaks=0: too many uninteresting leak errors in short-lived binaries UBSAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:verbosity=2 - ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0:detect_stack_use_after_return=0 + ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0 # SANITIZER_FLAGS is set in the tasks below CFLAGS: -Og -ggdb -fno-sanitize-recover=all $SANITIZER_FLAGS -- 2.53.0.1.gb2826b52eb --7e2sypi5gxvkgog6-- ^ permalink raw reply [nested|flat] 114+ messages in thread
* [PATCH v2 1/4] Make stack depth check work with asan's use-after-return @ 2026-02-07 19:04 Andres Freund <[email protected]> 0 siblings, 0 replies; 114+ messages in thread From: Andres Freund @ 2026-02-07 19:04 UTC (permalink / raw) With address sanitizer's stack-use-after-return check, stack variables are moved to heap allocations, to allow to detect references to the memory at a later time. That broke our stack-depth check, which is why we had to disable detect_stack_use_after_return in CI. Luckily __builtin_frame_address() works correctly, even under asan, so use that. Author: Reviewed-by: Discussion: https://postgr.es/m/ Backpatch: --- src/backend/utils/misc/stack_depth.c | 17 ++++++++++++++++- .cirrus.tasks.yml | 2 +- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/src/backend/utils/misc/stack_depth.c b/src/backend/utils/misc/stack_depth.c index 61a07cf824e..9955e28c176 100644 --- a/src/backend/utils/misc/stack_depth.c +++ b/src/backend/utils/misc/stack_depth.c @@ -108,13 +108,28 @@ check_stack_depth(void) bool stack_is_too_deep(void) { +#ifndef HAVE__BUILTIN_FRAME_ADDRESS char stack_top_loc; +#endif ssize_t stack_depth; + char *stack_address; + + /* + * With address sanitizer's stack-use-after-return check, stack variables + * are moved to heap allocations, to allow to detect references to the + * memory at a later time. That would break our stack-depth check. Luckily + * __builtin_frame_address() works correctly, even under asan. + */ +#ifndef HAVE__BUILTIN_FRAME_ADDRESS + stack_address = &stack_top_loc; +#else + stack_address = (char *) __builtin_frame_address(0); +#endif /* * Compute distance from reference point to my local variables */ - stack_depth = (ssize_t) (stack_base_ptr - &stack_top_loc); + stack_depth = (ssize_t) (stack_base_ptr - stack_address); /* * Take abs value, since stacks grow up on some machines, down on others diff --git a/.cirrus.tasks.yml b/.cirrus.tasks.yml index a22cef063f3..8683d1ae9c7 100644 --- a/.cirrus.tasks.yml +++ b/.cirrus.tasks.yml @@ -447,7 +447,7 @@ task: # print_stacktraces=1,verbosity=2, duh # detect_leaks=0: too many uninteresting leak errors in short-lived binaries UBSAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:verbosity=2 - ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0:detect_stack_use_after_return=0 + ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0 # SANITIZER_FLAGS is set in the tasks below CFLAGS: -Og -ggdb -fno-sanitize-recover=all $SANITIZER_FLAGS -- 2.53.0.1.gb2826b52eb --7e2sypi5gxvkgog6-- ^ permalink raw reply [nested|flat] 114+ messages in thread
* [PATCH v2 1/4] Make stack depth check work with asan's use-after-return @ 2026-02-07 19:04 Andres Freund <[email protected]> 0 siblings, 0 replies; 114+ messages in thread From: Andres Freund @ 2026-02-07 19:04 UTC (permalink / raw) With address sanitizer's stack-use-after-return check, stack variables are moved to heap allocations, to allow to detect references to the memory at a later time. That broke our stack-depth check, which is why we had to disable detect_stack_use_after_return in CI. Luckily __builtin_frame_address() works correctly, even under asan, so use that. Author: Reviewed-by: Discussion: https://postgr.es/m/ Backpatch: --- src/backend/utils/misc/stack_depth.c | 17 ++++++++++++++++- .cirrus.tasks.yml | 2 +- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/src/backend/utils/misc/stack_depth.c b/src/backend/utils/misc/stack_depth.c index 61a07cf824e..9955e28c176 100644 --- a/src/backend/utils/misc/stack_depth.c +++ b/src/backend/utils/misc/stack_depth.c @@ -108,13 +108,28 @@ check_stack_depth(void) bool stack_is_too_deep(void) { +#ifndef HAVE__BUILTIN_FRAME_ADDRESS char stack_top_loc; +#endif ssize_t stack_depth; + char *stack_address; + + /* + * With address sanitizer's stack-use-after-return check, stack variables + * are moved to heap allocations, to allow to detect references to the + * memory at a later time. That would break our stack-depth check. Luckily + * __builtin_frame_address() works correctly, even under asan. + */ +#ifndef HAVE__BUILTIN_FRAME_ADDRESS + stack_address = &stack_top_loc; +#else + stack_address = (char *) __builtin_frame_address(0); +#endif /* * Compute distance from reference point to my local variables */ - stack_depth = (ssize_t) (stack_base_ptr - &stack_top_loc); + stack_depth = (ssize_t) (stack_base_ptr - stack_address); /* * Take abs value, since stacks grow up on some machines, down on others diff --git a/.cirrus.tasks.yml b/.cirrus.tasks.yml index a22cef063f3..8683d1ae9c7 100644 --- a/.cirrus.tasks.yml +++ b/.cirrus.tasks.yml @@ -447,7 +447,7 @@ task: # print_stacktraces=1,verbosity=2, duh # detect_leaks=0: too many uninteresting leak errors in short-lived binaries UBSAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:verbosity=2 - ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0:detect_stack_use_after_return=0 + ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0 # SANITIZER_FLAGS is set in the tasks below CFLAGS: -Og -ggdb -fno-sanitize-recover=all $SANITIZER_FLAGS -- 2.53.0.1.gb2826b52eb --7e2sypi5gxvkgog6-- ^ permalink raw reply [nested|flat] 114+ messages in thread
* [PATCH v2 1/4] Make stack depth check work with asan's use-after-return @ 2026-02-07 19:04 Andres Freund <[email protected]> 0 siblings, 0 replies; 114+ messages in thread From: Andres Freund @ 2026-02-07 19:04 UTC (permalink / raw) With address sanitizer's stack-use-after-return check, stack variables are moved to heap allocations, to allow to detect references to the memory at a later time. That broke our stack-depth check, which is why we had to disable detect_stack_use_after_return in CI. Luckily __builtin_frame_address() works correctly, even under asan, so use that. Author: Reviewed-by: Discussion: https://postgr.es/m/ Backpatch: --- src/backend/utils/misc/stack_depth.c | 17 ++++++++++++++++- .cirrus.tasks.yml | 2 +- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/src/backend/utils/misc/stack_depth.c b/src/backend/utils/misc/stack_depth.c index 61a07cf824e..9955e28c176 100644 --- a/src/backend/utils/misc/stack_depth.c +++ b/src/backend/utils/misc/stack_depth.c @@ -108,13 +108,28 @@ check_stack_depth(void) bool stack_is_too_deep(void) { +#ifndef HAVE__BUILTIN_FRAME_ADDRESS char stack_top_loc; +#endif ssize_t stack_depth; + char *stack_address; + + /* + * With address sanitizer's stack-use-after-return check, stack variables + * are moved to heap allocations, to allow to detect references to the + * memory at a later time. That would break our stack-depth check. Luckily + * __builtin_frame_address() works correctly, even under asan. + */ +#ifndef HAVE__BUILTIN_FRAME_ADDRESS + stack_address = &stack_top_loc; +#else + stack_address = (char *) __builtin_frame_address(0); +#endif /* * Compute distance from reference point to my local variables */ - stack_depth = (ssize_t) (stack_base_ptr - &stack_top_loc); + stack_depth = (ssize_t) (stack_base_ptr - stack_address); /* * Take abs value, since stacks grow up on some machines, down on others diff --git a/.cirrus.tasks.yml b/.cirrus.tasks.yml index a22cef063f3..8683d1ae9c7 100644 --- a/.cirrus.tasks.yml +++ b/.cirrus.tasks.yml @@ -447,7 +447,7 @@ task: # print_stacktraces=1,verbosity=2, duh # detect_leaks=0: too many uninteresting leak errors in short-lived binaries UBSAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:verbosity=2 - ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0:detect_stack_use_after_return=0 + ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0 # SANITIZER_FLAGS is set in the tasks below CFLAGS: -Og -ggdb -fno-sanitize-recover=all $SANITIZER_FLAGS -- 2.53.0.1.gb2826b52eb --7e2sypi5gxvkgog6-- ^ permalink raw reply [nested|flat] 114+ messages in thread
* [PATCH v2 1/4] Make stack depth check work with asan's use-after-return @ 2026-02-07 19:04 Andres Freund <[email protected]> 0 siblings, 0 replies; 114+ messages in thread From: Andres Freund @ 2026-02-07 19:04 UTC (permalink / raw) With address sanitizer's stack-use-after-return check, stack variables are moved to heap allocations, to allow to detect references to the memory at a later time. That broke our stack-depth check, which is why we had to disable detect_stack_use_after_return in CI. Luckily __builtin_frame_address() works correctly, even under asan, so use that. Author: Reviewed-by: Discussion: https://postgr.es/m/ Backpatch: --- src/backend/utils/misc/stack_depth.c | 17 ++++++++++++++++- .cirrus.tasks.yml | 2 +- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/src/backend/utils/misc/stack_depth.c b/src/backend/utils/misc/stack_depth.c index 61a07cf824e..9955e28c176 100644 --- a/src/backend/utils/misc/stack_depth.c +++ b/src/backend/utils/misc/stack_depth.c @@ -108,13 +108,28 @@ check_stack_depth(void) bool stack_is_too_deep(void) { +#ifndef HAVE__BUILTIN_FRAME_ADDRESS char stack_top_loc; +#endif ssize_t stack_depth; + char *stack_address; + + /* + * With address sanitizer's stack-use-after-return check, stack variables + * are moved to heap allocations, to allow to detect references to the + * memory at a later time. That would break our stack-depth check. Luckily + * __builtin_frame_address() works correctly, even under asan. + */ +#ifndef HAVE__BUILTIN_FRAME_ADDRESS + stack_address = &stack_top_loc; +#else + stack_address = (char *) __builtin_frame_address(0); +#endif /* * Compute distance from reference point to my local variables */ - stack_depth = (ssize_t) (stack_base_ptr - &stack_top_loc); + stack_depth = (ssize_t) (stack_base_ptr - stack_address); /* * Take abs value, since stacks grow up on some machines, down on others diff --git a/.cirrus.tasks.yml b/.cirrus.tasks.yml index a22cef063f3..8683d1ae9c7 100644 --- a/.cirrus.tasks.yml +++ b/.cirrus.tasks.yml @@ -447,7 +447,7 @@ task: # print_stacktraces=1,verbosity=2, duh # detect_leaks=0: too many uninteresting leak errors in short-lived binaries UBSAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:verbosity=2 - ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0:detect_stack_use_after_return=0 + ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0 # SANITIZER_FLAGS is set in the tasks below CFLAGS: -Og -ggdb -fno-sanitize-recover=all $SANITIZER_FLAGS -- 2.53.0.1.gb2826b52eb --7e2sypi5gxvkgog6-- ^ permalink raw reply [nested|flat] 114+ messages in thread
* [PATCH v2 1/4] Make stack depth check work with asan's use-after-return @ 2026-02-07 19:04 Andres Freund <[email protected]> 0 siblings, 0 replies; 114+ messages in thread From: Andres Freund @ 2026-02-07 19:04 UTC (permalink / raw) With address sanitizer's stack-use-after-return check, stack variables are moved to heap allocations, to allow to detect references to the memory at a later time. That broke our stack-depth check, which is why we had to disable detect_stack_use_after_return in CI. Luckily __builtin_frame_address() works correctly, even under asan, so use that. Author: Reviewed-by: Discussion: https://postgr.es/m/ Backpatch: --- src/backend/utils/misc/stack_depth.c | 17 ++++++++++++++++- .cirrus.tasks.yml | 2 +- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/src/backend/utils/misc/stack_depth.c b/src/backend/utils/misc/stack_depth.c index 61a07cf824e..9955e28c176 100644 --- a/src/backend/utils/misc/stack_depth.c +++ b/src/backend/utils/misc/stack_depth.c @@ -108,13 +108,28 @@ check_stack_depth(void) bool stack_is_too_deep(void) { +#ifndef HAVE__BUILTIN_FRAME_ADDRESS char stack_top_loc; +#endif ssize_t stack_depth; + char *stack_address; + + /* + * With address sanitizer's stack-use-after-return check, stack variables + * are moved to heap allocations, to allow to detect references to the + * memory at a later time. That would break our stack-depth check. Luckily + * __builtin_frame_address() works correctly, even under asan. + */ +#ifndef HAVE__BUILTIN_FRAME_ADDRESS + stack_address = &stack_top_loc; +#else + stack_address = (char *) __builtin_frame_address(0); +#endif /* * Compute distance from reference point to my local variables */ - stack_depth = (ssize_t) (stack_base_ptr - &stack_top_loc); + stack_depth = (ssize_t) (stack_base_ptr - stack_address); /* * Take abs value, since stacks grow up on some machines, down on others diff --git a/.cirrus.tasks.yml b/.cirrus.tasks.yml index a22cef063f3..8683d1ae9c7 100644 --- a/.cirrus.tasks.yml +++ b/.cirrus.tasks.yml @@ -447,7 +447,7 @@ task: # print_stacktraces=1,verbosity=2, duh # detect_leaks=0: too many uninteresting leak errors in short-lived binaries UBSAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:verbosity=2 - ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0:detect_stack_use_after_return=0 + ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0 # SANITIZER_FLAGS is set in the tasks below CFLAGS: -Og -ggdb -fno-sanitize-recover=all $SANITIZER_FLAGS -- 2.53.0.1.gb2826b52eb --7e2sypi5gxvkgog6-- ^ permalink raw reply [nested|flat] 114+ messages in thread
* [PATCH v2 1/4] Make stack depth check work with asan's use-after-return @ 2026-02-07 19:04 Andres Freund <[email protected]> 0 siblings, 0 replies; 114+ messages in thread From: Andres Freund @ 2026-02-07 19:04 UTC (permalink / raw) With address sanitizer's stack-use-after-return check, stack variables are moved to heap allocations, to allow to detect references to the memory at a later time. That broke our stack-depth check, which is why we had to disable detect_stack_use_after_return in CI. Luckily __builtin_frame_address() works correctly, even under asan, so use that. Author: Reviewed-by: Discussion: https://postgr.es/m/ Backpatch: --- src/backend/utils/misc/stack_depth.c | 17 ++++++++++++++++- .cirrus.tasks.yml | 2 +- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/src/backend/utils/misc/stack_depth.c b/src/backend/utils/misc/stack_depth.c index 61a07cf824e..9955e28c176 100644 --- a/src/backend/utils/misc/stack_depth.c +++ b/src/backend/utils/misc/stack_depth.c @@ -108,13 +108,28 @@ check_stack_depth(void) bool stack_is_too_deep(void) { +#ifndef HAVE__BUILTIN_FRAME_ADDRESS char stack_top_loc; +#endif ssize_t stack_depth; + char *stack_address; + + /* + * With address sanitizer's stack-use-after-return check, stack variables + * are moved to heap allocations, to allow to detect references to the + * memory at a later time. That would break our stack-depth check. Luckily + * __builtin_frame_address() works correctly, even under asan. + */ +#ifndef HAVE__BUILTIN_FRAME_ADDRESS + stack_address = &stack_top_loc; +#else + stack_address = (char *) __builtin_frame_address(0); +#endif /* * Compute distance from reference point to my local variables */ - stack_depth = (ssize_t) (stack_base_ptr - &stack_top_loc); + stack_depth = (ssize_t) (stack_base_ptr - stack_address); /* * Take abs value, since stacks grow up on some machines, down on others diff --git a/.cirrus.tasks.yml b/.cirrus.tasks.yml index a22cef063f3..8683d1ae9c7 100644 --- a/.cirrus.tasks.yml +++ b/.cirrus.tasks.yml @@ -447,7 +447,7 @@ task: # print_stacktraces=1,verbosity=2, duh # detect_leaks=0: too many uninteresting leak errors in short-lived binaries UBSAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:verbosity=2 - ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0:detect_stack_use_after_return=0 + ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0 # SANITIZER_FLAGS is set in the tasks below CFLAGS: -Og -ggdb -fno-sanitize-recover=all $SANITIZER_FLAGS -- 2.53.0.1.gb2826b52eb --7e2sypi5gxvkgog6-- ^ permalink raw reply [nested|flat] 114+ messages in thread
* [PATCH v2 1/4] Make stack depth check work with asan's use-after-return @ 2026-02-07 19:04 Andres Freund <[email protected]> 0 siblings, 0 replies; 114+ messages in thread From: Andres Freund @ 2026-02-07 19:04 UTC (permalink / raw) With address sanitizer's stack-use-after-return check, stack variables are moved to heap allocations, to allow to detect references to the memory at a later time. That broke our stack-depth check, which is why we had to disable detect_stack_use_after_return in CI. Luckily __builtin_frame_address() works correctly, even under asan, so use that. Author: Reviewed-by: Discussion: https://postgr.es/m/ Backpatch: --- src/backend/utils/misc/stack_depth.c | 17 ++++++++++++++++- .cirrus.tasks.yml | 2 +- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/src/backend/utils/misc/stack_depth.c b/src/backend/utils/misc/stack_depth.c index 61a07cf824e..9955e28c176 100644 --- a/src/backend/utils/misc/stack_depth.c +++ b/src/backend/utils/misc/stack_depth.c @@ -108,13 +108,28 @@ check_stack_depth(void) bool stack_is_too_deep(void) { +#ifndef HAVE__BUILTIN_FRAME_ADDRESS char stack_top_loc; +#endif ssize_t stack_depth; + char *stack_address; + + /* + * With address sanitizer's stack-use-after-return check, stack variables + * are moved to heap allocations, to allow to detect references to the + * memory at a later time. That would break our stack-depth check. Luckily + * __builtin_frame_address() works correctly, even under asan. + */ +#ifndef HAVE__BUILTIN_FRAME_ADDRESS + stack_address = &stack_top_loc; +#else + stack_address = (char *) __builtin_frame_address(0); +#endif /* * Compute distance from reference point to my local variables */ - stack_depth = (ssize_t) (stack_base_ptr - &stack_top_loc); + stack_depth = (ssize_t) (stack_base_ptr - stack_address); /* * Take abs value, since stacks grow up on some machines, down on others diff --git a/.cirrus.tasks.yml b/.cirrus.tasks.yml index a22cef063f3..8683d1ae9c7 100644 --- a/.cirrus.tasks.yml +++ b/.cirrus.tasks.yml @@ -447,7 +447,7 @@ task: # print_stacktraces=1,verbosity=2, duh # detect_leaks=0: too many uninteresting leak errors in short-lived binaries UBSAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:verbosity=2 - ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0:detect_stack_use_after_return=0 + ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0 # SANITIZER_FLAGS is set in the tasks below CFLAGS: -Og -ggdb -fno-sanitize-recover=all $SANITIZER_FLAGS -- 2.53.0.1.gb2826b52eb --7e2sypi5gxvkgog6-- ^ permalink raw reply [nested|flat] 114+ messages in thread
* [PATCH v2 1/4] Make stack depth check work with asan's use-after-return @ 2026-02-07 19:04 Andres Freund <[email protected]> 0 siblings, 0 replies; 114+ messages in thread From: Andres Freund @ 2026-02-07 19:04 UTC (permalink / raw) With address sanitizer's stack-use-after-return check, stack variables are moved to heap allocations, to allow to detect references to the memory at a later time. That broke our stack-depth check, which is why we had to disable detect_stack_use_after_return in CI. Luckily __builtin_frame_address() works correctly, even under asan, so use that. Author: Reviewed-by: Discussion: https://postgr.es/m/ Backpatch: --- src/backend/utils/misc/stack_depth.c | 17 ++++++++++++++++- .cirrus.tasks.yml | 2 +- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/src/backend/utils/misc/stack_depth.c b/src/backend/utils/misc/stack_depth.c index 61a07cf824e..9955e28c176 100644 --- a/src/backend/utils/misc/stack_depth.c +++ b/src/backend/utils/misc/stack_depth.c @@ -108,13 +108,28 @@ check_stack_depth(void) bool stack_is_too_deep(void) { +#ifndef HAVE__BUILTIN_FRAME_ADDRESS char stack_top_loc; +#endif ssize_t stack_depth; + char *stack_address; + + /* + * With address sanitizer's stack-use-after-return check, stack variables + * are moved to heap allocations, to allow to detect references to the + * memory at a later time. That would break our stack-depth check. Luckily + * __builtin_frame_address() works correctly, even under asan. + */ +#ifndef HAVE__BUILTIN_FRAME_ADDRESS + stack_address = &stack_top_loc; +#else + stack_address = (char *) __builtin_frame_address(0); +#endif /* * Compute distance from reference point to my local variables */ - stack_depth = (ssize_t) (stack_base_ptr - &stack_top_loc); + stack_depth = (ssize_t) (stack_base_ptr - stack_address); /* * Take abs value, since stacks grow up on some machines, down on others diff --git a/.cirrus.tasks.yml b/.cirrus.tasks.yml index a22cef063f3..8683d1ae9c7 100644 --- a/.cirrus.tasks.yml +++ b/.cirrus.tasks.yml @@ -447,7 +447,7 @@ task: # print_stacktraces=1,verbosity=2, duh # detect_leaks=0: too many uninteresting leak errors in short-lived binaries UBSAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:verbosity=2 - ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0:detect_stack_use_after_return=0 + ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0 # SANITIZER_FLAGS is set in the tasks below CFLAGS: -Og -ggdb -fno-sanitize-recover=all $SANITIZER_FLAGS -- 2.53.0.1.gb2826b52eb --7e2sypi5gxvkgog6-- ^ permalink raw reply [nested|flat] 114+ messages in thread
* [PATCH v2 1/4] Make stack depth check work with asan's use-after-return @ 2026-02-07 19:04 Andres Freund <[email protected]> 0 siblings, 0 replies; 114+ messages in thread From: Andres Freund @ 2026-02-07 19:04 UTC (permalink / raw) With address sanitizer's stack-use-after-return check, stack variables are moved to heap allocations, to allow to detect references to the memory at a later time. That broke our stack-depth check, which is why we had to disable detect_stack_use_after_return in CI. Luckily __builtin_frame_address() works correctly, even under asan, so use that. Author: Reviewed-by: Discussion: https://postgr.es/m/ Backpatch: --- src/backend/utils/misc/stack_depth.c | 17 ++++++++++++++++- .cirrus.tasks.yml | 2 +- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/src/backend/utils/misc/stack_depth.c b/src/backend/utils/misc/stack_depth.c index 61a07cf824e..9955e28c176 100644 --- a/src/backend/utils/misc/stack_depth.c +++ b/src/backend/utils/misc/stack_depth.c @@ -108,13 +108,28 @@ check_stack_depth(void) bool stack_is_too_deep(void) { +#ifndef HAVE__BUILTIN_FRAME_ADDRESS char stack_top_loc; +#endif ssize_t stack_depth; + char *stack_address; + + /* + * With address sanitizer's stack-use-after-return check, stack variables + * are moved to heap allocations, to allow to detect references to the + * memory at a later time. That would break our stack-depth check. Luckily + * __builtin_frame_address() works correctly, even under asan. + */ +#ifndef HAVE__BUILTIN_FRAME_ADDRESS + stack_address = &stack_top_loc; +#else + stack_address = (char *) __builtin_frame_address(0); +#endif /* * Compute distance from reference point to my local variables */ - stack_depth = (ssize_t) (stack_base_ptr - &stack_top_loc); + stack_depth = (ssize_t) (stack_base_ptr - stack_address); /* * Take abs value, since stacks grow up on some machines, down on others diff --git a/.cirrus.tasks.yml b/.cirrus.tasks.yml index a22cef063f3..8683d1ae9c7 100644 --- a/.cirrus.tasks.yml +++ b/.cirrus.tasks.yml @@ -447,7 +447,7 @@ task: # print_stacktraces=1,verbosity=2, duh # detect_leaks=0: too many uninteresting leak errors in short-lived binaries UBSAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:verbosity=2 - ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0:detect_stack_use_after_return=0 + ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0 # SANITIZER_FLAGS is set in the tasks below CFLAGS: -Og -ggdb -fno-sanitize-recover=all $SANITIZER_FLAGS -- 2.53.0.1.gb2826b52eb --7e2sypi5gxvkgog6-- ^ permalink raw reply [nested|flat] 114+ messages in thread
* [PATCH v2 1/4] Make stack depth check work with asan's use-after-return @ 2026-02-07 19:04 Andres Freund <[email protected]> 0 siblings, 0 replies; 114+ messages in thread From: Andres Freund @ 2026-02-07 19:04 UTC (permalink / raw) With address sanitizer's stack-use-after-return check, stack variables are moved to heap allocations, to allow to detect references to the memory at a later time. That broke our stack-depth check, which is why we had to disable detect_stack_use_after_return in CI. Luckily __builtin_frame_address() works correctly, even under asan, so use that. Author: Reviewed-by: Discussion: https://postgr.es/m/ Backpatch: --- src/backend/utils/misc/stack_depth.c | 17 ++++++++++++++++- .cirrus.tasks.yml | 2 +- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/src/backend/utils/misc/stack_depth.c b/src/backend/utils/misc/stack_depth.c index 61a07cf824e..9955e28c176 100644 --- a/src/backend/utils/misc/stack_depth.c +++ b/src/backend/utils/misc/stack_depth.c @@ -108,13 +108,28 @@ check_stack_depth(void) bool stack_is_too_deep(void) { +#ifndef HAVE__BUILTIN_FRAME_ADDRESS char stack_top_loc; +#endif ssize_t stack_depth; + char *stack_address; + + /* + * With address sanitizer's stack-use-after-return check, stack variables + * are moved to heap allocations, to allow to detect references to the + * memory at a later time. That would break our stack-depth check. Luckily + * __builtin_frame_address() works correctly, even under asan. + */ +#ifndef HAVE__BUILTIN_FRAME_ADDRESS + stack_address = &stack_top_loc; +#else + stack_address = (char *) __builtin_frame_address(0); +#endif /* * Compute distance from reference point to my local variables */ - stack_depth = (ssize_t) (stack_base_ptr - &stack_top_loc); + stack_depth = (ssize_t) (stack_base_ptr - stack_address); /* * Take abs value, since stacks grow up on some machines, down on others diff --git a/.cirrus.tasks.yml b/.cirrus.tasks.yml index a22cef063f3..8683d1ae9c7 100644 --- a/.cirrus.tasks.yml +++ b/.cirrus.tasks.yml @@ -447,7 +447,7 @@ task: # print_stacktraces=1,verbosity=2, duh # detect_leaks=0: too many uninteresting leak errors in short-lived binaries UBSAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:verbosity=2 - ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0:detect_stack_use_after_return=0 + ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0 # SANITIZER_FLAGS is set in the tasks below CFLAGS: -Og -ggdb -fno-sanitize-recover=all $SANITIZER_FLAGS -- 2.53.0.1.gb2826b52eb --7e2sypi5gxvkgog6-- ^ permalink raw reply [nested|flat] 114+ messages in thread
* [PATCH v2 1/4] Make stack depth check work with asan's use-after-return @ 2026-02-07 19:04 Andres Freund <[email protected]> 0 siblings, 0 replies; 114+ messages in thread From: Andres Freund @ 2026-02-07 19:04 UTC (permalink / raw) With address sanitizer's stack-use-after-return check, stack variables are moved to heap allocations, to allow to detect references to the memory at a later time. That broke our stack-depth check, which is why we had to disable detect_stack_use_after_return in CI. Luckily __builtin_frame_address() works correctly, even under asan, so use that. Author: Reviewed-by: Discussion: https://postgr.es/m/ Backpatch: --- src/backend/utils/misc/stack_depth.c | 17 ++++++++++++++++- .cirrus.tasks.yml | 2 +- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/src/backend/utils/misc/stack_depth.c b/src/backend/utils/misc/stack_depth.c index 61a07cf824e..9955e28c176 100644 --- a/src/backend/utils/misc/stack_depth.c +++ b/src/backend/utils/misc/stack_depth.c @@ -108,13 +108,28 @@ check_stack_depth(void) bool stack_is_too_deep(void) { +#ifndef HAVE__BUILTIN_FRAME_ADDRESS char stack_top_loc; +#endif ssize_t stack_depth; + char *stack_address; + + /* + * With address sanitizer's stack-use-after-return check, stack variables + * are moved to heap allocations, to allow to detect references to the + * memory at a later time. That would break our stack-depth check. Luckily + * __builtin_frame_address() works correctly, even under asan. + */ +#ifndef HAVE__BUILTIN_FRAME_ADDRESS + stack_address = &stack_top_loc; +#else + stack_address = (char *) __builtin_frame_address(0); +#endif /* * Compute distance from reference point to my local variables */ - stack_depth = (ssize_t) (stack_base_ptr - &stack_top_loc); + stack_depth = (ssize_t) (stack_base_ptr - stack_address); /* * Take abs value, since stacks grow up on some machines, down on others diff --git a/.cirrus.tasks.yml b/.cirrus.tasks.yml index a22cef063f3..8683d1ae9c7 100644 --- a/.cirrus.tasks.yml +++ b/.cirrus.tasks.yml @@ -447,7 +447,7 @@ task: # print_stacktraces=1,verbosity=2, duh # detect_leaks=0: too many uninteresting leak errors in short-lived binaries UBSAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:verbosity=2 - ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0:detect_stack_use_after_return=0 + ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0 # SANITIZER_FLAGS is set in the tasks below CFLAGS: -Og -ggdb -fno-sanitize-recover=all $SANITIZER_FLAGS -- 2.53.0.1.gb2826b52eb --7e2sypi5gxvkgog6-- ^ permalink raw reply [nested|flat] 114+ messages in thread
* [PATCH v2 1/4] Make stack depth check work with asan's use-after-return @ 2026-02-07 19:04 Andres Freund <[email protected]> 0 siblings, 0 replies; 114+ messages in thread From: Andres Freund @ 2026-02-07 19:04 UTC (permalink / raw) With address sanitizer's stack-use-after-return check, stack variables are moved to heap allocations, to allow to detect references to the memory at a later time. That broke our stack-depth check, which is why we had to disable detect_stack_use_after_return in CI. Luckily __builtin_frame_address() works correctly, even under asan, so use that. Author: Reviewed-by: Discussion: https://postgr.es/m/ Backpatch: --- src/backend/utils/misc/stack_depth.c | 17 ++++++++++++++++- .cirrus.tasks.yml | 2 +- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/src/backend/utils/misc/stack_depth.c b/src/backend/utils/misc/stack_depth.c index 61a07cf824e..9955e28c176 100644 --- a/src/backend/utils/misc/stack_depth.c +++ b/src/backend/utils/misc/stack_depth.c @@ -108,13 +108,28 @@ check_stack_depth(void) bool stack_is_too_deep(void) { +#ifndef HAVE__BUILTIN_FRAME_ADDRESS char stack_top_loc; +#endif ssize_t stack_depth; + char *stack_address; + + /* + * With address sanitizer's stack-use-after-return check, stack variables + * are moved to heap allocations, to allow to detect references to the + * memory at a later time. That would break our stack-depth check. Luckily + * __builtin_frame_address() works correctly, even under asan. + */ +#ifndef HAVE__BUILTIN_FRAME_ADDRESS + stack_address = &stack_top_loc; +#else + stack_address = (char *) __builtin_frame_address(0); +#endif /* * Compute distance from reference point to my local variables */ - stack_depth = (ssize_t) (stack_base_ptr - &stack_top_loc); + stack_depth = (ssize_t) (stack_base_ptr - stack_address); /* * Take abs value, since stacks grow up on some machines, down on others diff --git a/.cirrus.tasks.yml b/.cirrus.tasks.yml index a22cef063f3..8683d1ae9c7 100644 --- a/.cirrus.tasks.yml +++ b/.cirrus.tasks.yml @@ -447,7 +447,7 @@ task: # print_stacktraces=1,verbosity=2, duh # detect_leaks=0: too many uninteresting leak errors in short-lived binaries UBSAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:verbosity=2 - ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0:detect_stack_use_after_return=0 + ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0 # SANITIZER_FLAGS is set in the tasks below CFLAGS: -Og -ggdb -fno-sanitize-recover=all $SANITIZER_FLAGS -- 2.53.0.1.gb2826b52eb --7e2sypi5gxvkgog6-- ^ permalink raw reply [nested|flat] 114+ messages in thread
* [PATCH v2 1/4] Make stack depth check work with asan's use-after-return @ 2026-02-07 19:04 Andres Freund <[email protected]> 0 siblings, 0 replies; 114+ messages in thread From: Andres Freund @ 2026-02-07 19:04 UTC (permalink / raw) With address sanitizer's stack-use-after-return check, stack variables are moved to heap allocations, to allow to detect references to the memory at a later time. That broke our stack-depth check, which is why we had to disable detect_stack_use_after_return in CI. Luckily __builtin_frame_address() works correctly, even under asan, so use that. Author: Reviewed-by: Discussion: https://postgr.es/m/ Backpatch: --- src/backend/utils/misc/stack_depth.c | 17 ++++++++++++++++- .cirrus.tasks.yml | 2 +- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/src/backend/utils/misc/stack_depth.c b/src/backend/utils/misc/stack_depth.c index 61a07cf824e..9955e28c176 100644 --- a/src/backend/utils/misc/stack_depth.c +++ b/src/backend/utils/misc/stack_depth.c @@ -108,13 +108,28 @@ check_stack_depth(void) bool stack_is_too_deep(void) { +#ifndef HAVE__BUILTIN_FRAME_ADDRESS char stack_top_loc; +#endif ssize_t stack_depth; + char *stack_address; + + /* + * With address sanitizer's stack-use-after-return check, stack variables + * are moved to heap allocations, to allow to detect references to the + * memory at a later time. That would break our stack-depth check. Luckily + * __builtin_frame_address() works correctly, even under asan. + */ +#ifndef HAVE__BUILTIN_FRAME_ADDRESS + stack_address = &stack_top_loc; +#else + stack_address = (char *) __builtin_frame_address(0); +#endif /* * Compute distance from reference point to my local variables */ - stack_depth = (ssize_t) (stack_base_ptr - &stack_top_loc); + stack_depth = (ssize_t) (stack_base_ptr - stack_address); /* * Take abs value, since stacks grow up on some machines, down on others diff --git a/.cirrus.tasks.yml b/.cirrus.tasks.yml index a22cef063f3..8683d1ae9c7 100644 --- a/.cirrus.tasks.yml +++ b/.cirrus.tasks.yml @@ -447,7 +447,7 @@ task: # print_stacktraces=1,verbosity=2, duh # detect_leaks=0: too many uninteresting leak errors in short-lived binaries UBSAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:verbosity=2 - ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0:detect_stack_use_after_return=0 + ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0 # SANITIZER_FLAGS is set in the tasks below CFLAGS: -Og -ggdb -fno-sanitize-recover=all $SANITIZER_FLAGS -- 2.53.0.1.gb2826b52eb --7e2sypi5gxvkgog6-- ^ permalink raw reply [nested|flat] 114+ messages in thread
* [PATCH v2 1/4] Make stack depth check work with asan's use-after-return @ 2026-02-07 19:04 Andres Freund <[email protected]> 0 siblings, 0 replies; 114+ messages in thread From: Andres Freund @ 2026-02-07 19:04 UTC (permalink / raw) With address sanitizer's stack-use-after-return check, stack variables are moved to heap allocations, to allow to detect references to the memory at a later time. That broke our stack-depth check, which is why we had to disable detect_stack_use_after_return in CI. Luckily __builtin_frame_address() works correctly, even under asan, so use that. Author: Reviewed-by: Discussion: https://postgr.es/m/ Backpatch: --- src/backend/utils/misc/stack_depth.c | 17 ++++++++++++++++- .cirrus.tasks.yml | 2 +- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/src/backend/utils/misc/stack_depth.c b/src/backend/utils/misc/stack_depth.c index 61a07cf824e..9955e28c176 100644 --- a/src/backend/utils/misc/stack_depth.c +++ b/src/backend/utils/misc/stack_depth.c @@ -108,13 +108,28 @@ check_stack_depth(void) bool stack_is_too_deep(void) { +#ifndef HAVE__BUILTIN_FRAME_ADDRESS char stack_top_loc; +#endif ssize_t stack_depth; + char *stack_address; + + /* + * With address sanitizer's stack-use-after-return check, stack variables + * are moved to heap allocations, to allow to detect references to the + * memory at a later time. That would break our stack-depth check. Luckily + * __builtin_frame_address() works correctly, even under asan. + */ +#ifndef HAVE__BUILTIN_FRAME_ADDRESS + stack_address = &stack_top_loc; +#else + stack_address = (char *) __builtin_frame_address(0); +#endif /* * Compute distance from reference point to my local variables */ - stack_depth = (ssize_t) (stack_base_ptr - &stack_top_loc); + stack_depth = (ssize_t) (stack_base_ptr - stack_address); /* * Take abs value, since stacks grow up on some machines, down on others diff --git a/.cirrus.tasks.yml b/.cirrus.tasks.yml index a22cef063f3..8683d1ae9c7 100644 --- a/.cirrus.tasks.yml +++ b/.cirrus.tasks.yml @@ -447,7 +447,7 @@ task: # print_stacktraces=1,verbosity=2, duh # detect_leaks=0: too many uninteresting leak errors in short-lived binaries UBSAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:verbosity=2 - ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0:detect_stack_use_after_return=0 + ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0 # SANITIZER_FLAGS is set in the tasks below CFLAGS: -Og -ggdb -fno-sanitize-recover=all $SANITIZER_FLAGS -- 2.53.0.1.gb2826b52eb --7e2sypi5gxvkgog6-- ^ permalink raw reply [nested|flat] 114+ messages in thread
* [PATCH v2 1/4] Make stack depth check work with asan's use-after-return @ 2026-02-07 19:04 Andres Freund <[email protected]> 0 siblings, 0 replies; 114+ messages in thread From: Andres Freund @ 2026-02-07 19:04 UTC (permalink / raw) With address sanitizer's stack-use-after-return check, stack variables are moved to heap allocations, to allow to detect references to the memory at a later time. That broke our stack-depth check, which is why we had to disable detect_stack_use_after_return in CI. Luckily __builtin_frame_address() works correctly, even under asan, so use that. Author: Reviewed-by: Discussion: https://postgr.es/m/ Backpatch: --- src/backend/utils/misc/stack_depth.c | 17 ++++++++++++++++- .cirrus.tasks.yml | 2 +- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/src/backend/utils/misc/stack_depth.c b/src/backend/utils/misc/stack_depth.c index 61a07cf824e..9955e28c176 100644 --- a/src/backend/utils/misc/stack_depth.c +++ b/src/backend/utils/misc/stack_depth.c @@ -108,13 +108,28 @@ check_stack_depth(void) bool stack_is_too_deep(void) { +#ifndef HAVE__BUILTIN_FRAME_ADDRESS char stack_top_loc; +#endif ssize_t stack_depth; + char *stack_address; + + /* + * With address sanitizer's stack-use-after-return check, stack variables + * are moved to heap allocations, to allow to detect references to the + * memory at a later time. That would break our stack-depth check. Luckily + * __builtin_frame_address() works correctly, even under asan. + */ +#ifndef HAVE__BUILTIN_FRAME_ADDRESS + stack_address = &stack_top_loc; +#else + stack_address = (char *) __builtin_frame_address(0); +#endif /* * Compute distance from reference point to my local variables */ - stack_depth = (ssize_t) (stack_base_ptr - &stack_top_loc); + stack_depth = (ssize_t) (stack_base_ptr - stack_address); /* * Take abs value, since stacks grow up on some machines, down on others diff --git a/.cirrus.tasks.yml b/.cirrus.tasks.yml index a22cef063f3..8683d1ae9c7 100644 --- a/.cirrus.tasks.yml +++ b/.cirrus.tasks.yml @@ -447,7 +447,7 @@ task: # print_stacktraces=1,verbosity=2, duh # detect_leaks=0: too many uninteresting leak errors in short-lived binaries UBSAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:verbosity=2 - ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0:detect_stack_use_after_return=0 + ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0 # SANITIZER_FLAGS is set in the tasks below CFLAGS: -Og -ggdb -fno-sanitize-recover=all $SANITIZER_FLAGS -- 2.53.0.1.gb2826b52eb --7e2sypi5gxvkgog6-- ^ permalink raw reply [nested|flat] 114+ messages in thread
* [PATCH v2 1/4] Make stack depth check work with asan's use-after-return @ 2026-02-07 19:04 Andres Freund <[email protected]> 0 siblings, 0 replies; 114+ messages in thread From: Andres Freund @ 2026-02-07 19:04 UTC (permalink / raw) With address sanitizer's stack-use-after-return check, stack variables are moved to heap allocations, to allow to detect references to the memory at a later time. That broke our stack-depth check, which is why we had to disable detect_stack_use_after_return in CI. Luckily __builtin_frame_address() works correctly, even under asan, so use that. Author: Reviewed-by: Discussion: https://postgr.es/m/ Backpatch: --- src/backend/utils/misc/stack_depth.c | 17 ++++++++++++++++- .cirrus.tasks.yml | 2 +- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/src/backend/utils/misc/stack_depth.c b/src/backend/utils/misc/stack_depth.c index 61a07cf824e..9955e28c176 100644 --- a/src/backend/utils/misc/stack_depth.c +++ b/src/backend/utils/misc/stack_depth.c @@ -108,13 +108,28 @@ check_stack_depth(void) bool stack_is_too_deep(void) { +#ifndef HAVE__BUILTIN_FRAME_ADDRESS char stack_top_loc; +#endif ssize_t stack_depth; + char *stack_address; + + /* + * With address sanitizer's stack-use-after-return check, stack variables + * are moved to heap allocations, to allow to detect references to the + * memory at a later time. That would break our stack-depth check. Luckily + * __builtin_frame_address() works correctly, even under asan. + */ +#ifndef HAVE__BUILTIN_FRAME_ADDRESS + stack_address = &stack_top_loc; +#else + stack_address = (char *) __builtin_frame_address(0); +#endif /* * Compute distance from reference point to my local variables */ - stack_depth = (ssize_t) (stack_base_ptr - &stack_top_loc); + stack_depth = (ssize_t) (stack_base_ptr - stack_address); /* * Take abs value, since stacks grow up on some machines, down on others diff --git a/.cirrus.tasks.yml b/.cirrus.tasks.yml index a22cef063f3..8683d1ae9c7 100644 --- a/.cirrus.tasks.yml +++ b/.cirrus.tasks.yml @@ -447,7 +447,7 @@ task: # print_stacktraces=1,verbosity=2, duh # detect_leaks=0: too many uninteresting leak errors in short-lived binaries UBSAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:verbosity=2 - ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0:detect_stack_use_after_return=0 + ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0 # SANITIZER_FLAGS is set in the tasks below CFLAGS: -Og -ggdb -fno-sanitize-recover=all $SANITIZER_FLAGS -- 2.53.0.1.gb2826b52eb --7e2sypi5gxvkgog6-- ^ permalink raw reply [nested|flat] 114+ messages in thread
* [PATCH v2 1/4] Make stack depth check work with asan's use-after-return @ 2026-02-07 19:04 Andres Freund <[email protected]> 0 siblings, 0 replies; 114+ messages in thread From: Andres Freund @ 2026-02-07 19:04 UTC (permalink / raw) With address sanitizer's stack-use-after-return check, stack variables are moved to heap allocations, to allow to detect references to the memory at a later time. That broke our stack-depth check, which is why we had to disable detect_stack_use_after_return in CI. Luckily __builtin_frame_address() works correctly, even under asan, so use that. Author: Reviewed-by: Discussion: https://postgr.es/m/ Backpatch: --- src/backend/utils/misc/stack_depth.c | 17 ++++++++++++++++- .cirrus.tasks.yml | 2 +- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/src/backend/utils/misc/stack_depth.c b/src/backend/utils/misc/stack_depth.c index 61a07cf824e..9955e28c176 100644 --- a/src/backend/utils/misc/stack_depth.c +++ b/src/backend/utils/misc/stack_depth.c @@ -108,13 +108,28 @@ check_stack_depth(void) bool stack_is_too_deep(void) { +#ifndef HAVE__BUILTIN_FRAME_ADDRESS char stack_top_loc; +#endif ssize_t stack_depth; + char *stack_address; + + /* + * With address sanitizer's stack-use-after-return check, stack variables + * are moved to heap allocations, to allow to detect references to the + * memory at a later time. That would break our stack-depth check. Luckily + * __builtin_frame_address() works correctly, even under asan. + */ +#ifndef HAVE__BUILTIN_FRAME_ADDRESS + stack_address = &stack_top_loc; +#else + stack_address = (char *) __builtin_frame_address(0); +#endif /* * Compute distance from reference point to my local variables */ - stack_depth = (ssize_t) (stack_base_ptr - &stack_top_loc); + stack_depth = (ssize_t) (stack_base_ptr - stack_address); /* * Take abs value, since stacks grow up on some machines, down on others diff --git a/.cirrus.tasks.yml b/.cirrus.tasks.yml index a22cef063f3..8683d1ae9c7 100644 --- a/.cirrus.tasks.yml +++ b/.cirrus.tasks.yml @@ -447,7 +447,7 @@ task: # print_stacktraces=1,verbosity=2, duh # detect_leaks=0: too many uninteresting leak errors in short-lived binaries UBSAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:verbosity=2 - ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0:detect_stack_use_after_return=0 + ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0 # SANITIZER_FLAGS is set in the tasks below CFLAGS: -Og -ggdb -fno-sanitize-recover=all $SANITIZER_FLAGS -- 2.53.0.1.gb2826b52eb --7e2sypi5gxvkgog6-- ^ permalink raw reply [nested|flat] 114+ messages in thread
* [PATCH v2 1/4] Make stack depth check work with asan's use-after-return @ 2026-02-07 19:04 Andres Freund <[email protected]> 0 siblings, 0 replies; 114+ messages in thread From: Andres Freund @ 2026-02-07 19:04 UTC (permalink / raw) With address sanitizer's stack-use-after-return check, stack variables are moved to heap allocations, to allow to detect references to the memory at a later time. That broke our stack-depth check, which is why we had to disable detect_stack_use_after_return in CI. Luckily __builtin_frame_address() works correctly, even under asan, so use that. Author: Reviewed-by: Discussion: https://postgr.es/m/ Backpatch: --- src/backend/utils/misc/stack_depth.c | 17 ++++++++++++++++- .cirrus.tasks.yml | 2 +- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/src/backend/utils/misc/stack_depth.c b/src/backend/utils/misc/stack_depth.c index 61a07cf824e..9955e28c176 100644 --- a/src/backend/utils/misc/stack_depth.c +++ b/src/backend/utils/misc/stack_depth.c @@ -108,13 +108,28 @@ check_stack_depth(void) bool stack_is_too_deep(void) { +#ifndef HAVE__BUILTIN_FRAME_ADDRESS char stack_top_loc; +#endif ssize_t stack_depth; + char *stack_address; + + /* + * With address sanitizer's stack-use-after-return check, stack variables + * are moved to heap allocations, to allow to detect references to the + * memory at a later time. That would break our stack-depth check. Luckily + * __builtin_frame_address() works correctly, even under asan. + */ +#ifndef HAVE__BUILTIN_FRAME_ADDRESS + stack_address = &stack_top_loc; +#else + stack_address = (char *) __builtin_frame_address(0); +#endif /* * Compute distance from reference point to my local variables */ - stack_depth = (ssize_t) (stack_base_ptr - &stack_top_loc); + stack_depth = (ssize_t) (stack_base_ptr - stack_address); /* * Take abs value, since stacks grow up on some machines, down on others diff --git a/.cirrus.tasks.yml b/.cirrus.tasks.yml index a22cef063f3..8683d1ae9c7 100644 --- a/.cirrus.tasks.yml +++ b/.cirrus.tasks.yml @@ -447,7 +447,7 @@ task: # print_stacktraces=1,verbosity=2, duh # detect_leaks=0: too many uninteresting leak errors in short-lived binaries UBSAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:verbosity=2 - ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0:detect_stack_use_after_return=0 + ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0 # SANITIZER_FLAGS is set in the tasks below CFLAGS: -Og -ggdb -fno-sanitize-recover=all $SANITIZER_FLAGS -- 2.53.0.1.gb2826b52eb --7e2sypi5gxvkgog6-- ^ permalink raw reply [nested|flat] 114+ messages in thread
* [PATCH v2 1/4] Make stack depth check work with asan's use-after-return @ 2026-02-07 19:04 Andres Freund <[email protected]> 0 siblings, 0 replies; 114+ messages in thread From: Andres Freund @ 2026-02-07 19:04 UTC (permalink / raw) With address sanitizer's stack-use-after-return check, stack variables are moved to heap allocations, to allow to detect references to the memory at a later time. That broke our stack-depth check, which is why we had to disable detect_stack_use_after_return in CI. Luckily __builtin_frame_address() works correctly, even under asan, so use that. Author: Reviewed-by: Discussion: https://postgr.es/m/ Backpatch: --- src/backend/utils/misc/stack_depth.c | 17 ++++++++++++++++- .cirrus.tasks.yml | 2 +- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/src/backend/utils/misc/stack_depth.c b/src/backend/utils/misc/stack_depth.c index 61a07cf824e..9955e28c176 100644 --- a/src/backend/utils/misc/stack_depth.c +++ b/src/backend/utils/misc/stack_depth.c @@ -108,13 +108,28 @@ check_stack_depth(void) bool stack_is_too_deep(void) { +#ifndef HAVE__BUILTIN_FRAME_ADDRESS char stack_top_loc; +#endif ssize_t stack_depth; + char *stack_address; + + /* + * With address sanitizer's stack-use-after-return check, stack variables + * are moved to heap allocations, to allow to detect references to the + * memory at a later time. That would break our stack-depth check. Luckily + * __builtin_frame_address() works correctly, even under asan. + */ +#ifndef HAVE__BUILTIN_FRAME_ADDRESS + stack_address = &stack_top_loc; +#else + stack_address = (char *) __builtin_frame_address(0); +#endif /* * Compute distance from reference point to my local variables */ - stack_depth = (ssize_t) (stack_base_ptr - &stack_top_loc); + stack_depth = (ssize_t) (stack_base_ptr - stack_address); /* * Take abs value, since stacks grow up on some machines, down on others diff --git a/.cirrus.tasks.yml b/.cirrus.tasks.yml index a22cef063f3..8683d1ae9c7 100644 --- a/.cirrus.tasks.yml +++ b/.cirrus.tasks.yml @@ -447,7 +447,7 @@ task: # print_stacktraces=1,verbosity=2, duh # detect_leaks=0: too many uninteresting leak errors in short-lived binaries UBSAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:verbosity=2 - ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0:detect_stack_use_after_return=0 + ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0 # SANITIZER_FLAGS is set in the tasks below CFLAGS: -Og -ggdb -fno-sanitize-recover=all $SANITIZER_FLAGS -- 2.53.0.1.gb2826b52eb --7e2sypi5gxvkgog6-- ^ permalink raw reply [nested|flat] 114+ messages in thread
* [PATCH v2 1/4] Make stack depth check work with asan's use-after-return @ 2026-02-07 19:04 Andres Freund <[email protected]> 0 siblings, 0 replies; 114+ messages in thread From: Andres Freund @ 2026-02-07 19:04 UTC (permalink / raw) With address sanitizer's stack-use-after-return check, stack variables are moved to heap allocations, to allow to detect references to the memory at a later time. That broke our stack-depth check, which is why we had to disable detect_stack_use_after_return in CI. Luckily __builtin_frame_address() works correctly, even under asan, so use that. Author: Reviewed-by: Discussion: https://postgr.es/m/ Backpatch: --- src/backend/utils/misc/stack_depth.c | 17 ++++++++++++++++- .cirrus.tasks.yml | 2 +- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/src/backend/utils/misc/stack_depth.c b/src/backend/utils/misc/stack_depth.c index 61a07cf824e..9955e28c176 100644 --- a/src/backend/utils/misc/stack_depth.c +++ b/src/backend/utils/misc/stack_depth.c @@ -108,13 +108,28 @@ check_stack_depth(void) bool stack_is_too_deep(void) { +#ifndef HAVE__BUILTIN_FRAME_ADDRESS char stack_top_loc; +#endif ssize_t stack_depth; + char *stack_address; + + /* + * With address sanitizer's stack-use-after-return check, stack variables + * are moved to heap allocations, to allow to detect references to the + * memory at a later time. That would break our stack-depth check. Luckily + * __builtin_frame_address() works correctly, even under asan. + */ +#ifndef HAVE__BUILTIN_FRAME_ADDRESS + stack_address = &stack_top_loc; +#else + stack_address = (char *) __builtin_frame_address(0); +#endif /* * Compute distance from reference point to my local variables */ - stack_depth = (ssize_t) (stack_base_ptr - &stack_top_loc); + stack_depth = (ssize_t) (stack_base_ptr - stack_address); /* * Take abs value, since stacks grow up on some machines, down on others diff --git a/.cirrus.tasks.yml b/.cirrus.tasks.yml index a22cef063f3..8683d1ae9c7 100644 --- a/.cirrus.tasks.yml +++ b/.cirrus.tasks.yml @@ -447,7 +447,7 @@ task: # print_stacktraces=1,verbosity=2, duh # detect_leaks=0: too many uninteresting leak errors in short-lived binaries UBSAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:verbosity=2 - ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0:detect_stack_use_after_return=0 + ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0 # SANITIZER_FLAGS is set in the tasks below CFLAGS: -Og -ggdb -fno-sanitize-recover=all $SANITIZER_FLAGS -- 2.53.0.1.gb2826b52eb --7e2sypi5gxvkgog6-- ^ permalink raw reply [nested|flat] 114+ messages in thread
* [PATCH v2 1/4] Make stack depth check work with asan's use-after-return @ 2026-02-07 19:04 Andres Freund <[email protected]> 0 siblings, 0 replies; 114+ messages in thread From: Andres Freund @ 2026-02-07 19:04 UTC (permalink / raw) With address sanitizer's stack-use-after-return check, stack variables are moved to heap allocations, to allow to detect references to the memory at a later time. That broke our stack-depth check, which is why we had to disable detect_stack_use_after_return in CI. Luckily __builtin_frame_address() works correctly, even under asan, so use that. Author: Reviewed-by: Discussion: https://postgr.es/m/ Backpatch: --- src/backend/utils/misc/stack_depth.c | 17 ++++++++++++++++- .cirrus.tasks.yml | 2 +- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/src/backend/utils/misc/stack_depth.c b/src/backend/utils/misc/stack_depth.c index 61a07cf824e..9955e28c176 100644 --- a/src/backend/utils/misc/stack_depth.c +++ b/src/backend/utils/misc/stack_depth.c @@ -108,13 +108,28 @@ check_stack_depth(void) bool stack_is_too_deep(void) { +#ifndef HAVE__BUILTIN_FRAME_ADDRESS char stack_top_loc; +#endif ssize_t stack_depth; + char *stack_address; + + /* + * With address sanitizer's stack-use-after-return check, stack variables + * are moved to heap allocations, to allow to detect references to the + * memory at a later time. That would break our stack-depth check. Luckily + * __builtin_frame_address() works correctly, even under asan. + */ +#ifndef HAVE__BUILTIN_FRAME_ADDRESS + stack_address = &stack_top_loc; +#else + stack_address = (char *) __builtin_frame_address(0); +#endif /* * Compute distance from reference point to my local variables */ - stack_depth = (ssize_t) (stack_base_ptr - &stack_top_loc); + stack_depth = (ssize_t) (stack_base_ptr - stack_address); /* * Take abs value, since stacks grow up on some machines, down on others diff --git a/.cirrus.tasks.yml b/.cirrus.tasks.yml index a22cef063f3..8683d1ae9c7 100644 --- a/.cirrus.tasks.yml +++ b/.cirrus.tasks.yml @@ -447,7 +447,7 @@ task: # print_stacktraces=1,verbosity=2, duh # detect_leaks=0: too many uninteresting leak errors in short-lived binaries UBSAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:verbosity=2 - ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0:detect_stack_use_after_return=0 + ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0 # SANITIZER_FLAGS is set in the tasks below CFLAGS: -Og -ggdb -fno-sanitize-recover=all $SANITIZER_FLAGS -- 2.53.0.1.gb2826b52eb --7e2sypi5gxvkgog6-- ^ permalink raw reply [nested|flat] 114+ messages in thread
* [PATCH v2 1/4] Make stack depth check work with asan's use-after-return @ 2026-02-07 19:04 Andres Freund <[email protected]> 0 siblings, 0 replies; 114+ messages in thread From: Andres Freund @ 2026-02-07 19:04 UTC (permalink / raw) With address sanitizer's stack-use-after-return check, stack variables are moved to heap allocations, to allow to detect references to the memory at a later time. That broke our stack-depth check, which is why we had to disable detect_stack_use_after_return in CI. Luckily __builtin_frame_address() works correctly, even under asan, so use that. Author: Reviewed-by: Discussion: https://postgr.es/m/ Backpatch: --- src/backend/utils/misc/stack_depth.c | 17 ++++++++++++++++- .cirrus.tasks.yml | 2 +- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/src/backend/utils/misc/stack_depth.c b/src/backend/utils/misc/stack_depth.c index 61a07cf824e..9955e28c176 100644 --- a/src/backend/utils/misc/stack_depth.c +++ b/src/backend/utils/misc/stack_depth.c @@ -108,13 +108,28 @@ check_stack_depth(void) bool stack_is_too_deep(void) { +#ifndef HAVE__BUILTIN_FRAME_ADDRESS char stack_top_loc; +#endif ssize_t stack_depth; + char *stack_address; + + /* + * With address sanitizer's stack-use-after-return check, stack variables + * are moved to heap allocations, to allow to detect references to the + * memory at a later time. That would break our stack-depth check. Luckily + * __builtin_frame_address() works correctly, even under asan. + */ +#ifndef HAVE__BUILTIN_FRAME_ADDRESS + stack_address = &stack_top_loc; +#else + stack_address = (char *) __builtin_frame_address(0); +#endif /* * Compute distance from reference point to my local variables */ - stack_depth = (ssize_t) (stack_base_ptr - &stack_top_loc); + stack_depth = (ssize_t) (stack_base_ptr - stack_address); /* * Take abs value, since stacks grow up on some machines, down on others diff --git a/.cirrus.tasks.yml b/.cirrus.tasks.yml index a22cef063f3..8683d1ae9c7 100644 --- a/.cirrus.tasks.yml +++ b/.cirrus.tasks.yml @@ -447,7 +447,7 @@ task: # print_stacktraces=1,verbosity=2, duh # detect_leaks=0: too many uninteresting leak errors in short-lived binaries UBSAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:verbosity=2 - ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0:detect_stack_use_after_return=0 + ASAN_OPTIONS: print_stacktrace=1:disable_coredump=0:abort_on_error=1:detect_leaks=0 # SANITIZER_FLAGS is set in the tasks below CFLAGS: -Og -ggdb -fno-sanitize-recover=all $SANITIZER_FLAGS -- 2.53.0.1.gb2826b52eb --7e2sypi5gxvkgog6-- ^ permalink raw reply [nested|flat] 114+ messages in thread
end of thread, other threads:[~2026-02-07 19:04 UTC | newest] Thread overview: 114+ messages (download: mbox mbox.gz follow: Atom feed) -- links below jump to the message on this page -- 2018-11-13 20:58 [PATCH v14 6/7] Restructure TupleTableSlot to allow tuples other than HeapTuple Andres Freund <[email protected]> 2026-01-12 16:30 [PATCH 4/6] Add CONCURRENTLY option to REPACK command. Antonin Houska <[email protected]> 2026-02-07 19:04 [PATCH v2 1/4] Make stack depth check work with asan's use-after-return Andres Freund <[email protected]> 2026-02-07 19:04 [PATCH v2 1/4] Make stack depth check work with asan's use-after-return Andres Freund <[email protected]> 2026-02-07 19:04 [PATCH v2 1/4] Make stack depth check work with asan's use-after-return Andres Freund <[email protected]> 2026-02-07 19:04 [PATCH v2 1/4] Make stack depth check work with asan's use-after-return Andres Freund <[email protected]> 2026-02-07 19:04 [PATCH v2 1/4] Make stack depth check work with asan's use-after-return Andres Freund <[email protected]> 2026-02-07 19:04 [PATCH v2 1/4] Make stack depth check work with asan's use-after-return Andres Freund <[email protected]> 2026-02-07 19:04 [PATCH v2 1/4] Make stack depth check work with asan's use-after-return Andres Freund <[email protected]> 2026-02-07 19:04 [PATCH v2 1/4] Make stack depth check work with asan's use-after-return Andres Freund <[email protected]> 2026-02-07 19:04 [PATCH v2 1/4] Make stack depth check work with asan's use-after-return Andres Freund <[email protected]> 2026-02-07 19:04 [PATCH v2 1/4] Make stack depth check work with asan's use-after-return Andres Freund <[email protected]> 2026-02-07 19:04 [PATCH v2 1/4] Make stack depth check work with asan's use-after-return Andres Freund <[email protected]> 2026-02-07 19:04 [PATCH v2 1/4] Make stack depth check work with asan's use-after-return Andres Freund <[email protected]> 2026-02-07 19:04 [PATCH v2 1/4] Make stack depth check work with asan's use-after-return Andres Freund <[email protected]> 2026-02-07 19:04 [PATCH v2 1/4] Make stack depth check work with asan's use-after-return Andres Freund <[email protected]> 2026-02-07 19:04 [PATCH v2 1/4] Make stack depth check work with asan's use-after-return Andres Freund <[email protected]> 2026-02-07 19:04 [PATCH v2 1/4] Make stack depth check work with asan's use-after-return Andres Freund <[email protected]> 2026-02-07 19:04 [PATCH v2 1/4] Make stack depth check work with asan's use-after-return Andres Freund <[email protected]> 2026-02-07 19:04 [PATCH v2 1/4] Make stack depth check work with asan's use-after-return Andres Freund <[email protected]> 2026-02-07 19:04 [PATCH v2 1/4] Make stack depth check work with asan's use-after-return Andres Freund <[email protected]> 2026-02-07 19:04 [PATCH v2 1/4] Make stack depth check work with asan's use-after-return Andres Freund <[email protected]> 2026-02-07 19:04 [PATCH v2 1/4] Make stack depth check work with asan's use-after-return Andres Freund <[email protected]> 2026-02-07 19:04 [PATCH v2 1/4] Make stack depth check work with asan's use-after-return Andres Freund <[email protected]> 2026-02-07 19:04 [PATCH v2 1/4] Make stack depth check work with asan's use-after-return Andres Freund <[email protected]> 2026-02-07 19:04 [PATCH v2 1/4] Make stack depth check work with asan's use-after-return Andres Freund <[email protected]> 2026-02-07 19:04 [PATCH v2 1/4] Make stack depth check work with asan's use-after-return Andres Freund <[email protected]> 2026-02-07 19:04 [PATCH v2 1/4] Make stack depth check work with asan's use-after-return Andres Freund <[email protected]> 2026-02-07 19:04 [PATCH v2 1/4] Make stack depth check work with asan's use-after-return Andres Freund <[email protected]> 2026-02-07 19:04 [PATCH v2 1/4] Make stack depth check work with asan's use-after-return Andres Freund <[email protected]> 2026-02-07 19:04 [PATCH v2 1/4] Make stack depth check work with asan's use-after-return Andres Freund <[email protected]> 2026-02-07 19:04 [PATCH v2 1/4] Make stack depth check work with asan's use-after-return Andres Freund <[email protected]> 2026-02-07 19:04 [PATCH v2 1/4] Make stack depth check work with asan's use-after-return Andres Freund <[email protected]> 2026-02-07 19:04 [PATCH v2 1/4] Make stack depth check work with asan's use-after-return Andres Freund <[email protected]> 2026-02-07 19:04 [PATCH v2 1/4] Make stack depth check work with asan's use-after-return Andres Freund <[email protected]> 2026-02-07 19:04 [PATCH v2 1/4] Make stack depth check work with asan's use-after-return Andres Freund <[email protected]> 2026-02-07 19:04 [PATCH v2 1/4] Make stack depth check work with asan's use-after-return Andres Freund <[email protected]> 2026-02-07 19:04 [PATCH v2 1/4] Make stack depth check work with asan's use-after-return Andres Freund <[email protected]> 2026-02-07 19:04 [PATCH v2 1/4] Make stack depth check work with asan's use-after-return Andres Freund <[email protected]> 2026-02-07 19:04 [PATCH v2 1/4] Make stack depth check work with asan's use-after-return Andres Freund <[email protected]> 2026-02-07 19:04 [PATCH v2 1/4] Make stack depth check work with asan's use-after-return Andres Freund <[email protected]> 2026-02-07 19:04 [PATCH v2 1/4] Make stack depth check work with asan's use-after-return Andres Freund <[email protected]> 2026-02-07 19:04 [PATCH v2 1/4] Make stack depth check work with asan's use-after-return Andres Freund <[email protected]> 2026-02-07 19:04 [PATCH v2 1/4] Make stack depth check work with asan's use-after-return Andres Freund <[email protected]> 2026-02-07 19:04 [PATCH v2 1/4] Make stack depth check work with asan's use-after-return Andres Freund <[email protected]> 2026-02-07 19:04 [PATCH v2 1/4] Make stack depth check work with asan's use-after-return Andres Freund <[email protected]> 2026-02-07 19:04 [PATCH v2 1/4] Make stack depth check work with asan's use-after-return Andres Freund <[email protected]> 2026-02-07 19:04 [PATCH v2 1/4] Make stack depth check work with asan's use-after-return Andres Freund <[email protected]> 2026-02-07 19:04 [PATCH v2 1/4] Make stack depth check work with asan's use-after-return Andres Freund <[email protected]> 2026-02-07 19:04 [PATCH v2 1/4] Make stack depth check work with asan's use-after-return Andres Freund <[email protected]> 2026-02-07 19:04 [PATCH v2 1/4] Make stack depth check work with asan's use-after-return Andres Freund <[email protected]> 2026-02-07 19:04 [PATCH v2 1/4] Make stack depth check work with asan's use-after-return Andres Freund <[email protected]> 2026-02-07 19:04 [PATCH v2 1/4] Make stack depth check work with asan's use-after-return Andres Freund <[email protected]> 2026-02-07 19:04 [PATCH v2 1/4] Make stack depth check work with asan's use-after-return Andres Freund <[email protected]> 2026-02-07 19:04 [PATCH v2 1/4] Make stack depth check work with asan's use-after-return Andres Freund <[email protected]> 2026-02-07 19:04 [PATCH v2 1/4] Make stack depth check work with asan's use-after-return Andres Freund <[email protected]> 2026-02-07 19:04 [PATCH v2 1/4] Make stack depth check work with asan's use-after-return Andres Freund <[email protected]> 2026-02-07 19:04 [PATCH v2 1/4] Make stack depth check work with asan's use-after-return Andres Freund <[email protected]> 2026-02-07 19:04 [PATCH v2 1/4] Make stack depth check work with asan's use-after-return Andres Freund <[email protected]> 2026-02-07 19:04 [PATCH v2 1/4] Make stack depth check work with asan's use-after-return Andres Freund <[email protected]> 2026-02-07 19:04 [PATCH v2 1/4] Make stack depth check work with asan's use-after-return Andres Freund <[email protected]> 2026-02-07 19:04 [PATCH v2 1/4] Make stack depth check work with asan's use-after-return Andres Freund <[email protected]> 2026-02-07 19:04 [PATCH v2 1/4] Make stack depth check work with asan's use-after-return Andres Freund <[email protected]> 2026-02-07 19:04 [PATCH v2 1/4] Make stack depth check work with asan's use-after-return Andres Freund <[email protected]> 2026-02-07 19:04 [PATCH v2 1/4] Make stack depth check work with asan's use-after-return Andres Freund <[email protected]> 2026-02-07 19:04 [PATCH v2 1/4] Make stack depth check work with asan's use-after-return Andres Freund <[email protected]> 2026-02-07 19:04 [PATCH v2 1/4] Make stack depth check work with asan's use-after-return Andres Freund <[email protected]> 2026-02-07 19:04 [PATCH v2 1/4] Make stack depth check work with asan's use-after-return Andres Freund <[email protected]> 2026-02-07 19:04 [PATCH v2 1/4] Make stack depth check work with asan's use-after-return Andres Freund <[email protected]> 2026-02-07 19:04 [PATCH v2 1/4] Make stack depth check work with asan's use-after-return Andres Freund <[email protected]> 2026-02-07 19:04 [PATCH v2 1/4] Make stack depth check work with asan's use-after-return Andres Freund <[email protected]> 2026-02-07 19:04 [PATCH v2 1/4] Make stack depth check work with asan's use-after-return Andres Freund <[email protected]> 2026-02-07 19:04 [PATCH v2 1/4] Make stack depth check work with asan's use-after-return Andres Freund <[email protected]> 2026-02-07 19:04 [PATCH v2 1/4] Make stack depth check work with asan's use-after-return Andres Freund <[email protected]> 2026-02-07 19:04 [PATCH v2 1/4] Make stack depth check work with asan's use-after-return Andres Freund <[email protected]> 2026-02-07 19:04 [PATCH v2 1/4] Make stack depth check work with asan's use-after-return Andres Freund <[email protected]> 2026-02-07 19:04 [PATCH v2 1/4] Make stack depth check work with asan's use-after-return Andres Freund <[email protected]> 2026-02-07 19:04 [PATCH v2 1/4] Make stack depth check work with asan's use-after-return Andres Freund <[email protected]> 2026-02-07 19:04 [PATCH v2 1/4] Make stack depth check work with asan's use-after-return Andres Freund <[email protected]> 2026-02-07 19:04 [PATCH v2 1/4] Make stack depth check work with asan's use-after-return Andres Freund <[email protected]> 2026-02-07 19:04 [PATCH v2 1/4] Make stack depth check work with asan's use-after-return Andres Freund <[email protected]> 2026-02-07 19:04 [PATCH v2 1/4] Make stack depth check work with asan's use-after-return Andres Freund <[email protected]> 2026-02-07 19:04 [PATCH v2 1/4] Make stack depth check work with asan's use-after-return Andres Freund <[email protected]> 2026-02-07 19:04 [PATCH v2 1/4] Make stack depth check work with asan's use-after-return Andres Freund <[email protected]> 2026-02-07 19:04 [PATCH v2 1/4] Make stack depth check work with asan's use-after-return Andres Freund <[email protected]> 2026-02-07 19:04 [PATCH v2 1/4] Make stack depth check work with asan's use-after-return Andres Freund <[email protected]> 2026-02-07 19:04 [PATCH v2 1/4] Make stack depth check work with asan's use-after-return Andres Freund <[email protected]> 2026-02-07 19:04 [PATCH v2 1/4] Make stack depth check work with asan's use-after-return Andres Freund <[email protected]> 2026-02-07 19:04 [PATCH v2 1/4] Make stack depth check work with asan's use-after-return Andres Freund <[email protected]> 2026-02-07 19:04 [PATCH v2 1/4] Make stack depth check work with asan's use-after-return Andres Freund <[email protected]> 2026-02-07 19:04 [PATCH v2 1/4] Make stack depth check work with asan's use-after-return Andres Freund <[email protected]> 2026-02-07 19:04 [PATCH v2 1/4] Make stack depth check work with asan's use-after-return Andres Freund <[email protected]> 2026-02-07 19:04 [PATCH v2 1/4] Make stack depth check work with asan's use-after-return Andres Freund <[email protected]> 2026-02-07 19:04 [PATCH v2 1/4] Make stack depth check work with asan's use-after-return Andres Freund <[email protected]> 2026-02-07 19:04 [PATCH v2 1/4] Make stack depth check work with asan's use-after-return Andres Freund <[email protected]> 2026-02-07 19:04 [PATCH v2 1/4] Make stack depth check work with asan's use-after-return Andres Freund <[email protected]> 2026-02-07 19:04 [PATCH v2 1/4] Make stack depth check work with asan's use-after-return Andres Freund <[email protected]> 2026-02-07 19:04 [PATCH v2 1/4] Make stack depth check work with asan's use-after-return Andres Freund <[email protected]> 2026-02-07 19:04 [PATCH v2 1/4] Make stack depth check work with asan's use-after-return Andres Freund <[email protected]> 2026-02-07 19:04 [PATCH v2 1/4] Make stack depth check work with asan's use-after-return Andres Freund <[email protected]> 2026-02-07 19:04 [PATCH v2 1/4] Make stack depth check work with asan's use-after-return Andres Freund <[email protected]> 2026-02-07 19:04 [PATCH v2 1/4] Make stack depth check work with asan's use-after-return Andres Freund <[email protected]> 2026-02-07 19:04 [PATCH v2 1/4] Make stack depth check work with asan's use-after-return Andres Freund <[email protected]> 2026-02-07 19:04 [PATCH v2 1/4] Make stack depth check work with asan's use-after-return Andres Freund <[email protected]> 2026-02-07 19:04 [PATCH v2 1/4] Make stack depth check work with asan's use-after-return Andres Freund <[email protected]> 2026-02-07 19:04 [PATCH v2 1/4] Make stack depth check work with asan's use-after-return Andres Freund <[email protected]> 2026-02-07 19:04 [PATCH v2 1/4] Make stack depth check work with asan's use-after-return Andres Freund <[email protected]> 2026-02-07 19:04 [PATCH v2 1/4] Make stack depth check work with asan's use-after-return Andres Freund <[email protected]> 2026-02-07 19:04 [PATCH v2 1/4] Make stack depth check work with asan's use-after-return Andres Freund <[email protected]> 2026-02-07 19:04 [PATCH v2 1/4] Make stack depth check work with asan's use-after-return Andres Freund <[email protected]> 2026-02-07 19:04 [PATCH v2 1/4] Make stack depth check work with asan's use-after-return Andres Freund <[email protected]> 2026-02-07 19:04 [PATCH v2 1/4] Make stack depth check work with asan's use-after-return Andres Freund <[email protected]> 2026-02-07 19:04 [PATCH v2 1/4] Make stack depth check work with asan's use-after-return Andres Freund <[email protected]> 2026-02-07 19:04 [PATCH v2 1/4] Make stack depth check work with asan's use-after-return Andres Freund <[email protected]>
This inbox is served by agora; see mirroring instructions for how to clone and mirror all data and code used for this inbox