<div dir="ltr"><div dir="ltr"><div dir="ltr"><br></div><br><div class="gmail_quote"><div dir="ltr" class="gmail_attr">On Mon, Jan 28, 2019 at 10:44 AM Louis Dionne via libcxx-dev <<a href="mailto:libcxx-dev@lists.llvm.org">libcxx-dev@lists.llvm.org</a>> wrote:<br></div><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left:1px solid rgb(204,204,204);padding-left:1ex"><div style="overflow-wrap: break-word;"><div><div>Hi,</div><div><br></div><div>My understanding is that libc++ has a no-exceptions mode under which we never throw and never use try-catch blocks. This mode is enabled by defining <font face="Menlo">_LIBCPP_NO_EXCEPTIONS</font>, or by passing <font face="Menlo">LIBCXX_ENABLE_EXCEPTIONS=OFF</font> when configuring CMake.</div><div><br></div><div>However, I'd like to understand the design of this mode a bit better. It seems like we sometimes call <font face="Menlo">abort()</font> instead of throwing, but sometimes we just swallow the would-be exception and carry on. For example, instead of throwing <font face="Menlo">std::bad_optional_access</font>, we call <font face="Menlo">std::abort()</font>:</div><div><br></div><div><font face="Menlo">    void __throw_bad_optional_access() {</font></div><div><font face="Menlo">    #ifndef _LIBCPP_NO_EXCEPTIONS</font></div><div><font face="Menlo">            throw bad_optional_access();</font></div><div><font face="Menlo">    #else</font></div><div><font face="Menlo">            _VSTD::abort();</font></div><div><font face="Menlo">    #endif</font></div><div><font face="Menlo">    }</font></div><div><br></div><div>This makes sense to me. However, in other cases, we just swallow the exception. For example, in <font face="Menlo">std::map::at</font> when we can't find the key, we just don't throw an exception, and we dereference a null pointer unless I'm mistaken:</div><div><br></div><div><font face="Menlo">    template <class _Key, class _Tp, class _Compare, class _Allocator></font></div><div><font face="Menlo">    const _Tp& map<_Key, _Tp, _Compare, _Allocator>::at(const key_type& __k) const {</font></div><div><font face="Menlo">        __parent_pointer __parent;</font></div><div><font face="Menlo">        __node_base_pointer __child = __tree_.__find_equal(__parent, __k);</font></div><div><font face="Menlo">    #ifndef _LIBCPP_NO_EXCEPTIONS</font></div><div><font face="Menlo">        if (__child == nullptr)</font></div><div><font face="Menlo">            throw out_of_range("map::at:  key not found");</font></div><div><font face="Menlo">    #endif  // _LIBCPP_NO_EXCEPTIONS</font></div><div><font face="Menlo">        return static_cast<__node_pointer>(__child)->__value_.__get_value().second;</font></div><div><font face="Menlo">    }</font></div><div><br></div><div>Here, when <font face="Menlo">__child == nullptr</font>, we do NOT throw an exception and we end up dereferencing it, which is UB. I haven't made a full survey of libc++, but I'd like to know whether this is by design, and if so, what is the rationale for it. Otherwise, I'd like for us to agree on what the behaviour should be (I suggest <font face="Menlo">std::abort()</font>) so that we can fix places that don't behave correctly.</div></div></div><br></blockquote><div><br></div><div>std::abort is the "correct" behavior.</div><div>We have a "__throw_out_of_range" call that does what we want.</div><div>Change the code to:</div><div><br></div><div><div><font face="Menlo">- #ifndef _LIBCPP_NO_EXCEPTIONS</font></div><div><font face="Menlo">        if (__child == nullptr)</font></div><div><font face="Menlo">-            throw out_of_range("map::at:  key not found");</font></div><div><font face="Menlo">+           __throw_out_of_range("map::at:  key not found");</font></div><div><span style="font-family:Menlo">- #endif  // _LIBCPP_NO_EXCEPTIONS</span><br></div></div><div><br></div><div>AND it gets rid of ifdefs!</div><div><br></div><div>-- Marshall</div><div><br></div><div> </div></div></div></div>