7

Is it possible to apply attributes to destructors? Example:

#if defined (__GNUC__) && !defined(__clang__)
#  define TEST_PRE_ATTR [[deprecated]] __attribute__((deprecated))
#  define TEST_POST_ATTR __attribute__((error("test")))
#elif defined(_MSC_VER) && !defined(__clang__)
#  define TEST_PRE_ATTR [[deprecated]] __declspec(deprecated)
#  define TEST_POST_ATTR
#elif defined (__clang__)
#  define TEST_PRE_ATTR [[deprecated]] __attribute__((deprecated))
#  define TEST_POST_ATTR __attribute__((diagnose_if(true, "test", "error")))
#endif

struct Foo {
    //TEST_PRE_ATTR 
    void bar() 
    //TEST_POST_ATTR 
    {}

    TEST_PRE_ATTR
    ~Foo()
    TEST_POST_ATTR
    = default;
};

int main() {
    Foo* f = new Foo();
    f->bar();
    delete f;
    return 0;
}

GCC, Clang, and MSVC ignore all of these attributes on Foo::~Foo. If you apply the same attributes to Foo::bar, they create warnings as expected. According to the standard and cppreference, the grammar for destructors should allow leading and trailing attributes. (And I suppose the fact that this does not produce any syntax errors confirms that.)

In my specific case, I have an UndefinedBehaviorSanitizer build (-fsanitize=undefined) and I am trying to use __attribute__((no_sanitize("undefined"))) on a destructor to suppress an error from an upstream library (out of my control). However, I can't suppress the error because all compilers seem to be ignoring this attribute.

I have a feeling that the answer will be something extremely unsatisfying like "compilers are allowed to ignore any attribute for any reason." If that is the case, can someone suggest a workaround? I would rather not do something as heavy-handed as disabling UBSan for the entire target.

0x5453
  • 12,753
  • 1
  • 32
  • 61
  • I guess the `deprecated` attribute in particular may not necessarily be ideal for testing this, as it’s pretty much a pure QoI attribute that in principle implementations are free to interpret in any way they please or even ignore. (It also makes little sense to deprecate a destructor specifically, since ordinarily you can’t actually avoid calling it.) – user3840170 Apr 13 '21 at 13:16
  • That's fair - I chose it only for its simplicity and broad compiler support. Happy to add other attributes to the example if they make sense. – 0x5453 Apr 13 '21 at 13:31

1 Answers1

2

GCC will emit the warning with a prepositional attribute, if you replace defaulting the destructor by an empty body. Postpositional attribute will not trigger warnings.

struct Foo {
    [[deprecated,gnu::noinline,gnu::error("test")]]
    ~Foo() { __asm__(""); }
};

int main() {
    Foo* f = new Foo();
    delete f;
    return 0;
}

This outputs:

<source>: In function 'int main()':
<source>:8:12: warning: 'Foo::~Foo()' is deprecated [-Wdeprecated-declarations]
    8 |     delete f;
      |            ^
<source>:3:5: note: declared here
    3 |     ~Foo() { __asm__(""); }
      |     ^
<source>:8:12: error: call to 'Foo::~Foo' declared with attribute error: test
    8 |     delete f;
      |            ^
Compiler returned: 1

The empty __asm__ prevents inlining, to ensure the gnu::error diagnostic is triggered. Clang behaves similarly; going by the latter’s diagnostics, postpositional attributes are apparently applied to the type of this (!). MSVC, on the other hand, always ignores the [[deprecated]] attribute on destructors. (I’m not too surprised, given that it makes little sense to deprecate destructors. They may have never bothered to test this.)

I imagine that defaulting the destructor makes GCC and Clang ignore the destructor entirely and instead use built-in destruction logic to generate code instead. I haven’t yet consulted the standard to know how the specification plays into this.

user3840170
  • 26,597
  • 4
  • 30
  • 62
  • This is interesting, and good to know. In my case the destructor is not trivial (which is an oversight in my example above), but maybe it's still being aggressively inlined? Need to experiment a bit more. – 0x5453 Apr 13 '21 at 19:18
  • 2
    I *was* able to successfully use `__attribute__((no_sanitize("undefined")))` on a destructor in Compiler Explorer, so I think this behavior was just a red herring. It looks like my specific problem is more due to the behavior of the `no_sanitize` attribute - it seems to apply *only* to the annotated function, and not any nested function calls. – 0x5453 Apr 13 '21 at 19:43
  • Posted another question for the `no_sanitize` attribute: https://stackoverflow.com/q/67082158/3282436 – 0x5453 Apr 13 '21 at 21:05