Repository: duckdb/duckdb
File: src/common/error_data.cpp (C++)

Code before:
ErrorData::ErrorData(ExceptionType type, const string &message)
    : initialized(true), type(type), raw_message(SanitizeErrorMessage(message)),
      final_message(ConstructFinalMessage()) {
}

ErrorData::ErrorData(const string &message)
    : initialized(true), type(ExceptionType::INVALID), raw_message(string()), final_message(string()) {

	// parse the constructed JSON
	if (message.empty() || message[0] != '{') {
		// not JSON! Use the message as a raw Exception message and leave type as uninitialized

		if (message == std::bad_alloc().what()) {
			type = ExceptionType::OUT_OF_MEMORY;
			raw_message = "Allocation failure";
		} else {
			raw_message = message;
		}
	} else {
		auto info = StringUtil::ParseJSONMap(message)->Flatten();
		for (auto &entry : info) {
			if (entry.first == "exception_type") {
				type = Exception::StringToExceptionType(entry.second);
			} else if (entry.first == "exception_message") {
				raw_message = SanitizeErrorMessage(entry.second);
			} else {
				extra_info[entry.first] = entry.second;
			}
		}
	}

	final_message = ConstructFinalMessage();
}

string ErrorData::SanitizeErrorMessage(string error) {
	return StringUtil::Replace(std::move(error), string("\0", 1), "\\0");
}

string ErrorData::ConstructFinalMessage() const {
	std::string error;
	if (type != ExceptionType::UNKNOWN_TYPE) {
		error = Exception::ExceptionTypeToString(type) + " ";
	}
	error += "Error: " + raw_message;
	if (type == ExceptionType::INTERNAL) {
		error += "\nThis error signals an assertion failure within DuckDB. This usually occurs due to "
		         "unexpected conditions or errors in the program's logic.\nFor more information, see "
		         "https://duckdb.org/docs/stable/dev/internal_errors";

		// Ensure that we print the stack trace for internal exceptions.
		auto entry = extra_info.find("stack_trace_pointers");

Review comment (suggestion):
Given we are here, what happens if the message is not valid JSON?

I would guess `duckdb_create_error_data(DUCKDB_ERROR_INTERNAL, "{foo");` would still be problematic here.

Problem is pre-existing, but while throwing might be fine elsewhere, it's not via the C-API.

Code after:
ErrorData::ErrorData(ExceptionType type, const string &message)
    : initialized(true), type(type), raw_message(SanitizeErrorMessage(message)) {
	// In the case of ExceptionType::INTERNAL, the stack trace is part of the final message.
	// To construct it, we need to access extra_info, which has to be initialized first.
	// Thus, we only set final_message in the constructor's body.
	final_message = ConstructFinalMessage();
}

ErrorData::ErrorData(const string &message)
    : initialized(true), type(ExceptionType::INVALID), raw_message(string()), final_message(string()) {

	if (message.empty() || message[0] != '{') {
		// Not a JSON-formatted message.
		// Use the message as a raw Exception message and leave the type as uninitialized.
		if (message == std::bad_alloc().what()) {
			type = ExceptionType::OUT_OF_MEMORY;
			raw_message = "Allocation failure";
		} else {
			raw_message = message;
		}
		final_message = ConstructFinalMessage();
		return;
	}

	// JSON-formatted message.
	auto info = StringUtil::ParseJSONMap(message)->Flatten();
	for (auto &entry : info) {
		if (entry.first == "exception_type") {
			type = Exception::StringToExceptionType(entry.second);
		} else if (entry.first == "exception_message") {
			raw_message = SanitizeErrorMessage(entry.second);
		} else {
			extra_info[entry.first] = entry.second;
		}
	}
	final_message = ConstructFinalMessage();
}

string ErrorData::SanitizeErrorMessage(string error) {
	return StringUtil::Replace(std::move(error), string("\0", 1), "\\0");
}

string ErrorData::ConstructFinalMessage() const {
	std::string error;
	if (type != ExceptionType::UNKNOWN_TYPE) {
		error = Exception::ExceptionTypeToString(type) + " ";
	}
	error += "Error: " + raw_message;
	if (type == ExceptionType::INTERNAL) {
		error += "\nThis error signals an assertion failure within DuckDB. This usually occurs due to "
		         "unexpected conditions or errors in the program's logic.\nFor more information, see "