Reland [skjson] Size-constrained input API
Pass an explicit input size instead of requiring a C string.
Thanks to mtklein's clever trick, this has no measurable perf impact.
TBR=
Change-Id: Ic8cb1dc75f4d0814e5b2c80038d1b8d3a7b072ab
Reviewed-on: https://skia-review.googlesource.com/134946
Reviewed-by: Florin Malita <fmalita@chromium.org>
Reviewed-by: Mike Klein <mtklein@chromium.org>
Commit-Queue: Florin Malita <fmalita@chromium.org>
diff --git a/modules/skjson/src/FuzzSkJSON.cpp b/modules/skjson/src/FuzzSkJSON.cpp
index ce33cc1..2e971ce 100644
--- a/modules/skjson/src/FuzzSkJSON.cpp
+++ b/modules/skjson/src/FuzzSkJSON.cpp
@@ -5,20 +5,12 @@
* found in the LICENSE file.
*/
-#include "SkAutoMalloc.h"
#include "SkData.h"
#include "SkJSON.h"
#include "SkStream.h"
void FuzzSkJSON(sk_sp<SkData> bytes) {
- // TODO: add a size + len skjson::DOM factory?
- SkAutoMalloc data(bytes->size() + 1);
- auto* c_str = static_cast<char*>(data.get());
-
- memcpy(c_str, bytes->data(), bytes->size());
- c_str[bytes->size()] = '\0';
-
- skjson::DOM dom(c_str);
+ skjson::DOM dom(static_cast<const char*>(bytes->data()), bytes->size());
SkDynamicMemoryWStream wstream;
dom.write(&wstream);
}
diff --git a/modules/skjson/src/SkJSON.cpp b/modules/skjson/src/SkJSON.cpp
index bf176c5..d6585e0 100644
--- a/modules/skjson/src/SkJSON.cpp
+++ b/modules/skjson/src/SkJSON.cpp
@@ -11,12 +11,12 @@
#include "SkString.h"
#include <cmath>
+#include <tuple>
#include <vector>
namespace skjson {
-//#define SK_JSON_REPORT_ERRORS
-
+// #define SK_JSON_REPORT_ERRORS
static_assert( sizeof(Value) == 8, "");
static_assert(alignof(Value) == 8, "");
@@ -236,7 +236,22 @@
fScopeStack.reserve(kScopeStackReserve);
}
- const Value parse(const char* p) {
+ const Value parse(const char* p, size_t size) {
+ if (!size) {
+ return this->error(NullValue(), p, "invalid empty input");
+ }
+
+ const char* p_stop = p + size - 1;
+
+ // We're only checking for end-of-stream on object/array close('}',']'),
+ // so we must trim any whitespace from the buffer tail.
+ while (p_stop > p && is_ws(*p_stop)) --p_stop;
+
+ SkASSERT(p_stop >= p && p_stop < p + size);
+ if (*p_stop != '}' && *p_stop != ']') {
+ return this->error(NullValue(), p_stop, "invalid top-level value");
+ }
+
p = skip_ws(p);
switch (*p) {
@@ -340,17 +355,17 @@
pop_common:
SkASSERT(*p == '}' || *p == ']');
- ++p;
-
if (fScopeStack.empty()) {
SkASSERT(fValueStack.size() == 1);
- // Stop condition: parsed the top level element and there is no trailing garbage.
- return *skip_ws(p) == '\0'
+ // Success condition: parsed the top level element and reached the stop token.
+ return p == p_stop
? fValueStack.front()
- : this->error(NullValue(), p, "trailing root garbage");
+ : this->error(NullValue(), p + 1, "trailing root garbage");
}
+ ++p;
+
goto match_post_value;
match_array:
@@ -377,19 +392,20 @@
return NullValue();
}
- const SkString& getError() const {
- return fError;
+ std::tuple<const char*, const SkString> getError() const {
+ return std::make_tuple(fErrorToken, fErrorMessage);
}
private:
- SkArenaAlloc& fAlloc;
+ SkArenaAlloc& fAlloc;
static constexpr size_t kValueStackReserve = 256;
static constexpr size_t kScopeStackReserve = 128;
std::vector<Value > fValueStack;
std::vector<intptr_t> fScopeStack;
- SkString fError;
+ const char* fErrorToken = nullptr;
+ SkString fErrorMessage;
template <typename VectorT>
void popScopeAsVec(size_t scope_start) {
@@ -490,9 +506,8 @@
template <typename T>
T error(T&& ret_val, const char* p, const char* msg) {
#if defined(SK_JSON_REPORT_ERRORS)
- static constexpr size_t kMaxContext = 128;
- fError = SkStringPrintf("%s: >", msg);
- fError.append(p, std::min(strlen(p), kMaxContext));
+ fErrorToken = p;
+ fErrorMessage.set(msg);
#endif
return ret_val;
}
@@ -718,11 +733,11 @@
static constexpr size_t kMinChunkSize = 4096;
-DOM::DOM(const char* c_str)
+DOM::DOM(const char* data, size_t size)
: fAlloc(kMinChunkSize) {
DOMParser parser(fAlloc);
- fRoot = parser.parse(c_str);
+ fRoot = parser.parse(data, size);
}
void DOM::write(SkWStream* stream) const {
diff --git a/modules/skjson/src/SkJSONBench.cpp b/modules/skjson/src/SkJSONBench.cpp
index 4be3b97..b9c3664 100644
--- a/modules/skjson/src/SkJSONBench.cpp
+++ b/modules/skjson/src/SkJSONBench.cpp
@@ -25,27 +25,21 @@
bool isSuitableFor(Backend backend) override { return backend == kNonRendering_Backend; }
void onPerCanvasPreDraw(SkCanvas*) override {
- if (auto stream = SkStream::MakeFromFile(kBenchFile)) {
- SkASSERT(stream->hasLength());
- fCStringData = SkData::MakeUninitialized(stream->getLength() + 1);
- auto* data8 = reinterpret_cast<uint8_t*>(fCStringData->writable_data());
- SkAssertResult(stream->read(data8, stream->getLength()) == stream->getLength());
- data8[stream->getLength()] = '\0';
-
- } else {
+ fData = SkData::MakeFromFileName(kBenchFile);
+ if (!fData) {
SkDebugf("!! Could not open bench file: %s\n", kBenchFile);
}
}
void onPerCanvasPostDraw(SkCanvas*) override {
- fCStringData = nullptr;
+ fData = nullptr;
}
void onDraw(int loops, SkCanvas*) override {
- if (!fCStringData) return;
+ if (!fData) return;
for (int i = 0; i < loops; i++) {
- skjson::DOM dom(static_cast<const char*>(fCStringData->data()));
+ skjson::DOM dom(static_cast<const char*>(fData->data()), fData->size());
if (dom.root().is<skjson::NullValue>()) {
SkDebugf("!! Parsing failed.\n");
return;
@@ -54,7 +48,7 @@
}
private:
- sk_sp<SkData> fCStringData;
+ sk_sp<SkData> fData;
using INHERITED = Benchmark;
};
diff --git a/modules/skjson/src/SkJSONTest.cpp b/modules/skjson/src/SkJSONTest.cpp
index 6d4338c..91ae497 100644
--- a/modules/skjson/src/SkJSONTest.cpp
+++ b/modules/skjson/src/SkJSONTest.cpp
@@ -22,8 +22,14 @@
{ "" , nullptr },
{ "[" , nullptr },
{ "]" , nullptr },
+ { "[[]" , nullptr },
+ { "[]]" , nullptr },
+ { "[]f" , nullptr },
{ "{" , nullptr },
{ "}" , nullptr },
+ { "{{}" , nullptr },
+ { "{}}" , nullptr },
+ { "{}f" , nullptr },
{ "{]" , nullptr },
{ "[}" , nullptr },
{ "1" , nullptr },
@@ -51,7 +57,9 @@
{ "{ \"k\" : null \"k\" : 1 }", nullptr },
+ { "[]" , "[]" },
{ " \n\r\t [ \n\r\t ] \n\r\t " , "[]" },
+ { "[[]]" , "[[]]" },
{ "[ null ]" , "[null]" },
{ "[ true ]" , "[true]" },
{ "[ false ]" , "[false]" },
@@ -66,6 +74,7 @@
{ "[ \"123456789\" ]" , "[\"123456789\"]" },
{ "[ null , true, false,0,12.8 ]", "[null,true,false,0,12.8]" },
+ { "{}" , "{}" },
{ " \n\r\t { \n\r\t } \n\r\t " , "{}" },
{ "{ \"k\" : null }" , "{\"k\":null}" },
{ "{ \"k1\" : null, \"k2 \":0 }", "{\"k1\":null,\"k2 \":0}" },
@@ -89,7 +98,7 @@
};
for (const auto& tst : g_tests) {
- DOM dom(tst.in);
+ DOM dom(tst.in, strlen(tst.in));
const auto success = !dom.root().is<NullValue>();
REPORTER_ASSERT(reporter, success == (tst.out != nullptr));
if (!success) continue;
@@ -153,7 +162,7 @@
\"k8\": { \"kk1\": 2, \"kk2\": false, \"kk1\": \"baz\" } \n\
}";
- DOM dom(json);
+ DOM dom(json, strlen(json));
const auto& jroot = dom.root().as<ObjectValue>();
REPORTER_ASSERT(reporter, jroot.is<ObjectValue>());