diff --git a/_drgn.pyi b/_drgn.pyi index 6b2ae7e64..60f94560b 100644 --- a/_drgn.pyi +++ b/_drgn.pyi @@ -1690,18 +1690,48 @@ def NULL(prog: Program, type: Union[str, Type]) -> Object: def cast(type: Union[str, Type], obj: Object) -> Object: """ - Get the value of the given object casted to another type. + Get the value of an object explicitly casted to another type. - Objects with a scalar type (integer, boolean, enumerated, floating-point, - or pointer) can be casted to a different scalar type. Other objects can - only be casted to the same type. This always results in a value object. See - also :func:`drgn.reinterpret()`. + This uses the programming language's rules for explicit conversions, like + the cast operator. >>> cast("unsigned int", Object(prog, "float", 2.0)) (unsigned int)2 + >>> cast("void *", Object(prog, "int", 0)) + (void *)0x0 + + See also :func:`implicit_convert()` for implicit conversions (which usually + do stricter type checking) and :func:`reinterpret()` for reinterpreting the + raw memory of an object. :param type: Type to cast to. :param obj: Object to cast. + :return: Casted object. This is always a value object. + :raises TypeError: if casting *obj* to *type* is not allowed + """ + ... + +def implicit_convert(type: Union[str, Type], obj: Object) -> Object: + """ + Get the value of an object implicitly converted to another type. + + This uses the programming language's rules for implicit conversions, like + when assigning to a variable or passing arguments to a function call. + + >>> implicit_convert("unsigned int", Object(prog, "float", 2.0)) + (unsigned int)2 + >>> implicit_convert("void *", Object(prog, "int", 0)) + Traceback (most recent call last): + ... + TypeError: cannot convert 'int' to incompatible type 'void *' + + See also :func:`cast()` for explicit conversions and :func:`reinterpret()` + for reinterpreting the raw memory of an object. + + :param type: Type to convert to. + :param obj: Object to convert. + :return: Converted object. This is always a value object. + :raises TypeError: if converting *obj* to *type* is not allowed """ ... @@ -1710,15 +1740,24 @@ def reinterpret(type: Union[str, Type], obj: Object) -> Object: Get the representation of an object reinterpreted as another type. This reinterprets the raw memory of the object, so an object can be - reinterpreted as any other type. Reinterpreting a reference results in a - reference, and reinterpreting a value results in a value. See also - :func:`drgn.cast()`. + reinterpreted as any other type. >>> reinterpret("unsigned int", Object(prog, "float", 2.0)) (unsigned int)1073741824 + .. note:: + + You usually want :func:`cast()` or :func:`implicit_convert()` instead, + which convert the *value* of an object instead of its in-memory + representation. + :param type: Type to reinterpret as. :param obj: Object to reinterpret. + :return: Reinterpreted object. If *obj* is a reference object, then this is + a reference object. If *obj* is a value object, then this is a value + object. + :raises OutOfBoundsError: if *obj* is a value object and *type* is larger + than *obj* """ ... @@ -2401,9 +2440,6 @@ class TypeKind(enum.Enum): FLOAT = ... """Floating-point type.""" - COMPLEX = ... - """Complex type.""" - STRUCT = ... """Structure type.""" diff --git a/docs/api_reference.rst b/docs/api_reference.rst index 63169e798..413dc38e1 100644 --- a/docs/api_reference.rst +++ b/docs/api_reference.rst @@ -99,6 +99,7 @@ Objects .. drgndoc:: Object .. drgndoc:: NULL .. drgndoc:: cast +.. drgndoc:: implicit_convert .. drgndoc:: reinterpret .. drgndoc:: container_of diff --git a/drgn/__init__.py b/drgn/__init__.py index 903884b75..9017d7787 100644 --- a/drgn/__init__.py +++ b/drgn/__init__.py @@ -84,6 +84,7 @@ filename_matches, get_default_prog, host_platform, + implicit_convert, offsetof, program_from_core_dump, program_from_kernel, @@ -140,6 +141,7 @@ "filename_matches", "get_default_prog", "host_platform", + "implicit_convert", "offsetof", "program_from_core_dump", "program_from_kernel", diff --git a/libdrgn/build-aux/gen_c_keywords_inc_strswitch.py b/libdrgn/build-aux/gen_c_keywords_inc_strswitch.py index f3e82a8c6..42ffbaa9b 100755 --- a/libdrgn/build-aux/gen_c_keywords_inc_strswitch.py +++ b/libdrgn/build-aux/gen_c_keywords_inc_strswitch.py @@ -5,7 +5,6 @@ C_KEYWORDS = ( "_Atomic", "_Bool", - "_Complex", "char", "class", "const", diff --git a/libdrgn/c_lexer.h b/libdrgn/c_lexer.h index 6351ae978..4808fe50a 100644 --- a/libdrgn/c_lexer.h +++ b/libdrgn/c_lexer.h @@ -25,8 +25,7 @@ enum { C_TOKEN_BOOL, C_TOKEN_FLOAT, C_TOKEN_DOUBLE, - C_TOKEN_COMPLEX, - MAX_SPECIFIER_TOKEN = C_TOKEN_COMPLEX, + MAX_SPECIFIER_TOKEN = C_TOKEN_DOUBLE, MIN_QUALIFIER_TOKEN, C_TOKEN_CONST = MIN_QUALIFIER_TOKEN, C_TOKEN_RESTRICT, diff --git a/libdrgn/configure.ac b/libdrgn/configure.ac index 1de4c84d0..e69ada381 100644 --- a/libdrgn/configure.ac +++ b/libdrgn/configure.ac @@ -20,6 +20,7 @@ AC_SYS_LARGEFILE AC_REQUIRE_AUX_FILE([tap-driver.sh]) MY_C_AUTO +MY_C_SWITCH_ENUM MY_CHECK_VA_ARGS_COMMA_DELETION AC_ARG_ENABLE([openmp], diff --git a/libdrgn/drgn.h b/libdrgn/drgn.h index b322d362c..15983438a 100644 --- a/libdrgn/drgn.h +++ b/libdrgn/drgn.h @@ -2083,23 +2083,42 @@ struct drgn_error *drgn_format_object(const struct drgn_object *obj, */ /** - * Set a @ref drgn_object to the value of an object casted to a another type. + * Set a @ref drgn_object to the value of an object explicitly casted to a + * another type. * - * Objects with a scalar type can be casted to a different scalar type. Other - * objects can only be casted to the same type. @p res is always set to a value - * object. + * This uses the programming language's rules for explicit conversions, like the + * cast operator. * - * @sa drgn_object_reinterpret() + * @sa drgn_object_implicit_convert(), drgn_object_reinterpret() * - * @param[out] res Object to set. + * @param[out] res Object to set. Always set to a value object. * @param[in] qualified_type New type. - * @param[in] obj Object to read. + * @param[in] obj Object to cast. * @return @c NULL on success, non-@c NULL on error. */ struct drgn_error *drgn_object_cast(struct drgn_object *res, struct drgn_qualified_type qualified_type, const struct drgn_object *obj); +/** + * Set a @ref drgn_object to the value of an object implicitly converted to a + * another type. + * + * This uses the programming language's rules for implicit conversions, like + * when assigning to a variable or passing arguments to a function call. + * + * @sa drgn_object_cast(), drgn_object_reinterpret() + * + * @param[out] res Object to set. Always set to a value object. + * @param[in] qualified_type New type. + * @param[in] obj Object to convert. + * @return @c NULL on success, non-@c NULL on error. + */ +struct drgn_error * +drgn_object_implicit_convert(struct drgn_object *res, + struct drgn_qualified_type qualified_type, + const struct drgn_object *obj); + /** * Set a @ref drgn_object to the representation of an object reinterpreted as * another type. @@ -2107,12 +2126,10 @@ struct drgn_error *drgn_object_cast(struct drgn_object *res, * This reinterprets the raw memory of the object, so an object can be * reinterpreted as any other type. * - * If @c obj is a value, then @c res is set to a value; if @c obj is a - * reference, then @c res is set to a reference. - * - * @sa drgn_object_cast() + * @sa drgn_object_cast(), drgn_object_implicit_convert() * - * @param[out] res Object to set. + * @param[out] res Object to set. If @p obj is a value, set to a value. If @p + * obj is a reference, set to a reference. * @param[in] qualified_type New type. * @param[in] obj Object to reinterpret. * @return @c NULL on success, non-@c NULL on error. diff --git a/libdrgn/error.h b/libdrgn/error.h index 7d30f27ca..404c1d74a 100644 --- a/libdrgn/error.h +++ b/libdrgn/error.h @@ -84,6 +84,19 @@ drgn_qualified_type_error(const char *format, struct drgn_qualified_type qualified_type) __attribute__((__returns_nonnull__)); +/** + * Create a @ref drgn_error with two qualified type names. + * + * @param[in] format Format string for the type error. Must contain two `%s`, + * which will be replaced with the two type names, and no other conversion + * specifications. + */ +struct drgn_error * +drgn_2_qualified_types_error(const char *format, + struct drgn_qualified_type qualified_type1, + struct drgn_qualified_type qualified_type2) + __attribute__((__returns_nonnull__)); + /** * Create a @ref drgn_error for an incomplete type. * diff --git a/libdrgn/language.h b/libdrgn/language.h index 00f9ee315..6b76ee64b 100644 --- a/libdrgn/language.h +++ b/libdrgn/language.h @@ -133,6 +133,7 @@ struct drgn_language { */ drgn_float_literal_fn *float_literal; drgn_cast_op *op_cast; + drgn_cast_op *op_implicit_convert; drgn_bool_op *op_bool; drgn_cmp_op *op_cmp; drgn_binary_op *op_add; diff --git a/libdrgn/language_c.c b/libdrgn/language_c.c index fe420ad63..7b25bbb94 100644 --- a/libdrgn/language_c.c +++ b/libdrgn/language_c.c @@ -342,7 +342,7 @@ c_declare_variable(struct drgn_qualified_type qualified_type, struct string_callback *name, size_t indent, bool define_anonymous_type, struct string_builder *sb) { - SWITCH_ENUM(drgn_type_kind(qualified_type.type), + SWITCH_ENUM(drgn_type_kind(qualified_type.type)) { case DRGN_TYPE_VOID: case DRGN_TYPE_INT: case DRGN_TYPE_BOOL: @@ -361,7 +361,9 @@ c_declare_variable(struct drgn_qualified_type qualified_type, return c_declare_array(qualified_type, name, indent, sb); case DRGN_TYPE_FUNCTION: return c_declare_function(qualified_type, name, indent, sb); - ) + default: + UNREACHABLE(); + } } static struct drgn_error * @@ -493,7 +495,7 @@ static struct drgn_error * c_define_type(struct drgn_qualified_type qualified_type, size_t indent, struct string_builder *sb) { - SWITCH_ENUM(drgn_type_kind(qualified_type.type), + SWITCH_ENUM(drgn_type_kind(qualified_type.type)) { case DRGN_TYPE_VOID: case DRGN_TYPE_INT: case DRGN_TYPE_BOOL: @@ -514,7 +516,9 @@ c_define_type(struct drgn_qualified_type qualified_type, size_t indent, case DRGN_TYPE_FUNCTION: return drgn_error_create(DRGN_ERROR_INVALID_ARGUMENT, "function type cannot be formatted"); - ) + default: + UNREACHABLE(); + } } static struct drgn_error * @@ -1442,7 +1446,7 @@ c_format_array_object(const struct drgn_object *obj, if ((flags & DRGN_FORMAT_OBJECT_STRING) && iter.length && is_character_type(iter.element_type.type)) { - SWITCH_ENUM(obj->kind, + SWITCH_ENUM(obj->kind) { case DRGN_OBJECT_VALUE: { const unsigned char *buf; uint64_t size, i; @@ -1467,7 +1471,9 @@ c_format_array_object(const struct drgn_object *obj, return c_format_string(drgn_object_program(obj), obj->address, iter.length, sb); case DRGN_OBJECT_ABSENT: - ) + default: + UNREACHABLE(); + } } err = drgn_type_bit_size(iter.element_type.type, @@ -1564,7 +1570,7 @@ c_format_object_impl(const struct drgn_object *obj, size_t indent, return NULL; } - SWITCH_ENUM(drgn_type_kind(underlying_type), + SWITCH_ENUM(drgn_type_kind(underlying_type)) { case DRGN_TYPE_VOID: return drgn_error_create(DRGN_ERROR_TYPE, "cannot format void object"); @@ -1589,7 +1595,9 @@ c_format_object_impl(const struct drgn_object *obj, size_t indent, return c_format_function_object(obj, sb); case DRGN_TYPE_TYPEDEF: case DRGN_TYPE_POINTER: - ) + default: + UNREACHABLE(); + } } static struct drgn_error *c_format_object(const struct drgn_object *obj, @@ -3136,6 +3144,165 @@ static struct drgn_error *c_common_real_type(struct drgn_program *prog, return NULL; } +static struct drgn_error * +c_types_compatible_impl(struct drgn_qualified_type qualified_type1, + struct drgn_qualified_type qualified_type2, + bool *ret) +{ + struct drgn_error *err; + + // The types must have the same qualifiers. + if (qualified_type1.qualifiers != qualified_type2.qualifiers) { + *ret = false; + return NULL; + } + + struct drgn_type *type1 = drgn_underlying_type(qualified_type1.type); + struct drgn_type *type2 = drgn_underlying_type(qualified_type2.type); + + // If the type descriptors are the same, then the types are definitely + // compatible. + if (type1 == type2) + return NULL; + + if (drgn_type_kind(type1) != drgn_type_kind(type2)) { + // Enum types are compatible with their compatible integer type. + // but not with different enum types with the same compatible + // integer type. + if (drgn_type_kind(type1) == DRGN_TYPE_ENUM) { + qualified_type1.type = drgn_type_type(type1).type; + if (qualified_type1.type) { + return c_types_compatible_impl(qualified_type1, + qualified_type2, + ret); + } + } else if (drgn_type_kind(type2) == DRGN_TYPE_ENUM) { + qualified_type2.type = drgn_type_type(type2).type; + if (qualified_type2.type) { + return c_types_compatible_impl(qualified_type1, + qualified_type2, + ret); + } + } + *ret = false; + return NULL; + } + + SWITCH_ENUM(drgn_type_kind(type1)) { + case DRGN_TYPE_VOID: + case DRGN_TYPE_INT: + case DRGN_TYPE_BOOL: + case DRGN_TYPE_FLOAT: + // These types are deduplicated, so if they were compatible they + // would have had the same type descriptor. + *ret = false; + return NULL; + case DRGN_TYPE_STRUCT: + case DRGN_TYPE_UNION: + case DRGN_TYPE_CLASS: { + // It's expensive to check all of the members, so we do a sloppy + // check: if the tag and size are the same, then the types are + // _probably_ compatible. + if (drgn_type_is_complete(type1) && drgn_type_is_complete(type2) + && drgn_type_size(type1) != drgn_type_size(type2)) { + *ret = false; + return NULL; + } + const char *tag1 = drgn_type_tag(type1); + const char *tag2 = drgn_type_tag(type2); + if ((!tag1 != !tag2) || (tag1 && strcmp(tag1, tag2) != 0)) + *ret = false; + return NULL; + } + case DRGN_TYPE_ENUM: { + // We do a similar sloppy check here: if the tag and compatible + // type are the same, then the types are _probably_ compatible. + if (drgn_type_is_complete(type1) && drgn_type_is_complete(type2) + && drgn_underlying_type(drgn_type_type(type1).type) + != drgn_underlying_type(drgn_type_type(type2).type)) { + *ret = false; + return NULL; + } + const char *tag1 = drgn_type_tag(type1); + const char *tag2 = drgn_type_tag(type2); + if ((!tag1 != !tag2) || (tag1 && strcmp(tag1, tag2) != 0)) + *ret = false; + return NULL; + } + case DRGN_TYPE_POINTER: + // The types are compatible iff their referenced types are + // compatible. + return c_types_compatible_impl(drgn_type_type(type1), + drgn_type_type(type2), ret); + case DRGN_TYPE_ARRAY: + // The types are compatible iff their element types are + // compatible and, if both types are complete, their lengths are + // equal. + if (drgn_type_is_complete(type1) && drgn_type_is_complete(type2) + && drgn_type_length(type1) != drgn_type_length(type2)) { + *ret = false; + return NULL; + } + return c_types_compatible_impl(drgn_type_type(type1), + drgn_type_type(type2), ret); + case DRGN_TYPE_FUNCTION: { + // The types are compatible iff their return types are + // compatible, they have the same number of parameters, their + // corresponding parameter types are compatible, and neither is + // variadic or both are variadic. + // + // This is expensive, but there's no good shortcut like for + // structs and enums. + size_t num_parameters = drgn_type_num_parameters(type1); + if (num_parameters != drgn_type_num_parameters(type2) + || drgn_type_is_variadic(type1) + != drgn_type_is_variadic(type2)) { + *ret = false; + return NULL; + } + err = c_types_compatible_impl(drgn_type_type(type1), + drgn_type_type(type2), + ret); + if (err || !*ret) + return err; + struct drgn_type_parameter *parameters1 = + drgn_type_parameters(type1); + struct drgn_type_parameter *parameters2 = + drgn_type_parameters(type2); + for (size_t i = 0; i < num_parameters; i++) { + struct drgn_qualified_type parameter_type1; + err = drgn_parameter_type(¶meters1[i], + ¶meter_type1); + if (err) + return err; + struct drgn_qualified_type parameter_type2; + err = drgn_parameter_type(¶meters2[i], + ¶meter_type2); + if (err) + return err; + err = c_types_compatible_impl(parameter_type1, + parameter_type2, ret); + if (err || !*ret) + return err; + } + return NULL; + } + // This is already the underlying type, so it can't be a typedef. + case DRGN_TYPE_TYPEDEF: + default: + UNREACHABLE(); + } +} + +static struct drgn_error * +c_types_compatible(struct drgn_qualified_type qualified_type1, + struct drgn_qualified_type qualified_type2, + bool *ret) +{ + *ret = true; + return c_types_compatible_impl(qualified_type1, qualified_type2, ret); +} + static struct drgn_error *c_operand_type(const struct drgn_object *obj, struct drgn_operand_type *type_ret, bool *is_pointer_ret, @@ -3163,10 +3330,8 @@ static struct drgn_error *c_operand_type(const struct drgn_object *obj, break; } case DRGN_TYPE_FUNCTION: { - struct drgn_qualified_type function_type = { - .type = type_ret->underlying_type, - .qualifiers = type_ret->qualifiers, - }; + struct drgn_qualified_type function_type = + drgn_operand_type_qualified(type_ret); uint8_t address_size; err = drgn_program_address_size(drgn_object_program(obj), &address_size); @@ -3216,11 +3381,149 @@ static struct drgn_error *c_op_cast(struct drgn_object *res, const struct drgn_object *obj) { struct drgn_error *err; - struct drgn_operand_type type; - err = c_operand_type(obj, &type, NULL, NULL); + + struct drgn_object_type type; + err = drgn_object_type(qualified_type, 0, &type); + if (err) + return err; + + switch (drgn_type_kind(type.underlying_type)) { + case DRGN_TYPE_VOID: + drgn_object_set_absent_internal(res, &type); + return NULL; + case DRGN_TYPE_BOOL: { + bool truthy; + err = drgn_object_bool(obj, &truthy); + if (err) + return err; + return drgn_object_set_unsigned_internal(res, &type, truthy); + } + default: + break; + } + + struct drgn_operand_type obj_type; + err = c_operand_type(obj, &obj_type, NULL, NULL); if (err) return err; - return drgn_op_cast(res, qualified_type, obj, &type); + return drgn_op_cast(res, &type, obj, &obj_type); +} + +static struct drgn_error * +c_op_implicit_convert(struct drgn_object *res, + struct drgn_qualified_type qualified_type, + const struct drgn_object *obj) +{ + struct drgn_error *err; + + struct drgn_object_type type; + err = drgn_object_type(qualified_type, 0, &type); + if (err) + return err; + + if (drgn_type_kind(type.underlying_type) == DRGN_TYPE_BOOL) { + bool truthy; + err = drgn_object_bool(obj, &truthy); + if (err) + return err; + return drgn_object_set_unsigned_internal(res, &type, truthy); + } + + struct drgn_operand_type obj_type; + err = c_operand_type(obj, &obj_type, NULL, NULL); + if (err) + return err; + + SWITCH_ENUM(drgn_type_kind(type.underlying_type)) { + case DRGN_TYPE_INT: + case DRGN_TYPE_FLOAT: + case DRGN_TYPE_ENUM: + switch (drgn_type_kind(obj_type.underlying_type)) { + case DRGN_TYPE_INT: + case DRGN_TYPE_BOOL: + case DRGN_TYPE_FLOAT: + case DRGN_TYPE_ENUM: + break; + default: + goto incompatible_type_error; + } + break; + case DRGN_TYPE_STRUCT: + case DRGN_TYPE_UNION: + case DRGN_TYPE_CLASS: { + struct drgn_qualified_type unqualified_type1 = { + .type = type.underlying_type, + }; + struct drgn_qualified_type unqualified_type2 = { + .type = obj_type.underlying_type, + }; + bool compatible; + err = c_types_compatible(unqualified_type1, unqualified_type2, + &compatible); + if (err) + return err; + if (!compatible) + goto incompatible_type_error; + return drgn_object_slice_internal(res, obj, &type, 0, 0); + } + case DRGN_TYPE_POINTER: { + if (drgn_type_kind(obj_type.underlying_type) + != DRGN_TYPE_POINTER) + goto incompatible_type_error; + + struct drgn_qualified_type referenced_type = + drgn_type_type(type.underlying_type); + referenced_type.type = + drgn_underlying_type(referenced_type.type); + struct drgn_qualified_type obj_referenced_type = + drgn_type_type(obj_type.underlying_type); + obj_referenced_type.type = + drgn_underlying_type(obj_referenced_type.type); + + // The type pointed to by the left must have all of the + // qualifiers of the type pointed to by the right: + // (lhs.qualifiers & rhs.qualifiers) == rhs.qualifiers. + // We mask here and do the equality test below or in + // c_types_compatible(). + referenced_type.qualifiers &= obj_referenced_type.qualifiers; + + // The type pointed to by the left and the type pointed to by + // the right must be compatible, or at least one must be void. + if (drgn_type_kind(referenced_type.type) == DRGN_TYPE_VOID + || drgn_type_kind(obj_referenced_type.type) == DRGN_TYPE_VOID) { + if (referenced_type.qualifiers + != obj_referenced_type.qualifiers) + goto incompatible_type_error; + } else { + bool compatible; + err = c_types_compatible(referenced_type, obj_referenced_type, + &compatible); + if (err) + return err; + if (!compatible) + goto incompatible_type_error; + } + break; + } + case DRGN_TYPE_VOID: + case DRGN_TYPE_ARRAY: + case DRGN_TYPE_FUNCTION: + return drgn_qualified_type_error("cannot convert to '%s'", + qualified_type); + // We handled bool earlier. + case DRGN_TYPE_BOOL: + // This is already the underlying type, so it can't be a typedef. + case DRGN_TYPE_TYPEDEF: + default: + UNREACHABLE(); + } + + return drgn_op_cast(res, &type, obj, &obj_type); + +incompatible_type_error: + return drgn_2_qualified_types_error("cannot convert '%s' to incompatible type '%s'", + drgn_object_qualified_type(obj), + qualified_type); } /* @@ -3245,9 +3548,23 @@ static struct drgn_error *c_op_bool(const struct drgn_object *obj, bool *ret) struct drgn_type *underlying_type; underlying_type = drgn_underlying_type(obj->type); - if (drgn_type_kind(underlying_type) == DRGN_TYPE_ARRAY) { - *ret = true; - return NULL; + switch (drgn_type_kind(underlying_type)) { + case DRGN_TYPE_ARRAY: + case DRGN_TYPE_FUNCTION: + SWITCH_ENUM(obj->kind) { + case DRGN_OBJECT_VALUE: + *ret = true; + return NULL; + case DRGN_OBJECT_REFERENCE: + *ret = obj->address != 0; + return NULL; + case DRGN_OBJECT_ABSENT: + return &drgn_error_object_absent; + default: + UNREACHABLE(); + } + default: + break; } if (!drgn_type_is_scalar(underlying_type)) { @@ -3491,6 +3808,7 @@ LIBDRGN_PUBLIC const struct drgn_language drgn_language_c = { .bool_literal = c_bool_literal, .float_literal = c_float_literal, .op_cast = c_op_cast, + .op_implicit_convert = c_op_implicit_convert, .op_bool = c_op_bool, .op_cmp = c_op_cmp, .op_add = c_op_add, @@ -3521,6 +3839,7 @@ LIBDRGN_PUBLIC const struct drgn_language drgn_language_cpp = { .bool_literal = c_bool_literal, .float_literal = c_float_literal, .op_cast = c_op_cast, + .op_implicit_convert = c_op_implicit_convert, .op_bool = c_op_bool, .op_cmp = c_op_cmp, .op_add = c_op_add, diff --git a/libdrgn/m4/.gitignore b/libdrgn/m4/.gitignore index db7672f0b..5e048193b 100644 --- a/libdrgn/m4/.gitignore +++ b/libdrgn/m4/.gitignore @@ -5,4 +5,5 @@ !/ax_check_compile_flag.m4 !/ax_require_defined.m4 !/my_c_auto.m4 +!/my_c_switch_enum.m4 !/my_check_va_args_comma_deletion.m4 diff --git a/libdrgn/m4/my_c_switch_enum.m4 b/libdrgn/m4/my_c_switch_enum.m4 new file mode 100644 index 000000000..f64ac7279 --- /dev/null +++ b/libdrgn/m4/my_c_switch_enum.m4 @@ -0,0 +1,88 @@ +# Copyright (c) Meta Platforms, Inc. and affiliates. +# SPDX-License-Identifier: LGPL-2.1-or-later + +# Check whether our SWITCH_ENUM macro works with the current compiler. There +# are a few known limitations: +# +# - Before GCC 12.1, GCC can't parse it. This was fixed by GCC commit +# 1bf976a5de69 ("openmp: Actually ignore pragma_stmt pragmas for which +# c_parser_pragma returns false"). +# - Before GCC 12.2, GCC doesn't actually apply the warnings. This was fixed by +# GCC commit 98e2676558f6 ("c: Fix location for _Pragma tokens [PR97498]"). +# - Before Clang 18.1, Clang ignores -Wswitch-default. It was implemented in +# llvm-project commit c28178298513 ("[clang][Sema] Add -Wswitch-default +# warning option (#73077)"). +# +# Keep this in sync with util.h. +AC_DEFUN([MY_C_SWITCH_ENUM], +[AC_CACHE_CHECK([whether SWITCH_ENUM compiles], [my_cv_c_switch_enum_compiles], + [AC_COMPILE_IFELSE([AC_LANG_SOURCE([[ +#define SWITCH_ENUM(expr) \ + _Pragma("GCC diagnostic push") \ + _Pragma("GCC diagnostic error \"-Wswitch-enum\"") \ + _Pragma("GCC diagnostic error \"-Wswitch-default\"") \ + switch (expr) \ + _Pragma("GCC diagnostic pop") + +int main(void) +{ + enum { FOO, BAR } x; + SWITCH_ENUM(x) { + case FOO: + case BAR: + default: + break; + } + return 0; +} +]])], [my_cv_c_switch_enum_compiles=yes], [my_cv_c_switch_enum_compiles=no])]) + +if test "x$my_cv_c_switch_enum_compiles" = xyes; then + dnl Now we know that the macro compiles. Check whether it actually + dnl works. We don't do anything with this beyond logging it. + AC_CACHE_CHECK([whether SWITCH_ENUM catches missing enumeration values], + [my_cv_c_switch_enum_works], + [AC_COMPILE_IFELSE([AC_LANG_SOURCE([[ +#define SWITCH_ENUM(expr) \ + _Pragma("GCC diagnostic push") \ + _Pragma("GCC diagnostic error \"-Wswitch-enum\"") \ + _Pragma("GCC diagnostic error \"-Wswitch-default\"") \ + switch (expr) \ + _Pragma("GCC diagnostic pop") + +int main(void) +{ + enum { FOO, BAR } x; + SWITCH_ENUM(x) { + case FOO: + default: + break; + } + return 0; +} +]])], [my_cv_c_switch_enum_works=no], [my_cv_c_switch_enum_works=yes])]) + AC_CACHE_CHECK([whether SWITCH_ENUM catches missing default case], + [my_cv_c_switch_enum_default_works], + [AC_COMPILE_IFELSE([AC_LANG_SOURCE([[ +#define SWITCH_ENUM(expr) \ + _Pragma("GCC diagnostic push") \ + _Pragma("GCC diagnostic error \"-Wswitch-enum\"") \ + _Pragma("GCC diagnostic error \"-Wswitch-default\"") \ + switch (expr) \ + _Pragma("GCC diagnostic pop") + +int main(void) +{ + enum { FOO, BAR } x; + SWITCH_ENUM(x) { + case FOO: + case BAR: + break; + } + return 0; +} +]])], [my_cv_c_switch_enum_default_works=no], [my_cv_c_switch_enum_default_works=yes])]) +else + AC_DEFINE([SWITCH_ENUM(expr)], [switch (expr)]) +fi +]) diff --git a/libdrgn/object.c b/libdrgn/object.c index 43277a109..ad22c9d14 100644 --- a/libdrgn/object.c +++ b/libdrgn/object.c @@ -94,7 +94,7 @@ drgn_object_type_impl(struct drgn_type *type, struct drgn_type *underlying_type, } struct drgn_type *compatible_type = underlying_type; - SWITCH_ENUM(drgn_type_kind(compatible_type), + SWITCH_ENUM(drgn_type_kind(compatible_type)) { case DRGN_TYPE_ENUM: if (!drgn_type_is_complete(compatible_type)) { ret->encoding = DRGN_OBJECT_ENCODING_INCOMPLETE_INTEGER; @@ -161,7 +161,9 @@ drgn_object_type_impl(struct drgn_type *type, struct drgn_type *underlying_type, break; // This is already the underlying type, so it can't be a typedef. case DRGN_TYPE_TYPEDEF: - ) + default: + UNREACHABLE(); + } if (bit_field_size != 0 && drgn_type_kind(compatible_type) != DRGN_TYPE_INT @@ -450,7 +452,7 @@ drgn_object_set_reference_internal(struct drgn_object *res, address &= address_mask; bit_offset %= 8; if (bit_offset != 0) { - SWITCH_ENUM(type->encoding, + SWITCH_ENUM(type->encoding) { case DRGN_OBJECT_ENCODING_SIGNED: case DRGN_OBJECT_ENCODING_UNSIGNED: case DRGN_OBJECT_ENCODING_SIGNED_BIG: @@ -463,7 +465,9 @@ drgn_object_set_reference_internal(struct drgn_object *res, case DRGN_OBJECT_ENCODING_INCOMPLETE_BUFFER: return drgn_error_create(DRGN_ERROR_INVALID_ARGUMENT, "non-scalar must be byte-aligned"); - ) + default: + UNREACHABLE(); + } } if (type->bit_size > UINT64_MAX - bit_offset) { return drgn_error_format(DRGN_ERROR_OVERFLOW, @@ -501,7 +505,7 @@ drgn_object_set_absent(struct drgn_object *res, err = drgn_object_type(qualified_type, bit_field_size, &type); if (err) return err; - drgn_object_reinit(res, &type, DRGN_OBJECT_ABSENT); + drgn_object_set_absent_internal(res, &type); return NULL; } @@ -516,7 +520,7 @@ drgn_object_copy(struct drgn_object *res, const struct drgn_object *obj) "objects are from different programs"); } - SWITCH_ENUM(obj->kind, + SWITCH_ENUM(obj->kind) { case DRGN_OBJECT_VALUE: if (obj->encoding == DRGN_OBJECT_ENCODING_BUFFER || obj->encoding == DRGN_OBJECT_ENCODING_SIGNED_BIG @@ -554,29 +558,24 @@ drgn_object_copy(struct drgn_object *res, const struct drgn_object *obj) drgn_object_reinit_copy(res, obj); res->kind = DRGN_OBJECT_ABSENT; break; - ) + default: + UNREACHABLE(); + } return NULL; } -LIBDRGN_PUBLIC struct drgn_error * -drgn_object_slice(struct drgn_object *res, const struct drgn_object *obj, - struct drgn_qualified_type qualified_type, - uint64_t bit_offset, uint64_t bit_field_size) +struct drgn_error * +drgn_object_slice_internal(struct drgn_object *res, + const struct drgn_object *obj, + const struct drgn_object_type *type, + uint64_t bit_offset, uint64_t bit_field_size) { struct drgn_error *err; - if (drgn_object_program(res) != drgn_object_program(obj)) { - return drgn_error_create(DRGN_ERROR_INVALID_ARGUMENT, - "objects are from different programs"); - } - struct drgn_object_type type; - err = drgn_object_type(qualified_type, bit_field_size, &type); - if (err) - return err; - SWITCH_ENUM(obj->kind, + SWITCH_ENUM(obj->kind) { case DRGN_OBJECT_VALUE: { uint64_t bit_end; - if (__builtin_add_overflow(bit_offset, type.bit_size, &bit_end) + if (__builtin_add_overflow(bit_offset, type->bit_size, &bit_end) || bit_end > obj->bit_size) { return drgn_error_create(DRGN_ERROR_OUT_OF_BOUNDS, "out of bounds of value"); @@ -599,20 +598,40 @@ drgn_object_slice(struct drgn_object *res, const struct drgn_object *obj, if (err) return err; } - return drgn_object_set_from_buffer_internal(res, &type, buf, + return drgn_object_set_from_buffer_internal(res, type, buf, bit_offset); } case DRGN_OBJECT_REFERENCE: // obj->bit_offset + bit_offset can overflow, so apply the // byte-aligned part of bit_offset now. - return drgn_object_set_reference_internal(res, &type, + return drgn_object_set_reference_internal(res, type, obj->address + (bit_offset / 8), obj->bit_offset + (bit_offset % 8)); case DRGN_OBJECT_ABSENT: return &drgn_error_object_absent; - ) + default: + UNREACHABLE(); + } +} + +LIBDRGN_PUBLIC struct drgn_error * +drgn_object_slice(struct drgn_object *res, const struct drgn_object *obj, + struct drgn_qualified_type qualified_type, + uint64_t bit_offset, uint64_t bit_field_size) +{ + struct drgn_error *err; + if (drgn_object_program(res) != drgn_object_program(obj)) { + return drgn_error_create(DRGN_ERROR_INVALID_ARGUMENT, + "objects are from different programs"); + } + struct drgn_object_type type; + err = drgn_object_type(qualified_type, bit_field_size, &type); + if (err) + return err; + return drgn_object_slice_internal(res, obj, &type, bit_offset, + bit_field_size); } LIBDRGN_PUBLIC struct drgn_error * @@ -752,7 +771,7 @@ drgn_object_read(struct drgn_object *res, const struct drgn_object *obj) { struct drgn_error *err; - SWITCH_ENUM(obj->kind, + SWITCH_ENUM(obj->kind) { case DRGN_OBJECT_VALUE: return drgn_object_copy(res, obj); case DRGN_OBJECT_REFERENCE: { @@ -771,7 +790,9 @@ drgn_object_read(struct drgn_object *res, const struct drgn_object *obj) } case DRGN_OBJECT_ABSENT: return &drgn_error_object_absent; - ) + default: + UNREACHABLE(); + } } LIBDRGN_PUBLIC struct drgn_error * @@ -780,7 +801,7 @@ drgn_object_read_value(const struct drgn_object *obj, union drgn_value *value, { struct drgn_error *err; - SWITCH_ENUM(obj->kind, + SWITCH_ENUM(obj->kind) { case DRGN_OBJECT_VALUE: *ret = &obj->value; return NULL; @@ -791,7 +812,9 @@ drgn_object_read_value(const struct drgn_object *obj, union drgn_value *value, return err; case DRGN_OBJECT_ABSENT: return &drgn_error_object_absent; - ) + default: + UNREACHABLE(); + } } LIBDRGN_PUBLIC struct drgn_error * @@ -804,7 +827,7 @@ drgn_object_read_bytes(const struct drgn_object *obj, void *buf) obj->type); } - SWITCH_ENUM(obj->kind, + SWITCH_ENUM(obj->kind) { case DRGN_OBJECT_VALUE: if (obj->encoding == DRGN_OBJECT_ENCODING_BUFFER || obj->encoding == DRGN_OBJECT_ENCODING_SIGNED_BIG @@ -891,7 +914,9 @@ drgn_object_read_bytes(const struct drgn_object *obj, void *buf) } case DRGN_OBJECT_ABSENT: return &drgn_error_object_absent; - ) + default: + UNREACHABLE(); + } } static struct drgn_error * @@ -1032,7 +1057,7 @@ drgn_object_read_c_string(const struct drgn_object *obj, char **ret) } else { max_size = SIZE_MAX; } - SWITCH_ENUM(obj->kind, + SWITCH_ENUM(obj->kind) { case DRGN_OBJECT_VALUE: { const char *buf; uint64_t value_size; @@ -1058,7 +1083,9 @@ drgn_object_read_c_string(const struct drgn_object *obj, char **ret) break; case DRGN_OBJECT_ABSENT: return &drgn_error_object_absent; - ) + default: + UNREACHABLE(); + } break; default: return drgn_type_error("string() argument must be an array or pointer, not '%s'", @@ -1337,6 +1364,20 @@ drgn_object_cast(struct drgn_object *res, return lang->op_cast(res, qualified_type, obj); } +LIBDRGN_PUBLIC struct drgn_error * +drgn_object_implicit_convert(struct drgn_object *res, + struct drgn_qualified_type qualified_type, + const struct drgn_object *obj) +{ + const struct drgn_language *lang = drgn_type_language(qualified_type.type); + + if (drgn_object_program(res) != drgn_object_program(obj)) { + return drgn_error_create(DRGN_ERROR_INVALID_ARGUMENT, + "objects are from different programs"); + } + return lang->op_implicit_convert(res, qualified_type, obj); +} + LIBDRGN_PUBLIC struct drgn_error * drgn_object_reinterpret(struct drgn_object *res, struct drgn_qualified_type qualified_type, @@ -1394,20 +1435,14 @@ struct drgn_error *drgn_error_binary_op(const char *op_name, struct drgn_operand_type *rhs_type) { struct drgn_error *err; - struct drgn_qualified_type lhs_qualified_type = { - .type = lhs_type->type, - .qualifiers = lhs_type->qualifiers, - }; - struct drgn_qualified_type rhs_qualified_type = { - .type = rhs_type->type, - .qualifiers = rhs_type->qualifiers, - }; _cleanup_free_ char *lhs_type_name = NULL; - err = drgn_format_type_name(lhs_qualified_type, &lhs_type_name); + err = drgn_format_type_name(drgn_operand_type_qualified(lhs_type), + &lhs_type_name); if (err) return err; _cleanup_free_ char *rhs_type_name = NULL; - err = drgn_format_type_name(rhs_qualified_type, &rhs_type_name); + err = drgn_format_type_name(drgn_operand_type_qualified(rhs_type), + &rhs_type_name); if (err) return err; return drgn_error_format(DRGN_ERROR_TYPE, @@ -1420,12 +1455,9 @@ struct drgn_error *drgn_error_unary_op(const char *op_name, struct drgn_operand_type *type) { struct drgn_error *err; - struct drgn_qualified_type qualified_type = { - .type = type->type, - .qualifiers = type->qualifiers, - }; _cleanup_free_ char *type_name = NULL; - err = drgn_format_type_name(qualified_type, &type_name); + err = drgn_format_type_name(drgn_operand_type_qualified(type), + &type_name); if (err) return err; return drgn_error_format(DRGN_ERROR_TYPE, @@ -1494,7 +1526,7 @@ drgn_object_address_of(struct drgn_object *res, const struct drgn_object *obj) "objects are from different programs"); } - SWITCH_ENUM(obj->kind, + SWITCH_ENUM(obj->kind) { case DRGN_OBJECT_VALUE: return drgn_error_format(DRGN_ERROR_INVALID_ARGUMENT, "cannot take address of value"); @@ -1502,7 +1534,9 @@ drgn_object_address_of(struct drgn_object *res, const struct drgn_object *obj) break; case DRGN_OBJECT_ABSENT: return &drgn_error_object_absent; - ) + default: + UNREACHABLE(); + } if (obj->is_bit_field || obj->bit_offset) { return drgn_error_format(DRGN_ERROR_INVALID_ARGUMENT, @@ -1723,7 +1757,7 @@ static struct drgn_error *pointer_operand(const struct drgn_object *ptr, case DRGN_OBJECT_ENCODING_BUFFER: case DRGN_OBJECT_ENCODING_NONE: case DRGN_OBJECT_ENCODING_INCOMPLETE_BUFFER: - SWITCH_ENUM(ptr->kind, + SWITCH_ENUM(ptr->kind) { case DRGN_OBJECT_VALUE: return drgn_error_format(DRGN_ERROR_INVALID_ARGUMENT, "cannot get address of value"); @@ -1732,7 +1766,9 @@ static struct drgn_error *pointer_operand(const struct drgn_object *ptr, return NULL; case DRGN_OBJECT_ABSENT: return &drgn_error_object_absent; - ) + default: + UNREACHABLE(); + } default: return drgn_error_create(DRGN_ERROR_TYPE, "invalid operand type for pointer arithmetic"); @@ -1740,23 +1776,18 @@ static struct drgn_error *pointer_operand(const struct drgn_object *ptr, } struct drgn_error *drgn_op_cast(struct drgn_object *res, - struct drgn_qualified_type qualified_type, + const struct drgn_object_type *type, const struct drgn_object *obj, const struct drgn_operand_type *obj_type) { struct drgn_error *err; - struct drgn_object_type type; - err = drgn_object_type(qualified_type, 0, &type); - if (err) - goto err; - bool is_pointer = (drgn_type_kind(obj_type->underlying_type) == DRGN_TYPE_POINTER); - switch (type.encoding) { + switch (type->encoding) { case DRGN_OBJECT_ENCODING_BUFFER: return drgn_qualified_type_error("cannot cast to '%s'", - qualified_type); + drgn_object_type_qualified(type)); case DRGN_OBJECT_ENCODING_SIGNED: { union { int64_t svalue; @@ -1765,24 +1796,24 @@ struct drgn_error *drgn_op_cast(struct drgn_object *res, if (is_pointer) { err = pointer_operand(obj, &tmp.uvalue); } else { - err = drgn_object_convert_signed(obj, type.bit_size, + err = drgn_object_convert_signed(obj, type->bit_size, &tmp.svalue); } if (err) goto err; - return drgn_object_set_signed_internal(res, &type, tmp.svalue); + return drgn_object_set_signed_internal(res, type, tmp.svalue); } case DRGN_OBJECT_ENCODING_UNSIGNED: { uint64_t uvalue; if (is_pointer) { err = pointer_operand(obj, &uvalue); } else { - err = drgn_object_convert_unsigned(obj, type.bit_size, + err = drgn_object_convert_unsigned(obj, type->bit_size, &uvalue); } if (err) goto err; - return drgn_object_set_unsigned_internal(res, &type, uvalue); + return drgn_object_set_unsigned_internal(res, type, uvalue); } case DRGN_OBJECT_ENCODING_FLOAT: { if (is_pointer) @@ -1792,12 +1823,12 @@ struct drgn_error *drgn_op_cast(struct drgn_object *res, err = drgn_object_convert_float(obj, &fvalue); if (err) goto err; - return drgn_object_set_float_internal(res, &type, fvalue); + return drgn_object_set_float_internal(res, type, fvalue); } default: - if (!drgn_object_encoding_is_complete(type.encoding)) { + if (!drgn_object_encoding_is_complete(type->encoding)) { return drgn_error_incomplete_type("cannot cast to %s type", - type.type); + type->type); } goto type_error; } @@ -1809,21 +1840,10 @@ struct drgn_error *drgn_op_cast(struct drgn_object *res, } return err; -type_error:; - struct drgn_qualified_type from_qualified_type = { - .type = obj_type->type, - .qualifiers = obj_type->qualifiers, - }; - _cleanup_free_ char *to_type_name = NULL; - err = drgn_format_type_name(qualified_type, &to_type_name); - if (err) - return err; - _cleanup_free_ char *from_type_name = NULL; - err = drgn_format_type_name(from_qualified_type, &from_type_name); - if (err) - return err; - return drgn_error_format(DRGN_ERROR_TYPE, "cannot convert '%s' to '%s'", - from_type_name, to_type_name); +type_error: + return drgn_2_qualified_types_error("cannot convert '%s' to '%s'", + drgn_operand_type_qualified(obj_type), + drgn_object_type_qualified(type)); } /* diff --git a/libdrgn/object.h b/libdrgn/object.h index 2b8e82747..a598c677c 100644 --- a/libdrgn/object.h +++ b/libdrgn/object.h @@ -103,6 +103,16 @@ struct drgn_operand_type { uint64_t bit_field_size; }; +/** Convert a @ref drgn_operand_type to a @ref drgn_qualified_type. */ +static inline struct drgn_qualified_type +drgn_operand_type_qualified(const struct drgn_operand_type *type) +{ + return (struct drgn_qualified_type){ + .type = type->type, + .qualifiers = type->qualifiers, + }; +} + /** Get the @ref drgn_operand_type of a @ref drgn_object. */ static inline struct drgn_operand_type drgn_object_operand_type(const struct drgn_object *obj) @@ -186,6 +196,23 @@ drgn_object_set_reference_internal(struct drgn_object *res, const struct drgn_object_type *type, uint64_t address, uint64_t bit_offset); +/** + * Like @ref drgn_object_set_absent() but @ref drgn_object_type() was already + * called. + */ +static inline void +drgn_object_set_absent_internal(struct drgn_object *res, + const struct drgn_object_type *type) +{ + drgn_object_reinit(res, type, DRGN_OBJECT_ABSENT); +} + +struct drgn_error * +drgn_object_slice_internal(struct drgn_object *res, + const struct drgn_object *obj, + const struct drgn_object_type *type, + uint64_t bit_offset, uint64_t bit_field_size); + /** * Binary operator implementation. * @@ -337,7 +364,7 @@ drgn_unary_op_impl drgn_op_not_impl; * address of @p obj is used. */ struct drgn_error *drgn_op_cast(struct drgn_object *res, - struct drgn_qualified_type qualified_type, + const struct drgn_object_type *type, const struct drgn_object *obj, const struct drgn_operand_type *obj_type); diff --git a/libdrgn/platform.c b/libdrgn/platform.c index f4018d4ae..b751f92be 100644 --- a/libdrgn/platform.c +++ b/libdrgn/platform.c @@ -57,7 +57,7 @@ drgn_platform_create(enum drgn_architecture arch, const struct drgn_architecture_info *arch_info; struct drgn_platform *platform; - SWITCH_ENUM_DEFAULT(arch, + SWITCH_ENUM(arch) { case DRGN_ARCH_UNKNOWN: arch_info = &arch_info_unknown; break; @@ -91,7 +91,7 @@ drgn_platform_create(enum drgn_architecture arch, default: return drgn_error_create(DRGN_ERROR_INVALID_ARGUMENT, "invalid architecture"); - ) + } if (flags == DRGN_PLATFORM_DEFAULT_FLAGS) { if (arch == DRGN_ARCH_UNKNOWN) { return drgn_error_create(DRGN_ERROR_INVALID_ARGUMENT, diff --git a/libdrgn/python/drgnpy.h b/libdrgn/python/drgnpy.h index f9652d533..661d47140 100644 --- a/libdrgn/python/drgnpy.h +++ b/libdrgn/python/drgnpy.h @@ -289,6 +289,7 @@ static inline Program *DrgnObject_prog(DrgnObject *obj) } PyObject *DrgnObject_NULL(PyObject *self, PyObject *args, PyObject *kwds); DrgnObject *cast(PyObject *self, PyObject *args, PyObject *kwds); +DrgnObject *implicit_convert(PyObject *self, PyObject *args, PyObject *kwds); DrgnObject *reinterpret(PyObject *self, PyObject *args, PyObject *kwds); DrgnObject *DrgnObject_container_of(PyObject *self, PyObject *args, PyObject *kwds); diff --git a/libdrgn/python/main.c b/libdrgn/python/main.c index c57340071..207f5d1cd 100644 --- a/libdrgn/python/main.c +++ b/libdrgn/python/main.c @@ -143,6 +143,8 @@ static PyMethodDef drgn_methods[] = { drgn_offsetof_DOC}, {"cast", (PyCFunction)cast, METH_VARARGS | METH_KEYWORDS, drgn_cast_DOC}, + {"implicit_convert", (PyCFunction)implicit_convert, + METH_VARARGS | METH_KEYWORDS, drgn_implicit_convert_DOC}, {"reinterpret", (PyCFunction)reinterpret, METH_VARARGS | METH_KEYWORDS, drgn_reinterpret_DOC}, {"container_of", (PyCFunction)DrgnObject_container_of, diff --git a/libdrgn/python/object.c b/libdrgn/python/object.c index 51d2318d9..a99de55e5 100644 --- a/libdrgn/python/object.c +++ b/libdrgn/python/object.c @@ -447,7 +447,7 @@ static DrgnObject *DrgnObject_new(PyTypeObject *subtype, PyObject *args, if (err) return set_drgn_error(err); - SWITCH_ENUM(object_type.encoding, + SWITCH_ENUM(object_type.encoding) { case DRGN_OBJECT_ENCODING_BUFFER: if (buffer_object_from_value(&obj->obj, &object_type, value_obj) == -1) @@ -518,7 +518,9 @@ static DrgnObject *DrgnObject_new(PyTypeObject *subtype, PyObject *args, err = drgn_error_incomplete_type("cannot create value with %s type", qualified_type.type); break; - ) + default: + UNREACHABLE(); + } } else { if (!qualified_type.type) { PyErr_SetString(PyExc_ValueError, @@ -734,7 +736,7 @@ static DrgnObject *DrgnObject_address_of(DrgnObject *self) static DrgnObject *DrgnObject_read(DrgnObject *self) { struct drgn_error *err; - SWITCH_ENUM(self->obj.kind, + SWITCH_ENUM(self->obj.kind) { case DRGN_OBJECT_VALUE: Py_INCREF(self); return self; @@ -750,7 +752,9 @@ static DrgnObject *DrgnObject_read(DrgnObject *self) } case DRGN_OBJECT_ABSENT: return set_drgn_error(&drgn_error_object_absent); - ) + default: + UNREACHABLE(); + } } static PyObject *DrgnObject_to_bytes(DrgnObject *self) @@ -841,7 +845,7 @@ static PyObject *DrgnObject_repr(DrgnObject *self) if (append_format(parts, "Object(prog, %R", tmp) == -1) return NULL; - SWITCH_ENUM(self->obj.kind, + SWITCH_ENUM(self->obj.kind) { case DRGN_OBJECT_VALUE: { if (append_string(parts, ", value=") == -1) return NULL; @@ -870,7 +874,9 @@ static PyObject *DrgnObject_repr(DrgnObject *self) } case DRGN_OBJECT_ABSENT: break; - ) + default: + UNREACHABLE(); + } if (self->obj.is_bit_field && append_format(parts, ", bit_field_size=%llu", @@ -1006,13 +1012,15 @@ static PyObject *DrgnObject_get_address(DrgnObject *self, void *arg) static PyObject *DrgnObject_get_bit_offset(DrgnObject *self, void *arg) { - SWITCH_ENUM(self->obj.kind, + SWITCH_ENUM(self->obj.kind) { case DRGN_OBJECT_REFERENCE: return PyLong_FromUint8(self->obj.bit_offset); case DRGN_OBJECT_VALUE: case DRGN_OBJECT_ABSENT: Py_RETURN_NONE; - ) + default: + UNREACHABLE(); + } } static PyObject *DrgnObject_get_bit_field_size(DrgnObject *self, void *arg) @@ -1129,7 +1137,7 @@ static int DrgnObject_bool(DrgnObject *self) static PyObject *DrgnObject_int(DrgnObject *self) { struct drgn_error *err; - SWITCH_ENUM(self->obj.encoding, + SWITCH_ENUM(self->obj.encoding) { case DRGN_OBJECT_ENCODING_SIGNED: case DRGN_OBJECT_ENCODING_UNSIGNED: case DRGN_OBJECT_ENCODING_SIGNED_BIG: @@ -1148,13 +1156,15 @@ static PyObject *DrgnObject_int(DrgnObject *self) case DRGN_OBJECT_ENCODING_INCOMPLETE_INTEGER: return set_error_type_name("cannot convert '%s' to int", drgn_object_qualified_type(&self->obj)); - ) + default: + UNREACHABLE(); + } } static PyObject *DrgnObject_float(DrgnObject *self) { struct drgn_error *err; - SWITCH_ENUM(self->obj.encoding, + SWITCH_ENUM(self->obj.encoding) { case DRGN_OBJECT_ENCODING_FLOAT: { double fvalue; err = drgn_object_read_float(&self->obj, &fvalue); @@ -1183,12 +1193,14 @@ static PyObject *DrgnObject_float(DrgnObject *self) case DRGN_OBJECT_ENCODING_INCOMPLETE_INTEGER: return set_error_type_name("cannot convert '%s' to float", drgn_object_qualified_type(&self->obj)); - ) + default: + UNREACHABLE(); + } } static PyObject *DrgnObject_index(DrgnObject *self) { - SWITCH_ENUM(self->obj.encoding, + SWITCH_ENUM(self->obj.encoding) { case DRGN_OBJECT_ENCODING_SIGNED: case DRGN_OBJECT_ENCODING_UNSIGNED: case DRGN_OBJECT_ENCODING_SIGNED_BIG: @@ -1201,7 +1213,9 @@ static PyObject *DrgnObject_index(DrgnObject *self) case DRGN_OBJECT_ENCODING_INCOMPLETE_INTEGER: return set_error_type_name("'%s' object cannot be interpreted as an integer", drgn_object_qualified_type(&self->obj)); - ) + default: + UNREACHABLE(); + } } static PyObject *DrgnObject_round(DrgnObject *self, PyObject *args, @@ -1618,58 +1632,38 @@ PyObject *DrgnObject_NULL(PyObject *self, PyObject *args, PyObject *kwds) prog_obj, type_obj, 0); } -DrgnObject *cast(PyObject *self, PyObject *args, PyObject *kwds) -{ - static char *keywords[] = {"type", "obj", NULL}; - struct drgn_error *err; - struct drgn_qualified_type qualified_type; - PyObject *type_obj; - DrgnObject *obj; - if (!PyArg_ParseTupleAndKeywords(args, kwds, "OO!:cast", keywords, - &type_obj, &DrgnObject_type, &obj)) - return NULL; - - if (Program_type_arg(DrgnObject_prog(obj), type_obj, false, - &qualified_type) == -1) - return NULL; - - _cleanup_pydecref_ DrgnObject *res = - DrgnObject_alloc(DrgnObject_prog(obj)); - if (!res) - return NULL; - - err = drgn_object_cast(&res->obj, qualified_type, &obj->obj); - if (err) - return set_drgn_error(err); - return_ptr(res); +#define DrgnObject_CAST_OP(op) \ +DrgnObject *op(PyObject *self, PyObject *args, PyObject *kwds) \ +{ \ + static char *keywords[] = {"type", "obj", NULL}; \ + struct drgn_error *err; \ + PyObject *type_obj; \ + DrgnObject *obj; \ + if (!PyArg_ParseTupleAndKeywords(args, kwds, "OO!:" #op, keywords, \ + &type_obj, &DrgnObject_type, &obj)) \ + return NULL; \ + \ + struct drgn_qualified_type qualified_type; \ + if (Program_type_arg(DrgnObject_prog(obj), type_obj, false, \ + &qualified_type) == -1) \ + return NULL; \ + \ + _cleanup_pydecref_ DrgnObject *res = \ + DrgnObject_alloc(DrgnObject_prog(obj)); \ + if (!res) \ + return NULL; \ + \ + err = drgn_object_##op(&res->obj, qualified_type, &obj->obj); \ + if (err) \ + return set_drgn_error(err); \ + return_ptr(res); \ } -DrgnObject *reinterpret(PyObject *self, PyObject *args, PyObject *kwds) -{ - static char *keywords[] = {"type", "obj", NULL}; - struct drgn_error *err; - PyObject *type_obj; - struct drgn_qualified_type qualified_type; - DrgnObject *obj; - if (!PyArg_ParseTupleAndKeywords(args, kwds, "OO!:reinterpret", - keywords, &type_obj, &DrgnObject_type, - &obj)) - return NULL; - - if (Program_type_arg(DrgnObject_prog(obj), type_obj, false, - &qualified_type) == -1) - return NULL; - - _cleanup_pydecref_ DrgnObject *res = - DrgnObject_alloc(DrgnObject_prog(obj)); - if (!res) - return NULL; +DrgnObject_CAST_OP(cast) +DrgnObject_CAST_OP(implicit_convert) +DrgnObject_CAST_OP(reinterpret) - err = drgn_object_reinterpret(&res->obj, qualified_type, &obj->obj); - if (err) - return set_drgn_error(err); - return_ptr(res); -} +#undef DrgnObject_CAST_OP DrgnObject *DrgnObject_container_of(PyObject *self, PyObject *args, PyObject *kwds) diff --git a/libdrgn/python/type_kind_set.c b/libdrgn/python/type_kind_set.c index 460df4a4a..7a14ec349 100644 --- a/libdrgn/python/type_kind_set.c +++ b/libdrgn/python/type_kind_set.c @@ -34,7 +34,7 @@ int init_type_kind_set(void) static inline const char *type_kind_to_str(enum drgn_type_kind kind) { - SWITCH_ENUM(kind, + SWITCH_ENUM(kind) { case DRGN_TYPE_VOID: return "TypeKind.VOID"; case DRGN_TYPE_INT: @@ -59,7 +59,9 @@ static inline const char *type_kind_to_str(enum drgn_type_kind kind) return "TypeKind.ARRAY"; case DRGN_TYPE_FUNCTION: return "TypeKind.FUNCTION"; - ) + default: + UNREACHABLE(); + } } static PyObject *TypeKindSet_repr(TypeKindSet *self) diff --git a/libdrgn/stack_trace.c b/libdrgn/stack_trace.c index ef9c69cb5..22561ed69 100644 --- a/libdrgn/stack_trace.c +++ b/libdrgn/stack_trace.c @@ -973,7 +973,7 @@ drgn_unwind_one_register(struct drgn_program *prog, struct drgn_elf_file *file, { struct drgn_error *err; bool little_endian = drgn_platform_is_little_endian(&prog->platform); - SWITCH_ENUM(rule->kind, + SWITCH_ENUM(rule->kind) { case DRGN_CFI_RULE_UNDEFINED: return &drgn_not_found; case DRGN_CFI_RULE_AT_CFA_PLUS_OFFSET: { @@ -1031,7 +1031,9 @@ drgn_unwind_one_register(struct drgn_program *prog, struct drgn_elf_file *file, copy_lsbytes(buf, size, little_endian, &rule->constant, sizeof(rule->constant), HOST_LITTLE_ENDIAN); return NULL; - ) + default: + UNREACHABLE(); + } /* * If we couldn't read from memory, leave the register unknown instead * of failing hard. diff --git a/libdrgn/tests/language_c.c.in b/libdrgn/tests/language_c.c.in index 304d3ec27..77936a46f 100644 --- a/libdrgn/tests/language_c.c.in +++ b/libdrgn/tests/language_c.c.in @@ -41,7 +41,7 @@ static void assert_lexes(const char *s, const struct test_token *tokens, #define assert_lexes_cpp(s, tokens) assert_lexes(s, tokens, true) static const char c_keywords[] = - "void char short int long signed unsigned _Bool float double _Complex " + "void char short int long signed unsigned _Bool float double " "const restrict volatile _Atomic struct union class enum"; #test lexer_empty @@ -86,7 +86,6 @@ static const char c_keywords[] = { C_TOKEN_BOOL, "_Bool" }, { C_TOKEN_FLOAT, "float" }, { C_TOKEN_DOUBLE, "double" }, - { C_TOKEN_COMPLEX, "_Complex" }, { C_TOKEN_CONST, "const" }, { C_TOKEN_RESTRICT, "restrict" }, { C_TOKEN_VOLATILE, "volatile" }, @@ -116,7 +115,6 @@ static const char c_keywords[] = { C_TOKEN_BOOL, "_Bool" }, { C_TOKEN_FLOAT, "float" }, { C_TOKEN_DOUBLE, "double" }, - { C_TOKEN_COMPLEX, "_Complex" }, { C_TOKEN_CONST, "const" }, { C_TOKEN_RESTRICT, "restrict" }, { C_TOKEN_VOLATILE, "volatile" }, diff --git a/libdrgn/type.c b/libdrgn/type.c index 371247525..68e361d43 100644 --- a/libdrgn/type.c +++ b/libdrgn/type.c @@ -372,7 +372,7 @@ static struct drgn_error * drgn_byte_order_to_little_endian(struct drgn_program *prog, enum drgn_byte_order byte_order, bool *ret) { - SWITCH_ENUM_DEFAULT(byte_order, + SWITCH_ENUM(byte_order) { case DRGN_BIG_ENDIAN: *ret = false; return NULL; @@ -384,7 +384,7 @@ drgn_byte_order_to_little_endian(struct drgn_program *prog, default: return drgn_error_create(DRGN_ERROR_INVALID_ARGUMENT, "invalid byte order"); - ) + } } struct drgn_error *drgn_int_type_create(struct drgn_program *prog, @@ -1156,7 +1156,7 @@ LIBDRGN_PUBLIC struct drgn_error *drgn_type_sizeof(struct drgn_type *type, "cannot get size of incomplete %s type", drgn_type_kind_spelling[kind]); } - SWITCH_ENUM(kind, + SWITCH_ENUM(kind) { case DRGN_TYPE_INT: case DRGN_TYPE_BOOL: case DRGN_TYPE_FLOAT: @@ -1186,7 +1186,9 @@ LIBDRGN_PUBLIC struct drgn_error *drgn_type_sizeof(struct drgn_type *type, case DRGN_TYPE_FUNCTION: return drgn_error_create(DRGN_ERROR_TYPE, "cannot get size of function type"); - ) + default: + UNREACHABLE(); + } } struct drgn_error *drgn_type_bit_size(struct drgn_type *type, uint64_t *ret) @@ -1222,6 +1224,23 @@ drgn_qualified_type_error(const char *format, return drgn_error_format(DRGN_ERROR_TYPE, format, name); } +struct drgn_error * +drgn_2_qualified_types_error(const char *format, + struct drgn_qualified_type qualified_type1, + struct drgn_qualified_type qualified_type2) +{ + struct drgn_error *err; + _cleanup_free_ char *name1 = NULL; + err = drgn_format_type_name(qualified_type1, &name1); + if (err) + return err; + _cleanup_free_ char *name2 = NULL; + err = drgn_format_type_name(qualified_type2, &name2); + if (err) + return err; + return drgn_error_format(DRGN_ERROR_TYPE, format, name1, name2); +} + struct drgn_error *drgn_error_incomplete_type(const char *format, struct drgn_type *type) { diff --git a/libdrgn/util.h b/libdrgn/util.h index 1a6dec3aa..78c0c4a32 100644 --- a/libdrgn/util.h +++ b/libdrgn/util.h @@ -39,28 +39,18 @@ /** * Switch statement with an enum controlling expression that must have a case * for every enumeration value and a default case. + * + * m4/my_c_switch_enum.m4 checks whether this works and defines a stub version + * if not. Keep this definition in sync with the check. */ -#define SWITCH_ENUM_DEFAULT(expr, ...) { \ - _Pragma("GCC diagnostic push"); \ - _Pragma("GCC diagnostic error \"-Wswitch-enum\""); \ - _Pragma("GCC diagnostic error \"-Wswitch-default\""); \ - switch (expr) { \ - __VA_ARGS__ \ - } \ - _Pragma("GCC diagnostic pop"); \ -} - -/** - * Switch statement with an enum controlling expression that must have a case - * for every enumeration value. The expression is assumed to have a valid - * enumeration value. Cases which are assumed not to be possible can be placed - * at the end of the statement. - */ -#define SWITCH_ENUM(expr, ...) \ - SWITCH_ENUM_DEFAULT(expr, \ - __VA_ARGS__ \ - default: UNREACHABLE(); \ - ) +#ifndef SWITCH_ENUM +#define SWITCH_ENUM(expr) \ + _Pragma("GCC diagnostic push") \ + _Pragma("GCC diagnostic error \"-Wswitch-enum\"") \ + _Pragma("GCC diagnostic error \"-Wswitch-default\"") \ + switch (expr) \ + _Pragma("GCC diagnostic pop") +#endif #define likely(x) __builtin_expect(!!(x), 1) #define unlikely(x) __builtin_expect(!!(x), 0) diff --git a/tests/test_language_c.py b/tests/test_language_c.py index 62b7198a3..1012a9aa6 100644 --- a/tests/test_language_c.py +++ b/tests/test_language_c.py @@ -12,6 +12,7 @@ TypeParameter, cast, container_of, + implicit_convert, ) from tests import MockProgramTestCase @@ -1113,6 +1114,27 @@ def test_typedef(self): class TestOperators(MockProgramTestCase): + def test_bool_arrays(self): + self.assertTrue(Object(self.prog, "int [2]", [0, 0])) + self.assertTrue(Object(self.prog, "int [0]", address=0x1234)) + self.assertFalse(Object(self.prog, "int [2]", address=0)) + + def test_bool_functions(self): + self.assertTrue( + Object( + self.prog, + self.prog.function_type(self.prog.type("void"), ()), + address=0x1234, + ) + ) + self.assertFalse( + Object( + self.prog, + self.prog.function_type(self.prog.type("void"), ()), + address=0, + ) + ) + def test_cast_array(self): obj = Object(self.prog, "int []", address=0xFFFF0000) self.assertIdentical( @@ -1139,6 +1161,34 @@ def test_cast_function(self): cast("void *", func), Object(self.prog, "void *", value=0xFFFF0000) ) + def test_cast_to_void(self): + self.assertIdentical( + cast("void", Object(self.prog, "int", 0)), Object(self.prog, "void") + ) + self.assertIdentical( + cast("void", Object(self.prog, "int [2]")), Object(self.prog, "void") + ) + + def test_cast_to_bool(self): + self.assertIdentical( + cast("_Bool", Object(self.prog, "int", 0)), + Object(self.prog, "_Bool", False), + ) + for value in (1, -1, 2, 256, 32767): + with self.subTest(value=value): + self.assertIdentical( + cast("_Bool", Object(self.prog, "int", value)), + Object(self.prog, "_Bool", True), + ) + self.assertIdentical( + cast("_Bool", Object(self.prog, "void *", 0)), + Object(self.prog, "_Bool", False), + ) + self.assertIdentical( + cast("_Bool", Object(self.prog, "void *", 1)), + Object(self.prog, "_Bool", True), + ) + def _test_arithmetic( self, op, lhs, rhs, result, integral=True, floating_point=False ): @@ -1597,6 +1647,767 @@ def test_container_of(self): ) +class TestImplicitConvert(MockProgramTestCase): + def test_to_bool(self): + self.assertIdentical( + implicit_convert("_Bool", Object(self.prog, "int", 0)), + Object(self.prog, "_Bool", False), + ) + for value in (1, -1, 2, 256, 32767): + with self.subTest(value=value): + self.assertIdentical( + implicit_convert("_Bool", Object(self.prog, "int", value)), + Object(self.prog, "_Bool", True), + ) + self.assertIdentical( + implicit_convert("_Bool", Object(self.prog, "void *", 0)), + Object(self.prog, "_Bool", False), + ) + self.assertIdentical( + implicit_convert("_Bool", Object(self.prog, "void *", 1)), + Object(self.prog, "_Bool", True), + ) + + def test_to_int(self): + self.assertIdentical( + implicit_convert("int", Object(self.prog, "unsigned long", 1234)), + Object(self.prog, "int", 1234), + ) + self.assertIdentical( + implicit_convert("int", Object(self.prog, "double", 3.5)), + Object(self.prog, "int", 3), + ) + self.assertIdentical( + implicit_convert("int", Object(self.prog, "_Bool", False)), + Object(self.prog, "int", 0), + ) + self.assertIdentical( + implicit_convert("int", Object(self.prog, self.color_type, 2)), + Object(self.prog, "int", 2), + ) + + def test_to_float(self): + self.assertIdentical( + implicit_convert("double", Object(self.prog, "unsigned long", 1234)), + Object(self.prog, "double", 1234.0), + ) + self.assertIdentical( + implicit_convert("double", Object(self.prog, "float", 3.5)), + Object(self.prog, "double", 3.5), + ) + self.assertIdentical( + implicit_convert("double", Object(self.prog, "_Bool", False)), + Object(self.prog, "double", 0.0), + ) + self.assertIdentical( + implicit_convert("double", Object(self.prog, self.color_type, 2)), + Object(self.prog, "double", 2.0), + ) + + def test_to_enum(self): + self.assertIdentical( + implicit_convert(self.color_type, Object(self.prog, "unsigned long", 1)), + Object(self.prog, self.color_type, 1), + ) + self.assertIdentical( + implicit_convert(self.color_type, Object(self.prog, "double", 3.5)), + Object(self.prog, self.color_type, 3), + ) + self.assertIdentical( + implicit_convert(self.color_type, Object(self.prog, "_Bool", False)), + Object(self.prog, self.color_type, 0), + ) + self.assertIdentical( + implicit_convert(self.color_type, Object(self.prog, self.color_type, 2)), + Object(self.prog, self.color_type, 2), + ) + + def test_to_int_typedef(self): + self.assertIdentical( + implicit_convert( + self.prog.typedef_type("INT", self.prog.type("int")), + Object(self.prog, "int", 1234), + ), + Object( + self.prog, self.prog.typedef_type("INT", self.prog.type("int")), 1234 + ), + ) + + def test_struct_to_self(self): + self.assertIdentical( + implicit_convert( + self.prog.struct_type( + "point", + 8, + ( + TypeMember(self.prog.int_type("int", 4, True), "x", 0), + TypeMember(self.prog.int_type("int", 4, True), "y", 32), + ), + ), + Object(self.prog, self.point_type, {"x": 1, "y": 2}), + ), + Object(self.prog, self.point_type, {"x": 1, "y": 2}), + ) + self.assertIdentical( + implicit_convert( + self.point_type.qualified(Qualifiers.CONST), + Object(self.prog, self.point_type, {"x": 1, "y": 2}), + ), + Object( + self.prog, self.point_type.qualified(Qualifiers.CONST), {"x": 1, "y": 2} + ), + ) + + def test_struct_to_wrong_kind(self): + self.assertRaisesRegex( + TypeError, + "cannot convert.*to incompatible type", + implicit_convert, + self.option_type, + Object(self.prog, self.point_type, {"x": 1, "y": 2}), + ) + + def test_struct_to_different_type_with_same_name(self): + self.assertRaisesRegex( + TypeError, + "cannot convert.*to incompatible type", + implicit_convert, + self.prog.struct_type( + "point", + 4, + ( + TypeMember(self.prog.int_type("short", 2, True), "x", 0), + TypeMember(self.prog.int_type("short", 2, True), "y", 16), + ), + ), + Object(self.prog, self.point_type, {"x": 1, "y": 2}), + ) + + def test_union_to_self(self): + self.assertIdentical( + implicit_convert( + self.prog.union_type( + "option", + 4, + ( + TypeMember(self.prog.int_type("int", 4, True), "i"), + TypeMember(self.prog.float_type("float", 4), "f"), + ), + ), + Object(self.prog, self.option_type, {"i": 99}), + ), + Object(self.prog, self.option_type, {"i": 99}), + ) + self.assertIdentical( + implicit_convert( + self.option_type.qualified(Qualifiers.CONST), + Object(self.prog, self.option_type, {"i": 99}), + ), + Object(self.prog, self.option_type.qualified(Qualifiers.CONST), {"i": 99}), + ) + + def test_pointer_to_self(self): + self.assertIdentical( + implicit_convert("int *", Object(self.prog, "int *", 0x4)), + Object(self.prog, "int *", 0x4), + ) + self.assertIdentical( + implicit_convert("void *", Object(self.prog, "void *", 0x4)), + Object(self.prog, "void *", 0x4), + ) + + def test_pointer_to_typedef_of_self(self): + self.assertIdentical( + implicit_convert( + "int *", + Object( + self.prog, + self.prog.pointer_type( + self.prog.typedef_type("INT", self.prog.type("int")) + ), + 0x4, + ), + ), + Object(self.prog, "int *", 0x4), + ) + self.assertIdentical( + implicit_convert( + self.prog.pointer_type( + self.prog.typedef_type("INT", self.prog.type("int")) + ), + Object(self.prog, "int *", 0x4), + ), + Object( + self.prog, + self.prog.pointer_type( + self.prog.typedef_type("INT", self.prog.type("int")) + ), + 0x4, + ), + ) + + def test_pointer_qualifiers(self): + self.assertIdentical( + implicit_convert("const int *", Object(self.prog, "const int *", 0x4)), + Object(self.prog, "const int *", 0x4), + ) + self.assertIdentical( + implicit_convert("const int *", Object(self.prog, "int *", 0x4)), + Object(self.prog, "const int *", 0x4), + ) + self.assertIdentical( + implicit_convert( + "const volatile int *", Object(self.prog, "volatile int *", 0x4) + ), + Object(self.prog, "const volatile int *", 0x4), + ) + + def test_void_pointer_qualifiers(self): + self.assertIdentical( + implicit_convert("const void *", Object(self.prog, "const void *", 0x4)), + Object(self.prog, "const void *", 0x4), + ) + self.assertIdentical( + implicit_convert("const void *", Object(self.prog, "void *", 0x4)), + Object(self.prog, "const void *", 0x4), + ) + self.assertIdentical( + implicit_convert( + "const volatile void *", Object(self.prog, "volatile void *", 0x4) + ), + Object(self.prog, "const volatile void *", 0x4), + ) + + def test_pointer_missing_qualifiers(self): + self.assertRaisesRegex( + TypeError, + "cannot convert.*to incompatible type", + implicit_convert, + "int *", + Object(self.prog, "const int *", 0x4), + ) + self.assertRaisesRegex( + TypeError, + "cannot convert.*to incompatible type", + implicit_convert, + "volatile int *", + Object(self.prog, "const int *", 0x4), + ) + self.assertRaisesRegex( + TypeError, + "cannot convert.*to incompatible type", + implicit_convert, + "const int *", + Object(self.prog, "const volatile int *", 0x4), + ) + + def test_void_pointer_missing_qualifiers(self): + self.assertRaisesRegex( + TypeError, + "cannot convert.*to incompatible type", + implicit_convert, + "void *", + Object(self.prog, "const void *", 0x4), + ) + self.assertRaisesRegex( + TypeError, + "cannot convert.*to incompatible type", + implicit_convert, + "volatile void *", + Object(self.prog, "const void *", 0x4), + ) + self.assertRaisesRegex( + TypeError, + "cannot convert.*to incompatible type", + implicit_convert, + "const void *", + Object(self.prog, "const volatile void *", 0x4), + ) + + def test_pointer_to_pointer(self): + self.assertRaisesRegex( + TypeError, + "cannot convert.*to incompatible type", + implicit_convert, + "int **", + Object(self.prog, "void **", 0x4), + ) + self.assertRaisesRegex( + TypeError, + "cannot convert.*to incompatible type", + implicit_convert, + "void **", + Object(self.prog, "int **", 0x4), + ) + + def test_pointer_to_pointer_qualifiers(self): + self.assertIdentical( + implicit_convert("void * const *", Object(self.prog, "void **", 0x4)), + Object(self.prog, "void * const *", 0x4), + ) + self.assertRaisesRegex( + TypeError, + "cannot convert.*to incompatible type", + implicit_convert, + "const void **", + Object(self.prog, "void **", 0x4), + ) + + def test_to_void_pointer(self): + self.assertIdentical( + implicit_convert("void *", Object(self.prog, "int *", 0x4)), + Object(self.prog, "void *", 0x4), + ) + self.assertIdentical( + implicit_convert("const void *", Object(self.prog, "int *", 0x4)), + Object(self.prog, "const void *", 0x4), + ) + + def test_from_void_pointer(self): + self.assertIdentical( + implicit_convert("int *", Object(self.prog, "void *", 0x4)), + Object(self.prog, "int *", 0x4), + ) + self.assertIdentical( + implicit_convert("const int *", Object(self.prog, "void *", 0x4)), + Object(self.prog, "const int *", 0x4), + ) + + def test_pointer_to_different_int_types(self): + self.assertRaisesRegex( + TypeError, + "cannot convert.*to incompatible type", + implicit_convert, + "long *", + Object(self.prog, "long long *", 0x4), + ) + + def test_pointer_to_same_struct(self): + self.assertIdentical( + implicit_convert( + self.prog.pointer_type(self.point_type), + Object(self.prog, self.prog.pointer_type(self.point_type), 0x4), + ), + Object(self.prog, self.prog.pointer_type(self.point_type), 0x4), + ) + + def test_pointer_to_incomplete_struct(self): + self.assertIdentical( + implicit_convert( + self.prog.pointer_type(self.point_type), + Object( + self.prog, + self.prog.pointer_type(self.prog.struct_type("point")), + 0x4, + ), + ), + Object(self.prog, self.prog.pointer_type(self.point_type), 0x4), + ) + self.assertIdentical( + implicit_convert( + self.prog.pointer_type(self.prog.struct_type("point")), + Object(self.prog, self.prog.pointer_type(self.point_type), 0x4), + ), + Object( + self.prog, self.prog.pointer_type(self.prog.struct_type("point")), 0x4 + ), + ) + + def test_pointer_to_different_struct(self): + self.assertRaisesRegex( + TypeError, + "cannot convert.*to incompatible type", + implicit_convert, + self.prog.pointer_type(self.prog.struct_type("foo")), + Object(self.prog, self.prog.pointer_type(self.point_type), 0x4), + ) + + def test_pointer_to_enum_same(self): + self.assertIdentical( + implicit_convert( + self.prog.pointer_type(self.color_type), + Object(self.prog, self.prog.pointer_type(self.color_type), 0x4), + ), + Object(self.prog, self.prog.pointer_type(self.color_type), 0x4), + ) + + def test_pointer_to_enum_compatible_type(self): + self.assertIdentical( + implicit_convert( + "unsigned int *", + Object(self.prog, self.prog.pointer_type(self.color_type), 0x4), + ), + Object(self.prog, "unsigned int *", 0x4), + ) + self.assertIdentical( + implicit_convert( + self.prog.pointer_type(self.color_type), + Object(self.prog, "unsigned int *", 0x4), + ), + Object(self.prog, self.prog.pointer_type(self.color_type), 0x4), + ) + + def test_pointer_to_incomplete_enum(self): + incomplete_type = self.prog.pointer_type(self.prog.enum_type("color")) + self.assertIdentical( + implicit_convert( + incomplete_type, + Object(self.prog, self.prog.pointer_type(self.color_type), 0x4), + ), + Object(self.prog, incomplete_type, 0x4), + ) + self.assertIdentical( + implicit_convert( + self.prog.pointer_type(self.color_type), + Object(self.prog, incomplete_type, 0x4), + ), + Object(self.prog, self.prog.pointer_type(self.color_type), 0x4), + ) + + def test_pointer_to_different_enum_with_same_name(self): + other_type = self.prog.pointer_type( + self.prog.enum_type("color", self.prog.type("int"), ()) + ) + self.assertRaisesRegex( + TypeError, + "cannot convert.*to incompatible type", + implicit_convert, + other_type, + Object(self.prog, self.prog.pointer_type(self.color_type), 0x4), + ) + self.assertRaisesRegex( + TypeError, + "cannot convert.*to incompatible type", + implicit_convert, + self.prog.pointer_type(self.color_type), + Object(self.prog, other_type, 0x4), + ) + + def test_pointer_to_different_enum_with_same_compatible_type(self): + other_type = self.prog.pointer_type( + self.prog.enum_type("foo", self.prog.type("unsigned int"), ()) + ) + self.assertRaisesRegex( + TypeError, + "cannot convert.*to incompatible type", + implicit_convert, + other_type, + Object(self.prog, self.prog.pointer_type(self.color_type), 0x4), + ) + self.assertRaisesRegex( + TypeError, + "cannot convert.*to incompatible type", + implicit_convert, + self.prog.pointer_type(self.color_type), + Object(self.prog, other_type, 0x4), + ) + + def test_pointer_to_different_incomplete_enum(self): + other_type = self.prog.pointer_type(self.prog.enum_type("foo")) + self.assertRaisesRegex( + TypeError, + "cannot convert.*to incompatible type", + implicit_convert, + other_type, + Object(self.prog, self.prog.pointer_type(self.color_type), 0x4), + ) + self.assertRaisesRegex( + TypeError, + "cannot convert.*to incompatible type", + implicit_convert, + self.prog.pointer_type(self.color_type), + Object(self.prog, other_type, 0x4), + ) + + def test_array_to_pointer(self): + self.assertIdentical( + implicit_convert("int *", Object(self.prog, "int [3]", address=0x8)), + Object(self.prog, "int *", 0x8), + ) + + def test_pointer_to_array(self): + self.assertIdentical( + implicit_convert("int (*)[3]", Object(self.prog, "int (*)[3]", 0x8)), + Object(self.prog, "int (*)[3]", 0x8), + ) + + def test_pointer_to_incomplete_array(self): + self.assertIdentical( + implicit_convert("int (*)[]", Object(self.prog, "int (*)[]", 0x8)), + Object(self.prog, "int (*)[]", 0x8), + ) + + def test_pointer_to_complete_and_incomplete_array(self): + self.assertIdentical( + implicit_convert("int (*)[3]", Object(self.prog, "int (*)[]", 0x8)), + Object(self.prog, "int (*)[3]", 0x8), + ) + self.assertIdentical( + implicit_convert("int (*)[]", Object(self.prog, "int (*)[3]", 0x8)), + Object(self.prog, "int (*)[]", 0x8), + ) + + def test_pointer_to_array_with_different_length(self): + self.assertRaisesRegex( + TypeError, + "cannot convert.*to incompatible type", + implicit_convert, + "int (*)[4]", + Object(self.prog, "int (*)[3]", 0x8), + ) + + def test_pointer_to_array_with_different_element_type(self): + self.assertRaisesRegex( + TypeError, + "cannot convert.*to incompatible type", + implicit_convert, + "long (*)[3]", + Object(self.prog, "long long (*)[3]", 0x8), + ) + + def test_function_pointer_to_self(self): + self.assertIdentical( + implicit_convert( + self.prog.pointer_type( + self.prog.function_type( + self.prog.type("int"), + ( + TypeParameter(self.prog.type("long")), + TypeParameter(self.prog.type("void *")), + ), + ) + ), + Object( + self.prog, + self.prog.pointer_type( + self.prog.function_type( + self.prog.type("int"), + ( + # Note: parameter names don't matter. + TypeParameter(self.prog.type("long"), "l"), + TypeParameter(self.prog.type("void *"), "p"), + ), + ) + ), + 0x8, + ), + ), + Object( + self.prog, + self.prog.pointer_type( + self.prog.function_type( + self.prog.type("int"), + ( + TypeParameter(self.prog.type("long")), + TypeParameter(self.prog.type("void *")), + ), + ) + ), + 0x8, + ), + ) + + def test_variadic_function_pointer_to_self(self): + self.assertIdentical( + implicit_convert( + self.prog.pointer_type( + self.prog.function_type(self.prog.type("int"), (), is_variadic=True) + ), + Object( + self.prog, + self.prog.pointer_type( + self.prog.function_type( + self.prog.type("int"), (), is_variadic=True + ) + ), + 0x8, + ), + ), + Object( + self.prog, + self.prog.pointer_type( + self.prog.function_type(self.prog.type("int"), (), is_variadic=True) + ), + 0x8, + ), + ) + + def test_function_to_function_pointer(self): + self.assertIdentical( + implicit_convert( + self.prog.pointer_type( + self.prog.function_type( + self.prog.type("int"), + ( + TypeParameter(self.prog.type("long")), + TypeParameter(self.prog.type("void *")), + ), + ) + ), + Object( + self.prog, + self.prog.function_type( + self.prog.type("int"), + ( + TypeParameter(self.prog.type("long"), "l"), + TypeParameter(self.prog.type("void *"), "p"), + ), + ), + address=0x8, + ), + ), + Object( + self.prog, + self.prog.pointer_type( + self.prog.function_type( + self.prog.type("int"), + ( + TypeParameter(self.prog.type("long")), + TypeParameter(self.prog.type("void *")), + ), + ) + ), + 0x8, + ), + ) + + def test_function_pointers_with_different_return_types(self): + type1 = self.prog.pointer_type( + self.prog.function_type(self.prog.type("long"), ()) + ) + type2 = self.prog.pointer_type( + self.prog.function_type(self.prog.type("long long"), ()) + ) + self.assertRaisesRegex( + TypeError, + "cannot convert.*to incompatible type", + implicit_convert, + type1, + Object(self.prog, type2, 0x8), + ) + self.assertRaisesRegex( + TypeError, + "cannot convert.*to incompatible type", + implicit_convert, + type2, + Object(self.prog, type1, 0x8), + ) + + def test_function_pointers_with_different_number_of_parameters( + self, + ): + type1 = self.prog.pointer_type( + self.prog.function_type(self.prog.type("int"), ()) + ) + type2 = self.prog.pointer_type( + self.prog.function_type( + self.prog.type("int"), (TypeParameter(self.prog.type("int")),) + ) + ) + self.assertRaisesRegex( + TypeError, + "cannot convert.*to incompatible type", + implicit_convert, + type1, + Object(self.prog, type2, 0x8), + ) + self.assertRaisesRegex( + TypeError, + "cannot convert.*to incompatible type", + implicit_convert, + type2, + Object(self.prog, type1, 0x8), + ) + + def test_function_pointers_with_incompatible_parameter_types(self): + type1 = self.prog.pointer_type( + self.prog.function_type( + self.prog.type("int"), (TypeParameter(self.prog.type("long")),) + ) + ) + type2 = self.prog.pointer_type( + self.prog.function_type( + self.prog.type("int"), (TypeParameter(self.prog.type("long long")),) + ) + ) + self.assertRaisesRegex( + TypeError, + "cannot convert.*to incompatible type", + implicit_convert, + type1, + Object(self.prog, type2, 0x8), + ) + self.assertRaisesRegex( + TypeError, + "cannot convert.*to incompatible type", + implicit_convert, + type2, + Object(self.prog, type1, 0x8), + ) + + def test_function_pointers_with_different_is_variadic(self): + type1 = self.prog.pointer_type( + self.prog.function_type(self.prog.type("int"), (), is_variadic=False) + ) + type2 = self.prog.pointer_type( + self.prog.function_type(self.prog.type("int"), (), is_variadic=True) + ) + self.assertRaisesRegex( + TypeError, + "cannot convert.*to incompatible type", + implicit_convert, + type1, + Object(self.prog, type2, 0x8), + ) + self.assertRaisesRegex( + TypeError, + "cannot convert.*to incompatible type", + implicit_convert, + type2, + Object(self.prog, type1, 0x8), + ) + + def test_pointer_to_int(self): + self.assertRaisesRegex( + TypeError, + "cannot convert.*to incompatible type", + implicit_convert, + "int", + Object(self.prog, "void *", 0), + ) + + def test_to_void(self): + self.assertRaisesRegex( + TypeError, + "cannot convert to", + implicit_convert, + "void", + Object(self.prog, "void"), + ) + + def test_to_array(self): + self.assertRaisesRegex( + TypeError, + "cannot convert to", + implicit_convert, + "int [3]", + Object(self.prog, "int [3]", address=0x8), + ) + + def test_to_function(self): + self.assertRaisesRegex( + TypeError, + "cannot convert to", + implicit_convert, + self.prog.function_type(self.prog.type("int"), ()), + Object( + self.prog, + self.prog.function_type(self.prog.type("int"), ()), + address=0x8, + ), + ) + + class TestPrettyPrintObject(MockProgramTestCase): def test_int(self): obj = Object(self.prog, "int", value=99) diff --git a/tests/test_object.py b/tests/test_object.py index 3823109b7..5c21eb422 100644 --- a/tests/test_object.py +++ b/tests/test_object.py @@ -1315,8 +1315,6 @@ def test_bool(self): self.assertTrue(Object(self.prog, "int *", value=0xFFFF0000)) self.assertFalse(Object(self.prog, "int *", value=0x0)) - self.assertTrue(Object(self.prog, "int []", address=0)) - self.assertRaisesRegex( TypeError, "cannot convert 'struct point' to bool", @@ -1777,9 +1775,14 @@ def test_cast_compound_value(self): obj, ) - def test_cast_invalid(self): - obj = Object(self.prog, "int", value=1) - self.assertRaisesRegex(TypeError, "cannot cast to void type", cast, "void", obj) + def test_cast_to_incomplete_type(self): + self.assertRaisesRegex( + TypeError, + "cannot cast to incomplete enumerated type", + cast, + self.prog.enum_type("foo"), + Object(self.prog, "int", 1), + ) def test_reinterpret_reference(self): obj = Object(self.prog, "int", address=0xFFFF0000)