Commit 96ab6ade authored by Wouter van Oortmerssen's avatar Wouter van Oortmerssen Committed by GitHub

Merge pull request #3993 from bhamiltoncx/generate-text-failure

Allow GenerateText() to indicate failure to encode flatbuffer to JSON
parents 63b240ec 94d5643f
...@@ -596,7 +596,9 @@ extern void GenComment(const std::vector<std::string> &dc, ...@@ -596,7 +596,9 @@ extern void GenComment(const std::vector<std::string> &dc,
// if it is less than 0, no linefeeds will be generated either. // if it is less than 0, no linefeeds will be generated either.
// See idl_gen_text.cpp. // See idl_gen_text.cpp.
// strict_json adds "quotes" around field names if true. // strict_json adds "quotes" around field names if true.
extern void GenerateText(const Parser &parser, // If the flatbuffer cannot be encoded in JSON (e.g., it contains non-UTF-8
// byte arrays in String values), returns false.
extern bool GenerateText(const Parser &parser,
const void *flatbuffer, const void *flatbuffer,
std::string *text); std::string *text);
extern bool GenerateTextFile(const Parser &parser, extern bool GenerateTextFile(const Parser &parser,
......
...@@ -46,7 +46,10 @@ int main(int /*argc*/, const char * /*argv*/[]) { ...@@ -46,7 +46,10 @@ int main(int /*argc*/, const char * /*argv*/[]) {
// to ensure it is correct, we now generate text back from the binary, // to ensure it is correct, we now generate text back from the binary,
// and compare the two: // and compare the two:
std::string jsongen; std::string jsongen;
GenerateText(parser, parser.builder_.GetBufferPointer(), &jsongen); if (!GenerateText(parser, parser.builder_.GetBufferPointer(), &jsongen)) {
printf("Couldn't serialize parsed data to JSON!\n");
return 1;
}
if (jsongen != jsonfile) { if (jsongen != jsonfile) {
printf("%s----------------\n%s", jsongen.c_str(), jsonfile.c_str()); printf("%s----------------\n%s", jsongen.c_str(), jsonfile.c_str());
......
...@@ -22,7 +22,7 @@ ...@@ -22,7 +22,7 @@
namespace flatbuffers { namespace flatbuffers {
static void GenStruct(const StructDef &struct_def, const Table *table, static bool GenStruct(const StructDef &struct_def, const Table *table,
int indent, const IDLOptions &opts, int indent, const IDLOptions &opts,
std::string *_text); std::string *_text);
...@@ -48,7 +48,7 @@ void OutputIdentifier(const std::string &name, const IDLOptions &opts, ...@@ -48,7 +48,7 @@ void OutputIdentifier(const std::string &name, const IDLOptions &opts,
// Print (and its template specialization below for pointers) generate text // Print (and its template specialization below for pointers) generate text
// for a single FlatBuffer value into JSON format. // for a single FlatBuffer value into JSON format.
// The general case for scalars: // The general case for scalars:
template<typename T> void Print(T val, Type type, int /*indent*/, template<typename T> bool Print(T val, Type type, int /*indent*/,
StructDef * /*union_sd*/, StructDef * /*union_sd*/,
const IDLOptions &opts, const IDLOptions &opts,
std::string *_text) { std::string *_text) {
...@@ -57,7 +57,7 @@ template<typename T> void Print(T val, Type type, int /*indent*/, ...@@ -57,7 +57,7 @@ template<typename T> void Print(T val, Type type, int /*indent*/,
auto enum_val = type.enum_def->ReverseLookup(static_cast<int>(val)); auto enum_val = type.enum_def->ReverseLookup(static_cast<int>(val));
if (enum_val) { if (enum_val) {
OutputIdentifier(enum_val->name, opts, _text); OutputIdentifier(enum_val->name, opts, _text);
return; return true;
} }
} }
...@@ -66,10 +66,12 @@ template<typename T> void Print(T val, Type type, int /*indent*/, ...@@ -66,10 +66,12 @@ template<typename T> void Print(T val, Type type, int /*indent*/,
} else { } else {
text += NumToString(val); text += NumToString(val);
} }
return true;
} }
// Print a vector a sequence of JSON values, comma separated, wrapped in "[]". // Print a vector a sequence of JSON values, comma separated, wrapped in "[]".
template<typename T> void PrintVector(const Vector<T> &v, Type type, template<typename T> bool PrintVector(const Vector<T> &v, Type type,
int indent, const IDLOptions &opts, int indent, const IDLOptions &opts,
std::string *_text) { std::string *_text) {
std::string &text = *_text; std::string &text = *_text;
...@@ -81,19 +83,25 @@ template<typename T> void PrintVector(const Vector<T> &v, Type type, ...@@ -81,19 +83,25 @@ template<typename T> void PrintVector(const Vector<T> &v, Type type,
text += NewLine(opts); text += NewLine(opts);
} }
text.append(indent + Indent(opts), ' '); text.append(indent + Indent(opts), ' ');
if (IsStruct(type)) if (IsStruct(type)) {
Print(v.GetStructFromOffset(i * type.struct_def->bytesize), type, if (!Print(v.GetStructFromOffset(i * type.struct_def->bytesize), type,
indent + Indent(opts), nullptr, opts, _text); indent + Indent(opts), nullptr, opts, _text)) {
else return false;
Print(v[i], type, indent + Indent(opts), nullptr, }
opts, _text); } else {
if (!Print(v[i], type, indent + Indent(opts), nullptr,
opts, _text)) {
return false;
}
}
} }
text += NewLine(opts); text += NewLine(opts);
text.append(indent, ' '); text.append(indent, ' ');
text += "]"; text += "]";
return true;
} }
static void EscapeString(const String &s, std::string *_text, const IDLOptions& opts) { static bool EscapeString(const String &s, std::string *_text, const IDLOptions& opts) {
std::string &text = *_text; std::string &text = *_text;
text += "\""; text += "\"";
for (uoffset_t i = 0; i < s.size(); i++) { for (uoffset_t i = 0; i < s.size(); i++) {
...@@ -118,9 +126,19 @@ static void EscapeString(const String &s, std::string *_text, const IDLOptions& ...@@ -118,9 +126,19 @@ static void EscapeString(const String &s, std::string *_text, const IDLOptions&
text += "\\x"; text += "\\x";
text += IntToStringHex(static_cast<uint8_t>(c), 2); text += IntToStringHex(static_cast<uint8_t>(c), 2);
} else { } else {
// We previously checked for non-UTF-8 and returned a parse error, // There are two cases here:
// so we shouldn't reach here. //
assert(0); // 1) We reached here by parsing an IDL file. In that case,
// we previously checked for non-UTF-8, so we shouldn't reach
// here.
//
// 2) We reached here by someone calling GenerateText()
// on a previously-serialized flatbuffer. The data might have
// non-UTF-8 Strings, or might be corrupt.
//
// In both cases, we have to give up and inform the caller
// they have no JSON.
return false;
} }
} else { } else {
if (ucc <= 0xFFFF) { if (ucc <= 0xFFFF) {
...@@ -145,10 +163,11 @@ static void EscapeString(const String &s, std::string *_text, const IDLOptions& ...@@ -145,10 +163,11 @@ static void EscapeString(const String &s, std::string *_text, const IDLOptions&
} }
} }
text += "\""; text += "\"";
return true;
} }
// Specialization of Print above for pointer types. // Specialization of Print above for pointer types.
template<> void Print<const void *>(const void *val, template<> bool Print<const void *>(const void *val,
Type type, int indent, Type type, int indent,
StructDef *union_sd, StructDef *union_sd,
const IDLOptions &opts, const IDLOptions &opts,
...@@ -158,21 +177,27 @@ template<> void Print<const void *>(const void *val, ...@@ -158,21 +177,27 @@ template<> void Print<const void *>(const void *val,
// If this assert hits, you have an corrupt buffer, a union type field // If this assert hits, you have an corrupt buffer, a union type field
// was not present or was out of range. // was not present or was out of range.
assert(union_sd); assert(union_sd);
GenStruct(*union_sd, if (!GenStruct(*union_sd,
reinterpret_cast<const Table *>(val), reinterpret_cast<const Table *>(val),
indent, indent,
opts, opts,
_text); _text)) {
return false;
}
break; break;
case BASE_TYPE_STRUCT: case BASE_TYPE_STRUCT:
GenStruct(*type.struct_def, if (!GenStruct(*type.struct_def,
reinterpret_cast<const Table *>(val), reinterpret_cast<const Table *>(val),
indent, indent,
opts, opts,
_text); _text)) {
return false;
}
break; break;
case BASE_TYPE_STRING: { case BASE_TYPE_STRING: {
EscapeString(*reinterpret_cast<const String *>(val), _text, opts); if (!EscapeString(*reinterpret_cast<const String *>(val), _text, opts)) {
return false;
}
break; break;
} }
case BASE_TYPE_VECTOR: case BASE_TYPE_VECTOR:
...@@ -182,31 +207,35 @@ template<> void Print<const void *>(const void *val, ...@@ -182,31 +207,35 @@ template<> void Print<const void *>(const void *val,
#define FLATBUFFERS_TD(ENUM, IDLTYPE, CTYPE, JTYPE, GTYPE, NTYPE, \ #define FLATBUFFERS_TD(ENUM, IDLTYPE, CTYPE, JTYPE, GTYPE, NTYPE, \
PTYPE) \ PTYPE) \
case BASE_TYPE_ ## ENUM: \ case BASE_TYPE_ ## ENUM: \
PrintVector<CTYPE>( \ if (!PrintVector<CTYPE>( \
*reinterpret_cast<const Vector<CTYPE> *>(val), \ *reinterpret_cast<const Vector<CTYPE> *>(val), \
type, indent, opts, _text); break; type, indent, opts, _text)) { \
return false; \
} \
break;
FLATBUFFERS_GEN_TYPES(FLATBUFFERS_TD) FLATBUFFERS_GEN_TYPES(FLATBUFFERS_TD)
#undef FLATBUFFERS_TD #undef FLATBUFFERS_TD
} }
break; break;
default: assert(0); default: assert(0);
} }
return true;
} }
// Generate text for a scalar field. // Generate text for a scalar field.
template<typename T> static void GenField(const FieldDef &fd, template<typename T> static bool GenField(const FieldDef &fd,
const Table *table, bool fixed, const Table *table, bool fixed,
const IDLOptions &opts, const IDLOptions &opts,
int indent, int indent,
std::string *_text) { std::string *_text) {
Print(fixed ? return Print(fixed ?
reinterpret_cast<const Struct *>(table)->GetField<T>(fd.value.offset) : reinterpret_cast<const Struct *>(table)->GetField<T>(fd.value.offset) :
table->GetField<T>(fd.value.offset, 0), fd.value.type, indent, nullptr, table->GetField<T>(fd.value.offset, 0), fd.value.type, indent, nullptr,
opts, _text); opts, _text);
} }
// Generate text for non-scalar field. // Generate text for non-scalar field.
static void GenFieldOffset(const FieldDef &fd, const Table *table, bool fixed, static bool GenFieldOffset(const FieldDef &fd, const Table *table, bool fixed,
int indent, StructDef *union_sd, int indent, StructDef *union_sd,
const IDLOptions &opts, std::string *_text) { const IDLOptions &opts, std::string *_text) {
const void *val = nullptr; const void *val = nullptr;
...@@ -220,12 +249,12 @@ static void GenFieldOffset(const FieldDef &fd, const Table *table, bool fixed, ...@@ -220,12 +249,12 @@ static void GenFieldOffset(const FieldDef &fd, const Table *table, bool fixed,
? table->GetStruct<const void *>(fd.value.offset) ? table->GetStruct<const void *>(fd.value.offset)
: table->GetPointer<const void *>(fd.value.offset); : table->GetPointer<const void *>(fd.value.offset);
} }
Print(val, fd.value.type, indent, union_sd, opts, _text); return Print(val, fd.value.type, indent, union_sd, opts, _text);
} }
// Generate text for a struct or table, values separated by commas, indented, // Generate text for a struct or table, values separated by commas, indented,
// and bracketed by "{}" // and bracketed by "{}"
static void GenStruct(const StructDef &struct_def, const Table *table, static bool GenStruct(const StructDef &struct_def, const Table *table,
int indent, const IDLOptions &opts, int indent, const IDLOptions &opts,
std::string *_text) { std::string *_text) {
std::string &text = *_text; std::string &text = *_text;
...@@ -253,8 +282,10 @@ static void GenStruct(const StructDef &struct_def, const Table *table, ...@@ -253,8 +282,10 @@ static void GenStruct(const StructDef &struct_def, const Table *table,
#define FLATBUFFERS_TD(ENUM, IDLTYPE, CTYPE, JTYPE, GTYPE, NTYPE, \ #define FLATBUFFERS_TD(ENUM, IDLTYPE, CTYPE, JTYPE, GTYPE, NTYPE, \
PTYPE) \ PTYPE) \
case BASE_TYPE_ ## ENUM: \ case BASE_TYPE_ ## ENUM: \
GenField<CTYPE>(fd, table, struct_def.fixed, \ if (!GenField<CTYPE>(fd, table, struct_def.fixed, \
opts, indent + Indent(opts), _text); \ opts, indent + Indent(opts), _text)) { \
return false; \
} \
break; break;
FLATBUFFERS_GEN_TYPES_SCALAR(FLATBUFFERS_TD) FLATBUFFERS_GEN_TYPES_SCALAR(FLATBUFFERS_TD)
#undef FLATBUFFERS_TD #undef FLATBUFFERS_TD
...@@ -264,8 +295,10 @@ static void GenStruct(const StructDef &struct_def, const Table *table, ...@@ -264,8 +295,10 @@ static void GenStruct(const StructDef &struct_def, const Table *table,
case BASE_TYPE_ ## ENUM: case BASE_TYPE_ ## ENUM:
FLATBUFFERS_GEN_TYPES_POINTER(FLATBUFFERS_TD) FLATBUFFERS_GEN_TYPES_POINTER(FLATBUFFERS_TD)
#undef FLATBUFFERS_TD #undef FLATBUFFERS_TD
GenFieldOffset(fd, table, struct_def.fixed, indent + Indent(opts), if (!GenFieldOffset(fd, table, struct_def.fixed, indent + Indent(opts),
union_sd, opts, _text); union_sd, opts, _text)) {
return false;
}
break; break;
} }
if (fd.value.type.base_type == BASE_TYPE_UTYPE) { if (fd.value.type.base_type == BASE_TYPE_UTYPE) {
...@@ -284,20 +317,24 @@ static void GenStruct(const StructDef &struct_def, const Table *table, ...@@ -284,20 +317,24 @@ static void GenStruct(const StructDef &struct_def, const Table *table,
text += NewLine(opts); text += NewLine(opts);
text.append(indent, ' '); text.append(indent, ' ');
text += "}"; text += "}";
return true;
} }
// Generate a text representation of a flatbuffer in JSON format. // Generate a text representation of a flatbuffer in JSON format.
void GenerateText(const Parser &parser, const void *flatbuffer, bool GenerateText(const Parser &parser, const void *flatbuffer,
std::string *_text) { std::string *_text) {
std::string &text = *_text; std::string &text = *_text;
assert(parser.root_struct_def_); // call SetRootType() assert(parser.root_struct_def_); // call SetRootType()
text.reserve(1024); // Reduce amount of inevitable reallocs. text.reserve(1024); // Reduce amount of inevitable reallocs.
GenStruct(*parser.root_struct_def_, if (!GenStruct(*parser.root_struct_def_,
GetRoot<Table>(flatbuffer), GetRoot<Table>(flatbuffer),
0, 0,
parser.opts, parser.opts,
_text); _text)) {
return false;
}
text += NewLine(parser.opts); text += NewLine(parser.opts);
return true;
} }
std::string TextFileName(const std::string &path, std::string TextFileName(const std::string &path,
...@@ -310,7 +347,9 @@ bool GenerateTextFile(const Parser &parser, ...@@ -310,7 +347,9 @@ bool GenerateTextFile(const Parser &parser,
const std::string &file_name) { const std::string &file_name) {
if (!parser.builder_.GetSize() || !parser.root_struct_def_) return true; if (!parser.builder_.GetSize() || !parser.root_struct_def_) return true;
std::string text; std::string text;
GenerateText(parser, parser.builder_.GetBufferPointer(), &text); if (!GenerateText(parser, parser.builder_.GetBufferPointer(), &text)) {
return false;
}
return flatbuffers::SaveFile(TextFileName(path, file_name).c_str(), return flatbuffers::SaveFile(TextFileName(path, file_name).c_str(),
text, text,
false); false);
......
...@@ -410,7 +410,8 @@ void ParseAndGenerateTextTest() { ...@@ -410,7 +410,8 @@ void ParseAndGenerateTextTest() {
// to ensure it is correct, we now generate text back from the binary, // to ensure it is correct, we now generate text back from the binary,
// and compare the two: // and compare the two:
std::string jsongen; std::string jsongen;
GenerateText(parser, parser.builder_.GetBufferPointer(), &jsongen); auto result = GenerateText(parser, parser.builder_.GetBufferPointer(), &jsongen);
TEST_EQ(result, true);
if (jsongen != jsonfile) { if (jsongen != jsonfile) {
printf("%s----------------\n%s", jsongen.c_str(), jsonfile.c_str()); printf("%s----------------\n%s", jsongen.c_str(), jsonfile.c_str());
...@@ -827,7 +828,8 @@ void FuzzTest2() { ...@@ -827,7 +828,8 @@ void FuzzTest2() {
std::string jsongen; std::string jsongen;
parser.opts.indent_step = 0; parser.opts.indent_step = 0;
GenerateText(parser, parser.builder_.GetBufferPointer(), &jsongen); auto result = GenerateText(parser, parser.builder_.GetBufferPointer(), &jsongen);
TEST_EQ(result, true);
if (jsongen != json) { if (jsongen != json) {
// These strings are larger than a megabyte, so we show the bytes around // These strings are larger than a megabyte, so we show the bytes around
...@@ -987,7 +989,8 @@ void UnicodeTest() { ...@@ -987,7 +989,8 @@ void UnicodeTest() {
true); true);
std::string jsongen; std::string jsongen;
parser.opts.indent_step = -1; parser.opts.indent_step = -1;
GenerateText(parser, parser.builder_.GetBufferPointer(), &jsongen); auto result = GenerateText(parser, parser.builder_.GetBufferPointer(), &jsongen);
TEST_EQ(result, true);
TEST_EQ(jsongen, TEST_EQ(jsongen,
std::string( std::string(
"{F: \"\\u20AC\\u00A2\\u30E6\\u30FC\\u30B6\\u30FC" "{F: \"\\u20AC\\u00A2\\u30E6\\u30FC\\u30B6\\u30FC"
...@@ -1003,13 +1006,31 @@ void UnicodeTestAllowNonUTF8() { ...@@ -1003,13 +1006,31 @@ void UnicodeTestAllowNonUTF8() {
"\\u5225\\u30B5\\u30A4\\u30C8\\x01\\x80\\u0080\\uD83D\\uDE0E\" }"), true); "\\u5225\\u30B5\\u30A4\\u30C8\\x01\\x80\\u0080\\uD83D\\uDE0E\" }"), true);
std::string jsongen; std::string jsongen;
parser.opts.indent_step = -1; parser.opts.indent_step = -1;
GenerateText(parser, parser.builder_.GetBufferPointer(), &jsongen); auto result = GenerateText(parser, parser.builder_.GetBufferPointer(), &jsongen);
TEST_EQ(result, true);
TEST_EQ(jsongen, TEST_EQ(jsongen,
std::string( std::string(
"{F: \"\\u20AC\\u00A2\\u30E6\\u30FC\\u30B6\\u30FC" "{F: \"\\u20AC\\u00A2\\u30E6\\u30FC\\u30B6\\u30FC"
"\\u5225\\u30B5\\u30A4\\u30C8\\u0001\\x80\\u0080\\uD83D\\uDE0E\"}")); "\\u5225\\u30B5\\u30A4\\u30C8\\u0001\\x80\\u0080\\uD83D\\uDE0E\"}"));
} }
void UnicodeTestGenerateTextFailsOnNonUTF8() {
flatbuffers::Parser parser;
// Allow non-UTF-8 initially to model what happens when we load a binary flatbuffer from disk
// which contains non-UTF-8 strings.
parser.opts.allow_non_utf8 = true;
TEST_EQ(parser.Parse("table T { F:string; }"
"root_type T;"
"{ F:\"\\u20AC\\u00A2\\u30E6\\u30FC\\u30B6\\u30FC"
"\\u5225\\u30B5\\u30A4\\u30C8\\x01\\x80\\u0080\\uD83D\\uDE0E\" }"), true);
std::string jsongen;
parser.opts.indent_step = -1;
// Now, disallow non-UTF-8 (the default behavior) so GenerateText indicates failure.
parser.opts.allow_non_utf8 = false;
auto result = GenerateText(parser, parser.builder_.GetBufferPointer(), &jsongen);
TEST_EQ(result, false);
}
void UnicodeSurrogatesTest() { void UnicodeSurrogatesTest() {
flatbuffers::Parser parser; flatbuffers::Parser parser;
...@@ -1157,7 +1178,8 @@ void UnknownFieldsTest() { ...@@ -1157,7 +1178,8 @@ void UnknownFieldsTest() {
std::string jsongen; std::string jsongen;
parser.opts.indent_step = -1; parser.opts.indent_step = -1;
GenerateText(parser, parser.builder_.GetBufferPointer(), &jsongen); auto result = GenerateText(parser, parser.builder_.GetBufferPointer(), &jsongen);
TEST_EQ(result, true);
TEST_EQ(jsongen == "{str: \"test\",i: 10}", true); TEST_EQ(jsongen == "{str: \"test\",i: 10}", true);
} }
...@@ -1222,6 +1244,7 @@ int main(int /*argc*/, const char * /*argv*/[]) { ...@@ -1222,6 +1244,7 @@ int main(int /*argc*/, const char * /*argv*/[]) {
IntegerOutOfRangeTest(); IntegerOutOfRangeTest();
UnicodeTest(); UnicodeTest();
UnicodeTestAllowNonUTF8(); UnicodeTestAllowNonUTF8();
UnicodeTestGenerateTextFailsOnNonUTF8();
UnicodeSurrogatesTest(); UnicodeSurrogatesTest();
UnicodeInvalidSurrogatesTest(); UnicodeInvalidSurrogatesTest();
InvalidUTF8Test(); InvalidUTF8Test();
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment