Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Parser][BoundsSafety] Print attribute as macro if it's system defined #107619

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 27 additions & 12 deletions clang/include/clang/Sema/ParsedAttr.h
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,10 @@ class ParsedAttr final
LLVM_PREFERRED_TYPE(bool)
mutable unsigned IsPragmaClangAttribute : 1;

/// Determines if the attribute will be printed as macro in the diagnostics.
LLVM_PREFERRED_TYPE(bool)
mutable unsigned PrintMacroName : 1;

/// The location of the 'unavailable' keyword in an
/// availability attribute.
SourceLocation UnavailableLoc;
Expand Down Expand Up @@ -225,7 +229,7 @@ class ParsedAttr final
UsedAsTypeAttr(false), IsAvailability(false),
IsTypeTagForDatatype(false), IsProperty(false), HasParsedType(false),
HasProcessingCache(false), IsPragmaClangAttribute(false),
Info(ParsedAttrInfo::get(*this)) {
PrintMacroName(false), Info(ParsedAttrInfo::get(*this)) {
if (numArgs)
memcpy(getArgsBuffer(), args, numArgs * sizeof(ArgsUnion));
}
Expand All @@ -242,8 +246,8 @@ class ParsedAttr final
NumArgs(1), Invalid(false), UsedAsTypeAttr(false), IsAvailability(true),
IsTypeTagForDatatype(false), IsProperty(false), HasParsedType(false),
HasProcessingCache(false), IsPragmaClangAttribute(false),
UnavailableLoc(unavailable), MessageExpr(messageExpr),
Info(ParsedAttrInfo::get(*this)) {
PrintMacroName(false), UnavailableLoc(unavailable),
MessageExpr(messageExpr), Info(ParsedAttrInfo::get(*this)) {
ArgsUnion PVal(Parm);
memcpy(getArgsBuffer(), &PVal, sizeof(ArgsUnion));
new (getAvailabilityData())
Expand All @@ -260,7 +264,8 @@ class ParsedAttr final
NumArgs(3), Invalid(false), UsedAsTypeAttr(false),
IsAvailability(false), IsTypeTagForDatatype(false), IsProperty(false),
HasParsedType(false), HasProcessingCache(false),
IsPragmaClangAttribute(false), Info(ParsedAttrInfo::get(*this)) {
IsPragmaClangAttribute(false), PrintMacroName(false),
Info(ParsedAttrInfo::get(*this)) {
ArgsUnion *Args = getArgsBuffer();
Args[0] = Parm1;
Args[1] = Parm2;
Expand All @@ -276,7 +281,8 @@ class ParsedAttr final
NumArgs(1), Invalid(false), UsedAsTypeAttr(false),
IsAvailability(false), IsTypeTagForDatatype(true), IsProperty(false),
HasParsedType(false), HasProcessingCache(false),
IsPragmaClangAttribute(false), Info(ParsedAttrInfo::get(*this)) {
IsPragmaClangAttribute(false), PrintMacroName(false),
Info(ParsedAttrInfo::get(*this)) {
ArgsUnion PVal(ArgKind);
memcpy(getArgsBuffer(), &PVal, sizeof(ArgsUnion));
detail::TypeTagForDatatypeData &ExtraData = getTypeTagForDatatypeDataSlot();
Expand All @@ -294,7 +300,7 @@ class ParsedAttr final
UsedAsTypeAttr(false), IsAvailability(false),
IsTypeTagForDatatype(false), IsProperty(false), HasParsedType(true),
HasProcessingCache(false), IsPragmaClangAttribute(false),
Info(ParsedAttrInfo::get(*this)) {
PrintMacroName(false), Info(ParsedAttrInfo::get(*this)) {
new (&getTypeBuffer()) ParsedType(typeArg);
}

Expand All @@ -306,7 +312,8 @@ class ParsedAttr final
NumArgs(0), Invalid(false), UsedAsTypeAttr(false),
IsAvailability(false), IsTypeTagForDatatype(false), IsProperty(true),
HasParsedType(false), HasProcessingCache(false),
IsPragmaClangAttribute(false), Info(ParsedAttrInfo::get(*this)) {
IsPragmaClangAttribute(false), PrintMacroName(false),
Info(ParsedAttrInfo::get(*this)) {
new (&getPropertyDataBuffer()) detail::PropertyData(getterId, setterId);
}

Expand Down Expand Up @@ -493,9 +500,11 @@ class ParsedAttr final
/// Set the macro identifier info object that this parsed attribute was
/// declared in if it was declared in a macro. Also set the expansion location
/// of the macro.
void setMacroIdentifier(IdentifierInfo *MacroName, SourceLocation Loc) {
void setMacroIdentifier(IdentifierInfo *MacroName, SourceLocation Loc,
bool PrintMcrName) {
MacroII = MacroName;
MacroExpansionLoc = Loc;
PrintMacroName = PrintMcrName;
}

/// Returns true if this attribute was declared in a macro.
Expand All @@ -511,6 +520,12 @@ class ParsedAttr final
return MacroExpansionLoc;
}

bool printMacroName() const {
if (!hasMacroIdentifier())
return false;
return PrintMacroName;
}

/// Check if the attribute has exactly as many args as Num. May output an
/// error. Returns false if a diagnostic is produced.
bool checkExactlyNumArgs(class Sema &S, unsigned Num) const;
Expand Down Expand Up @@ -1105,16 +1120,16 @@ enum AttributeDeclKind {

inline const StreamingDiagnostic &operator<<(const StreamingDiagnostic &DB,
const ParsedAttr &At) {
DB.AddTaggedVal(reinterpret_cast<uint64_t>(At.getAttrName()),
const IdentifierInfo *AttrName =
At.printMacroName() ? At.getMacroIdentifier() : At.getAttrName();
DB.AddTaggedVal(reinterpret_cast<uint64_t>(AttrName),
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of printing the macro name here, do we want to add a new tag ak_attr that takes a ParsedAttr * so that the diagnostics engine can automatically print both the macro name and the attribute name (with an aka)? Then we can suppress the follow-on note that lists macro expansions (maybe? If not, then maybe this is a bad idea because we're duplicating the information we show the user.)

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like the idea of printing the attribute name as well in the same message (or in a note), I think that is valuable.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@erichkeane Currently, a note is emitted to show the line where it's expanded from that includes the original attribute spelling: https://github.com/llvm/llvm-project/pull/107619/files#diff-3d962c5c4f372776e40887d273f9404efe3cd306f9915505d8566b791021914bR49

Not sure how I can suppress the follow-on note that lists macro expansions, but I can try the ak_attr approach as @AaronBallman suggested.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@AaronBallman @erichkeane Thanks for the valuable feedback!

Our motivation was that with -fbounds-safety, we would have a toolchain header defines this:
#define __counted_by(c) __attribute__((__counted_by(c)__))

then the user code uses it as __counted_by spelling,

we'd like it to be just printed as __counted_by and we don't want it to be printed as __counted_by__ nor having a redundant aka (__counted_by__ ) next to __counted_by.

Also, that was why we were limiting this change specifically to "system-defined" macros.

Does it make sense? I guess there might be a better way to implement this behavior in Clang?

Copy link
Contributor Author

@rapidsna rapidsna Sep 17, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How do you feel about always printing the macro name (if exists) and printing "(aka 'foo')" only when it's not system-defined macro?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't feel super comfortable deciding that system headers should drop detailed information in the diagnostic by virtue of being a system header. On the one hand, system headers often have a pile of implementation details and so I get it. On the other hand, the point to the diagnostic is to help the developer understand what they did wrong so they can fix it. I suspect any web search for __counted_by (macro name) will also return results for __counted_by (attribute name) and so the mapping won't cause problems in your use case. But WINAPI doesn't immediately help the user to know it means __stdcall and finding that through a web search isn't as straightforward, as an example.

DiagnosticsEngine::ak_identifierinfo);
return DB;
}

inline const StreamingDiagnostic &operator<<(const StreamingDiagnostic &DB,
const ParsedAttr *At) {
DB.AddTaggedVal(reinterpret_cast<uint64_t>(At->getAttrName()),
DiagnosticsEngine::ak_identifierinfo);
return DB;
return DB << *At;
}

/// AttributeCommonInfo has a non-explicit constructor which takes an
Expand Down
9 changes: 9 additions & 0 deletions clang/lib/Parse/ParseCXXInlineMethods.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -835,6 +835,15 @@ void Parser::ParseLexedAttribute(LateParsedAttribute &LA,
Diag(Tok, diag::warn_attribute_on_function_definition)
<< &LA.AttrName;

if (LA.MacroII) {
const auto &SM = PP.getSourceManager();
CharSourceRange ExpansionRange = SM.getExpansionRange(LA.AttrNameLoc);

for (unsigned i = 0; i < Attrs.size(); ++i)
Attrs[i].setMacroIdentifier(LA.MacroII, ExpansionRange.getBegin(),
SM.isInSystemMacro(LA.AttrNameLoc));
}

for (unsigned i = 0, ni = LA.Decls.size(); i < ni; ++i)
Actions.ActOnFinishDelayedAttribute(getCurScope(), LA.Decls[i], Attrs);

Expand Down
15 changes: 13 additions & 2 deletions clang/lib/Parse/ParseDecl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -322,11 +322,14 @@ void Parser::ParseGNUAttributes(ParsedAttributes &Attrs,
FindLocsWithCommonFileID(PP, AttrTokLoc, Loc)) {
CharSourceRange ExpansionRange = SM.getExpansionRange(AttrTokLoc);
StringRef FoundName =
Lexer::getSourceText(ExpansionRange, SM, PP.getLangOpts());
Lexer::getSourceText(ExpansionRange, SM, PP.getLangOpts())
.split('(')
.first;
IdentifierInfo *MacroII = PP.getIdentifierInfo(FoundName);

for (unsigned i = OldNumAttrs; i < Attrs.size(); ++i)
Attrs[i].setMacroIdentifier(MacroII, ExpansionRange.getBegin());
Attrs[i].setMacroIdentifier(MacroII, ExpansionRange.getBegin(),
SM.isInSystemMacro(AttrTokLoc));

if (LateAttrs) {
for (unsigned i = OldNumLateAttrs; i < LateAttrs->size(); ++i)
Expand Down Expand Up @@ -5080,6 +5083,14 @@ void Parser::ParseLexedCAttribute(LateParsedAttribute &LA, bool EnterScope,
ParseGNUAttributeArgs(&LA.AttrName, LA.AttrNameLoc, Attrs, nullptr, nullptr,
SourceLocation(), ParsedAttr::Form::GNU(), nullptr);

if (LA.MacroII) {
const auto &SM = PP.getSourceManager();
CharSourceRange ExpansionRange = SM.getExpansionRange(LA.AttrNameLoc);
for (unsigned i = 0; i < Attrs.size(); ++i)
Attrs[i].setMacroIdentifier(LA.MacroII, ExpansionRange.getBegin(),
SM.isInSystemMacro(LA.AttrNameLoc));
}

for (auto *D : LA.Decls)
Actions.ActOnFinishDelayedAttribute(getCurScope(), D, Attrs);

Expand Down
4 changes: 4 additions & 0 deletions clang/test/Frontend/Inputs/macro_defined_type.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
#pragma clang system_header

#define _SYS_NODEREF __attribute__((noderef))
#define _SYS_LIBCPP_FLOAT_ABI __attribute__((pcs("aapcs")))
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please also have tests using __declspec and [[]] style attributes as well.

22 changes: 22 additions & 0 deletions clang/test/Frontend/sys_macro_defined_type.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// RUN: %clang_cc1 -I%S/Inputs -fsyntax-only -triple x86_64-linux-gnu %s 2>&1 | FileCheck %s

#include <macro_defined_type.h>

// The diagnostic message is hard-coded as 'noderef' so using a system macro doesn't change the behavior
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems like it's going to be a confusing user experience because sometimes the user will see noderef and other times they may see _SYS_NODEREF. Maybe we should make a second pass an update hard-coded names (perhaps as a follow-up)?

void Func_system_macro() {
int _SYS_NODEREF i; // expected-warning{{'noderef' can only be used on an array or pointer type}}
// CHECK: :[[@LINE-1]]:{{.*}} warning: 'noderef' can only be used on an array or pointer type

int _SYS_NODEREF *i_ptr;

auto _SYS_NODEREF *auto_i_ptr2 = i_ptr;
auto _SYS_NODEREF auto_i2 = i; // expected-warning{{'noderef' can only be used on an array or pointer type}}
}

struct A_system_macro {
_SYS_LIBCPP_FLOAT_ABI int operator()() throw();
// CHECK: :[[@LINE-1]]:{{.*}} warning: '_SYS_LIBCPP_FLOAT_ABI' calling convention is not supported for this target
// CHECK: {{.*}}macro_defined_type.h:{{.*}}:{{.*}}: note: expanded from macro '_SYS_LIBCPP_FLOAT_ABI'
// CHECK: 4 | #define _SYS_LIBCPP_FLOAT_ABI __attribute__((pcs("aapcs")))
// CHECK: | ^
};
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add a newline to the end of the file (same below).

3 changes: 3 additions & 0 deletions clang/test/SemaCXX/Inputs/warn-thread-safety-parsing.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
#pragma clang system_header

#define _SYS_GUARDED_BY(x) __attribute__ ((guarded_by(x)))
65 changes: 65 additions & 0 deletions clang/test/SemaCXX/warn-thread-safety-parsing-system-macro.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
// RUN: %clang_cc1 -fsyntax-only -I%S/Inputs -Wthread-safety %s 2>&1 | FileCheck %s
// RUN: %clang_cc1 -fsyntax-only -I%S/Inputs -Wthread-safety -std=c++98 %s 2>&1 | FileCheck %s
// RUN: %clang_cc1 -fsyntax-only -I%S/Inputs -Wthread-safety -std=c++11 %s -D CPP11 2>&1 | FileCheck %s
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@rapidsna Looks like there might be some missing test coverage here

  • Late parsing isn't enabled in this test but the PR appears to be touching that code path
  • There's no C version of this test that enables late parsing but this PR appears to be touching that code path.


#include <warn-thread-safety-parsing.h>

#define LOCKABLE __attribute__ ((lockable))
#define SCOPED_LOCKABLE __attribute__ ((scoped_lockable))
#define GUARDED_BY(x) __attribute__ ((guarded_by(x)))
#define GUARDED_VAR __attribute__ ((guarded_var))
#define PT_GUARDED_BY(x) __attribute__ ((pt_guarded_by(x)))
#define PT_GUARDED_VAR __attribute__ ((pt_guarded_var))
#define ACQUIRED_AFTER(...) __attribute__ ((acquired_after(__VA_ARGS__)))
#define ACQUIRED_BEFORE(...) __attribute__ ((acquired_before(__VA_ARGS__)))
#define EXCLUSIVE_LOCK_FUNCTION(...) __attribute__ ((exclusive_lock_function(__VA_ARGS__)))
#define SHARED_LOCK_FUNCTION(...) __attribute__ ((shared_lock_function(__VA_ARGS__)))
#define ASSERT_EXCLUSIVE_LOCK(...) __attribute__ ((assert_exclusive_lock(__VA_ARGS__)))
#define ASSERT_SHARED_LOCK(...) __attribute__ ((assert_shared_lock(__VA_ARGS__)))
#define EXCLUSIVE_TRYLOCK_FUNCTION(...) __attribute__ ((exclusive_trylock_function(__VA_ARGS__)))
#define SHARED_TRYLOCK_FUNCTION(...) __attribute__ ((shared_trylock_function(__VA_ARGS__)))
#define UNLOCK_FUNCTION(...) __attribute__ ((unlock_function(__VA_ARGS__)))
#define LOCK_RETURNED(x) __attribute__ ((lock_returned(x)))
#define LOCKS_EXCLUDED(...) __attribute__ ((locks_excluded(__VA_ARGS__)))
#define EXCLUSIVE_LOCKS_REQUIRED(...) \
__attribute__ ((exclusive_locks_required(__VA_ARGS__)))
#define SHARED_LOCKS_REQUIRED(...) \
__attribute__ ((shared_locks_required(__VA_ARGS__)))
#define NO_THREAD_SAFETY_ANALYSIS __attribute__ ((no_thread_safety_analysis))


class LOCKABLE Mutex {
public:
void Lock() EXCLUSIVE_LOCK_FUNCTION();
void ReaderLock() SHARED_LOCK_FUNCTION();
void Unlock() UNLOCK_FUNCTION();

bool TryLock() EXCLUSIVE_TRYLOCK_FUNCTION(true);
bool ReaderTryLock() SHARED_TRYLOCK_FUNCTION(true);

void AssertHeld() ASSERT_EXCLUSIVE_LOCK();
void AssertReaderHeld() ASSERT_SHARED_LOCK();
};

Mutex mu1;

void gb_function_sys_macro() _SYS_GUARDED_BY(mu1);
// CHECK: :[[@LINE-1]]:{{.*}} warning: '_SYS_GUARDED_BY' attribute only applies to
// CHECK: {{.*}}warn-thread-safety-parsing.h:3:{{.*}}: note: expanded from macro '_SYS_GUARDED_BY'
// CHECK: 3 | #define _SYS_GUARDED_BY(x) __attribute__ ((guarded_by(x)))
// CHECK: | ^

void gb_function_params_sys_macro(int gv_lvar _SYS_GUARDED_BY(mu1));
// CHECK: :[[@LINE-1]]:{{.*}} warning: '_SYS_GUARDED_BY' attribute only applies to
// CHECK: {{.*}}warn-thread-safety-parsing.h:3:{{.*}}: note: expanded from macro '_SYS_GUARDED_BY'
// CHECK: 3 | #define _SYS_GUARDED_BY(x) __attribute__ ((guarded_by(x)))
// CHECK: | ^

int gb_testfn_sys_macro(int y){
int x _SYS_GUARDED_BY(mu1) = y;
// CHECK: :[[@LINE-1]]:{{.*}} warning: '_SYS_GUARDED_BY' attribute only applies to
// CHECK: {{.*}}warn-thread-safety-parsing.h:3:{{.*}}: note: expanded from macro '_SYS_GUARDED_BY'
// CHECK: 3 | #define _SYS_GUARDED_BY(x) __attribute__ ((guarded_by(x)))
// CHECK: | ^
return x;
}
Loading