fix StaticString test
* support zeroes in string_
* support zeroes in writer; provide getString(char**, unsigned*)
* valueToQuotedStringN(), isCC0(), etc
* allow zeroes for cpptl ConstString
* allocated => non-static
diff --git a/include/json/value.h b/include/json/value.h
index 229aa9e..04dc87d 100644
--- a/include/json/value.h
+++ b/include/json/value.h
@@ -99,22 +99,21 @@
* The type of the held value is represented by a #ValueType and
* can be obtained using type().
*
- * values of an #objectValue or #arrayValue can be accessed using operator[]()
- *methods.
- * Non const methods will automatically create the a #nullValue element
+ * Values of an #objectValue or #arrayValue can be accessed using operator[]()
+ * methods.
+ * Non-const methods will automatically create the a #nullValue element
* if it does not exist.
- * The sequence of an #arrayValue will be automatically resize and initialized
+ * The sequence of an #arrayValue will be automatically resized and initialized
* with #nullValue. resize() can be used to enlarge or truncate an #arrayValue.
*
- * The get() methods can be used to obtanis default value in the case the
- *required element
- * does not exist.
+ * The get() methods can be used to obtain default value in the case the
+ * required element does not exist.
*
* It is possible to iterate over the list of a #objectValue values using
* the getMemberNames() method.
*
- * \note Value string-length fit in size_t, but keys must fit in unsigned.
- * (The reason is an implementation detail.) The readers will raise an
+ * \note #Value string-length fit in size_t, but keys must be < 2^30.
+ * (The reason is an implementation detail.) A #CharReader will raise an
* exception if a bound is exceeded to avoid security holes in your app,
* but the Value API does *not* check bounds. That is the responsibility
* of the caller.
@@ -170,24 +169,27 @@
duplicateOnCopy
};
CZString(ArrayIndex index);
- CZString(char const* cstr, unsigned length, DuplicationPolicy allocate);
- CZString(const CZString& other);
+ CZString(char const* str, unsigned length, DuplicationPolicy allocate);
+ CZString(CZString const& other);
~CZString();
CZString& operator=(CZString other);
- bool operator<(const CZString& other) const;
- bool operator==(const CZString& other) const;
+ bool operator<(CZString const& other) const;
+ bool operator==(CZString const& other) const;
ArrayIndex index() const;
- const char* c_str() const;
+ //const char* c_str() const; ///< \deprecated
+ char const* data() const;
+ unsigned length() const;
bool isStaticString() const;
private:
void swap(CZString& other);
+
struct StringStorage {
DuplicationPolicy policy_: 2;
unsigned length_: 30; // 1GB max
};
- const char* cstr_;
+ char const* cstr_; // actually, a prefixed string, unless policy is noDup
union {
ArrayIndex index_;
StringStorage storage_;
@@ -233,9 +235,14 @@
* Like other value string constructor but do not duplicate the string for
* internal storage. The given string must remain alive after the call to this
* constructor.
+ * \note This works only for null-terminated strings. (We cannot change the
+ * size of this class, so we have nowhere to store the length,
+ * which might be computed later for various operations.)
+ *
* Example of usage:
* \code
- * Json::Value aValue( StaticString("some text") );
+ * static StaticString foo("some text");
+ * Json::Value aValue(foo);
* \endcode
*/
Value(const StaticString& value);
@@ -268,6 +275,11 @@
const char* asCString() const; ///! Embedded zeroes could cause you trouble!
std::string asString() const; ///! Embedded zeroes are possible.
+ /** Get raw char* of string-value.
+ * \return false if !string. (Seg-fault if str or end are NULL.)
+ */
+ bool getString(
+ char const** str, char const** end) const;
#ifdef JSON_USE_CPPTL
CppTL::ConstString asConstString() const;
#endif
@@ -374,8 +386,6 @@
/** \brief Access an object value by name, create a null member if it does not
exist.
- * \param key may contain embedded nulls.
- *
* If the object has no entry for that name, then the member name used to store
* the new entry is not duplicated.
* Example of use:
@@ -409,6 +419,10 @@
/// and operator[]const
/// \note As stated elsewhere, behavior is undefined if (end-key) >= 2^30
Value const* find(char const* key, char const* end) const;
+ /// Most general and efficient version of object-mutators.
+ /// \note As stated elsewhere, behavior is undefined if (end-key) >= 2^30
+ /// \return non-zero, but JSON_ASSERT if this is neither object nor nullValue.
+ Value const* demand(char const* key, char const* end);
/// \brief Remove and return the named member.
///
/// Do nothing if it did not exist.
@@ -442,6 +456,7 @@
bool removeIndex(ArrayIndex i, Value* removed);
/// Return true if the object has a member named key.
+ /// \note 'key' must be null-terminated.
bool isMember(const char* key) const;
/// Return true if the object has a member named key.
/// \param key may contain embedded nulls.
@@ -493,7 +508,8 @@
private:
void initBasic(ValueType type, bool allocated = false);
- Value& resolveReference(const char* key, bool isStatic);
+ Value& resolveReference(const char* key);
+ Value& resolveReference(const char* key, const char* end);
struct CommentInfo {
CommentInfo();
@@ -518,11 +534,12 @@
LargestUInt uint_;
double real_;
bool bool_;
- char* string_; // actually ptr to unsigned, followed by str
+ char* string_; // actually ptr to unsigned, followed by str, unless !allocated_
ObjectValues* map_;
} value_;
ValueType type_ : 8;
unsigned int allocated_ : 1; // Notes: if declared as bool, bitfield is useless.
+ // If not allocated_, string_ must be null-terminated.
CommentInfo* comments_;
// [start, limit) byte offsets in the source JSON text from which this Value
@@ -624,7 +641,12 @@
/// Return the member name of the referenced Value. "" if it is not an
/// objectValue.
- const char* memberName() const;
+ /// \deprecated This cannot be used for UTF-8 strings, since there can be embedded nulls.
+ char const* memberName() const;
+ /// Return the member name of the referenced Value, or NULL if it is not an
+ /// objectValue.
+ /// Better version than memberName(). Allows embedded nulls.
+ char const* memberName(char const** end) const;
protected:
Value& deref() const;
@@ -653,8 +675,8 @@
public:
typedef const Value value_type;
- typedef unsigned int size_t;
- typedef int difference_type;
+ //typedef unsigned int size_t;
+ //typedef int difference_type;
typedef const Value& reference;
typedef const Value* pointer;
typedef ValueConstIterator SelfType;
diff --git a/src/lib_json/json_value.cpp b/src/lib_json/json_value.cpp
index eae87db..3452ba4 100644
--- a/src/lib_json/json_value.cpp
+++ b/src/lib_json/json_value.cpp
@@ -102,27 +102,38 @@
/* Record the length as a prefix.
*/
-static inline char* duplicatePrefixedStringValue(
+static inline char* duplicateAndPrefixStringValue(
const char* value,
- unsigned int length = unknown)
+ unsigned int length)
{
- if (length == unknown)
- length = (unsigned int)strlen(value);
-
// Avoid an integer overflow in the call to malloc below by limiting length
// to a sane value.
- if (length >= (unsigned)Value::maxInt)
- length = Value::maxInt - 1;
-
- char* newString = static_cast<char*>(malloc(length + 1));
+ JSON_ASSERT_MESSAGE(length <= (unsigned)Value::maxInt - sizeof(unsigned) - 1U,
+ "in Json::Value::duplicateAndPrefixStringValue(): "
+ "length too big for prefixing");
+ unsigned actualLength = length + sizeof(unsigned) + 1U;
+ char* newString = static_cast<char*>(malloc(actualLength));
JSON_ASSERT_MESSAGE(newString != 0,
- "in Json::Value::duplicateStringValue(): "
+ "in Json::Value::duplicateAndPrefixStringValue(): "
"Failed to allocate string value buffer");
- memcpy(newString, value, length);
- newString[length] = 0;
+ *reinterpret_cast<unsigned*>(newString) = length;
+ memcpy(newString + sizeof(unsigned), value, length);
+ newString[actualLength - 1U] = 0; // to avoid buffer over-run accidents by users later
return newString;
}
-/** Free the string duplicated by duplicateStringValue().
+inline static void decodePrefixedString(
+ bool isPrefixed, char const* prefixed,
+ unsigned* length, char const** value)
+{
+ if (!isPrefixed) {
+ *length = strlen(prefixed);
+ *value = prefixed;
+ } else {
+ *length = *reinterpret_cast<unsigned const*>(prefixed);
+ *value = prefixed + sizeof(unsigned);
+ }
+}
+/** Free the string duplicated by duplicateStringValue()/duplicateAndPrefixStringValue().
*/
static inline void releaseStringValue(char* value) { free(value); }
@@ -241,8 +252,9 @@
ArrayIndex Value::CZString::index() const { return index_; }
-const char* Value::CZString::c_str() const { return cstr_; }
-
+//const char* Value::CZString::c_str() const { return cstr_; }
+const char* Value::CZString::data() const { return cstr_; }
+unsigned Value::CZString::length() const { return storage_.length_; }
bool Value::CZString::isStaticString() const { return storage_.policy_ == noDuplication; }
// //////////////////////////////////////////////////////////////////
@@ -311,19 +323,19 @@
Value::Value(const char* value) {
initBasic(stringValue, true);
- value_.string_ = duplicateStringValue(value);
+ value_.string_ = duplicateAndPrefixStringValue(value, strlen(value));
}
Value::Value(const char* beginValue, const char* endValue) {
initBasic(stringValue, true);
value_.string_ =
- duplicateStringValue(beginValue, (unsigned int)(endValue - beginValue));
+ duplicateAndPrefixStringValue(beginValue, (unsigned int)(endValue - beginValue));
}
Value::Value(const std::string& value) {
initBasic(stringValue, true);
value_.string_ =
- duplicateStringValue(value.c_str(), (unsigned int)value.length());
+ duplicateAndPrefixStringValue(value.data(), (unsigned int)value.length());
}
Value::Value(const StaticString& value) {
@@ -334,7 +346,7 @@
#ifdef JSON_USE_CPPTL
Value::Value(const CppTL::ConstString& value) {
initBasic(stringValue, true);
- value_.string_ = duplicateStringValue(value, value.length());
+ value_.string_ = duplicateAndPrefixStringValue(value, value.length());
}
#endif
@@ -357,7 +369,11 @@
break;
case stringValue:
if (other.value_.string_) {
- value_.string_ = duplicateStringValue(other.value_.string_);
+ unsigned len;
+ char const* str;
+ decodePrefixedString(other.allocated_, other.value_.string_,
+ &len, &str);
+ value_.string_ = duplicateAndPrefixStringValue(str, len);
allocated_ = true;
} else {
value_.string_ = 0;
@@ -454,9 +470,23 @@
case booleanValue:
return value_.bool_ < other.value_.bool_;
case stringValue:
- return (value_.string_ == 0 && other.value_.string_) ||
- (other.value_.string_ && value_.string_ &&
- strcmp(value_.string_, other.value_.string_) < 0);
+ {
+ if ((value_.string_ == 0) || (other.value_.string_ == 0)) {
+ if (other.value_.string_) return true;
+ else return false;
+ }
+ unsigned this_len;
+ unsigned other_len;
+ char const* this_str;
+ char const* other_str;
+ decodePrefixedString(this->allocated_, this->value_.string_, &this_len, &this_str);
+ decodePrefixedString(other.allocated_, other.value_.string_, &other_len, &other_str);
+ unsigned min_len = std::min(this_len, other_len);
+ int comp = memcmp(this_str, other_str, min_len);
+ if (comp < 0) return true;
+ if (comp > 0) return false;
+ return (this_len < other_len);
+ }
case arrayValue:
case objectValue: {
int delta = int(value_.map_->size() - other.value_.map_->size());
@@ -496,9 +526,20 @@
case booleanValue:
return value_.bool_ == other.value_.bool_;
case stringValue:
- return (value_.string_ == other.value_.string_) ||
- (other.value_.string_ && value_.string_ &&
- strcmp(value_.string_, other.value_.string_) == 0);
+ {
+ if ((value_.string_ == 0) || (other.value_.string_ == 0)) {
+ return (value_.string_ == other.value_.string_);
+ }
+ unsigned this_len;
+ unsigned other_len;
+ char const* this_str;
+ char const* other_str;
+ decodePrefixedString(this->allocated_, this->value_.string_, &this_len, &this_str);
+ decodePrefixedString(other.allocated_, other.value_.string_, &other_len, &other_str);
+ if (this_len != other_len) return false;
+ int comp = memcmp(this_str, other_str, this_len);
+ return comp == 0;
+ }
case arrayValue:
case objectValue:
return value_.map_->size() == other.value_.map_->size() &&
@@ -514,7 +555,20 @@
const char* Value::asCString() const {
JSON_ASSERT_MESSAGE(type_ == stringValue,
"in Json::Value::asCString(): requires stringValue");
- return value_.string_;
+ if (value_.string_ == 0) return 0;
+ unsigned this_len;
+ char const* this_str;
+ decodePrefixedString(this->allocated_, this->value_.string_, &this_len, &this_str);
+ return this_str;
+}
+
+bool Value::getString(char const** str, char const** end) const {
+ if (type_ != stringValue) return false;
+ if (value_.string_ == 0) return false;
+ unsigned length;
+ decodePrefixedString(this->allocated_, this->value_.string_, &length, str);
+ *end = *str + length;
+ return true;
}
std::string Value::asString() const {
@@ -522,7 +576,13 @@
case nullValue:
return "";
case stringValue:
- return value_.string_ ? value_.string_ : "";
+ {
+ if (value_.string_ == 0) return "";
+ unsigned this_len;
+ char const* this_str;
+ decodePrefixedString(this->allocated_, this->value_.string_, &this_len, &this_str);
+ return std::string(this_str, this_len);
+ }
case booleanValue:
return value_.bool_ ? "true" : "false";
case intValue:
@@ -538,7 +598,11 @@
#ifdef JSON_USE_CPPTL
CppTL::ConstString Value::asConstString() const {
- return CppTL::ConstString(asString().c_str());
+ unsigned len;
+ char const* str;
+ decodePrefixedString(allocated_, value_.string_,
+ &len, &str);
+ return CppTL::ConstString(str, len);
}
#endif
@@ -852,10 +916,6 @@
return (*this)[ArrayIndex(index)];
}
-Value& Value::operator[](const char* key) {
- return resolveReference(key, false);
-}
-
void Value::initBasic(ValueType type, bool allocated) {
type_ = type;
allocated_ = allocated;
@@ -864,14 +924,37 @@
limit_ = 0;
}
-Value& Value::resolveReference(const char* key, bool isStatic) {
+// Access an object value by name, create a null member if it does not exist.
+// @pre Type of '*this' is object or null.
+// @param key is null-terminated.
+Value& Value::resolveReference(const char* key) {
JSON_ASSERT_MESSAGE(
type_ == nullValue || type_ == objectValue,
"in Json::Value::resolveReference(): requires objectValue");
if (type_ == nullValue)
*this = Value(objectValue);
CZString actualKey(
- key, strlen(key), isStatic ? CZString::noDuplication : CZString::duplicateOnCopy);
+ key, strlen(key), CZString::noDuplication); // NOTE!
+ ObjectValues::iterator it = value_.map_->lower_bound(actualKey);
+ if (it != value_.map_->end() && (*it).first == actualKey)
+ return (*it).second;
+
+ ObjectValues::value_type defaultValue(actualKey, null);
+ it = value_.map_->insert(it, defaultValue);
+ Value& value = (*it).second;
+ return value;
+}
+
+// @param key is not null-terminated.
+Value& Value::resolveReference(char const* key, char const* end)
+{
+ JSON_ASSERT_MESSAGE(
+ type_ == nullValue || type_ == objectValue,
+ "in Json::Value::resolveReference(key, end): requires objectValue");
+ if (type_ == nullValue)
+ *this = Value(objectValue);
+ CZString actualKey(
+ key, (end-key), CZString::duplicateOnCopy);
ObjectValues::iterator it = value_.map_->lower_bound(actualKey);
if (it != value_.map_->end() && (*it).first == actualKey)
return (*it).second;
@@ -906,24 +989,28 @@
if (!found) return null;
return *found;
}
-Value& Value::operator[](const std::string& key)
-{
- return (*this)[key.c_str()];
-}
Value const& Value::operator[](std::string const& key) const
{
Value const* found = find(key.data(), key.data() + key.length());
if (!found) return null;
return *found;
}
-Value& Value::operator[](const StaticString& key)
-{
- return resolveReference(key, true);
+
+Value& Value::operator[](const char* key) {
+ return resolveReference(key, key + strlen(key));
+}
+
+Value& Value::operator[](const std::string& key) {
+ return resolveReference(key.data(), key.data() + key.length());
+}
+
+Value& Value::operator[](const StaticString& key) {
+ return resolveReference(key.c_str());
}
#ifdef JSON_USE_CPPTL
Value& Value::operator[](const CppTL::ConstString& key) {
- return (*this)[key.c_str()];
+ return resolveReference(key.c_str(), key.end_c_str());
}
Value const& Value::operator[](CppTL::ConstString const& key) const
{
@@ -1022,18 +1109,18 @@
Value const* value = find(key, end);
return NULL != value;
}
-bool Value::isMember(const char* key) const
+bool Value::isMember(char const* key) const
{
return isMember(key, key + strlen(key));
}
-bool Value::isMember(const std::string& key) const
+bool Value::isMember(std::string const& key) const
{
return isMember(key.data(), key.data() + key.length());
}
#ifdef JSON_USE_CPPTL
bool Value::isMember(const CppTL::ConstString& key) const {
- return isMember(key.c_str());
+ return isMember(key.c_str(), key.end_c_str());
}
#endif
@@ -1047,8 +1134,10 @@
members.reserve(value_.map_->size());
ObjectValues::const_iterator it = value_.map_->begin();
ObjectValues::const_iterator itEnd = value_.map_->end();
- for (; it != itEnd; ++it)
- members.push_back(std::string((*it).first.c_str()));
+ for (; it != itEnd; ++it) {
+ members.push_back(std::string((*it).first.data(),
+ (*it).first.length()));
+ }
return members;
}
//
diff --git a/src/lib_json/json_valueiterator.inl b/src/lib_json/json_valueiterator.inl
index 3cb9e06..1e9a247 100644
--- a/src/lib_json/json_valueiterator.inl
+++ b/src/lib_json/json_valueiterator.inl
@@ -77,26 +77,36 @@
Value ValueIteratorBase::key() const {
const Value::CZString czstring = (*current_).first;
- if (czstring.c_str()) {
+ if (czstring.data()) {
if (czstring.isStaticString())
- return Value(StaticString(czstring.c_str()));
- return Value(czstring.c_str());
+ return Value(StaticString(czstring.data()));
+ return Value(czstring.data(), czstring.data() + czstring.length());
}
return Value(czstring.index());
}
UInt ValueIteratorBase::index() const {
const Value::CZString czstring = (*current_).first;
- if (!czstring.c_str())
+ if (!czstring.data())
return czstring.index();
return Value::UInt(-1);
}
-const char* ValueIteratorBase::memberName() const {
- const char* name = (*current_).first.c_str();
+char const* ValueIteratorBase::memberName() const {
+ const char* name = (*current_).first.data();
return name ? name : "";
}
+char const* ValueIteratorBase::memberName(char const** end) const {
+ const char* name = (*current_).first.data();
+ if (!name) {
+ *end = NULL;
+ return NULL;
+ }
+ *end = name + (*current_).first.length();
+ return name;
+}
+
// //////////////////////////////////////////////////////////////////
// //////////////////////////////////////////////////////////////////
// //////////////////////////////////////////////////////////////////
diff --git a/src/lib_json/json_writer.cpp b/src/lib_json/json_writer.cpp
index 29f6619..f7c630d 100644
--- a/src/lib_json/json_writer.cpp
+++ b/src/lib_json/json_writer.cpp
@@ -50,6 +50,16 @@
return false;
}
+static bool containsControlCharacter0(const char* str, unsigned len) {
+ char const* end = str + len;
+ while (end != str) {
+ if (isControlCharacter(*str) || 0==*str)
+ return true;
+ ++str;
+ }
+ return false;
+}
+
std::string valueToString(LargestInt value) {
UIntToStringBuffer buffer;
char* current = buffer + sizeof(buffer);
@@ -184,6 +194,84 @@
return result;
}
+// https://github.com/upcaste/upcaste/blob/master/src/upcore/src/cstring/strnpbrk.cpp
+static char const* strnpbrk(char const* s, char const* accept, size_t n) {
+ assert((s || !n) && accept);
+
+ char const* const end = s + n;
+ for (char const* cur = s; cur < end; ++cur) {
+ int const c = *cur;
+ for (char const* a = accept; *a; ++a) {
+ if (*a == c) {
+ return cur;
+ }
+ }
+ }
+ return NULL;
+}
+static std::string valueToQuotedStringN(const char* value, unsigned length) {
+ if (value == NULL)
+ return "";
+ // Not sure how to handle unicode...
+ if (strnpbrk(value, "\"\\\b\f\n\r\t", length) == NULL &&
+ !containsControlCharacter0(value, length))
+ return std::string("\"") + value + "\"";
+ // We have to walk value and escape any special characters.
+ // Appending to std::string is not efficient, but this should be rare.
+ // (Note: forward slashes are *not* rare, but I am not escaping them.)
+ std::string::size_type maxsize =
+ length * 2 + 3; // allescaped+quotes+NULL
+ std::string result;
+ result.reserve(maxsize); // to avoid lots of mallocs
+ result += "\"";
+ char const* end = value + length;
+ for (const char* c = value; c != end; ++c) {
+ switch (*c) {
+ case '\"':
+ result += "\\\"";
+ break;
+ case '\\':
+ result += "\\\\";
+ break;
+ case '\b':
+ result += "\\b";
+ break;
+ case '\f':
+ result += "\\f";
+ break;
+ case '\n':
+ result += "\\n";
+ break;
+ case '\r':
+ result += "\\r";
+ break;
+ case '\t':
+ result += "\\t";
+ break;
+ // case '/':
+ // Even though \/ is considered a legal escape in JSON, a bare
+ // slash is also legal, so I see no reason to escape it.
+ // (I hope I am not misunderstanding something.)
+ // blep notes: actually escaping \/ may be useful in javascript to avoid </
+ // sequence.
+ // Should add a flag to allow this compatibility mode and prevent this
+ // sequence from occurring.
+ default:
+ if ((isControlCharacter(*c)) || (*c == 0)) {
+ std::ostringstream oss;
+ oss << "\\u" << std::hex << std::uppercase << std::setfill('0')
+ << std::setw(4) << static_cast<int>(*c);
+ result += oss.str();
+ } else {
+ result += *c;
+ }
+ break;
+ }
+ }
+ result += "\"";
+ return result;
+}
+
// Class Writer
// //////////////////////////////////////////////////////////////////
Writer::~Writer() {}
@@ -248,7 +336,7 @@
const std::string& name = *it;
if (it != members.begin())
document_ += ',';
- document_ += valueToQuotedString(name.c_str());
+ document_ += valueToQuotedStringN(name.data(), name.length());
document_ += yamlCompatiblityEnabled_ ? ": " : ":";
writeValue(value[name]);
}
@@ -289,8 +377,15 @@
pushValue(valueToString(value.asDouble()));
break;
case stringValue:
- pushValue(valueToQuotedString(value.asCString()));
+ {
+ // Is NULL is possible for value.string_?
+ char const* str;
+ char const* end;
+ bool ok = value.getString(&str, &end);
+ if (ok) pushValue(valueToQuotedStringN(str, static_cast<unsigned>(end-str)));
+ else pushValue("");
break;
+ }
case booleanValue:
pushValue(valueToString(value.asBool()));
break;
@@ -767,8 +862,15 @@
pushValue(valueToString(value.asDouble()));
break;
case stringValue:
- pushValue(valueToQuotedString(value.asCString()));
+ {
+ // Is NULL is possible for value.string_?
+ char const* str;
+ char const* end;
+ bool ok = value.getString(&str, &end);
+ if (ok) pushValue(valueToQuotedStringN(str, static_cast<unsigned>(end-str)));
+ else pushValue("");
break;
+ }
case booleanValue:
pushValue(valueToString(value.asBool()));
break;
@@ -787,7 +889,7 @@
std::string const& name = *it;
Value const& childValue = value[name];
writeCommentBeforeValue(childValue);
- writeWithIndent(valueToQuotedString(name.c_str()));
+ writeWithIndent(valueToQuotedStringN(name.data(), name.length()));
*sout_ << colonSymbol_;
writeValue(childValue);
if (++it == members.end()) {