Skip to content

[clang] SemaFunctionEffects: When verifying a function, ignore any trailing 'requires' clause. #114266

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
merged 3 commits into from
Nov 5, 2024

Conversation

dougsonos
Copy link
Contributor

Clearly there's an omission here, that a trailing requires clause in a called function is being subject to effect analysis.

But despite many hours of effort I haven't been able to create a self-contained reproducer. My best effort:

#include <valarray>
#include <type_traits>
#include <expected>

template <class _Tp, _Tp __v>
struct integral_constant {
  static constexpr _Tp value = __v;
  typedef _Tp value_type;
  typedef integral_constant type;
  constexpr operator value_type() const noexcept { return value; }
  constexpr value_type operator()() const noexcept { return value; }
};

template <typename T>
struct IsInt : public integral_constant<bool, false> {};

template <>
struct IsInt<int> : public integral_constant<bool, true> {};

template <typename T>
inline constexpr bool IsInt_V = IsInt<T>::value;

template <typename T, typename E>
struct ExpectedLike {
	ExpectedLike() = default;
	
	constexpr ExpectedLike(const ExpectedLike&)
// 	requires(IsInt_V<T> && IsInt_V<E> && IsInt_V<T>)
    requires(std::is_copy_constructible_v<T> && std::is_copy_constructible_v<E> && std::is_trivially_copy_constructible_v<T> &&
             std::is_trivially_copy_constructible_v<E>)

	= default;
	
};

void nb_xx() [[clang::nonblocking]]
{
	ExpectedLike<int, int> a;
	auto b = a;
// No warning. I don't know why.

	std::expected<int, int> c;
	std::expected<int, int> d = c;
// ^^ warning: function with 'nonblocking' attribute must not call non-'nonblocking' constructor 'std::expected<int, int>::expected' [-Wfunction-effects]
}

The two elements of the mystery are:

  • why doesn't my ExpectedLike<T, E> copy constructor reproduce the behavior of std::expected<T, E>?
  • why does reproduction with std::expected depend on the global operator && in <valarray>?

I've verified that the change to SemaFunctionEffects fixes the issue, but I'd sure like to be able to construct a self-contained test.

@llvmbot llvmbot added clang Clang issues not falling into any other category clang:frontend Language frontend issues, e.g. anything involving "Sema" labels Oct 30, 2024
@llvmbot
Copy link
Member

llvmbot commented Oct 30, 2024

@llvm/pr-subscribers-clang

Author: Doug Wyatt (dougsonos)

Changes

Clearly there's an omission here, that a trailing requires clause in a called function is being subject to effect analysis.

But despite many hours of effort I haven't been able to create a self-contained reproducer. My best effort:

#include &lt;valarray&gt;
#include &lt;type_traits&gt;
#include &lt;expected&gt;

template &lt;class _Tp, _Tp __v&gt;
struct integral_constant {
  static constexpr _Tp value = __v;
  typedef _Tp value_type;
  typedef integral_constant type;
  constexpr operator value_type() const noexcept { return value; }
  constexpr value_type operator()() const noexcept { return value; }
};

template &lt;typename T&gt;
struct IsInt : public integral_constant&lt;bool, false&gt; {};

template &lt;&gt;
struct IsInt&lt;int&gt; : public integral_constant&lt;bool, true&gt; {};

template &lt;typename T&gt;
inline constexpr bool IsInt_V = IsInt&lt;T&gt;::value;

template &lt;typename T, typename E&gt;
struct ExpectedLike {
	ExpectedLike() = default;
	
	constexpr ExpectedLike(const ExpectedLike&amp;)
// 	requires(IsInt_V&lt;T&gt; &amp;&amp; IsInt_V&lt;E&gt; &amp;&amp; IsInt_V&lt;T&gt;)
    requires(std::is_copy_constructible_v&lt;T&gt; &amp;&amp; std::is_copy_constructible_v&lt;E&gt; &amp;&amp; std::is_trivially_copy_constructible_v&lt;T&gt; &amp;&amp;
             std::is_trivially_copy_constructible_v&lt;E&gt;)

	= default;
	
};

void nb_xx() [[clang::nonblocking]]
{
	ExpectedLike&lt;int, int&gt; a;
	auto b = a;
// No warning. I don't know why.

	std::expected&lt;int, int&gt; c;
	std::expected&lt;int, int&gt; d = c;
// ^^ warning: function with 'nonblocking' attribute must not call non-'nonblocking' constructor 'std::expected&lt;int, int&gt;::expected' [-Wfunction-effects]
}

The two elements of the mystery are:

  • why doesn't my ExpectedLike&lt;T, E&gt; copy constructor reproduce the behavior of std::expected&lt;T, E&gt;?
  • why does reproduction with std::expected depend on the global operator &amp;&amp; in &lt;valarray&gt;?

I've verified that the change to SemaFunctionEffects fixes the issue, but I'd sure like to be able to construct a self-contained test.


Full diff: https://github.com/llvm/llvm-project/pull/114266.diff

1 Files Affected:

  • (modified) clang/lib/Sema/SemaFunctionEffects.cpp (+10)
diff --git a/clang/lib/Sema/SemaFunctionEffects.cpp b/clang/lib/Sema/SemaFunctionEffects.cpp
index 3fa326db06ee41..f7ff8b92d8a929 100644
--- a/clang/lib/Sema/SemaFunctionEffects.cpp
+++ b/clang/lib/Sema/SemaFunctionEffects.cpp
@@ -971,6 +971,7 @@ class Analyzer {
     PendingFunctionAnalysis &CurrentFunction;
     CallableInfo &CurrentCaller;
     ViolationSite VSite;
+    const Expr *TrailingRequiresClause = nullptr;
 
     FunctionBodyASTVisitor(Analyzer &Outer,
                            PendingFunctionAnalysis &CurrentFunction,
@@ -985,6 +986,9 @@ class Analyzer {
       if (auto *Dtor = dyn_cast<CXXDestructorDecl>(CurrentCaller.CDecl))
         followDestructor(dyn_cast<CXXRecordDecl>(Dtor->getParent()), Dtor);
 
+      if (auto *FD = dyn_cast<FunctionDecl>(CurrentCaller.CDecl))
+        TrailingRequiresClause = FD->getTrailingRequiresClause();
+
       // Do an AST traversal of the function/block body
       TraverseDecl(const_cast<Decl *>(CurrentCaller.CDecl));
     }
@@ -1259,6 +1263,12 @@ class Analyzer {
       return true;
     }
 
+    bool TraverseStmt(Stmt *Statement) {
+      if (Statement != TrailingRequiresClause)
+        return Base::TraverseStmt(Statement);
+      return true;
+    }
+
     bool TraverseConstructorInitializer(CXXCtorInitializer *Init) {
       ViolationSite PrevVS = VSite;
       if (Init->isAnyMemberInitializer())

@cjappl
Copy link
Contributor

cjappl commented Oct 30, 2024

@dougsonos - I was recently made aware of C-reduce, which takes some code, how to compile it, and an "interestingness test" and eventually spits out the smallest subset of things that are interesting.

https://www.mikeash.com/pyblog/friday-qa-2018-06-29-debugging-with-c-reduce.html

Could be useful in your hunt for an example/test.

This is something the clang frontend folks recommend using when they get compiler bugs (https://shafik.github.io/c++/llvm/2024/10/17/triaging-clang-fronend-bugs.html, cmd+f "reduce")

@dougsonos
Copy link
Contributor Author

@dougsonos - I was recently made aware of C-reduce, which takes some code, how to compile it, and an "interestingness test" and eventually spits out the smallest subset of things that are interesting.

https://www.mikeash.com/pyblog/friday-qa-2018-06-29-debugging-with-c-reduce.html

Could be useful in your hunt for an example/test.

This is something the clang frontend folks recommend using when they get compiler bugs (https://shafik.github.io/c++/llvm/2024/10/17/triaging-clang-fronend-bugs.html, cmd+f "reduce")

Yeah, I tried creduce earlier this morning when the reproducer was 120,000 lines :-P It ran for maybe half an hour and gobbled up 20 GB of disk space so I cancelled and worked out the above by hand. I'm pretty sure the first missing ingredient has to do with the space-saving optimizations in __expected_base, and that might also be what's triggering valarray's operator&&...

@dougsonos
Copy link
Contributor Author

@dougsonos - I was recently made aware of C-reduce, which takes some code, how to compile it, and an "interestingness test" and eventually spits out the smallest subset of things that are interesting.
https://www.mikeash.com/pyblog/friday-qa-2018-06-29-debugging-with-c-reduce.html
Could be useful in your hunt for an example/test.
This is something the clang frontend folks recommend using when they get compiler bugs (https://shafik.github.io/c++/llvm/2024/10/17/triaging-clang-fronend-bugs.html, cmd+f "reduce")

Yeah, I tried creduce earlier this morning when the reproducer was 120,000 lines :-P It ran for maybe half an hour and gobbled up 20 GB of disk space so I cancelled and worked out the above by hand. I'm pretty sure the first missing ingredient has to do with the space-saving optimizations in __expected_base, and that might also be what's triggering valarray's operator&&...

but thanks for the nudge -- creduce is making progress with this more minimal starting point!

@Sirraide
Copy link
Member

The two elements of the mystery are:

  • why doesn't my ExpectedLike<T, E> copy constructor reproduce the behavior of std::expected<T, E>?
  • why does reproduction with std::expected depend on the global operator && in <valarray>?

Yeah, that sounds pretty horrible to reduce. Dumping the AST to figure out what’s going on might be worth a try, but I’m not sure this would actually help too much...

@Sirraide
Copy link
Member

Also, @dougsonos, can you make a godbolt link for this? I’m trying to reproduce it, but compiling the code you provided doesn’t result in any diagnostics for me, but I might be missing a flag.

@dougsonos
Copy link
Contributor Author

Also, @dougsonos, can you make a godbolt link for this? I’m trying to reproduce it, but compiling the code you provided doesn’t result in any diagnostics for me, but I might be missing a flag.

Yeah, I can't repro in godbolt either. This shows that all the effect analysis is in the clang-20 trunk on godbolt though:
https://godbolt.org/z/rP1E5ebhG

That makes me think that the issue may be specific to the um, older, version of libc++ that I've been testing against.

I'm pretty sure it has something to do with type traits in the requires() clause of the std::expected copy constructor, interacting badly with a global operator&& in <valarray>.

What I found impossible about creduce is that at some point it started ignoring requirements like the requires clause and collapsing all the templates.

Thanks for taking a look.

@dougsonos
Copy link
Contributor Author

dougsonos commented Nov 2, 2024

I spent a bit more time trying to extract a reduction from libc++ today but failed. Also I noted that my "older" libc++ has no suspicious diffs with the one on main. Haven't quite figured out how to build it locally.

Here are the diagnostics that (fortunately) were enough to devise the fix in this PR:

...../nonblocking-wip.cpp:54:30: warning: function with 'nonblocking' attribute must not call non-'nonblocking' constructor 'std::expected<int, int>::expected' [-Wfunction-effects]
   54 |  std::expected<int, int> d = c;
      |                              ^
...../usr/include/c++/v1/__expected/expected.h:483:14: note: function pointer cannot be inferred 'nonblocking'
  483 |     requires(is_copy_constructible_v<_Tp> && is_copy_constructible_v<_Err> && is_trivially_copy_constructible_v<_Tp> &&
      |              ^
.....//nonblocking-wip.cpp:54:30: note: in template expansion here
   54 |  std::expected<int, int> d = c;
      |                              ^

That showed me that the requires clause is being traversed (inappropriately) and doing something that looks like an indirect call (i.e. through a function pointer). Underneath is_copy_constructible_v is a builtin function __is_constructible(), so the indirect call isn't surprising (though I can't say I 100% understand yet).

@dougsonos
Copy link
Contributor Author

I was able to (manually) extract a reduction from libc++ and write a test that exposed the issue / verified that it's fixed. It's also here: https://godbolt.org/z/9absooo6G

Copy link
Contributor

@cjappl cjappl left a comment

Choose a reason for hiding this comment

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

for whatever it's worth - LGTM. Leaving to more experienced heads in this area to actually approve.

Good sleuthing!

@Sirraide
Copy link
Member

Sirraide commented Nov 4, 2024

is a builtin function __is_constructible()

Technically a type trait not a function—those are different because they take types as arguments, and there are separate expressions to represent them (UnaryExprOrTypeTraitExpr, TypeTraitExpr, and a few more); maybe we’re somehow not accounting for those?

Do we have tests for effect analysis that use builtin type traits?

Copy link
Member

@Sirraide Sirraide left a comment

Choose a reason for hiding this comment

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

Now that we have a test, this definitely lgtm. We should still look into the type traits thing, but that can be a separate pr if it turns out that any changes are necessary to account for them.

@Sirraide
Copy link
Member

Sirraide commented Nov 4, 2024

I was able to (manually) extract a reduction from libc++

Also, thanks for reducing this!

@Sirraide Sirraide merged commit 7f9d348 into llvm:main Nov 5, 2024
8 checks passed
@dougsonos
Copy link
Contributor Author

is a builtin function __is_constructible()

Technically a type trait not a function—those are different because they take types as arguments, and there are separate expressions to represent them (UnaryExprOrTypeTraitExpr, TypeTraitExpr, and a few more); maybe we’re somehow not accounting for those?

Do we have tests for effect analysis that use builtin type traits?

Thanks, I didn't know that these were type traits, they looked like special builtin functions to me, and that ignorance may be reflected in some of the diagnostics.

Generally expressions are ignored except CallExpr IIRC.

I'll look into this further. Thanks for the review here.

PhilippRados pushed a commit to PhilippRados/llvm-project that referenced this pull request Nov 6, 2024
…ailing `requires` clause. (llvm#114266)

---------

Co-authored-by: Doug Wyatt <[email protected]>
jroelofs pushed a commit to swiftlang/llvm-project that referenced this pull request Nov 12, 2024
…ailing `requires` clause. (llvm#114266)

---------

Co-authored-by: Doug Wyatt <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
clang:frontend Language frontend issues, e.g. anything involving "Sema" clang Clang issues not falling into any other category
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants