Skip to content

[BoundsSafety] Delay processing of bounds attrs in templates #9929

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

Merged

Conversation

hnrklssn
Copy link

Dynamic bounds attributes do not handle value dependent arguments. To enable wider interop with C++ code bases we delay the processing of these attributes inside templated contexts. Instead we apply the new type while instantiating the function.

One issue with this approach is that instantiation happens after parsing is finished, and the Scope information we use to prevent attributes from referring to arguments from outer scopes is only available during parsing. These diagnostics were emitted at the end of the type processing. In templated contexts we instead perform that analysis during parsing, and delay the rest of the processing. To avoid churn in unrelated tests the rest of the attributes keep their processing as is, until we've investigated what impact hoisting it would have on the user experience.

@hnrklssn hnrklssn requested a review from a team as a code owner January 31, 2025 06:45
@hnrklssn
Copy link
Author

@swift-ci please test llvm
@swift-ci please smoke test

@hnrklssn hnrklssn added the clang:bounds-safety Issue relating to the experimental -fbounds-safety feature in Clang label Jan 31, 2025
@@ -3485,6 +3485,9 @@ namespace clang {

void DynamicCountPointerAssignmentAnalysis::run() {
AnalysisDeclContext AC(/* AnalysisDeclContextManager */ nullptr, dcl);
if (isa<TemplateDecl>(dcl)) // delay processing until template has been
// instantiated
return;

Choose a reason for hiding this comment

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

How does it work? Will DynamicCountPointerAssignmentAnalysis::run be called again when dcl is instantiated?

Copy link
Author

Choose a reason for hiding this comment

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

Yes, a new function decl will be created for the instantiated version and all the normal analyses will be run over it

Choose a reason for hiding this comment

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

Is this sufficient? We could have dependent expressions when the declaration itself is not templated:

  • Non-templated method of a templated struct referencing the template argument
  • Lambda inside a templated function referencing the template argument
  • Method of a non-templated struct nested inside a templated struct referencing the outer struct's template argument

Copy link
Author

Choose a reason for hiding this comment

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

Changed to an isTemplated call instead

Choose a reason for hiding this comment

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

Dumb question: what's the difference between using isTemplated and isDependentContext() to rule out templates?

Copy link
Author

Choose a reason for hiding this comment

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

isTemplated covers some extra cases:

bool Decl::isTemplated() const {
  // A declaration is templated if it is a template or a template pattern, or
  // is within (lexcially for a friend or local function declaration,
  // semantically otherwise) a dependent context.
  if (auto *AsDC = dyn_cast<DeclContext>(this))
    return AsDC->isDependentContext();
  auto *DC = getFriendObjectKind() || isLocalExternDecl()
      ? getLexicalDeclContext() : getDeclContext();
  return DC->isDependentContext() || isTemplateDecl() ||
         getDescribedTemplateParams();
}

@patrykstefanski
Copy link

CC @ziqingluo-90 since we were discussing templates recently.

@hnrklssn
Copy link
Author

hnrklssn commented Feb 1, 2025

@swift-ci please test

@hnrklssn
Copy link
Author

hnrklssn commented Feb 1, 2025

@swift-ci please test llvm

Copy link

@Xazax-hun Xazax-hun left a comment

Choose a reason for hiding this comment

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

I am not sure non-templated entities nested in templated entities are correctly handled, the very least we should have some tests for that. Added some examples of such cases inline.

@@ -3485,6 +3485,9 @@ namespace clang {

void DynamicCountPointerAssignmentAnalysis::run() {
AnalysisDeclContext AC(/* AnalysisDeclContextManager */ nullptr, dcl);
if (isa<TemplateDecl>(dcl)) // delay processing until template has been
// instantiated
return;

Choose a reason for hiding this comment

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

Is this sufficient? We could have dependent expressions when the declaration itself is not templated:

  • Non-templated method of a templated struct referencing the template argument
  • Lambda inside a templated function referencing the template argument
  • Method of a non-templated struct nested inside a templated struct referencing the outer struct's template argument

static void handlePtrCountedByEndedByAttr(Sema &S, Decl *D,
const ParsedAttr &AL) {
unsigned Level;
if (!S.checkUInt32Argument(AL, AL.getArgAsExpr(1), Level))
return;

if (D->getDescribedTemplate() || S.CurContext->isDependentContext()) {

Choose a reason for hiding this comment

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

Same question here, wondering if this is sufficient for non-templated entities nested in templates.

Copy link
Author

Choose a reason for hiding this comment

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

Also replaced with a call to isTemplated

@hnrklssn hnrklssn force-pushed the bounds-safety-value-dependence branch from 427922f to 9858ef7 Compare February 11, 2025 04:55
@hnrklssn hnrklssn requested a review from a team as a code owner February 11, 2025 04:55
@hnrklssn
Copy link
Author

@swift-ci please test llvm

@hnrklssn
Copy link
Author

@swift-ci please smoke test

@ziqingluo-90
Copy link

Just curious: will you allow T being replaced with an annotated type? E.g.,

template<typename T> void f(T x);

void g(int * __counted_by(10) p) {
  f(p);
}

@hnrklssn
Copy link
Author

template void f(T x);

void g(int * __counted_by(10) p) {
f(p);
}

This is currently ignored. I think it's related to how these attributes are completely ignored in casts, even C-style casts. I don't have any plans to fix this right now, but I do think that it would be good to get there eventually.

@hnrklssn
Copy link
Author

@swift-ci please test llvm

@hnrklssn
Copy link
Author

@swift-ci please smoke test

Dynamic bounds attributes do not handle value dependent arguments. To
enable wider interop with C++ code bases we delay the processing of
these attributes inside templated contexts. Instead we apply the new
type while instantiating the function.

One issue with this approach is that instantiation happens after parsing
is finished, and the Scope information we use to prevent attributes from
referring to arguments from outer scopes is only available during
parsing. These diagnostics were emitted at the end of the type
processing. In templated contexts we instead perform that analysis
during parsing, and delay the rest of the processing. To avoid churn in
unrelated tests the rest of the attributes keep their processing as is,
until we've investigated what impact hoisting it would have on the user
experience.
This analysis is now done after inserting BoundsCheckExpr, so we need to
peek through them to emit these warnings where we previously only had a
BoundsSafetyPointerCast.
@hnrklssn hnrklssn force-pushed the bounds-safety-value-dependence branch from 6cabde8 to 8510657 Compare February 13, 2025 06:15
@hnrklssn
Copy link
Author

@swift-ci please test

@hnrklssn
Copy link
Author

windows failure seems unrelated

@hnrklssn
Copy link
Author

@swift-ci please test llvm

@hnrklssn hnrklssn merged commit c87bc75 into swiftlang:stable/20240723 Feb 13, 2025
4 of 5 checks passed
delcypher pushed a commit to delcypher/apple-llvm-project that referenced this pull request May 22, 2025
This is a cherry-pick of change that was landed to `stable/20240723`
(swiftlang#9929)
but not landed on the `next` branch.

Aside from resolving merge conflicts it was also necessary to modify
the `clang/test/BoundsSafety/(AST|Sema)/value-dependence.cpp` tests
because they fail to work when the new bounds checks are enabled
(rdar://150044760). To workaround that the test has been made to disable
the new `return_size` bounds check when `-fbounds-safety` is enabled
with C++.

---
* [BoundsSafety] Delay processing of bounds attrs in templates

Dynamic bounds attributes do not handle value dependent arguments. To
enable wider interop with C++ code bases we delay the processing of
these attributes inside templated contexts. Instead we apply the new
type while instantiating the function.

One issue with this approach is that instantiation happens after parsing
is finished, and the Scope information we use to prevent attributes from
referring to arguments from outer scopes is only available during
parsing. These diagnostics were emitted at the end of the type
processing. In templated contexts we instead perform that analysis
during parsing, and delay the rest of the processing. To avoid churn in
unrelated tests the rest of the attributes keep their processing as is,
until we've investigated what impact hoisting it would have on the user
experience.

 Conflicts:
	clang/include/clang/Sema/Sema.h
	clang/lib/Sema/SemaDecl.cpp
	clang/lib/Sema/SemaTemplateInstantiateDecl.cpp
	clang/test/SemaCXX/warn-unsafe-buffer-usage-count-attributed-pointer-argument.cpp

rdar://150694971

(cherry picked from commit c87bc75)
delcypher pushed a commit that referenced this pull request May 23, 2025
This is a cherry-pick of change that was landed to `stable/20240723`
(#9929)
but not landed on the `next` branch.

Aside from resolving merge conflicts it was also necessary to modify
the `clang/test/BoundsSafety/(AST|Sema)/value-dependence.cpp` tests
because they fail to work when the new bounds checks are enabled
(rdar://150044760). To workaround that the test has been made to disable
the new `return_size` bounds check when `-fbounds-safety` is enabled
with C++.

---
* [BoundsSafety] Delay processing of bounds attrs in templates

Dynamic bounds attributes do not handle value dependent arguments. To
enable wider interop with C++ code bases we delay the processing of
these attributes inside templated contexts. Instead we apply the new
type while instantiating the function.

One issue with this approach is that instantiation happens after parsing
is finished, and the Scope information we use to prevent attributes from
referring to arguments from outer scopes is only available during
parsing. These diagnostics were emitted at the end of the type
processing. In templated contexts we instead perform that analysis
during parsing, and delay the rest of the processing. To avoid churn in
unrelated tests the rest of the attributes keep their processing as is,
until we've investigated what impact hoisting it would have on the user
experience.

 Conflicts:
	clang/include/clang/Sema/Sema.h
	clang/lib/Sema/SemaDecl.cpp
	clang/lib/Sema/SemaTemplateInstantiateDecl.cpp
	clang/test/SemaCXX/warn-unsafe-buffer-usage-count-attributed-pointer-argument.cpp

rdar://150694971

(cherry picked from commit c87bc75)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
clang:bounds-safety Issue relating to the experimental -fbounds-safety feature in Clang
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants