Merge pull request #211 from cdunn2001/except

* Add Json::Exception and derivatives.
* Clarify when exceptions are thrown, to avoid crashes caused by malicious input.
* Use our own type (derived fro std::exception) so they are trappable.
This commit is contained in:
Christopher Dunn 2015-03-08 12:50:34 -05:00
commit 0d33cb3639
7 changed files with 92 additions and 24 deletions

View File

@ -13,22 +13,30 @@
#include "config.h" #include "config.h"
#endif // if !defined(JSON_IS_AMALGAMATION) #endif // if !defined(JSON_IS_AMALGAMATION)
/** It should not be possible for a maliciously designed file to
* cause an abort() or seg-fault, so these macros are used only
* for pre-condition violations and internal logic errors.
*/
#if JSON_USE_EXCEPTION #if JSON_USE_EXCEPTION
#include <stdexcept>
#define JSON_ASSERT(condition) \ // @todo <= add detail about condition in exception
{if (!(condition)) {throw std::logic_error( "assert json failed" );}} // @todo <= add detail about condition in exception # define JSON_ASSERT(condition) \
#define JSON_FAIL_MESSAGE(message) \ {if (!(condition)) {Json::throwLogicError( "assert json failed" );}}
# define JSON_FAIL_MESSAGE(message) \
{ \ { \
std::ostringstream oss; oss << message; \ std::ostringstream oss; oss << message; \
throw std::logic_error(oss.str()); \ Json::throwLogicError(oss.str()); \
abort(); \
} }
//#define JSON_FAIL_MESSAGE(message) throw std::logic_error(message)
#else // JSON_USE_EXCEPTION #else // JSON_USE_EXCEPTION
#define JSON_ASSERT(condition) assert(condition)
# define JSON_ASSERT(condition) assert(condition)
// The call to assert() will show the failure message in debug builds. In // The call to assert() will show the failure message in debug builds. In
// release bugs we abort, for a core-dump or debugger. // release builds we abort, for a core-dump or debugger.
#define JSON_FAIL_MESSAGE(message) \ # define JSON_FAIL_MESSAGE(message) \
{ \ { \
std::ostringstream oss; oss << message; \ std::ostringstream oss; oss << message; \
assert(false && oss.str().c_str()); \ assert(false && oss.str().c_str()); \

View File

@ -11,6 +11,7 @@
#endif // if !defined(JSON_IS_AMALGAMATION) #endif // if !defined(JSON_IS_AMALGAMATION)
#include <string> #include <string>
#include <vector> #include <vector>
#include <exception>
#ifndef JSON_USE_CPPTL_SMALLMAP #ifndef JSON_USE_CPPTL_SMALLMAP
#include <map> #include <map>
@ -32,6 +33,23 @@
*/ */
namespace Json { namespace Json {
/** Base class for all exceptions we throw.
*/
class JSON_API Exception;
/** Exceptions which the user cannot easily avoid.
*
* E.g. out-of-memory, stack-overflow, malicious input
*/
class JSON_API RuntimeError;
/** Exceptions throw by JSON_ASSERT/JSON_FAIL macros.
*
* These are precondition-violations (user bugs) and internal errors (our bugs).
*/
class JSON_API LogicError;
JSON_API void throwRuntimeError(std::string const& msg);
JSON_API void throwLogicError(std::string const& msg);
/** \brief Type of the value held by a Value object. /** \brief Type of the value held by a Value object.
*/ */
enum ValueType { enum ValueType {

View File

@ -46,7 +46,7 @@ public:
/** Write Value into document as configured in sub-class. /** Write Value into document as configured in sub-class.
Do not take ownership of sout, but maintain a reference during function. Do not take ownership of sout, but maintain a reference during function.
\pre sout != NULL \pre sout != NULL
\return zero on success \return zero on success (For now, we always return zero, so check the stream instead.)
\throw std::exception possibly, depending on configuration \throw std::exception possibly, depending on configuration
*/ */
virtual int write(Value const& root, std::ostream* sout) = 0; virtual int write(Value const& root, std::ostream* sout) = 0;

View File

@ -17,7 +17,6 @@
#include <sstream> #include <sstream>
#include <memory> #include <memory>
#include <set> #include <set>
#include <stdexcept>
#if defined(_MSC_VER) && _MSC_VER < 1500 // VC++ 8.0 and below #if defined(_MSC_VER) && _MSC_VER < 1500 // VC++ 8.0 and below
#define snprintf _snprintf #define snprintf _snprintf
@ -148,7 +147,7 @@ bool Reader::readValue() {
// But this deprecated class has a security problem: Bad input can // But this deprecated class has a security problem: Bad input can
// cause a seg-fault. This seems like a fair, binary-compatible way // cause a seg-fault. This seems like a fair, binary-compatible way
// to prevent the problem. // to prevent the problem.
if (stackDepth_g >= stackLimit_g) throw std::runtime_error("Exceeded stackLimit in readValue()."); if (stackDepth_g >= stackLimit_g) throwRuntimeError("Exceeded stackLimit in readValue().");
++stackDepth_g; ++stackDepth_g;
Token token; Token token;
@ -1107,7 +1106,7 @@ bool OurReader::parse(const char* beginDoc,
} }
bool OurReader::readValue() { bool OurReader::readValue() {
if (stackDepth_ >= features_.stackLimit_) throw std::runtime_error("Exceeded stackLimit in readValue()."); if (stackDepth_ >= features_.stackLimit_) throwRuntimeError("Exceeded stackLimit in readValue().");
++stackDepth_; ++stackDepth_;
Token token; Token token;
skipCommentTokens(token); skipCommentTokens(token);
@ -1431,7 +1430,7 @@ bool OurReader::readObject(Token& tokenStart) {
return addErrorAndRecover( return addErrorAndRecover(
"Missing ':' after object member name", colon, tokenObjectEnd); "Missing ':' after object member name", colon, tokenObjectEnd);
} }
if (name.length() >= (1U<<30)) throw std::runtime_error("keylength >= 2^30"); if (name.length() >= (1U<<30)) throwRuntimeError("keylength >= 2^30");
if (features_.rejectDupKeys_ && currentValue().isMember(name)) { if (features_.rejectDupKeys_ && currentValue().isMember(name)) {
std::string msg = "Duplicate key: '" + name + "'"; std::string msg = "Duplicate key: '" + name + "'";
return addErrorAndRecover( return addErrorAndRecover(
@ -1994,7 +1993,7 @@ std::istream& operator>>(std::istream& sin, Value& root) {
"Error from reader: %s", "Error from reader: %s",
errs.c_str()); errs.c_str());
JSON_FAIL_MESSAGE("reader error"); throwRuntimeError("reader error");
} }
return sin; return sin;
} }

View File

@ -87,9 +87,11 @@ static inline char* duplicateStringValue(const char* value,
length = Value::maxInt - 1; length = Value::maxInt - 1;
char* newString = static_cast<char*>(malloc(length + 1)); char* newString = static_cast<char*>(malloc(length + 1));
JSON_ASSERT_MESSAGE(newString != 0, if (newString == NULL) {
"in Json::Value::duplicateStringValue(): " throwRuntimeError(
"Failed to allocate string value buffer"); "in Json::Value::duplicateStringValue(): "
"Failed to allocate string value buffer");
}
memcpy(newString, value, length); memcpy(newString, value, length);
newString[length] = 0; newString[length] = 0;
return newString; return newString;
@ -108,9 +110,11 @@ static inline char* duplicateAndPrefixStringValue(
"length too big for prefixing"); "length too big for prefixing");
unsigned actualLength = length + sizeof(unsigned) + 1U; unsigned actualLength = length + sizeof(unsigned) + 1U;
char* newString = static_cast<char*>(malloc(actualLength)); char* newString = static_cast<char*>(malloc(actualLength));
JSON_ASSERT_MESSAGE(newString != 0, if (newString == 0) {
"in Json::Value::duplicateAndPrefixStringValue(): " throwRuntimeError(
"Failed to allocate string value buffer"); "in Json::Value::duplicateAndPrefixStringValue(): "
"Failed to allocate string value buffer");
}
*reinterpret_cast<unsigned*>(newString) = length; *reinterpret_cast<unsigned*>(newString) = length;
memcpy(newString + sizeof(unsigned), value, length); memcpy(newString + sizeof(unsigned), value, length);
newString[actualLength - 1U] = 0; // to avoid buffer over-run accidents by users later newString[actualLength - 1U] = 0; // to avoid buffer over-run accidents by users later
@ -148,6 +152,47 @@ static inline void releaseStringValue(char* value) { free(value); }
namespace Json { namespace Json {
class JSON_API Exception : public std::exception {
public:
Exception(std::string const& msg);
virtual ~Exception() throw();
virtual char const* what() const throw();
protected:
std::string const& msg_;
};
class JSON_API RuntimeError : public Exception {
public:
RuntimeError(std::string const& msg);
};
class JSON_API LogicError : public Exception {
public:
LogicError(std::string const& msg);
};
Exception::Exception(std::string const& msg)
: msg_(msg)
{}
Exception::~Exception() throw()
{}
char const* Exception::what() const throw()
{
return msg_.c_str();
}
RuntimeError::RuntimeError(std::string const& msg)
: Exception(msg)
{}
LogicError::LogicError(std::string const& msg)
: Exception(msg)
{}
void throwRuntimeError(std::string const& msg)
{
throw RuntimeError(msg);
}
void throwLogicError(std::string const& msg)
{
throw LogicError(msg);
}
// ////////////////////////////////////////////////////////////////// // //////////////////////////////////////////////////////////////////
// ////////////////////////////////////////////////////////////////// // //////////////////////////////////////////////////////////////////
// ////////////////////////////////////////////////////////////////// // //////////////////////////////////////////////////////////////////

View File

@ -12,7 +12,6 @@
#include <sstream> #include <sstream>
#include <utility> #include <utility>
#include <set> #include <set>
#include <stdexcept>
#include <assert.h> #include <assert.h>
#include <math.h> #include <math.h>
#include <stdio.h> #include <stdio.h>
@ -1080,7 +1079,7 @@ StreamWriter* StreamWriterBuilder::newStreamWriter() const
} else if (cs_str == "None") { } else if (cs_str == "None") {
cs = CommentStyle::None; cs = CommentStyle::None;
} else { } else {
throw std::runtime_error("commentStyle must be 'All' or 'None'"); throwRuntimeError("commentStyle must be 'All' or 'None'");
} }
std::string colonSymbol = " : "; std::string colonSymbol = " : ";
if (eyc) { if (eyc) {

View File

@ -6,7 +6,6 @@
#include "jsontest.h" #include "jsontest.h"
#include <json/config.h> #include <json/config.h>
#include <json/json.h> #include <json/json.h>
#include <stdexcept>
#include <cstring> #include <cstring>
// Make numeric limits more convenient to talk about. // Make numeric limits more convenient to talk about.