Skip to content

Add pure/const function attribute (refs #12695)#6385

Draft
chrchr-github wants to merge 7 commits intocppcheck-opensource:mainfrom
chrchr-github:chr_pure2
Draft

Add pure/const function attribute (refs #12695)#6385
chrchr-github wants to merge 7 commits intocppcheck-opensource:mainfrom
chrchr-github:chr_pure2

Conversation

@chrchr-github
Copy link
Copy Markdown
Collaborator

No description provided.

Comment thread cfg/qt.cfg
<!-- bool QFile::exists(const QString &fileName) // static -->
<!-- bool QFile::exists() const -->
<function name="QFile::exists">
<const/>
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This is definitely not const as it relies on some state. And it is not even pure as it relies on the filesystem and might return different results at different points in the application.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

This is supposed to be member-const, not attribute-const. Overloading that term is very unfortunate.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

We have code which use ispure and isconst as equal so we have existing could which already mixes this. See CheckAssert::assertWithSideEffects().

Also the library loading handles it as "function attribute" const - see Library::loadFunction().

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Besides, assertWithSideEffects() also uses member-const as a sign of no-side-effects in user-declared functions.
A complete mess, as usual.

Comment thread cfg/std.cfg
</function>
<!-- int toupper(int c); -->
<function name="toupper,std::toupper">
<pure/>
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This is not pure as it depends on the locale which might change during run-time.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Yet we use pure as our (only) indicator if a function has side effects.
You can also change the floating-point rounding mode, which affects the results of certain math functions...

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

You can also change the floating-point rounding mode, which affects the results of certain math functions...

True. If you would limit this assumption to only the current scope it seems like you could apply that information but as it depends on a C library function which might be called in another library function that might lead to unexpected effects. I wonder if the compiler will consider this or if this is considered like something as a "data race" or if you are calling chdir() somewhere in the code meaning that all bets are off.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Maybe this is what the difference between pure and const is about? pure function might be affected by the mutable global state as a locale or floating-point modes and const ones are not.

That might open up another (whole program analysis) check which would warn if you use pure functions and any of these global state changing functions in your code base.

Comment thread cfg/std.cfg
<!-- bool std::map::contains( const Key& key ) const; // since C++20 -->
<!-- template< class K > bool std::map::contains( const K& x ) const; // since C++20 -->
<function name="std::map::contains">
<const/>
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This is not "function attribute" const.

Comment thread cfg/posix.cfg
<!-- http://pubs.opengroup.org/onlinepubs/000095399/functions/sigismember.html -->
<!-- int sigismember(const sigset_t *set, int signum);-->
<function name="sigismember">
<pure/>
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This is not pure as the object pointed to might change during run-time.

Copy link
Copy Markdown
Collaborator Author

@chrchr-github chrchr-github May 6, 2024

Choose a reason for hiding this comment

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

Edit: moved below

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Wrong comment.

Comment thread cfg/posix.cfg
<!-- see http://man7.org/linux/man-pages/man3/strnlen.3.html-->
<!-- size_t strnlen(const char *s, size_t maxlen); -->
<function name="strnlen,wcsnlen">
<pure/>
Copy link
Copy Markdown
Collaborator

@firewave firewave May 6, 2024

Choose a reason for hiding this comment

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

This is marked pure in the standard header but I do not understand how it could be.

Consider this (horrible) example:

int len = strlen(p);
while (len > 0)
{
     p[len-1] = '\0';
     len = strlen(p);
}

The result of strlen() on the pointer would obviously be different on each call. So if the compiler would eliminate multiple calls to it we would get unexpected results.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

strlen() is designated as a pure function here: https://gcc.gnu.org/onlinedocs/gcc/Common-Function-Attributes.html

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

The compiler somehow seems to know that it cannot be called less: https://godbolt.org/z/dKnfPreh7.

So if there is some additional logic involved within the compiler this attribute seems worthless (apart from implying nodiscard) in cases where we could apply that (like a possibly variableScope extension).

There is a proposal for [[pure]]]:
https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2015/p0078r0.pdf. That seems to get into the case where you pass mutable data into a pure function.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I assume the compiler might check if the input is somehow modified and leverages that information to make the decision.

Reading that paper I think it might sense to define our pure in the way section 6 Redefining pure does and even create a check for this.

I will also file a clang-tidy ticket about this upstream.

@chrchr-github
Copy link
Copy Markdown
Collaborator Author

How do we proceed here? @danmar @orbitcowboy

  • attribute-const and member-const are mixed up in the library, since there is only one <const/> for both
  • How will we deduce presence/absence of side effects going forward?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants