When such a trigger returns the old row version, it naturally get
stored in the slot for the trigger result. When a table AMs doesn't
store HeapTuples internally, ExecBRUpdateTriggers() frees the old row
version passed to triggers - but before this fix it might still be
referenced by the slot holding the new tuple.
Noticed when running the out-of-core zheap AM against the in-core
version of tableam.
Author: Andres Freund
        {
            ExecForceStoreHeapTuple(newtuple, newslot);
 
+           /*
+            * If the tuple returned by the trigger / being stored, is the old
+            * row version, and the heap tuple passed to the trigger was
+            * allocated locally, materialize the slot. Otherwise we might
+            * free it while still referenced by the slot.
+            */
+           if (should_free_trig && newtuple == trigtuple)
+               ExecMaterializeSlot(newslot);
+
            if (should_free_new)
                heap_freetuple(oldtuple);