GCC has gotten better at detecting conflicts between various attributes but it still doesn't do a perfect job of detecting similar problems due to mismatches between contradictory arguments to the same attribute. For example,
__attribute ((alloc_size (1))) void* allocate (size_t, size_t); followed by __attribute ((alloc_size (2))) void* allocate (size_t, size_t); is accepted with the former overriding the latter in calls to the function. Similar problem exists with a few other attributes that take arguments. The attached change adds a new utility function that checks for such mismatches and issues warnings. It also adds calls to it to detect the problem in attributes alloc_align, alloc_size, and section. This isn't meant to be a comprehensive fix but rather a starting point for one. Tested on x86_64-linux. Martin PS I ran into this again while debugging some unrelated changes and wondering about the behavior in similar situations to mine. Since the behavior seemed clearly suboptimal I figured I might as well fix it. PPS The improved checking triggers warnings in a few calls to __builtin_has_attribute due to apparent conflicts. I've xfailed those in the test since it's a known issue with some existing attributes that should be fixed at some point. Valid uses of the built-in shouldn't trigger diagnostics except for completely nonsensical arguments. Unfortunately, the line between valid and completely nonsensical is a blurry one (GCC either issues errors, or -Wattributes, or silently ignores some cases altogether, such as those that are the subject of this patch) and there is no internal mechanism to control the response.
Detect conflicts between incompatible uses of the same attribute (PR c/78666). Resolves: PR c/78666 - conflicting attribute alloc_size accepted PR c/96126 - conflicting attribute section accepted on redeclaration gcc/c-family/ChangeLog: PR c/78666 PR c/96126 * c-attribs.c (validate_attr_args): New function. (validate_attr_arg): Same. (handle_section_attribute): Call it. Introduce a local variable. (handle_alloc_size_attribute): Same. (handle_alloc_align_attribute): Same. gcc/testsuite/ChangeLog: PR c/78666 PR c/96126 * gcc.dg/attr-alloc_align-5.c: New test. * gcc.dg/attr-alloc_size-13.c: New test. * gcc.dg/attr-section.c: New test. diff --git a/gcc/c-family/c-attribs.c b/gcc/c-family/c-attribs.c index 37214831538..bc4f409e346 100644 --- a/gcc/c-family/c-attribs.c +++ b/gcc/c-family/c-attribs.c @@ -720,6 +725,124 @@ positional_argument (const_tree fntype, const_tree atname, tree pos, return pos; } +/* Given a pair of NODEs for arbitrary DECLs or TYPEs, validate one or + two integral or string attribute arguments NEWARGS to be applied to + NODE[0] for the absence of conflicts with the same attribute arguments + already applied to NODE[1]. Issue a warning for conflicts and return + false. Otherwise, when no conflicts are found, return true. */ + +static bool +validate_attr_args (tree node[2], tree name, tree newargs[2]) +{ + /* First validate the arguments against those already applied to + the same declaration (or type). */ + tree self[2] = { node[0], node[0] }; + if (node[0] != node[1] && !validate_attr_args (self, name, newargs)) + return false; + + if (!node[1]) + return true; + + /* Extract the same attribute from the previous declaration or type. */ + tree prevattr = NULL_TREE; + if (DECL_P (node[1])) + { + prevattr = DECL_ATTRIBUTES (node[1]); + if (!prevattr) + { + tree type = TREE_TYPE (node[1]); + prevattr = TYPE_ATTRIBUTES (type); + } + } + else if (TYPE_P (node[1])) + prevattr = TYPE_ATTRIBUTES (node[1]); + + const char* const namestr = IDENTIFIER_POINTER (name); + prevattr = lookup_attribute (namestr, prevattr); + if (!prevattr) + return true; + + /* Extract one or both attribute arguments. */ + tree prevargs[2]; + prevargs[0] = TREE_VALUE (TREE_VALUE (prevattr)); + prevargs[1] = TREE_CHAIN (TREE_VALUE (prevattr)); + if (prevargs[1]) + prevargs[1] = TREE_VALUE (prevargs[1]); + + /* Both arguments must be equal or, for the second pair, neither must + be provided to succeed. */ + bool arg1eq, arg2eq; + if (TREE_CODE (newargs[0]) == INTEGER_CST) + { + arg1eq = tree_int_cst_equal (newargs[0], prevargs[0]); + if (newargs[1] && prevargs[1]) + arg2eq = tree_int_cst_equal (newargs[1], prevargs[1]); + else + arg2eq = newargs[1] == prevargs[1]; + } + else if (TREE_CODE (newargs[0]) == STRING_CST) + { + const char *s0 = TREE_STRING_POINTER (newargs[0]); + const char *s1 = TREE_STRING_POINTER (prevargs[0]); + arg1eq = strcmp (s0, s1) == 0; + if (newargs[1] && prevargs[1]) + { + s0 = TREE_STRING_POINTER (newargs[1]); + s1 = TREE_STRING_POINTER (prevargs[1]); + arg2eq = strcmp (s0, s1) == 0; + } + else + arg2eq = newargs[1] == prevargs[1]; + } + else + gcc_unreachable (); + + if (arg1eq && arg2eq) + return true; + + /* If the two locations are different print a note pointing to + the previous one. */ + const location_t curloc = input_location; + const location_t prevloc = + DECL_P (node[1]) ? DECL_SOURCE_LOCATION (node[1]) : curloc; + + /* Format the attribute specification for convenience. */ + char newspec[80], prevspec[80]; + if (newargs[1]) + snprintf (newspec, sizeof newspec, "%s (%s, %s)", namestr, + print_generic_expr_to_str (newargs[0]), + print_generic_expr_to_str (newargs[1])); + else + snprintf (newspec, sizeof newspec, "%s (%s)", namestr, + print_generic_expr_to_str (newargs[0])); + + if (prevargs[1]) + snprintf (prevspec, sizeof prevspec, "%s (%s, %s)", namestr, + print_generic_expr_to_str (prevargs[0]), + print_generic_expr_to_str (prevargs[1])); + else + snprintf (prevspec, sizeof prevspec, "%s (%s)", namestr, + print_generic_expr_to_str (prevargs[0])); + + if (warning_at (curloc, OPT_Wattributes, + "ignoring attribute %qs because it conflicts " + "with previous %qs", + newspec, prevspec) + && curloc != prevloc) + inform (prevloc, "previous declaration here"); + + return false; +} + +/* Convenience wrapper for validate_attr_args to validate a single + attribute argument. */ + +static bool +validate_attr_arg (tree node[2], tree name, tree newarg) +{ + tree argarray[2] = { newarg, NULL_TREE }; + return validate_attr_args (node, name, argarray); +} /* Attribute handlers common to C front ends. */ @@ -1894,6 +2017,7 @@ handle_section_attribute (tree *node, tree ARG_UNUSED (name), tree args, { tree decl = *node; tree res = NULL_TREE; + tree argval = TREE_VALUE (args); if (!targetm_common.have_named_sections) { @@ -1908,7 +2032,7 @@ handle_section_attribute (tree *node, tree ARG_UNUSED (name), tree args, goto fail; } - if (TREE_CODE (TREE_VALUE (args)) != STRING_CST) + if (TREE_CODE (argval) != STRING_CST) { error ("section attribute argument not a string constant"); goto fail; @@ -1927,7 +2051,7 @@ handle_section_attribute (tree *node, tree ARG_UNUSED (name), tree args, from a previous declaration. Ensure they match. */ if (DECL_SECTION_NAME (decl) != NULL && strcmp (DECL_SECTION_NAME (decl), - TREE_STRING_POINTER (TREE_VALUE (args))) != 0) + TREE_STRING_POINTER (argval)) != 0) { error ("section of %q+D conflicts with previous declaration", *node); goto fail; @@ -1941,6 +2065,9 @@ handle_section_attribute (tree *node, tree ARG_UNUSED (name), tree args, goto fail; } + if (!validate_attr_arg (node, name, argval)) + goto fail; + res = targetm.handle_generic_attribute (node, name, args, flags, no_add_attrs); @@ -1948,7 +2075,7 @@ handle_section_attribute (tree *node, tree ARG_UNUSED (name), tree args, final processing. */ if (!(*no_add_attrs)) { - set_decl_section_name (decl, TREE_STRING_POINTER (TREE_VALUE (args))); + set_decl_section_name (decl, TREE_STRING_POINTER (argval)); return res; } @@ -2905,15 +3033,15 @@ handle_malloc_attribute (tree *node, tree name, tree ARG_UNUSED (args), return NULL_TREE; } -/* Handle a "alloc_size" attribute; arguments as in - struct attribute_spec.handler. */ +/* Handle the "alloc_size (argpos1 [, argpos2])" function type attribute. + *NODE is the type of the function the attribute is being applied to. */ static tree handle_alloc_size_attribute (tree *node, tree name, tree args, int ARG_UNUSED (flags), bool *no_add_attrs) { - tree decl = *node; - tree rettype = TREE_TYPE (decl); + tree fntype = *node; + tree rettype = TREE_TYPE (fntype); if (!POINTER_TYPE_P (rettype)) { warning (OPT_Wattributes, @@ -2923,6 +3051,7 @@ handle_alloc_size_attribute (tree *node, tree name, tree args, return NULL_TREE; } + tree newargs[2] = { NULL_TREE, NULL_TREE }; for (int i = 1; args; ++i) { tree pos = TREE_VALUE (args); @@ -2931,30 +3060,36 @@ handle_alloc_size_attribute (tree *node, tree name, tree args, the argument number in diagnostics (since there's just one mentioning it is unnecessary and coule be confusing). */ tree next = TREE_CHAIN (args); - if (tree val = positional_argument (decl, name, pos, INTEGER_TYPE, + if (tree val = positional_argument (fntype, name, pos, INTEGER_TYPE, next || i > 1 ? i : 0)) - TREE_VALUE (args) = val; + { + TREE_VALUE (args) = val; + newargs[i - 1] = val; + } else { *no_add_attrs = true; - break; + return NULL_TREE; } args = next; } + if (!validate_attr_args (node, name, newargs)) + *no_add_attrs = true; + return NULL_TREE; } -/* Handle a "alloc_align" attribute; arguments as in - struct attribute_spec.handler. */ + +/* Handle an "alloc_align (argpos)" attribute. */ static tree handle_alloc_align_attribute (tree *node, tree name, tree args, int, bool *no_add_attrs) { - tree decl = *node; - tree rettype = TREE_TYPE (decl); + tree fntype = *node; + tree rettype = TREE_TYPE (fntype); if (!POINTER_TYPE_P (rettype)) { warning (OPT_Wattributes, @@ -2964,9 +3099,12 @@ handle_alloc_align_attribute (tree *node, tree name, tree args, int, return NULL_TREE; } - if (!positional_argument (*node, name, TREE_VALUE (args), INTEGER_TYPE)) - *no_add_attrs = true; + if (tree val = positional_argument (*node, name, TREE_VALUE (args), + INTEGER_TYPE)) + if (validate_attr_arg (node, name, val)) + return NULL_TREE; + *no_add_attrs = true; return NULL_TREE; } diff --git a/gcc/testsuite/gcc.dg/attr-alloc_align-5.c b/gcc/testsuite/gcc.dg/attr-alloc_align-5.c new file mode 100644 index 00000000000..d6f2bc19da8 --- /dev/null +++ b/gcc/testsuite/gcc.dg/attr-alloc_align-5.c @@ -0,0 +1,23 @@ +/* PR c/78666 - conflicting attribute alloc_size accepted + { dg-do compile } + { dg-options "-Wall" } */ + +#define A(pos) __attribute__ ((alloc_align (pos))) + +A (1) char* f2_1 (int, int); +A (1) char* f2_1 (int, int); // { dg-message "previous declaration here" } + +A (2) char* f2_1 (int, int); // { dg-warning "ignoring attribute 'alloc_align \\\(2\\\)' because it conflicts with previous 'alloc_align \\\(1\\\)'" } + + +A (2) char* f2_2 (int, int); +A (2) char* f2_2 (int, int); // { dg-message "previous declaration here" } + +A (1) char* f2_2 (int, int); // { dg-warning "ignoring attribute 'alloc_align \\\(1\\\)' because it conflicts with previous 'alloc_align \\\(2\\\)'" } + + +A (1) char* f3_1 (int, int, int); +A (1) char* f3_1 (int, int, int); // { dg-message "previous declaration here" } + +A (2) char* f3_1 (int, int, int); // { dg-warning "ignoring attribute 'alloc_align \\\(2\\\)' because it conflicts with previous 'alloc_align \\\(1\\\)'" } +A (3) char* f3_1 (int, int, int); // { dg-warning "ignoring attribute 'alloc_align \\\(3\\\)' because it conflicts with previous 'alloc_align \\\(1\\\)'" } diff --git a/gcc/testsuite/gcc.dg/attr-alloc_size-13.c b/gcc/testsuite/gcc.dg/attr-alloc_size-13.c new file mode 100644 index 00000000000..df44f479e07 --- /dev/null +++ b/gcc/testsuite/gcc.dg/attr-alloc_size-13.c @@ -0,0 +1,34 @@ +/* PR c/78666 - conflicting attribute alloc_size accepted + { dg-do compile } + { dg-options "-Wall" } */ + +#define A(...) __attribute__ ((alloc_size (__VA_ARGS__))) + +A (1) char* f2_1 (int, int); +A (1) A (1) char* f2_1 (int, int); + +A (1) char* f2_1 (int, int); // { dg-message "previous declaration here" } + +A (2) char* f2_1 (int, int); // { dg-warning "ignoring attribute 'alloc_size \\\(2\\\)' because it conflicts with previous 'alloc_size \\\(1\\\)'" } + + +A (2) char* f2_2 (int, int); +A (2) char* f2_2 (int, int); // { dg-message "previous declaration here" } + +A (1) char* f2_2 (int, int); // { dg-warning "ignoring attribute 'alloc_size \\\(1\\\)' because it conflicts with previous 'alloc_size \\\(2\\\)'" } + + +A (1) char* f3_1 (int, int, int); +A (1) char* f3_1 (int, int, int); // { dg-message "previous declaration here" } + +A (2) char* f3_1 (int, int, int); // { dg-warning "ignoring attribute 'alloc_size \\\(2\\\)' because it conflicts with previous 'alloc_size \\\(1\\\)'" } +A (3) char* f3_1 (int, int, int); // { dg-warning "ignoring attribute 'alloc_size \\\(3\\\)' because it conflicts with previous 'alloc_size \\\(1\\\)'" } +A (1, 2) char* f3_1 (int, int, int); // { dg-warning "ignoring attribute 'alloc_size \\\(1, 2\\\)' because it conflicts with previous 'alloc_size \\\(1\\\)'" } +A (1, 3) char* f3_1 (int, int, int); // { dg-warning "ignoring attribute 'alloc_size \\\(1, 3\\\)' because it conflicts with previous 'alloc_size \\\(1\\\)'" } + + +typedef A (2, 3) char* F3_2_3 (int, int, int); +typedef A (2, 3) char* F3_2_3 (int, int, int); +typedef A (2, 3) A (2, 3) char* F3_2_3 (int, int, int); + +typedef A (1) char* F3_2_3 (int, int, int); // { dg-warning "ignoring attribute 'alloc_size \\\(1\\\)' because it conflicts with previous 'alloc_size \\\(2, 3\\\)'" } diff --git a/gcc/testsuite/gcc.dg/attr-section.c b/gcc/testsuite/gcc.dg/attr-section.c new file mode 100644 index 00000000000..0062b544c71 --- /dev/null +++ b/gcc/testsuite/gcc.dg/attr-section.c @@ -0,0 +1,13 @@ +/* PR c/96126 - conflicting attribute section accepted on redeclaration + { dg-do compile } + { dg-options "-Wall" } + { dg-require-named-sections "" } */ + +__attribute__ ((section ("s1"))) void f1 (void); +__attribute__ ((section ("s2"))) void f1 (void); // { dg-warning "ignoring attribute 'section \\\(\"s2\"\\\)' because it conflicts with previous 'section \\\(\"s1\"\\\)'" } + +__attribute__ ((section ("s3"), section ("s4"))) +void f2 (void); // { dg-error "conflicts" } + +__attribute__ ((section ("s5"))) __attribute ((section ("s6"))) +void f3 (void); // { dg-error "conflicts" } diff --git a/gcc/testsuite/c-c++-common/builtin-has-attribute-3.c b/gcc/testsuite/c-c++-common/builtin-has-attribute-3.c index 2a59501b9f3..5736bab9443 100644 --- a/gcc/testsuite/c-c++-common/builtin-has-attribute-3.c +++ b/gcc/testsuite/c-c++-common/builtin-has-attribute-3.c @@ -75,7 +75,7 @@ void test_alloc_align (void) A (0, fnone, alloc_align (1)); /* { dg-warning "\\\[-Wattributes" } */ A (0, falloc_size_1, alloc_align (1)); A (1, falloc_align_1, alloc_align (1)); - A (0, falloc_align_2, alloc_align (1)); + A (0, falloc_align_2, alloc_align (1)); /* { dg-bogus "\\\[-Wattributes" "pr?????" { xfail *-*-* } }" */ A (1, falloc_align_2, alloc_align (2)); } @@ -88,26 +88,26 @@ void test_alloc_size_malloc (void) A (0, falloc_align_1, alloc_size (1)); A (0, falloc_align_2, alloc_size (1)); A (1, falloc_size_1, alloc_size (1)); - A (0, falloc_size_1, alloc_size (2)); - A (0, falloc_size_2, alloc_size (1)); + A (0, falloc_size_1, alloc_size (2)); /* { dg-bogus "\\\[-Wattributes" "pr?????" { xfail *-*-* } }" */ + A (0, falloc_size_2, alloc_size (1)); /* { dg-bogus "\\\[-Wattributes" "pr?????" { xfail *-*-* } }" */ A (1, falloc_size_2, alloc_size (2)); A (1, falloc_size_2_4, alloc_size); /* It would probably make more sense to have the built-in return true only when both alloc_size arguments match, not just one or the other. */ - A (0, falloc_size_2_4, alloc_size (1)); - A (1, falloc_size_2_4, alloc_size (2)); - A (0, falloc_size_2_4, alloc_size (3)); - A (1, falloc_size_2_4, alloc_size (4)); + A (0, falloc_size_2_4, alloc_size (1)); /* { dg-bogus "\\\[-Wattributes" "pr?????" { xfail *-*-* } }" */ + A (1, falloc_size_2_4, alloc_size (2)); /* { dg-bogus "\\\[-Wattributes" "pr?????" { xfail *-*-* } }" */ + A (0, falloc_size_2_4, alloc_size (3)); /* { dg-bogus "\\\[-Wattributes" "pr?????" { xfail *-*-* } }" */ + A (1, falloc_size_2_4, alloc_size (4)); /* { dg-bogus "\\\[-Wattributes" "pr?????" { xfail *-*-* } }" */ A (1, falloc_size_2_4, alloc_size (2, 4)); extern ATTR (alloc_size (3)) void* fmalloc_size_3 (int, int, int); A (1, fmalloc_size_3, alloc_size); - A (0, fmalloc_size_3, alloc_size (1)); - A (0, fmalloc_size_3, alloc_size (2)); + A (0, fmalloc_size_3, alloc_size (1)); /* { dg-bogus "\\\[-Wattributes" "pr?????" { xfail *-*-* } }" */ + A (0, fmalloc_size_3, alloc_size (2)); /* { dg-bogus "\\\[-Wattributes" "pr?????" { xfail *-*-* } }" */ A (1, fmalloc_size_3, alloc_size (3)); A (0, fmalloc_size_3, malloc);