Details
Description
Copy_field carries two members naming the fields it copies between:
Field *from_field,*to_field;
|
String tmp; // For items |
|
|
Copy_field() = default; |
~Copy_field() = default; |
void set(Field *to,Field *from,bool save); // Field to field |
void set(uchar *to,Field *from); // Field to string |
The default constructor is trivial and every array of these is made by an array
new carrying no initialiser, which default-initialises rather than
value-initialises, so both members start out indeterminate:
if (!(param->copy_field= new (thd->mem_root) Copy_field[field_count])) |
TMP_TABLE_PARAM::copy_field is allocated that way in
Create_tmp_table::start() and again in setup_copy_fields(), and
multi_update::initialize_tables() does the same for its own array.
Only one of the two set() overloads then fills them in.
The two overloads disagree
Copy_field::set(Field *to, Field *from, bool save) – the field-to-field
form – assigns from_field and to_field and installs do_copy
functions that use them.
Copy_field::set(uchar *to, Field *from) – the field-to-string-buffer form,
whose comment says "The 'to' buffer should have a size of
field->pack_length()+1" – assigns neither:
void Copy_field::set(uchar *to,Field *from) |
{
|
from_ptr=from->ptr;
|
to_ptr=to;
|
from_length=from->pack_length_in_rec();
|
if (from->maybe_null()) |
{
|
from_null_ptr=from->null_ptr;
|
from_bit= from->null_bit;
|
to_ptr[0]= 1; // Null as default value |
to_null_ptr= (uchar*) to_ptr++;
|
to_bit= 1;
|
if (from->table->maybe_null) |
{
|
null_row= &from->table->null_row;
|
do_copy= do_outer_field_to_null_str;
|
}
|
else |
do_copy= do_field_to_null_str;
|
}
|
else |
{
|
to_null_ptr= 0; // For easy debugging |
do_copy= Field::do_field_eq;
|
}
|
}
|
Note to_null_ptr= 0 carrying the comment "For easy debugging", so the
function does set a member it does not need purely so that a debugger shows
something sensible. The two Field * members are left alone.
Both kinds go into the same array, walked by one loop
Create_tmp_table::finalize() appends field-to-field entries and
setup_copy_fields() appends field-to-string-buffer entries, both to
param->copy_field, and copy_fields() walks the one array:
void
|
copy_fields(TMP_TABLE_PARAM *param)
|
{
|
Copy_field *ptr=param->copy_field;
|
Copy_field *end=param->copy_field_end;
|
|
|
DBUG_ASSERT((ptr != NULL && end >= ptr) || (ptr == NULL && end == NULL));
|
|
|
for (; ptr != end; ptr++) |
(*ptr->do_copy)(ptr);
|
Nothing distinguishes the two kinds. An entry's do_copy pointer is the only
record of which set() made it, and that is not readable as a discriminator.
So a walk of this array cannot ask an entry which fields it connects: reading
ptr->to_field is undefined behaviour for every entry the string form made,
and reading a wild pointer as a Field * follows it into arbitrary memory.
Nothing trips it today
The three do_copy functions the string form installs –
do_field_to_null_str, do_outer_field_to_null_str and
Field::do_field_eq – use only to_ptr, from_ptr, from_length,
the null pointers and null_row. None of them reads from_field or
to_field. Every function that does read those members
(do_copy_null, do_outer_field_null, do_copy_not_null,
do_save_blob, do_conv_blob, do_field_string and the rest) is
installed only by the field-to-field form, which fills them in. So the members
are read only where they were written, and the defect is latent rather than
live.
It is reachable the moment anything else walks param->copy_field. Observed
while doing exactly that: a guard of the form
if (ptr->to_field && ...) |
added to the copy_fields() loop segfaults on an ordinary
GROUP BY query, the indeterminate pointer being non-null and unmapped:
sql/field.h:1259(Field::is_valid_json_static() const)
|
sql/sql_select.cc(copy_fields(TMP_TABLE_PARAM*))
|
sql/sql_select.cc(end_send_group(JOIN*, st_join_table*, bool))
|
sql/sql_select.cc(evaluate_join_record(JOIN*, st_join_table*, int))
|
A null check is the obvious way to write such a guard and it does not work
here, which is what makes this worth recording rather than leaving to whoever
next reaches for these members.
Provenance
The class predates the current history: Copy_field arrives with the
initial import, and the members have never been initialised. 08c852026dd
("Apply clang-tidy to remove empty constructors / destructors", 2023-02-07)
changed the spelling from
Copy_field() {}
|
~Copy_field() {}
|
to = default, which leaves the behaviour identical – an empty
user-provided constructor default-initialises the members exactly as a trivial
one does. That commit neither introduced nor worsened this; it is named only so
that a reader who finds it in git blame does not stop there.
The asymmetry between the two set() overloads is present unchanged at the
tips of 10.5, 10.6, 10.11, 11.4, 11.8, 12.0, 12.1, 12.2, 12.3 and main.
Attachments
Issue Links
- split from
-
MDEV-40642 Project "JSON Phoenix" (placeholder)
-
- Open
-