Skip to content

<exception>: constructor does not copy what_arg if _HAS_EXCEPTIONS=0 #2114

Open

Description

Describe the bug

When _HAS_EXCEPTIONS is defined to 0, the constructors of the exception classes do not copy the what_arg, but only store the pointer:

explicit __CLR_OR_THIS_CALL exception(const char* _Message = "unknown", int = 1) noexcept : _Ptr(_Message) {}

This leads to errors if the pointee is a temporary string.

If _HAS_EXCEPTIONS is set to 1, a copy is made just as the standard would require it.

Command-line test case

The following code outputs [json.exception.parse_error] foo if _HAS_EXCEPTIONS is set to 1, and some garbage if _HAS_EXCEPTIONS is set to 0:

#include <exception> // exception
#include <stdexcept> // runtime_error
#include <string> // string
#include <iostream> // cout

namespace nlohmann
{
namespace detail2
{

class exception : public std::exception
{
  public:
    const char* what() const noexcept override
    {
        return m.what();
    }

  protected:
    exception(const char* what_arg) : m(what_arg) {}

  private:
    std::runtime_error m;
};

class parse_error : public exception
{
  public:
    static parse_error create(const std::string& what_arg)
    {
        std::string w = "[json.exception.parse_error] " + what_arg;
        return parse_error(w.c_str());
    }

  private:
    parse_error(const char* what_arg) : exception(what_arg) {}
};

}  // namespace detail2
}  // namespace nlohmann


int main()
{
    auto error = nlohmann::detail2::parse_error::create("foo");
    std::cout << error.what() << std::endl;
}

The example comes from https://github.com/nlohmann/json where exceptions are created by a SAX parser, but it is up to the client whether they are thrown or not. Therefore, using exception classes while disabling throwing exceptions is a usecase.

Expected behavior

The exception makes a copy of the what_arg argument, regardless of the value of _HAS_EXCEPTIONS.

STL version

I could reproduce the issue with MSVC 19.16.27045.0 and MSVC 19.29.30040.0.

Additional context

The issue was originally found here: nlohmann/json#2824

Activity

  1. StephanTLavavej commented on Aug 11, 2021

    @StephanTLavavej
    Member

    _HAS_EXCEPTIONS=0 is quasi-supported (it's not officially documented, and has various design/usability issues like this one, although we've tried to avoid breaking how it works). I've marked this as vNext because I believe that dynamically allocating/deallocating memory in the _HAS_EXCEPTIONS=0 implementation of exception would be ABI-breaking. Also marked as decision needed - as Miya Natsuhara (@mnatsuhara) mentioned, we need to decide whether to change this long-standing behavior (e.g. attempting to malloc memory, and storing a string literal as a fallback if we're unable to; that would also require storing whether the string is dynamically allocated, which is super-definitely ABI breaking).

    Finally, Casey Carter (@CaseyCarter) reminded me that this constructor itself is non-Standard (this applies to the normal _HAS_EXCEPTIONS=1 implementation too) - that's tracked by #882.

  2. nlohmann commented on Aug 12, 2021

    @nlohmann
    Author

    Thanks for the quick response!

    About the non-standard issue: I think it's not about the constructor of std::exception, but rather about the behavior of the derived classes like runtime_error(const char* what_arg) - there I would still expect a copy of the argument to be made.

  3. CaseyCarter commented on Aug 12, 2021

    @CaseyCarter
    Contributor

    About the non-standard issue: I think it's not about the constructor of std::exception, but rather about the behavior of the derived classes like runtime_error(const char* what_arg) - there I would still expect a copy of the argument to be made.

    Ah, I see - thanks for the clarification. It hadn't occurred to me the the standard constructors in the derived classes are implemented in terms of this non-standard constructor in exception.

  4. StephanTLavavej commented on Apr 19, 2023

    @StephanTLavavej
    Member

    We talked about this at the weekly maintainer meeting and we believe that in the _HAS_EXCEPTIONS=0 case we should do what we do for _HAS_EXCEPTIONS=1: try to copy the string, and if we can't allocate memory, then just store nothing. (Separately we should get rid of that non-Standard base class constructor although doing so will be an epic undertaking.)

  5. changed the title [-]<exception>: constructor does not copy what_arg if _HAS_EXCEPTIONS=0[/-] [+]`<exception>`: constructor does not copy what_arg if `_HAS_EXCEPTIONS=0`[/+] on Dec 18, 2024
  6. StephanTLavavej commented on Nov 12, 2025

    @StephanTLavavej
    Member

    Related: #5406 fixed #5276.

  7. kleisauke commented on Jan 22, 2026

    @kleisauke

    FWIW, there's a much simpler test case here: nodejs/node-addon-api#85 (comment).

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingvNextBreaks binary compatibility

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions

      ⌂ 换网址