Avoid dangling pointer in variant part reader
Checks
| Context |
Check |
Description |
| linaro-tcwg-bot/tcwg_gdb_build--master-arm |
success
|
Build passed
|
| linaro-tcwg-bot/tcwg_gdb_check--master-arm |
success
|
Test passed
|
| linaro-tcwg-bot/tcwg_gdb_build--master-aarch64 |
success
|
Build passed
|
| linaro-tcwg-bot/tcwg_gdb_check--master-aarch64 |
success
|
Test passed
|
Commit Message
I think the DWARF reader's variant part code may have a latent bug
with dangling pointers.
handle_variant_part does:
variant_field ¤t = fi->current_variant_part->variants.back ();
new_part = ¤t.variant_parts.emplace_back ();
Then stores a pointer to this:
scoped_restore save_current_variant_part
= make_scoped_restore (&fi->current_variant_part, new_part);
However, it then proceeds to call handle_struct_member_die -- which is
potentially recursive, if the type contains nested variant parts.
It seems to me that if the recursion causes the vector to be
reallocated, then the 'new_part' pointer will be invalidated.
There is a similar problem in handle_variant, where this reference:
variant_field &variant = fi->current_variant_part->variants.emplace_back ();
could conceivably be invalidated.
This patch fixes the problem by using a std::list instead of a
std::vector. This ensures that additions won't cause pointer or
reference invalidation.
---
gdb/dwarf2/read.c | 40 +++++++++++++++++++++++++---------------
1 file changed, 25 insertions(+), 15 deletions(-)
base-commit: 1b7a712f236504fdc4f741759eb45a21f122aec2
Comments
On 3/20/25 9:06 AM, Tom Tromey wrote:
> I think the DWARF reader's variant part code may have a latent bug
> with dangling pointers.
>
> handle_variant_part does:
>
> variant_field ¤t = fi->current_variant_part->variants.back ();
> new_part = ¤t.variant_parts.emplace_back ();
>
> Then stores a pointer to this:
>
> scoped_restore save_current_variant_part
> = make_scoped_restore (&fi->current_variant_part, new_part);
>
> However, it then proceeds to call handle_struct_member_die -- which is
> potentially recursive, if the type contains nested variant parts.
>
> It seems to me that if the recursion causes the vector to be
> reallocated, then the 'new_part' pointer will be invalidated.
>
> There is a similar problem in handle_variant, where this reference:
>
> variant_field &variant = fi->current_variant_part->variants.emplace_back ();
>
> could conceivably be invalidated.
>
> This patch fixes the problem by using a std::list instead of a
> std::vector. This ensures that additions won't cause pointer or
> reference invalidation.
I do see the problem with the "variant_parts" vector, but not with the
"variants" vector. I don't see exactly when an entry would be added to
the "variants" vector while holding a reference to an existing element
of it.
Simon
>>>>> "Simon" == Simon Marchi <simark@simark.ca> writes:
Simon> I do see the problem with the "variant_parts" vector, but not with the
Simon> "variants" vector. I don't see exactly when an entry would be added to
Simon> the "variants" vector while holding a reference to an existing element
Simon> of it.
In handle_variant:
variant_field &variant = fi->current_variant_part->variants.emplace_back ();
[...]
// possibly recursive call
handle_struct_member_die (variant_child, type, fi, template_args, cu);
[...]
variant.last_field = fi->fields.size ();
Tom
On 2025-03-20 13:55, Tom Tromey wrote:
>>>>>> "Simon" == Simon Marchi <simark@simark.ca> writes:
>
> Simon> I do see the problem with the "variant_parts" vector, but not with the
> Simon> "variants" vector. I don't see exactly when an entry would be added to
> Simon> the "variants" vector while holding a reference to an existing element
> Simon> of it.
>
> In handle_variant:
>
> variant_field &variant = fi->current_variant_part->variants.emplace_back ();
> [...]
> // possibly recursive call
> handle_struct_member_die (variant_child, type, fi, template_args, cu);
> [...]
> variant.last_field = fi->fields.size ();
But if some variants get added to a variant_part_builder::variants
vector during that recursion, won't it be another variant_part_builder
instance? Because you'll have something like:
<1> DW_TAG_variant_part
<2> DW_TAG_variant
<3> DW_TAG_variant_part
<4> DW_TAG_variant
During the handling of the inner DW_TAG_variant_part,
fi->current_variant_part will get temporarily replaced by another
variant_part_builder.
Unless you can have:
<1> DW_TAG_variant_part
<2> DW_TAG_variant
<3> DW_TAG_variant
But I'm not sure what it would mean.
Actually, even for the variant_part_builder vector, it looks like the
same situation. While handling one variant part, if recursion happens
and we process a nested variant part, that variant part will get added to
a different vector than the top-level variant part is in. The nested
variant part will be added to the vector owned by the variant just above
(<2>, in the first example).
Simon
Simon> Actually, even for the variant_part_builder vector, it looks like the
Simon> same situation. While handling one variant part, if recursion happens
Simon> and we process a nested variant part, that variant part will get added to
Simon> a different vector than the top-level variant part is in. The nested
Simon> variant part will be added to the vector owned by the variant just above
Simon> (<2>, in the first example).
Yeah, I think you're right -- oops.
At least this explains why we never saw a bug here.
Tom
@@ -97,6 +97,7 @@
#include "dwarf2/error.h"
#include "gdbsupport/unordered_set.h"
#include "extract-store-integer.h"
+#include <list>
/* When == 1, print basic high level tracing messages.
When > 1, be more verbose.
@@ -619,8 +620,9 @@ struct variant_field
int first_field = -1;
int last_field = -1;
- /* A variant can contain other variant parts. */
- std::vector<variant_part_builder> variant_parts;
+ /* A variant can contain other variant parts. A list is used so
+ that the contained objects don't move during reallocation. */
+ std::list<variant_part_builder> variant_parts;
/* If we see a DW_TAG_variant, then this will be set if this is the
default branch. */
@@ -640,8 +642,10 @@ struct variant_part_builder
/* The offset of the discriminant field. */
sect_offset discriminant_offset {};
- /* Variants that are direct children of this variant part. */
- std::vector<variant_field> variants;
+ /* Variants that are direct children of this variant part. A list
+ is used so that the contained objects don't move during
+ reallocation. */
+ std::list<variant_field> variants;
/* True if we're currently reading a variant. */
bool processing_variant = false;
@@ -689,7 +693,7 @@ struct field_info
variant_part_builder *current_variant_part = nullptr;
/* This holds all the top-level variant parts attached to the type
we're reading. */
- std::vector<variant_part_builder> variant_parts;
+ std::list<variant_part_builder> variant_parts;
/* Return the total number of fields (including baseclasses). */
int nfields () const
@@ -10426,7 +10430,7 @@ static const gdb::array_view<variant_part> create_variant_parts
(struct obstack *obstack,
const offset_map_type &offset_map,
struct field_info *fi,
- const std::vector<variant_part_builder> &variant_parts);
+ const std::list<variant_part_builder> &variant_parts);
/* Fill in a "struct variant" for a given variant field. RESULT is
the variant to fill in. OBSTACK is where any needed allocations
@@ -10475,12 +10479,15 @@ create_one_variant_part (variant_part &result,
}
size_t n = builder.variants.size ();
- variant *output = new (obstack) variant[n];
- for (size_t i = 0; i < n; ++i)
- create_one_variant (output[i], obstack, offset_map, fi,
- builder.variants[i]);
+ variant *new_variants = new (obstack) variant[n];
+ variant *out_iter = new_variants;
+ for (const variant_field &field : builder.variants)
+ {
+ create_one_variant (*out_iter, obstack, offset_map, fi, field);
+ ++out_iter;
+ }
- result.variants = gdb::array_view<variant> (output, n);
+ result.variants = gdb::array_view<variant> (new_variants, n);
}
/* Create a vector of variant parts that can be attached to a type.
@@ -10493,16 +10500,19 @@ static const gdb::array_view<variant_part>
create_variant_parts (struct obstack *obstack,
const offset_map_type &offset_map,
struct field_info *fi,
- const std::vector<variant_part_builder> &variant_parts)
+ const std::list<variant_part_builder> &variant_parts)
{
if (variant_parts.empty ())
return {};
size_t n = variant_parts.size ();
variant_part *result = new (obstack) variant_part[n];
- for (size_t i = 0; i < n; ++i)
- create_one_variant_part (result[i], obstack, offset_map, fi,
- variant_parts[i]);
+ variant_part *out_iter = result;
+ for (const variant_part_builder &builder : variant_parts)
+ {
+ create_one_variant_part (*out_iter, obstack, offset_map, fi, builder);
+ ++out_iter;
+ }
return gdb::array_view<variant_part> (result, n);
}