-
-
Notifications
You must be signed in to change notification settings - Fork 6.7k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add no thrown exception support to parse #798
Conversation
This avoids unnecessary string copies on often used find().
Copied from solution to nlohmann#464
Instead implement @gregmarr's PR comments that perfect forwarding should be used. Also cleaned up cpp language standard detection.
Already covered by AppVeyor
This allows users to parse json without raising exceptions but still get full exception details. This is very similar to how std::networking and std::filesystem work. In that if the overload with std::error_code& is used then no exceptions are raised. - Removed static create functions from exceptions. Replaced with normal constructor so that it can be created on heap for assignment to std::unique_ptr. - Removed bool allow_exceptions, no longer necessary with the &ex overload.
@@ -45,7 +45,8 @@ json parser_helper(const std::string& s) | |||
// if this line was reached, no exception ocurred | |||
// -> check if result is the same without exceptions | |||
json j_nothrow; | |||
CHECK_NOTHROW(json::parser(nlohmann::detail::input_adapter(s), nullptr, false).parse(true, j_nothrow)); | |||
std::unique_ptr<json::exception> ex; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What is the advantage of a unique_ptr
over std::exception_ptr
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
std::exception_ptr
isn't limited tojson::exception
types.- The only things you can do with an
std::exception_ptr
are convert to bool, compare to another pointer, and rethrow. You can't actually get the exception object out of it, so all the caller would be able to do is determine whether or not an exception was thrown, not what that exception was.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Right, there's no way to retrieve the exception, forgot about that.
I do think we should not use std::unique_ptr<json::exception>
though, but std::error_code
instead (this blog post is really useful on this topic).
std::filesystem
uses std::error_code
for the noexcept
overloads, one can still embed a lot of information with this facility. I've seen it mentioned in Outcome's documentation too.
What do you all think?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't work without exceptions, so I don't really care one way or another. I think the best solution in the end is probably to return an std::expected
or std::outcome
or whatever it ends up as, but that's years away.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree that long term using std::expected
or std::outcome
would be best. My original plan was to use std::error_code
(as you can see by my branch name) but it has it's own issues. Mostly creating a custom formatted error message with context does not seem cleanly possible. You can see in the blog post linked (which I used as a source as well) that aspect is glossed over.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I definitely see where you are coming from @theodelrieu and pretty much agree with every one of your points. I appreciate the links.
The issue is in regards to a specific, but I would argue potentially popular, scenario though. That is using this library for parsing json and getting nice context aware error messages on failures even if exception handling is completely disabled. (most common in game engines, embedded)
Being able to handle various error codes / categories at runtime for parse errors is not important here, and I can't see the ability to do so being useful other than a theoretical exercise.
@nlohmann does the above description answer your question on why this PR exists?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So basically all you want to have is access to parser diagnostics (error message + location) when exceptions are switched off?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yes
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@jseward I understand the use-case, I think a compromise can be reached.
The good solution would be outcome, you can indeed attach an extra payload to it. Unfortunately, I do not think we can use it in this library.
However, we could have the following type:
// not a good name (std::system_error is used as an exception).
struct json_error {
std::error_code ec;
std::string context; // empty if ec == 0, or no context provided.
};
This type would replace the std::unique_ptr<json::exception>
in this PR. Plus, the #ifdef
inside json::exception
definition can be removed.
I would also suggest adding an error_code
to json::exception
too (for the same reasons mentioned before in this thread, similarly to std::filesystem_error
).
What do you all think?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We already support parsing without exceptions (and return a discarded
type on parse errors). I think the desired outcome could be achieved by just adding the required member functions to the parser class to query the required diagnostics in case an error occurs. The same functions could be re-used in the code that creates the actual parse_error
exception so we would not even need to add too much code.
*/ | ||
void parse(const bool strict, BasicJsonType& result) | ||
{ | ||
std::unique_ptr<exception> ex; | ||
parse(strict, result, ex); | ||
try_throw_any_exception(ex.get()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
std::rethrow_exception
could be used here (assuming we replace ex
with a std::exception_ptr
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ah interesting thanks! I actually have never heard of std::exception_ptr
before. Unfortunately it looks like it isn't very useful in this situation. Specifically I want to be able to get at the message on why the parse failed.
src/json.hpp
Outdated
}; | ||
|
||
/*! | ||
@brief helper function to throw the a downcasted exception. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Typo: "throw the a downcasted" too many articles
src/json.hpp
Outdated
ex = ex; // suppress unreferenced formal parameter warnings | ||
std::abort(); | ||
#endif | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this looks cleaner
#ifdef JSON_EXCEPTIONS_ENABLED
template<class Exception>
void try_throw_exception(exception* ex)
{
// Only use RTTI if necessary (exceptions are enabled). This way
// can be compiled with no RTTI.
if(Exception* down = dynamic_cast<Exception*>(ex))
{
JSON_THROW(*down);
}
}
#else
template<class Exception>
void try_throw_exception(exception* /*unused*/)
{
std::abort();
}
#endif
On the other hand, why is this even needed? (It's definitely not if @theodelrieu 's suggested change is made, and I would say it's not even if it isn't, just throw ex.get()
, no need for the downcasting.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agreed, that does look cleaner thanks. I'll try it out. As for just throwing without downcasting I couldn't get that to work. catch (const SpecificError& e) {}
would never get hit.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, I missed that you were throwing by value instead of by pointer, which unfortunately does require all this extra casting over throwing directly when the exception is created. This delayed throw method does lose some of the call stack context, so it may be better to make the choice whether to throw or place in the unique_ptr<> at the point of the THROW macro.
- fix comment - clean up #ifdef organization based on PR feedback (agreed looks better)
Forgot not available for C++11
Can one of the admins verify this patch? |
Hey there. I am a bit confused about this PR. Can someone please provide a brief explanation on the benefits? |
This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions. |
LOL wtf? A stale bot? |
This PR adds support for parse to not throw exceptions. Use similar pattern as
std::experimental::filesystem
where overload that takes instd::error_code&
does not throw an exception.Did not add new tests as changes to existing tests provide full coverage of new codepath.
NOTE: accidentally branched from #795, ignore that for now.