mirror of
https://github.com/protocolbuffers/protobuf
synced 2026-08-26 02:23:14 -04:00
Add UTF-8 validation option levels to upb JSON decoder
This defaults to a log-err today, but will switch to enforce in the 2027-Q1 breaking change. This actually just runs the UTF-8 validate over the entire input buffer at the start. Technically it only needs to run over the contents inside quoted strings, since the other characters have to be a small subset of ascii characters anyway (brackets, quotes, digits, etc). But as JSON is string-heavy (every key is a string) utf8_range simd behavior is happy to be given large ascii buffers. Benchmark results show that the extra cost of this being enabled is within benchmark noise level. PiperOrigin-RevId: 970651879
This commit is contained in:
parent
6836552e25
commit
06d6ae6228
5 changed files with 98 additions and 4 deletions
|
|
@ -407,7 +407,7 @@ static absl::string_view UpbJsonEncode(upb_benchmark_FileDescriptorProto* proto,
|
|||
return absl::string_view(buf, size);
|
||||
}
|
||||
|
||||
static void BM_JsonParse_Upb(benchmark::State& state) {
|
||||
static void BM_JsonParse_Upb(benchmark::State& state, int options) {
|
||||
upb_Arena* arena = upb_Arena_New();
|
||||
upb_benchmark_FileDescriptorProto* set =
|
||||
upb_benchmark_FileDescriptorProto_parse(descriptor.data, descriptor.size,
|
||||
|
|
@ -429,14 +429,28 @@ static void BM_JsonParse_Upb(benchmark::State& state) {
|
|||
upb_Status status;
|
||||
upb_Status_Clear(&status);
|
||||
bool ok = upb_JsonDecode(json.data(), json.size(), UPB_UPCAST(proto), md,
|
||||
defpool.ptr(), 0, arena, &status);
|
||||
defpool.ptr(), options, arena, &status);
|
||||
ABSL_CHECK(ok) << "Failed to parse: " << status.msg;
|
||||
benchmark::DoNotOptimize(proto);
|
||||
upb_Arena_Free(arena);
|
||||
}
|
||||
state.SetBytesProcessed(state.iterations() * json.size());
|
||||
}
|
||||
BENCHMARK(BM_JsonParse_Upb);
|
||||
|
||||
static void BM_JsonParse_Upb_Utf8Off(benchmark::State& state) {
|
||||
BM_JsonParse_Upb(state, upb_JsonDecode_ValidateUtf8_Off);
|
||||
}
|
||||
BENCHMARK(BM_JsonParse_Upb_Utf8Off);
|
||||
|
||||
static void BM_JsonParse_Upb_Utf8Warn(benchmark::State& state) {
|
||||
BM_JsonParse_Upb(state, upb_JsonDecode_ValidateUtf8_Warn);
|
||||
}
|
||||
BENCHMARK(BM_JsonParse_Upb_Utf8Warn);
|
||||
|
||||
static void BM_JsonParse_Upb_Utf8Enforce(benchmark::State& state) {
|
||||
BM_JsonParse_Upb(state, upb_JsonDecode_ValidateUtf8_Enforce);
|
||||
}
|
||||
BENCHMARK(BM_JsonParse_Upb_Utf8Enforce);
|
||||
|
||||
static void BM_JsonParse_Proto2(benchmark::State& state) {
|
||||
protobuf::FileDescriptorProto proto;
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ cc_library(
|
|||
features = UPB_DEFAULT_FEATURES,
|
||||
visibility = ["//visibility:public"],
|
||||
deps = [
|
||||
"//third_party/utf8_range",
|
||||
"//upb/base",
|
||||
"//upb/lex",
|
||||
"//upb/mem",
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@
|
|||
#include <stdarg.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
|
|
@ -33,6 +34,7 @@
|
|||
#include "upb/reflection/def.h"
|
||||
#include "upb/reflection/message.h"
|
||||
#include "upb/wire/encode.h"
|
||||
#include "utf8_range.h"
|
||||
|
||||
// Must be last.
|
||||
#include "upb/port/def.inc"
|
||||
|
|
@ -1593,6 +1595,21 @@ int upb_JsonDecodeDetectingNonconformance(const char* buf, size_t size,
|
|||
|
||||
if (size == 0) return true;
|
||||
|
||||
int utf8_level = options & upb_JsonDecode_ValidateUtf8_Mask;
|
||||
if (utf8_level != upb_JsonDecode_ValidateUtf8_Off) {
|
||||
if (!utf8_range_IsValid(buf, size)) {
|
||||
if (utf8_level == upb_JsonDecode_ValidateUtf8_Warn) {
|
||||
fprintf(
|
||||
stderr,
|
||||
"Invalid UTF-8 in JSON input. This will be rejected starting in "
|
||||
"the 2027-Q1 breaking change\n");
|
||||
} else if (utf8_level == upb_JsonDecode_ValidateUtf8_Enforce) {
|
||||
upb_Status_SetErrorMessage(status, "Invalid UTF-8 in JSON input");
|
||||
return kUpb_JsonDecodeResult_Error;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
d.ptr = buf;
|
||||
d.end = buf + size;
|
||||
d.arena = arena;
|
||||
|
|
|
|||
|
|
@ -22,7 +22,14 @@
|
|||
extern "C" {
|
||||
#endif
|
||||
|
||||
enum { upb_JsonDecode_IgnoreUnknown = 1 };
|
||||
enum {
|
||||
upb_JsonDecode_IgnoreUnknown = 1 << 0,
|
||||
|
||||
upb_JsonDecode_ValidateUtf8_Off = 0 << 1,
|
||||
upb_JsonDecode_ValidateUtf8_Warn = 1 << 1,
|
||||
upb_JsonDecode_ValidateUtf8_Enforce = 2 << 1,
|
||||
upb_JsonDecode_ValidateUtf8_Mask = 3 << 1,
|
||||
};
|
||||
|
||||
enum {
|
||||
kUpb_JsonDecodeResult_Ok = 0,
|
||||
|
|
|
|||
|
|
@ -382,3 +382,58 @@ TEST(JsonTest, AllocationFailureAny) {
|
|||
TestJsonAllocationFailure(
|
||||
R"({"any_val": {"@type": "type.googleapis.com/google.protobuf.Value", "value": "foo"}})");
|
||||
}
|
||||
|
||||
TEST(JsonTest, Utf8ValidationLevels) {
|
||||
upb::Arena arena;
|
||||
upb::DefPool defpool;
|
||||
const upb_MessageDef* m = upb_test_Box_getmsgdef(defpool.ptr());
|
||||
ASSERT_TRUE(m != nullptr);
|
||||
|
||||
// Invalid UTF-8 sequence inside JSON string
|
||||
std::string invalid_json = "{\"name\": \"\xff\xff\"}";
|
||||
|
||||
// 1. OFF: Should not fail on UTF-8 (decoding succeeds)
|
||||
{
|
||||
upb_test_Box* box = upb_test_Box_new(arena.ptr());
|
||||
upb::Status status;
|
||||
bool ok = upb_JsonDecode(invalid_json.data(), invalid_json.size(),
|
||||
UPB_UPCAST(box), m, defpool.ptr(),
|
||||
upb_JsonDecode_ValidateUtf8_Off, arena.ptr(),
|
||||
status.ptr());
|
||||
EXPECT_TRUE(ok) << status.error_message();
|
||||
}
|
||||
|
||||
// 2. WARN: Should print warning to stderr but still succeed
|
||||
{
|
||||
upb_test_Box* box = upb_test_Box_new(arena.ptr());
|
||||
upb::Status status;
|
||||
bool ok = upb_JsonDecode(invalid_json.data(), invalid_json.size(),
|
||||
UPB_UPCAST(box), m, defpool.ptr(),
|
||||
upb_JsonDecode_ValidateUtf8_Warn, arena.ptr(),
|
||||
status.ptr());
|
||||
EXPECT_TRUE(ok) << status.error_message();
|
||||
}
|
||||
|
||||
// 3. ENFORCE: Should fail to parse
|
||||
{
|
||||
upb_test_Box* box = upb_test_Box_new(arena.ptr());
|
||||
upb::Status status;
|
||||
bool ok = upb_JsonDecode(invalid_json.data(), invalid_json.size(),
|
||||
UPB_UPCAST(box), m, defpool.ptr(),
|
||||
upb_JsonDecode_ValidateUtf8_Enforce, arena.ptr(),
|
||||
status.ptr());
|
||||
EXPECT_FALSE(ok);
|
||||
EXPECT_STREQ(status.error_message(), "Invalid UTF-8 in JSON input");
|
||||
}
|
||||
|
||||
// Valid UTF-8 should succeed with ENFORCE
|
||||
{
|
||||
std::string valid_json = "{\"name\": \"hello world\"}";
|
||||
upb_test_Box* box = upb_test_Box_new(arena.ptr());
|
||||
upb::Status status;
|
||||
bool ok = upb_JsonDecode(
|
||||
valid_json.data(), valid_json.size(), UPB_UPCAST(box), m, defpool.ptr(),
|
||||
upb_JsonDecode_ValidateUtf8_Enforce, arena.ptr(), status.ptr());
|
||||
EXPECT_TRUE(ok) << status.error_message();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue