aboutsummaryrefslogtreecommitdiff
path: root/gdb/rust-lang.c
diff options
context:
space:
mode:
authorTom Tromey <tromey@adacore.com>2024-01-30 10:06:46 -0700
committerTom Tromey <tromey@adacore.com>2024-02-20 13:51:30 -0700
commitb0dd661fa16a424f059b1e1d80e779508b1a9a12 (patch)
tree7d1c9253ab0284c90ab7859d402a7140980e23c3 /gdb/rust-lang.c
parent94a75b0363b1e09416e9bd24cac72d98864688d8 (diff)
downloadgdb-b0dd661fa16a424f059b1e1d80e779508b1a9a12.zip
gdb-b0dd661fa16a424f059b1e1d80e779508b1a9a12.tar.gz
gdb-b0dd661fa16a424f059b1e1d80e779508b1a9a12.tar.bz2
Rewrite Rust slice type handling
This patch rewrites the handling of slice types in Rust. More recent versions of the Rust compiler changed how unsized types were emitted, letting gdb inspect them more nicely. However, gdb did not do this, and in fact treated all such types as if they were slices of arrays, which is incorrect. This patch rewrites this handling and removes the restriction that unsized types must be array slices. I've added a comment explaining how unsized types are represented to rust-lang.c as well. I looked into a different approach, namely changing the DWARF reader to fix up slice types to have a dynamic type. However, the approach taken here turned out to be simpler. Tested on x86-64 Fedora 38 with a variety of Rust compiler versions. Bug: https://sourceware.org/bugzilla/show_bug.cgi?id=30330
Diffstat (limited to 'gdb/rust-lang.c')
-rw-r--r--gdb/rust-lang.c239
1 files changed, 174 insertions, 65 deletions
diff --git a/gdb/rust-lang.c b/gdb/rust-lang.c
index e0b5a88..ab537cc 100644
--- a/gdb/rust-lang.c
+++ b/gdb/rust-lang.c
@@ -153,9 +153,9 @@ rust_tuple_struct_type_p (struct type *type)
return type->num_fields () > 0 && rust_underscore_fields (type);
}
-/* See rust-lang.h. */
+/* Return true if TYPE is "slice-like"; false otherwise. */
-bool
+static bool
rust_slice_type_p (const struct type *type)
{
if (type->code () == TYPE_CODE_STRUCT
@@ -269,6 +269,125 @@ rust_get_trait_object_pointer (struct value *value)
return value_cast (pointer_type, value_field (value, 1 - vtable_field));
}
+/* Find and possibly rewrite the unsized part of a slice-like type.
+
+ This function has two modes. If the out parameters are both NULL,
+ it will return true if an unsized member of IN_TYPE is found.
+
+ If the out parameters are both non-NULL, it will do the same, but
+ will also rewrite the unsized member's type to be an array of the
+ appropriate type. BOUND is the upper bound of the new array.
+
+ See convert_slice to understand the different kinds of unsized type
+ and how they are represented.
+*/
+static bool
+rewrite_slice_type (struct type *in_type, struct type **new_type,
+ LONGEST bound, ULONGEST *additional_length)
+{
+ if (in_type->code () != TYPE_CODE_STRUCT)
+ return false;
+
+ unsigned nfields = in_type->num_fields ();
+ if (nfields == 0)
+ return false;
+
+ struct type *rewritten;
+ const field &field = in_type->field (nfields - 1);
+ struct type *field_type = field.type ();
+ if (field.loc_kind () == FIELD_LOC_KIND_BITPOS
+ && field.loc_bitpos () == 8 * in_type->length ())
+ {
+ if (additional_length == nullptr)
+ return true;
+ rewritten = lookup_array_range_type (field_type, 0, bound);
+ *additional_length = rewritten->length ();
+ }
+ else
+ {
+ if (!rewrite_slice_type (field_type, &rewritten, bound,
+ additional_length))
+ return false;
+ if (additional_length == nullptr)
+ return true;
+ }
+
+ struct type *result = copy_type (in_type);
+ result->copy_fields (in_type);
+ result->field (nfields - 1).set_type (rewritten);
+ result->set_length (result->length () + *additional_length);
+
+ *new_type = result;
+ return true;
+}
+
+/* Convert a Rust slice to its "true" representation.
+
+ The Rust compiler emits slices as "fat" pointers like:
+
+ struct { payload *data_ptr; usize length }
+
+ Any sort of unsized type is emitted this way.
+
+ If 'payload' is a struct type, then it must be searched to see if
+ the trailing field is unsized. This has to be done recursively (as
+ in, if the final field in the struct type itself has struct type,
+ then that type must be searched). In this scenario, the unsized
+ field can be recognized because it does not contribute to the
+ type's size.
+
+ If 'payload' does not have a trailing unsized type, or if it is not
+ of struct type, then this slice is "array-like". In this case
+ rewriting will return an array.
+*/
+static struct value *
+convert_slice (struct value *val)
+{
+ struct type *type = check_typedef (val->type ());
+ /* This must have been checked by the caller. */
+ gdb_assert (rust_slice_type_p (type));
+
+ struct value *len = value_struct_elt (&val, {}, "length", nullptr,
+ "slice");
+ LONGEST llen = value_as_long (len);
+
+ struct value *ptr = value_struct_elt (&val, {}, "data_ptr", nullptr,
+ "slice");
+ struct type *original_type = ptr->type ()->target_type ();
+ ULONGEST new_length_storage = 0;
+ struct type *new_type = nullptr;
+ if (!rewrite_slice_type (original_type, &new_type, llen - 1,
+ &new_length_storage))
+ new_type = lookup_array_range_type (original_type, 0, llen - 1);
+
+ struct value *result = value::allocate_lazy (new_type);
+ result->set_lval (lval_memory);
+ result->set_address (value_as_address (ptr));
+ result->fetch_lazy ();
+
+ return result;
+}
+
+/* If TYPE is an array-like slice, return the element type; otherwise
+ return NULL. */
+static struct type *
+rust_array_like_element_type (struct type *type)
+{
+ /* Caller must check this. */
+ gdb_assert (rust_slice_type_p (type));
+ for (int i = 0; i < type->num_fields (); ++i)
+ {
+ if (strcmp (type->field (i).name (), "data_ptr") == 0)
+ {
+ struct type *base_type = type->field (i).type ()->target_type ();
+ if (rewrite_slice_type (base_type, nullptr, 0, nullptr))
+ return nullptr;
+ return base_type;
+ }
+ }
+ return nullptr;
+}
+
/* See language.h. */
@@ -324,57 +443,40 @@ static const struct generic_val_print_decorations rust_decorations =
struct value *
rust_slice_to_array (struct value *val)
{
- struct type *type = check_typedef (val->type ());
- /* This must have been checked by the caller. */
- gdb_assert (rust_slice_type_p (type));
-
- struct value *base = value_struct_elt (&val, {}, "data_ptr", NULL,
- "slice");
- struct value *len = value_struct_elt (&val, {}, "length", NULL, "slice");
- LONGEST llen = value_as_long (len);
-
- struct type *elt_type = base->type ()->target_type ();
- struct type *array_type = lookup_array_range_type (elt_type, 0,
- llen - 1);
- struct value *array = value::allocate_lazy (array_type);
- array->set_lval (lval_memory);
- array->set_address (value_as_address (base));
-
- return array;
+ val = convert_slice (val);
+ if (val->type ()->code () != TYPE_CODE_ARRAY)
+ return nullptr;
+ return val;
}
/* Helper function to print a slice. */
-static void
-rust_val_print_slice (struct value *val, struct ui_file *stream, int recurse,
- const struct value_print_options *options)
+void
+rust_language::val_print_slice
+ (struct value *val, struct ui_file *stream, int recurse,
+ const struct value_print_options *options) const
{
- struct value *base = value_struct_elt (&val, {}, "data_ptr", NULL,
- "slice");
- struct value *len = value_struct_elt (&val, {}, "length", NULL, "slice");
+ struct type *orig_type = check_typedef (val->type ());
+ val = convert_slice (val);
struct type *type = check_typedef (val->type ());
- if (strcmp (type->name (), "&str") == 0)
- val_print_string (base->type ()->target_type (), "UTF-8",
- value_as_address (base), value_as_long (len), stream,
- options);
- else
- {
- LONGEST llen = value_as_long (len);
- type_print (val->type (), "", stream, -1);
- gdb_printf (stream, " ");
-
- if (llen == 0)
- gdb_printf (stream, "[]");
- else
+ /* &str is handled here; but for all other slice types it is fine to
+ simply print the contents. */
+ if (orig_type->name () != nullptr
+ && strcmp (orig_type->name (), "&str") == 0)
+ {
+ LONGEST low_bound, high_bound;
+ if (get_array_bounds (type, &low_bound, &high_bound))
{
- struct value *array = rust_slice_to_array (val);
- array->fetch_lazy ();
- generic_value_print (array, stream, recurse, options,
- &rust_decorations);
+ val_print_string (type->target_type (), "UTF-8",
+ val->address (), high_bound - low_bound + 1,
+ stream, options);
+ return;
}
}
+
+ value_print_inner (val, stream, recurse, options);
}
/* See rust-lang.h. */
@@ -390,7 +492,7 @@ rust_language::val_print_struct
if (rust_slice_type_p (type))
{
- rust_val_print_slice (val, stream, recurse, options);
+ val_print_slice (val, stream, recurse, options);
return;
}
@@ -1180,6 +1282,7 @@ rust_subscript (struct type *expect_type, struct expression *exp,
low = value_as_long (rhs);
struct type *type = check_typedef (lhs->type ());
+ struct type *orig_type = type;
if (noside == EVAL_AVOID_SIDE_EFFECTS)
{
struct type *base_type = nullptr;
@@ -1187,16 +1290,9 @@ rust_subscript (struct type *expect_type, struct expression *exp,
base_type = type->target_type ();
else if (rust_slice_type_p (type))
{
- for (int i = 0; i < type->num_fields (); ++i)
- {
- if (strcmp (type->field (i).name (), "data_ptr") == 0)
- {
- base_type = type->field (i).type ()->target_type ();
- break;
- }
- }
+ base_type = rust_array_like_element_type (type);
if (base_type == nullptr)
- error (_("Could not find 'data_ptr' in slice type"));
+ error (_("Cannot subscript non-array-like slice"));
}
else if (type->code () == TYPE_CODE_PTR)
base_type = type->target_type ();
@@ -1227,6 +1323,12 @@ rust_subscript (struct type *expect_type, struct expression *exp,
LONGEST low_bound;
struct value *base;
+ if (rust_slice_type_p (type))
+ {
+ lhs = convert_slice (lhs);
+ type = check_typedef (lhs->type ());
+ }
+
if (type->code () == TYPE_CODE_ARRAY)
{
base = lhs;
@@ -1236,15 +1338,6 @@ rust_subscript (struct type *expect_type, struct expression *exp,
error (_("Found array with non-zero lower bound"));
++high_bound;
}
- else if (rust_slice_type_p (type))
- {
- struct value *len;
-
- base = value_struct_elt (&lhs, {}, "data_ptr", NULL, "slice");
- len = value_struct_elt (&lhs, {}, "length", NULL, "slice");
- low_bound = 0;
- high_bound = value_as_long (len);
- }
else if (type->code () == TYPE_CODE_PTR)
{
base = lhs;
@@ -1284,9 +1377,11 @@ rust_subscript (struct type *expect_type, struct expression *exp,
usize = language_lookup_primitive_type (exp->language_defn,
exp->gdbarch,
"usize");
- const char *new_name = ((type != nullptr
- && rust_slice_type_p (type))
- ? type->name () : "&[*gdb*]");
+ /* Preserve the name for slice-of-slice; this lets
+ string-printing work a bit more nicely. */
+ const char *new_name = ((orig_type != nullptr
+ && rust_slice_type_p (orig_type))
+ ? orig_type->name () : "&[*gdb*]");
slice = rust_slice_type (new_name, result->type (), usize);
@@ -1477,7 +1572,11 @@ rust_structop::evaluate (struct type *expect_type,
}
}
else
- result = value_struct_elt (&lhs, {}, field_name, NULL, "structure");
+ {
+ if (rust_slice_type_p (type))
+ lhs = convert_slice (lhs);
+ result = value_struct_elt (&lhs, {}, field_name, NULL, "structure");
+ }
if (noside == EVAL_AVOID_SIDE_EFFECTS)
result = value::zero (result->type (), result->lval ());
return result;
@@ -1678,6 +1777,16 @@ rust_language::emitchar (int ch, struct type *chtype,
/* See language.h. */
bool
+rust_language::is_array_like (struct type *type) const
+{
+ if (!rust_slice_type_p (type))
+ return false;
+ return rust_array_like_element_type (type) != nullptr;
+}
+
+/* See language.h. */
+
+bool
rust_language::is_string_type_p (struct type *type) const
{
LONGEST low_bound, high_bound;