Skip to content

Commit

Permalink
[Attributes] Support Attributes being declared as supporting an exper…
Browse files Browse the repository at this point in the history
…imental late parsing mode "extension" (#88596)

This patch changes the `LateParsed` field of `Attr` in `Attr.td` to be
an instantiation of the new `LateAttrParseKind` class. The instation can be one of the following:

* `LateAttrParsingNever` - Corresponds with the false value of `LateParsed` prior to this patch (the default for an attribute).
* `LateAttrParseStandard` - Corresponds with the true value of `LateParsed` prior to this patch.
* `LateAttrParseExperimentalExt` - A new mode described below.

`LateAttrParseExperimentalExt` is an experimental extension to
`LateAttrParseStandard`. Essentially this allows
`Parser::ParseGNUAttributes(...)` to distinguish between these cases:

1. Only `LateAttrParseExperimentalExt` attributes should be late parsed.
2. Both `LateAttrParseExperimentalExt` and `LateAttrParseStandard`
  attributes should be late parsed.

Callers (and indirect callers) of `Parser::ParseGNUAttributes(...)`
indicate the desired behavior by setting a flag in the
`LateParsedAttrList` object that is passed to the function.

In addition to the above, a new driver and frontend flag
(`-fexperimental-late-parse-attributes`) with a corresponding LangOpt
(`ExperimentalLateParseAttributes`) is added that changes how
`LateAttrParseExperimentalExt` attributes are parsed.

* When the flag is disabled (default), in cases where only
  `LateAttrParsingExperimentalOnly` late parsing is requested, the
  attribute will be parsed immediately (i.e. **NOT** late parsed). This
  allows the attribute to act just like a `LateAttrParseStandard`
  attribute when the flag is disabled.

* When the flag is enabled, in cases where only
  `LateAttrParsingExperimentalOnly` late parsing is requested, the
  attribute will be late parsed.

The motivation behind this change is to allow the new `counted_by`
attribute (part of `-fbounds-safety`) to support late parsing but
**only** when `-fexperimental-late-parse-attributes` is enabled. This
attribute needs to support late parsing to allow it to refer to fields
later in a struct definition (or function parameters declared later).
However, there isn't a precedent for supporting late attribute parsing
in C so this flag allows the new behavior to exist in Clang but not be
on by default. This behavior was requested as part of the
`-fbounds-safety` RFC process
(https://discourse.llvm.org/t/rfc-enforcing-bounds-safety-in-c-fbounds-safety/70854/68).

This patch doesn't introduce any uses of `LateAttrParseExperimentalExt`.
This will be added for the `counted_by` attribute in a future patch
(#87596). A consequence is the
new behavior added in this patch is not yet testable. Hence, the lack of
tests covering the new behavior.

rdar://125400257
  • Loading branch information
delcypher committed Apr 30, 2024
1 parent 65ee8f1 commit b1867e1
Show file tree
Hide file tree
Showing 9 changed files with 254 additions and 42 deletions.
4 changes: 4 additions & 0 deletions clang/docs/ReleaseNotes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -256,6 +256,10 @@ New Compiler Flags
- ``-fexperimental-modules-reduced-bmi`` enables the Reduced BMI for C++20 named modules.
See the document of standard C++ modules for details.

- ``-fexperimental-late-parse-attributes`` enables an experimental feature to
allow late parsing certain attributes in specific contexts where they would
not normally be late parsed.

Deprecated Compiler Flags
-------------------------

Expand Down
79 changes: 61 additions & 18 deletions clang/include/clang/Basic/Attr.td
Original file line number Diff line number Diff line change
Expand Up @@ -592,6 +592,49 @@ class AttrSubjectMatcherAggregateRule<AttrSubject subject> {

def SubjectMatcherForNamed : AttrSubjectMatcherAggregateRule<Named>;

// Enumeration specifying what kind of behavior should be used for late
// parsing of attributes.
class LateAttrParseKind <int val> {
int Kind = val;
}

// Never late parsed
def LateAttrParseNever : LateAttrParseKind<0>;

// Standard late attribute parsing
//
// This is language dependent. For example:
//
// * For C++ enables late parsing of a declaration attributes
// * For C does not enable late parsing of attributes
//
def LateAttrParseStandard : LateAttrParseKind<1>;

// Experimental extension to standard late attribute parsing
//
// This extension behaves like `LateAttrParseStandard` but allows
// late parsing attributes in more contexts.
//
// In contexts where `LateAttrParseStandard` attributes are late
// parsed, `LateAttrParseExperimentalExt` attributes will also
// be late parsed.
//
// In contexts that only late parse `LateAttrParseExperimentalExt` attributes
// (see `LateParsedAttrList::lateAttrParseExperimentalExtOnly()`)
//
// * If `-fexperimental-late-parse-attributes`
// (`LangOpts.ExperimentalLateParseAttributes`) is enabled the attribute
// will be late parsed.
// * If `-fexperimental-late-parse-attributes`
// (`LangOpts.ExperimentalLateParseAttributes`) is disabled the attribute
// will **not** be late parsed (i.e parsed immediately).
//
// The following contexts are supported:
//
// * TODO: Add contexts here when they are implemented.
//
def LateAttrParseExperimentalExt : LateAttrParseKind<2>;

class Attr {
// The various ways in which an attribute can be spelled in source
list<Spelling> Spellings;
Expand All @@ -603,8 +646,8 @@ class Attr {
list<Accessor> Accessors = [];
// Specify targets for spellings.
list<TargetSpecificSpelling> TargetSpecificSpellings = [];
// Set to true for attributes with arguments which require delayed parsing.
bit LateParsed = 0;
// Specifies the late parsing kind.
LateAttrParseKind LateParsed = LateAttrParseNever;
// Set to false to prevent an attribute from being propagated from a template
// to the instantiation.
bit Clone = 1;
Expand Down Expand Up @@ -3173,7 +3216,7 @@ def DiagnoseIf : InheritableAttr {
BoolArgument<"ArgDependent", 0, /*fake*/ 1>,
DeclArgument<Named, "Parent", 0, /*fake*/ 1>];
let InheritEvenIfAlreadyPresent = 1;
let LateParsed = 1;
let LateParsed = LateAttrParseStandard;
let AdditionalMembers = [{
bool isError() const { return diagnosticType == DT_Error; }
bool isWarning() const { return diagnosticType == DT_Warning; }
Expand Down Expand Up @@ -3472,7 +3515,7 @@ def AssertCapability : InheritableAttr {
let Spellings = [Clang<"assert_capability", 0>,
Clang<"assert_shared_capability", 0>];
let Subjects = SubjectList<[Function]>;
let LateParsed = 1;
let LateParsed = LateAttrParseStandard;
let TemplateDependent = 1;
let ParseArgumentsAsUnevaluated = 1;
let InheritEvenIfAlreadyPresent = 1;
Expand All @@ -3488,7 +3531,7 @@ def AcquireCapability : InheritableAttr {
GNU<"exclusive_lock_function">,
GNU<"shared_lock_function">];
let Subjects = SubjectList<[Function]>;
let LateParsed = 1;
let LateParsed = LateAttrParseStandard;
let TemplateDependent = 1;
let ParseArgumentsAsUnevaluated = 1;
let InheritEvenIfAlreadyPresent = 1;
Expand All @@ -3504,7 +3547,7 @@ def TryAcquireCapability : InheritableAttr {
Clang<"try_acquire_shared_capability", 0>];
let Subjects = SubjectList<[Function],
ErrorDiag>;
let LateParsed = 1;
let LateParsed = LateAttrParseStandard;
let TemplateDependent = 1;
let ParseArgumentsAsUnevaluated = 1;
let InheritEvenIfAlreadyPresent = 1;
Expand All @@ -3520,7 +3563,7 @@ def ReleaseCapability : InheritableAttr {
Clang<"release_generic_capability", 0>,
Clang<"unlock_function", 0>];
let Subjects = SubjectList<[Function]>;
let LateParsed = 1;
let LateParsed = LateAttrParseStandard;
let TemplateDependent = 1;
let ParseArgumentsAsUnevaluated = 1;
let InheritEvenIfAlreadyPresent = 1;
Expand All @@ -3539,7 +3582,7 @@ def RequiresCapability : InheritableAttr {
Clang<"requires_shared_capability", 0>,
Clang<"shared_locks_required", 0>];
let Args = [VariadicExprArgument<"Args">];
let LateParsed = 1;
let LateParsed = LateAttrParseStandard;
let TemplateDependent = 1;
let ParseArgumentsAsUnevaluated = 1;
let InheritEvenIfAlreadyPresent = 1;
Expand All @@ -3559,7 +3602,7 @@ def NoThreadSafetyAnalysis : InheritableAttr {
def GuardedBy : InheritableAttr {
let Spellings = [GNU<"guarded_by">];
let Args = [ExprArgument<"Arg">];
let LateParsed = 1;
let LateParsed = LateAttrParseStandard;
let TemplateDependent = 1;
let ParseArgumentsAsUnevaluated = 1;
let InheritEvenIfAlreadyPresent = 1;
Expand All @@ -3570,7 +3613,7 @@ def GuardedBy : InheritableAttr {
def PtGuardedBy : InheritableAttr {
let Spellings = [GNU<"pt_guarded_by">];
let Args = [ExprArgument<"Arg">];
let LateParsed = 1;
let LateParsed = LateAttrParseStandard;
let TemplateDependent = 1;
let ParseArgumentsAsUnevaluated = 1;
let InheritEvenIfAlreadyPresent = 1;
Expand All @@ -3581,7 +3624,7 @@ def PtGuardedBy : InheritableAttr {
def AcquiredAfter : InheritableAttr {
let Spellings = [GNU<"acquired_after">];
let Args = [VariadicExprArgument<"Args">];
let LateParsed = 1;
let LateParsed = LateAttrParseStandard;
let TemplateDependent = 1;
let ParseArgumentsAsUnevaluated = 1;
let InheritEvenIfAlreadyPresent = 1;
Expand All @@ -3592,7 +3635,7 @@ def AcquiredAfter : InheritableAttr {
def AcquiredBefore : InheritableAttr {
let Spellings = [GNU<"acquired_before">];
let Args = [VariadicExprArgument<"Args">];
let LateParsed = 1;
let LateParsed = LateAttrParseStandard;
let TemplateDependent = 1;
let ParseArgumentsAsUnevaluated = 1;
let InheritEvenIfAlreadyPresent = 1;
Expand All @@ -3603,7 +3646,7 @@ def AcquiredBefore : InheritableAttr {
def AssertExclusiveLock : InheritableAttr {
let Spellings = [GNU<"assert_exclusive_lock">];
let Args = [VariadicExprArgument<"Args">];
let LateParsed = 1;
let LateParsed = LateAttrParseStandard;
let TemplateDependent = 1;
let ParseArgumentsAsUnevaluated = 1;
let InheritEvenIfAlreadyPresent = 1;
Expand All @@ -3614,7 +3657,7 @@ def AssertExclusiveLock : InheritableAttr {
def AssertSharedLock : InheritableAttr {
let Spellings = [GNU<"assert_shared_lock">];
let Args = [VariadicExprArgument<"Args">];
let LateParsed = 1;
let LateParsed = LateAttrParseStandard;
let TemplateDependent = 1;
let ParseArgumentsAsUnevaluated = 1;
let InheritEvenIfAlreadyPresent = 1;
Expand All @@ -3627,7 +3670,7 @@ def AssertSharedLock : InheritableAttr {
def ExclusiveTrylockFunction : InheritableAttr {
let Spellings = [GNU<"exclusive_trylock_function">];
let Args = [ExprArgument<"SuccessValue">, VariadicExprArgument<"Args">];
let LateParsed = 1;
let LateParsed = LateAttrParseStandard;
let TemplateDependent = 1;
let ParseArgumentsAsUnevaluated = 1;
let InheritEvenIfAlreadyPresent = 1;
Expand All @@ -3640,7 +3683,7 @@ def ExclusiveTrylockFunction : InheritableAttr {
def SharedTrylockFunction : InheritableAttr {
let Spellings = [GNU<"shared_trylock_function">];
let Args = [ExprArgument<"SuccessValue">, VariadicExprArgument<"Args">];
let LateParsed = 1;
let LateParsed = LateAttrParseStandard;
let TemplateDependent = 1;
let ParseArgumentsAsUnevaluated = 1;
let InheritEvenIfAlreadyPresent = 1;
Expand All @@ -3651,7 +3694,7 @@ def SharedTrylockFunction : InheritableAttr {
def LockReturned : InheritableAttr {
let Spellings = [GNU<"lock_returned">];
let Args = [ExprArgument<"Arg">];
let LateParsed = 1;
let LateParsed = LateAttrParseStandard;
let TemplateDependent = 1;
let ParseArgumentsAsUnevaluated = 1;
let Subjects = SubjectList<[Function]>;
Expand All @@ -3661,7 +3704,7 @@ def LockReturned : InheritableAttr {
def LocksExcluded : InheritableAttr {
let Spellings = [GNU<"locks_excluded">];
let Args = [VariadicExprArgument<"Args">];
let LateParsed = 1;
let LateParsed = LateAttrParseStandard;
let TemplateDependent = 1;
let ParseArgumentsAsUnevaluated = 1;
let InheritEvenIfAlreadyPresent = 1;
Expand Down
1 change: 1 addition & 0 deletions clang/include/clang/Basic/LangOptions.def
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,7 @@ LANGOPT(ExperimentalLibrary, 1, 0, "enable unstable and experimental library fea
LANGOPT(PointerAuthIntrinsics, 1, 0, "pointer authentication intrinsics")

LANGOPT(DoubleSquareBracketAttributes, 1, 0, "'[[]]' attributes extension for all language standard modes")
LANGOPT(ExperimentalLateParseAttributes, 1, 0, "experimental late parsing of attributes")

COMPATIBLE_LANGOPT(RecoveryAST, 1, 1, "Preserve expressions in AST when encountering errors")
COMPATIBLE_LANGOPT(RecoveryASTType, 1, 1, "Preserve the type in recovery expressions")
Expand Down
7 changes: 7 additions & 0 deletions clang/include/clang/Driver/Options.td
Original file line number Diff line number Diff line change
Expand Up @@ -1608,6 +1608,13 @@ defm double_square_bracket_attributes : BoolFOption<"double-square-bracket-attri
LangOpts<"DoubleSquareBracketAttributes">, DefaultTrue, PosFlag<SetTrue>,
NegFlag<SetFalse>>;

defm experimental_late_parse_attributes : BoolFOption<"experimental-late-parse-attributes",
LangOpts<"ExperimentalLateParseAttributes">, DefaultFalse,
PosFlag<SetTrue, [], [ClangOption], "Enable">,
NegFlag<SetFalse, [], [ClangOption], "Disable">,
BothFlags<[], [ClangOption, CC1Option],
" experimental late parsing of attributes">>;

defm autolink : BoolFOption<"autolink",
CodeGenOpts<"Autolink">, DefaultTrue,
NegFlag<SetFalse, [], [ClangOption, CC1Option],
Expand Down
13 changes: 11 additions & 2 deletions clang/include/clang/Parse/Parser.h
Original file line number Diff line number Diff line change
Expand Up @@ -1398,12 +1398,21 @@ class Parser : public CodeCompletionHandler {
// A list of late-parsed attributes. Used by ParseGNUAttributes.
class LateParsedAttrList: public SmallVector<LateParsedAttribute *, 2> {
public:
LateParsedAttrList(bool PSoon = false) : ParseSoon(PSoon) { }
LateParsedAttrList(bool PSoon = false,
bool LateAttrParseExperimentalExtOnly = false)
: ParseSoon(PSoon),
LateAttrParseExperimentalExtOnly(LateAttrParseExperimentalExtOnly) {}

bool parseSoon() { return ParseSoon; }
/// returns true iff the attribute to be parsed should only be late parsed
/// if it is annotated with `LateAttrParseExperimentalExt`
bool lateAttrParseExperimentalExtOnly() {
return LateAttrParseExperimentalExtOnly;
}

private:
bool ParseSoon; // Are we planning to parse these shortly after creation?
bool ParseSoon; // Are we planning to parse these shortly after creation?
bool LateAttrParseExperimentalExtOnly;
};

/// Contains the lexed tokens of a member function definition
Expand Down
3 changes: 3 additions & 0 deletions clang/lib/Driver/ToolChains/Clang.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7490,6 +7490,9 @@ void Clang::ConstructJob(Compilation &C, const JobAction &JA,
Args.addOptInFlag(CmdArgs, options::OPT_fsafe_buffer_usage_suggestions,
options::OPT_fno_safe_buffer_usage_suggestions);

Args.addOptInFlag(CmdArgs, options::OPT_fexperimental_late_parse_attributes,
options::OPT_fno_experimental_late_parse_attributes);

// Setup statistics file output.
SmallString<128> StatsFile = getStatsFileName(Args, Output, Input, D);
if (!StatsFile.empty()) {
Expand Down
40 changes: 34 additions & 6 deletions clang/lib/Parse/ParseDecl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -91,13 +91,23 @@ static StringRef normalizeAttrName(StringRef Name) {
return Name;
}

/// isAttributeLateParsed - Return true if the attribute has arguments that
/// require late parsing.
static bool isAttributeLateParsed(const IdentifierInfo &II) {
/// returns true iff attribute is annotated with `LateAttrParseExperimentalExt`
/// in `Attr.td`.
static bool IsAttributeLateParsedExperimentalExt(const IdentifierInfo &II) {
#define CLANG_ATTR_LATE_PARSED_EXPERIMENTAL_EXT_LIST
return llvm::StringSwitch<bool>(normalizeAttrName(II.getName()))
#include "clang/Parse/AttrParserStringSwitches.inc"
.Default(false);
#undef CLANG_ATTR_LATE_PARSED_EXPERIMENTAL_EXT_LIST
}

/// returns true iff attribute is annotated with `LateAttrParseStandard` in
/// `Attr.td`.
static bool IsAttributeLateParsedStandard(const IdentifierInfo &II) {
#define CLANG_ATTR_LATE_PARSED_LIST
return llvm::StringSwitch<bool>(normalizeAttrName(II.getName()))
return llvm::StringSwitch<bool>(normalizeAttrName(II.getName()))
#include "clang/Parse/AttrParserStringSwitches.inc"
.Default(false);
.Default(false);
#undef CLANG_ATTR_LATE_PARSED_LIST
}

Expand Down Expand Up @@ -222,8 +232,26 @@ void Parser::ParseGNUAttributes(ParsedAttributes &Attrs,
continue;
}

bool LateParse = false;
if (!LateAttrs)
LateParse = false;
else if (LateAttrs->lateAttrParseExperimentalExtOnly()) {
// The caller requested that this attribute **only** be late
// parsed for `LateAttrParseExperimentalExt` attributes. This will
// only be late parsed if the experimental language option is enabled.
LateParse = getLangOpts().ExperimentalLateParseAttributes &&
IsAttributeLateParsedExperimentalExt(*AttrName);
} else {
// The caller did not restrict late parsing to only
// `LateAttrParseExperimentalExt` attributes so late parse
// both `LateAttrParseStandard` and `LateAttrParseExperimentalExt`
// attributes.
LateParse = IsAttributeLateParsedExperimentalExt(*AttrName) ||
IsAttributeLateParsedStandard(*AttrName);
}

// Handle "parameterized" attributes
if (!LateAttrs || !isAttributeLateParsed(*AttrName)) {
if (!LateParse) {
ParseGNUAttributeArgs(AttrName, AttrNameLoc, Attrs, &EndLoc, nullptr,
SourceLocation(), ParsedAttr::Form::GNU(), D);
continue;
Expand Down
12 changes: 12 additions & 0 deletions clang/test/Driver/experimental-late-parse-attributes.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
// RUN: %clang %s -c -fexperimental-late-parse-attributes 2>&1 -### | FileCheck %s -check-prefix=CHECK-ON
// RUN: %clang %s -c -fno-experimental-late-parse-attributes -fexperimental-late-parse-attributes 2>&1 -### | FileCheck %s -check-prefix=CHECK-ON

// CHECK-ON: -cc1
// CHECK-ON: -fexperimental-late-parse-attributes

// RUN: %clang %s -c 2>&1 -### | FileCheck %s -check-prefix=CHECK-OFF
// RUN: %clang %s -c -fno-experimental-late-parse-attributes 2>&1 -### | FileCheck %s -check-prefix=CHECK-OFF
// RUN: %clang %s -c -fexperimental-late-parse-attributes -fno-experimental-late-parse-attributes 2>&1 -### | FileCheck %s -check-prefix=CHECK-OFF

// CHECK-OFF: -cc1
// CHECK-OFF-NOT: -fexperimental-late-parse-attributes

0 comments on commit b1867e1

Please sign in to comment.