================ @@ -0,0 +1,141 @@ +// RUN: %clang_cc1 -fsyntax-only -fblocks -fcxx-exceptions -verify %s +// RUN: %clang_cc1 -fsyntax-only -fblocks -verify -x c -std=c23 %s + +#if !__has_attribute(nonblocking) +#error "the 'nonblocking' attribute is not available" +#endif + +// --- ATTRIBUTE SYNTAX: SUBJECTS --- + +int nl_var [[clang::nonblocking]]; // expected-warning {{'nonblocking' only applies to function types; type here is 'int'}} +struct nl_struct {} [[clang::nonblocking]]; // expected-warning {{attribute 'nonblocking' is ignored, place it after "struct" to apply attribute to type declaration}} +struct [[clang::nonblocking]] nl_struct2 {}; // expected-error {{'nonblocking' attribute cannot be applied to a declaration}} + +// Positive case +typedef void (*fo)() [[clang::nonblocking]]; +void (*read_me_and_weep( + int val, void (*func)(int) [[clang::nonblocking]]) + [[clang::nonblocking]]) (int) + [[clang::nonblocking]]; + +// --- ATTRIBUTE SYNTAX: ARGUMENT COUNT --- +void nargs_1() [[clang::nonblocking(1, 2)]]; // expected-error {{'nonblocking' attribute takes no more than 1 argument}} +void nargs_2() [[clang::nonallocating(1, 2)]]; // expected-error {{'nonallocating' attribute takes no more than 1 argument}} +void nargs_3() [[clang::blocking(1)]]; // expected-error {{'blocking' attribute takes no arguments}} +void nargs_4() [[clang::allocating(1)]]; // expected-error {{'allocating' attribute takes no arguments}} + +// --- ATTRIBUTE SYNTAX: COMBINATIONS --- +// Check invalid combinations of nonblocking/nonallocating attributes + +void nl_true_false_1() [[clang::nonblocking(true)]] [[clang::blocking]]; // expected-error {{'blocking' and 'nonblocking' attributes are not compatible}} +void nl_true_false_2() [[clang::blocking]] [[clang::nonblocking(true)]]; // expected-error {{'nonblocking' and 'blocking' attributes are not compatible}} + +void na_true_false_1() [[clang::nonallocating(true)]] [[clang::allocating]]; // expected-error {{'allocating' and 'nonallocating' attributes are not compatible}} +void na_true_false_2() [[clang::allocating]] [[clang::nonallocating(true)]]; // expected-error {{'nonallocating' and 'allocating' attributes are not compatible}} + +void nl_true_na_true_1() [[clang::nonblocking]] [[clang::nonallocating]]; +void nl_true_na_true_2() [[clang::nonallocating]] [[clang::nonblocking]]; + +void nl_true_na_false_1() [[clang::nonblocking]] [[clang::allocating]]; // expected-error {{'allocating' and 'nonblocking' attributes are not compatible}} +void nl_true_na_false_2() [[clang::allocating]] [[clang::nonblocking]]; // expected-error {{'nonblocking' and 'allocating' attributes are not compatible}} + +void nl_false_na_true_1() [[clang::blocking]] [[clang::nonallocating]]; +void nl_false_na_true_2() [[clang::nonallocating]] [[clang::blocking]]; + +void nl_false_na_false_1() [[clang::blocking]] [[clang::allocating]]; +void nl_false_na_false_2() [[clang::allocating]] [[clang::blocking]]; + +// --- TYPE CONVERSIONS --- + +void unannotated(); +void nonblocking() [[clang::nonblocking]]; +void nonallocating() [[clang::nonallocating]]; +void type_conversions() +{ + // It's fine to remove a performance constraint. + void (*fp_plain)(); + + fp_plain = nullptr; + fp_plain = unannotated; + fp_plain = nonblocking; + fp_plain = nonallocating; + + // Adding/spoofing nonblocking is unsafe. + void (*fp_nonblocking)() [[clang::nonblocking]]; + fp_nonblocking = nullptr; + fp_nonblocking = nonblocking; + fp_nonblocking = unannotated; // expected-warning {{attribute 'nonblocking' should not be added via type conversion}} + fp_nonblocking = nonallocating; // expected-warning {{attribute 'nonblocking' should not be added via type conversion}} + + // Adding/spoofing nonallocating is unsafe. + void (*fp_nonallocating)() [[clang::nonallocating]]; + fp_nonallocating = nullptr; + fp_nonallocating = nonallocating; + fp_nonallocating = nonblocking; // no warning because nonblocking includes nonallocating fp_nonallocating = unannotated; + fp_nonallocating = unannotated; // expected-warning {{attribute 'nonallocating' should not be added via type conversion}} +} + +#ifdef __cplusplus +// There was a bug: noexcept and nonblocking could be individually removed in conversion, but not both +void type_conversions_2() +{ + auto receives_fp = [](void (*fp)()) { + }; + + auto ne = +[]() noexcept {}; + auto nl = +[]() [[clang::nonblocking]] {}; + auto nl_ne = +[]() noexcept [[clang::nonblocking]] {}; + + receives_fp(ne); + receives_fp(nl); + receives_fp(nl_ne); +} +#endif + +// --- VIRTUAL METHODS --- +// Attributes propagate to overridden methods, so no diagnostics except for conflicts. ---------------- dougsonos wrote:
Conceptually, we have to err on the side of conservative correctness here: ``` struct Base { virtual void process() [[clang::nonblocking]]; }; struct Derived { void process() override; } // inherits the attribute // (we did discuss making this a warning, but having to add the attribute to overrides // is an adoption hurdle) void foo(Base* ptr) [[clang::nonblocking]] { ptr->process(); } ``` In `foo()`, the incoming `Base*` might be a `Derived*`, which has an unsafe `process()` method. Thus when the attribute is present on a virtual method, it becomes part of the contract for any overrides in subclasses. The alternatives here are to fix `Derived::process()` to be non-blocking, or for `Derived` to create an unsafe alternative with a different name/signature and stop pretending that it correctly implements `Base::process()`. In the worst case, it is also possible for the implementation of `Derived` to suppress the diagnostic and instead caution its users that it does not fulfill the safety contract provided by `Base`. https://github.com/llvm/llvm-project/pull/84983 _______________________________________________ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits