[llvm-bugs] [Bug 43275] New: Pure attribute and C++ exceptions

via llvm-bugs llvm-bugs at lists.llvm.org
Wed Sep 11 05:01:00 PDT 2019


https://bugs.llvm.org/show_bug.cgi?id=43275

            Bug ID: 43275
           Summary: Pure attribute and C++ exceptions
           Product: clang
           Version: 8.0
          Hardware: All
                OS: All
            Status: NEW
          Severity: normal
          Priority: P
         Component: C++
          Assignee: unassignedclangbugs at nondot.org
          Reporter: frankhb1989 at gmail.com
                CC: blitzrakete at gmail.com, dgregor at apple.com,
                    erik.pilkington at gmail.com, llvm-bugs at lists.llvm.org,
                    richard-llvm at metafoo.co.uk

Case:

#include <map>
using namespace std;

struct M
{
        std::map<int, long> m{{1, 2L}};

        [[gnu::pure]] long&
        query(int k)
        {
                return m.at(k);
        }
};

int main()
{
        M m;

        try
        {
                m.query(3);
        }
        catch(std::out_of_range&)
        {}
}

Currently the case may behave differently with g++/clang++, depending on the
target and the unwinder used.

Using clang++ with ARM EHABI libunwind (with [1]), it will crash because the
generated index table entry (defined by ARM EHABI [2]) for the frame contains
`EXIDX_CANTUNWIND`, so the unwinder returns `_URC_FAILURE` as the result of
`_Unwind_RaiseException`. Then, `std::__terminate` is immediately called in
`__cxa_throw`, despite any enclosing `try` and `catch` clauses remained.

The `pure` attribute in the source code implies `nounwind` in IR, which causes
the non-intuitive behavior.

Since this divergence will cause real problems in transition g++ to clang++
(like in Android NDK, and in this case it is actually more troublesome than the
migration of the compiler itself [3]), clang++ should better handle the case
specifically. I prefer `pure` does not imply `nounwind`, and if the old
behavior is needed, `__attribute__((pure, nothrow))` should be sufficient.
Otherwise, if it should be the responsibility of the user to care the
divergence, at least a diagnostic message to warn about suspicious cases (where
`throw` and `pure` attribute are used together) will be helpful.

(After days of work to pull the logic from libcxxabi/libunwind to my code and
finally seeing this is not the problem of the runtime, I'd like to say, this is
really terrible experience.)

# Appendix

The root reason is that, historically, the semantics of the `pure` attribute is
a bit unclear, particularly with respect to exception handling.

In the documentation of some old version of GCC [4], use of the `pure`
attribute only implies that the function should be subject to CSE and loop
optimization, but not explicitly prevent the exception propagation (as a
precondition). However, it is known that, different to clang++, g++ does not
optimize when C++ exception handling is enabled, and such behavior might be
useful [5]. Since the documentation for the `pure` attribute is missing in
Clang for years [6], users can easily get confused which behavior is correct
(by design).

With the new release of GCC 9, the documentation [7] has been updated to make
it clearly "prohibits a function from modifying the state of the program that
is observable by means other than inspecting the function's return value".
However, this still not eliminate the potential code transition problem between
old versions of g++ and clang++. Avoiding the misuse is now the responsibility
of users.

But more problems still remain.

The semantics of the `pure` attribute that both g++ and clang++ follow has once
been proposed to C++17 [8]. They share many common problems.

(1) Even if the intent can be somewhat clear, the reasoning from the theory
presented in the proposal is flawed. Although traditional mathematical theories
have no notion about side effects, not all modern ones are the same. Rewrite
systems like lambda calculi are already extended (to be operational semantic
models of programming languages) with built-in support for side effects,
including states and control effects. And even pure functional programming
practice does not reject extra effects expressed implicitly in the programs
(for instance, by monadic syntax transformation or algebraic effect system).
Thus, the conclusion of equivalence between using of pure functions and
side-effect-free property (in section 5.1) is unsound. "The fact" of "function
should not have any side effect" mentioned here is only widely accepted by
users without experience of (some cutting-edge) effectful programming systems.
This does not mean it is true in nature. Such misconception may raise new
confusion about the extent of "pure" with different definitions from various
theories (not less than the confusion between "pure" and "const" attributes).

(2) The proposal did not make the control effects explicit. It only mentions
the requirements of preventing "observable" side effects and informally
excludes exception propagation in the note of the proposed wording. The
terminology of "observable side effects" is lack of definition. (And worse,
there can be more problems on "observable" [9].)

(3) More generally, non-strict evaluations (like clauses of an `if` statement
or short circuit evaluation of some subexpressions) can be restricted forms of
control effects (via some continuation-style representations) implemented by
branching. So does a `return` statement. So, why set `throw` aside?

(4) Not only concerns on debugging mentioned in the proposal, it also clash
with features probably but not always used for debugging like backtrace. This
is not only about the compiler frontend feature, see [10].

(5) When used as a part of the interface, it does not work well with the mental
model of narrow contracts [11] used for C++ library interface design.

As of the GCC documentation [7], it is unclear that what exactly "observable
effects" are. Besides, are the states of the implementation details (like those
in the unwinder) parts of the states of a C++ program? Moreover, it is still
not clear about the effects of violation. Since it does not state clearly that
the behavior will be undefined, violation of the precondition can be
interpreted as having unspecified but more or less predictable (due to the
nature of CSE) results in the program. Users may still find reasons to rely on
incompatible (to clang++) behavior. That's bad.

[1] https://reviews.llvm.org/rL238560
[2]
http://infocenter.arm.com/help/topic/com.arm.doc.ihi0038b/IHI0038B_ehabi.pdf
[3]
https://android.googlesource.com/platform/ndk/+/master/docs/BuildSystemMaintainers.md#Unwinding
[4]
https://gcc.gnu.org/onlinedocs/gcc-8.3.0/gcc/Common-Function-Attributes.html#index-pure-function-attribute
[5]
https://kristerw.blogspot.com/2016/12/gcc-attributepure-and-c-exceptions.html
[6] http://lists.llvm.org/pipermail/cfe-dev/2016-April/048295.html
[7]
https://gcc.gnu.org/onlinedocs/gcc-9.2.0/gcc/Common-Function-Attributes.html#index-pure-function-attribute
[8] http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2015/p0078r0.pdf
[9] https://github.com/cplusplus/draft/issues/3215
[10] http://logan.tw/llvm/nounwind.html
[11] http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2011/n3248.pdf

-- 
You are receiving this mail because:
You are on the CC list for the bug.
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.llvm.org/pipermail/llvm-bugs/attachments/20190911/08a86de7/attachment-0001.html>


More information about the llvm-bugs mailing list