TupleDescFinalize() failed to take into account virtual generated
columns, which are always stored as NULL in tuples. TupleDescFinalize()
didn't check for this, and that could result in attcacheoff being set for
and beyond virtual generated columns. Also, the TupleDesc's
firstNonGuaranteedAttr could also be set incorrectly, which could result
in the tuple deformation function deforming without checking for NULLs,
and deforming using incorrectly cached offsets.
This could result in tuples being deformed incorrectly, which could
result in incorrect results, ERRORs or possibly a crash.
This has been broken since
c456e39113.
Author: Chao Li <li.evan.chao@gmail.com>
Reported-by: Chao Li <li.evan.chao@gmail.com>
Reviewed-by: ChangAo Chen <cca5507@qq.com>
Reviewed-by: David Rowley <dgrowleyml@gmail.com>
Discussion: https://postgr.es/m/
A4BC563C-0CA3-4EF3-952A-
EA41F9E5BF1E%40gmail.com
for (int i = 0; i < tupdesc->natts; i++)
{
CompactAttribute *cattr = TupleDescCompactAttr(tupdesc, i);
for (int i = 0; i < tupdesc->natts; i++)
{
CompactAttribute *cattr = TupleDescCompactAttr(tupdesc, i);
+ Form_pg_attribute attr = TupleDescAttr(tupdesc, i);
/*
* Find the highest attnum which is guaranteed to exist in all tuples
/*
* Find the highest attnum which is guaranteed to exist in all tuples
*/
if (firstNonGuaranteedAttr == tupdesc->natts &&
(cattr->attnullability != ATTNULLABLE_VALID || !cattr->attbyval ||
*/
if (firstNonGuaranteedAttr == tupdesc->natts &&
(cattr->attnullability != ATTNULLABLE_VALID || !cattr->attbyval ||
- cattr->atthasmissing || cattr->attisdropped || cattr->attlen <= 0))
+ cattr->atthasmissing || cattr->attisdropped ||
+ cattr->attlen <= 0 ||
+ attr->attgenerated == ATTRIBUTE_GENERATED_VIRTUAL))
firstNonGuaranteedAttr = i;
firstNonGuaranteedAttr = i;
- if (cattr->attlen <= 0)
+ /*
+ * Don't cache offsets beyond fixed-width attributes. Virtual
+ * generated attributes are stored as NULLs in the tuple, so we don't
+ * cache offsets beyond these.
+ */
+ if (cattr->attlen <= 0 ||
+ attr->attgenerated == ATTRIBUTE_GENERATED_VIRTUAL)
break;
off = att_nominal_alignby(off, cattr->attalignby);
break;
off = att_nominal_alignby(off, cattr->attalignby);
{
/* Otherwise all required columns are guaranteed to exist */
firstNullAttr = natts;
{
/* Otherwise all required columns are guaranteed to exist */
firstNullAttr = natts;
+
+ /*
+ * Check TupleDescFinalize() didn't get confused when setting
+ * firstNonGuaranteedAttr. There should never be a NULL in a
+ * guaranteed column.
+ */
+ Assert(first_null_attr(tup->t_bits, natts) >= firstNullAttr);