This is an archive of the discontinued LLVM Phabricator instance.

Add the diagnose_if attribute to clang.
ClosedPublic

Authored by george.burgess.iv on Dec 5 2016, 12:12 PM.

Details

Summary

This patch adds the diagnose_if attribute to clang.

diagnose_if is meant to be a less powerful version of enable_if: it doesn't interact with overload resolution at all, and if the condition in the diagnose_if attribute can't be evaluated, compilation proceeds as though said diagnose_if attribute was not present. It can be used to emit either errors or warnings, like so:

void foo(int a) __attribute__((diagnose_if(a > 10, "a is too large!", "error")));
void bar(int a) __attribute__((diagnose_if(a > 10, "this takes a long time if a is larger than 10.", "warning")));
void baz(int a) __attribute__((diagnose_if(1, "don't use me anymore", "warning")));

void run(int a) {
  foo(10);
  foo(11); // error: a is too large
  foo(a);
  bar(10);
  bar(11); // warning: this takes a long time if a is larger than 10.
  bar(a);

  void (*b)(int) = baz; // warning: don't use me anymore.
}

Under the hood, we have two kinds of diagnose_if attributes: ones that are dependent on the value of arguments (which are handled a lot like enable_if), and ones that aren't (which are treated like unavailable/deprecated attributes). This split made this attribute *substantially* less complex to implement, since it lets us reuse existing code

Diff Detail

Repository
rL LLVM

Event Timeline

george.burgess.iv retitled this revision from to Add the diagnose_if attribute to clang..
george.burgess.iv updated this object.
george.burgess.iv added subscribers: EricWF, cfe-commits.
EricWF added a comment.Dec 5 2016, 8:14 PM

Awesome, this is exactly what I was hoping for! Thanks for working on this!

I'm a bit surprised that the attribute fails to evaluate the condition as a constant expression during the evaluation of constexpr functions.
Ex:

#define DIAG(...) __attribute__((diagnose_if(__VA_ARGS__, "message", "error")))
constexpr bool foo(int x) DIAG(x == 0) // fails to evaluate this
{ 
  // assert(x != 0); <-- ironically a simple assert would catch this
  return x == 0;
}  
constexpr bool bar(int x) { return foo(x); }
static_assert(bar(0), "");

Clearly the compiler can evaluate x == 0 as a constant expression, but the attribute fails to see this. If the condition could be evaluated as-if it were part of the function body (during constexpr evaluation) that would be incredibly powerful. Especially within the STL where everything is constexpr.

The second thing that would be amazing if this attribute was late parsed so it the ability to reference this when applied to member functions.
For example:

struct Foo {
  bool is_good;
  constexpr bool good() const { return is_good; }
  constexpr bool foo() const _diagnose_if(!good(), "message", "error")
  {return true; }
};
constexpr Foo f{false};
static_assert(f.foo(), "");

Thanks again for working on this! I'm very excited to get to use it!

Glad you like it!

The second thing that would be amazing if this attribute was late parsed so it the ability to reference this when applied to member functions

That seems like a good idea; I'll look into what that would take. (In the meantime, reviews would be appreciated. :) )

If the condition could be evaluated as-if it were part of the function body (during constexpr evaluation) that would be incredibly powerful. Especially within the STL where everything is constexpr.

Honestly, if we could take this attribute and turn it into a full-fledged precondition attribute (that optionally acts as if it's a part of the function, optionally emits dynamic checks, ...), I'd be thrilled. If the community thinks that'd be a good idea, I'm happy to poke at it + incrementally build off of this in order to make that*. That said, I think all of that is going to require a fair bit more code and design thought to get right, and diagnose_if is useful enough as-is to stand on its own IMO.

IOW, we need to start somewhere, and this is a minimial-ish feature set that I think would be useful.

∗ - We can do this in a non-breaking way by either introducing an independent precondition attribute that heavily piggybacks off of diagnose_if, or we can add diagnostic modes + flags to diagnose_if (e.g. can have warning, error, static-precondition, dynamic-precondition, ...).

ahatanak added inline comments.
include/clang/Sema/Overload.h
754 ↗(On Diff #80307)

I guess you could use "void *" instead of ImplicitConversionSequence for the alignment to make it clear that this has the alignment of a pointer type (or the larger of alignof(ImplicitConversionSequence) and alignof(DiagnoseIfAttr *))?

george.burgess.iv marked an inline comment as done.

Addressed alignment comment. Thanks! (Late parsing is still to-be-done)

aaron.ballman edited edge metadata.Dec 22 2016, 7:54 AM

I really like this attribute, thank you for working on it!

include/clang/Basic/Attr.td
1584 ↗(On Diff #81259)

I think this should also have a C++ spelling under the clang namespace.

include/clang/Basic/AttrDocs.td
359 ↗(On Diff #81259)

Capitalize the B in because.

include/clang/Basic/DiagnosticCommonKinds.td
164 ↗(On Diff #81259)

If we do add the clang namespaced spelling, we should have a test to ensure that this diagnostic is not triggered when the attribute is spelled [[clang::diagnose_if(...)]].

include/clang/Basic/DiagnosticSemaKinds.td
2145 ↗(On Diff #81259)

Diagnostics don't have full stops, so I think a better way to write this is: invalid diagnostic type for 'diagnose_if'; use \"error\" or \"warning\" instead.

3361 ↗(On Diff #81259)

Space before the colon.

4380 ↗(On Diff #81259)

Please single quote the use of diagnose_if.

include/clang/Sema/Overload.h
664 ↗(On Diff #81259)

an -> a

include/clang/Sema/Sema.h
2616 ↗(On Diff #81259)

Can this (and the other new functions) accept the FunctionDecl as a const FunctionDecl * instead?

lib/Sema/SemaDeclAttr.cpp
857 ↗(On Diff #81259)

This should accept FD as a const pointer.

lib/Sema/SemaExpr.cpp
368 ↗(On Diff #81259)

const DiagnoseIfAttr *?

388 ↗(On Diff #81259)

const auto *?

5186 ↗(On Diff #81259)

const auto *?

lib/Sema/SemaOverload.cpp
827 ↗(On Diff #81259)

DiagnoseIfAttrs aren't the only thing allocated with the slab allocator though, right? Since this is being generalized, I wonder if asserting somewhere that the slab allocator only deals with pointers would make sense?

6151 ↗(On Diff #81259)

Please spell out the type instead of using auto, since the type isn't spelled in the initialization.

6178–6179 ↗(On Diff #81259)

Instead of doing this, you can use specific_attrs<DiagnoseIfAttr>()

9009 ↗(On Diff #81259)

Can you run into a situation with stacked diagnose_if errors, so that there's > 1?

9101 ↗(On Diff #81259)

Can use const auto * here.

12672 ↗(On Diff #81259)

Can use const auto * here.

lib/Sema/SemaTemplateInstantiateDecl.cpp
193 ↗(On Diff #81259)

You shouldn't have to use getSpelling() here; I believe the diagnostics engine has an overload to properly handle Attr * objects.

194 ↗(On Diff #81259)

const auto &

george.burgess.iv edited edge metadata.
george.burgess.iv marked 18 inline comments as done.

Addressed all feedback + made diagnose_if late-parsed.

Do we have a page that details when we should/shouldn't use auto? I was under the impression that it was preferred only in cases where the type's spelled out (e.g. cast<Foo>, ...). (To be clear, I'm happy to use it in loops, too; I'd just like to know if we have guidelines about it. :) )

include/clang/Basic/Attr.td
1584 ↗(On Diff #81259)

Agreed. Though, it looks like that would requires us teaching the parser how to late-parse c++11 attrs if we wanted to do it properly. Given the size of this patch, I think that can be done as a follow-up.

include/clang/Basic/DiagnosticCommonKinds.td
164 ↗(On Diff #81259)

Added a disabled test for this.

include/clang/Sema/Sema.h
2616 ↗(On Diff #81259)

Not easily, unless you'd prefer to see const here and a const_cast or two in checkArgDependentDiagnoseIf over what we have now. I have no preference, so if you would prefer to see that, I'm happy to do it.

If ExprResult and initialization routines like Sema::PerformCopyInitialization were happy to take a const Expr *, this would be a different story :)

lib/Sema/SemaOverload.cpp
827 ↗(On Diff #81259)

It also allocates ImplicitConversionSequences, but those are properly cleaned up by destroyCandidates. Added another static_assert to slabAllocate to hopefully catch errors if others try to use this.

9009 ↗(On Diff #81259)

Not at the moment (we stop evaluating diagnose_if attrs as soon as we find one that's an error), though you're right that this could be more robust.

Do we have a page that details when we should/shouldn't use auto? I was under the impression that it was preferred only in cases where the type's spelled out (e.g. cast<Foo>, ...). (To be clear, I'm happy to use it in loops, too; I'd just like to know if we have guidelines about it. :) )

We do: http://llvm.org/docs/CodingStandards.html#use-auto-type-deduction-to-make-code-more-readable

Btw, there's a question in there about late parsing that Phab won't let me delete, feel free to ignore it. ;-)

include/clang/Basic/Attr.td
1584 ↗(On Diff #81259)

That seems reasonable to me.

include/clang/Sema/Sema.h
2616 ↗(On Diff #81259)

That sounds like more churn than it's worth, to me. It'd be nice if we were better about const-correctness, but that's not this patch's problem to solve. ;-)

lib/AST/ExprConstant.cpp
10371 ↗(On Diff #83188)

I think the message means to say "Don't provide 'this' for static methods."

lib/Parse/ParseDecl.cpp
371–372 ↗(On Diff #81259)

In the last patch, this code was checking for enable_if or diagnose_if, but now only checks for enable_if. Do you not need this for diagnose_if because it's now late parsed?

lib/Sema/SemaDeclAttr.cpp
949 ↗(On Diff #83188)

const auto *

george.burgess.iv marked 2 inline comments as done.

Addressed feedback. Thanks!

We do: http://llvm.org/docs/CodingStandards.html#use-auto-type-deduction-to-make-code-more-readable

Awesome. Thanks!

Btw, there's a question in there about late parsing that Phab won't let me delete, feel free to ignore it. ;-)

OK. The answer is "yes" anyway. :)

aaron.ballman accepted this revision.Jan 7 2017, 10:26 AM
aaron.ballman edited edge metadata.

LGTM, but please point this out in the release notes as well.

This revision is now accepted and ready to land.Jan 7 2017, 10:26 AM
EricWF added a comment.Jan 7 2017, 2:55 PM

Awesome! I'm so excited for this!

This revision was automatically updated to reflect the committed changes.

Thanks for the review! :)