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);
|
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_Arena* arena = upb_Arena_New();
|
||||||
upb_benchmark_FileDescriptorProto* set =
|
upb_benchmark_FileDescriptorProto* set =
|
||||||
upb_benchmark_FileDescriptorProto_parse(descriptor.data, descriptor.size,
|
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 status;
|
||||||
upb_Status_Clear(&status);
|
upb_Status_Clear(&status);
|
||||||
bool ok = upb_JsonDecode(json.data(), json.size(), UPB_UPCAST(proto), md,
|
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;
|
ABSL_CHECK(ok) << "Failed to parse: " << status.msg;
|
||||||
benchmark::DoNotOptimize(proto);
|
benchmark::DoNotOptimize(proto);
|
||||||
upb_Arena_Free(arena);
|
upb_Arena_Free(arena);
|
||||||
}
|
}
|
||||||
state.SetBytesProcessed(state.iterations() * json.size());
|
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) {
|
static void BM_JsonParse_Proto2(benchmark::State& state) {
|
||||||
protobuf::FileDescriptorProto proto;
|
protobuf::FileDescriptorProto proto;
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,7 @@ cc_library(
|
||||||
features = UPB_DEFAULT_FEATURES,
|
features = UPB_DEFAULT_FEATURES,
|
||||||
visibility = ["//visibility:public"],
|
visibility = ["//visibility:public"],
|
||||||
deps = [
|
deps = [
|
||||||
|
"//third_party/utf8_range",
|
||||||
"//upb/base",
|
"//upb/base",
|
||||||
"//upb/lex",
|
"//upb/lex",
|
||||||
"//upb/mem",
|
"//upb/mem",
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@
|
||||||
#include <stdarg.h>
|
#include <stdarg.h>
|
||||||
#include <stddef.h>
|
#include <stddef.h>
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
|
#include <stdio.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
|
||||||
|
|
@ -33,6 +34,7 @@
|
||||||
#include "upb/reflection/def.h"
|
#include "upb/reflection/def.h"
|
||||||
#include "upb/reflection/message.h"
|
#include "upb/reflection/message.h"
|
||||||
#include "upb/wire/encode.h"
|
#include "upb/wire/encode.h"
|
||||||
|
#include "utf8_range.h"
|
||||||
|
|
||||||
// Must be last.
|
// Must be last.
|
||||||
#include "upb/port/def.inc"
|
#include "upb/port/def.inc"
|
||||||
|
|
@ -1593,6 +1595,21 @@ int upb_JsonDecodeDetectingNonconformance(const char* buf, size_t size,
|
||||||
|
|
||||||
if (size == 0) return true;
|
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.ptr = buf;
|
||||||
d.end = buf + size;
|
d.end = buf + size;
|
||||||
d.arena = arena;
|
d.arena = arena;
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,14 @@
|
||||||
extern "C" {
|
extern "C" {
|
||||||
#endif
|
#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 {
|
enum {
|
||||||
kUpb_JsonDecodeResult_Ok = 0,
|
kUpb_JsonDecodeResult_Ok = 0,
|
||||||
|
|
|
||||||
|
|
@ -382,3 +382,58 @@ TEST(JsonTest, AllocationFailureAny) {
|
||||||
TestJsonAllocationFailure(
|
TestJsonAllocationFailure(
|
||||||
R"({"any_val": {"@type": "type.googleapis.com/google.protobuf.Value", "value": "foo"}})");
|
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