Compare commits

..

9 Commits

Author SHA1 Message Date
dependabot[bot]
7e68c3625a Bump gradle/actions from 5 to 6
Bumps [gradle/actions](https://github.com/gradle/actions) from 5 to 6.
- [Release notes](https://github.com/gradle/actions/releases)
- [Commits](https://github.com/gradle/actions/compare/v5...v6)

---
updated-dependencies:
- dependency-name: gradle/actions
  dependency-version: '6'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-03-30 15:06:24 +00:00
Björn Harrtell
3860f1cf7f [TS] Fixup TS test run at CI (#9004) 2026-03-30 13:32:24 +01:00
Thomas Köppe
4e582b0c1d [flexbuffers] Add "AlignedBlob", a version of "Blob" with explicit alignment. (#8993)
A blob is an array of bytes and has no intrinsic alignment (i.e. the
alignment is 1). The alignment of the existing flexbuffers blob is
solely affected by the width of the integer needed to store the blob's
size: that integer's width becomes the alignment of the blob.

The proposed AlignedBlob function here piggybacks on this effect and
simply uses a user-defined alignment for the width of the integer that
stores the blob's size; this automatically imparts that same alignment
on the blob itself. (The width is bounded below by the actual width
needed to store the blob's size.)

The ability to control the alignment of a blob is important for use
cases in which the blob itself stores structured data that we want to
access without further copies (e.g. other flatbuffer messages).
2026-03-23 10:28:03 -07:00
Fedor Osetrov
8396e00dd8 allow to use reflection in constant time evaluation (#8978)
* Update reflection.h

allow to use reflection in constant time evaluation

* make GetTypeSize constexpr

* fix clang-format
2026-03-20 02:01:45 +00:00
dependabot[bot]
48babd417d Bump flatted in the npm_and_yarn group across 1 directory (#8989)
Bumps the npm_and_yarn group with 1 update in the / directory: [flatted](https://github.com/WebReflection/flatted).


Updates `flatted` from 3.3.1 to 3.4.2
- [Commits](https://github.com/WebReflection/flatted/compare/v3.3.1...v3.4.2)

---
updated-dependencies:
- dependency-name: flatted
  dependency-version: 3.4.2
  dependency-type: indirect
  dependency-group: npm_and_yarn
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-03-19 21:48:28 -04:00
tmimmanuel
22770f7e85 Fix inconsistent Python union creator function naming (#8981) 2026-03-19 12:36:37 +00:00
Dexter.k
21b033227e Add bounds check for root offset in AddFlatBuffer (#8982) 2026-03-19 08:22:26 -04:00
dataCenter430
93f587a6d3 fix: annotated output for size-prefixed binaries (#8976) 2026-03-18 22:54:46 -04:00
Kevin Zhao
8afb68f074 codegen: escape string default values to prevent code injection (#8964)
String default values parsed from .fbs schemas are un-escaped by the IDL
parser (e.g., \x22 becomes a raw " byte), but code generators embed these
raw values directly into generated source code string literals. This allows
specially crafted .fbs files to break out of string literals and inject
arbitrary code into generated C++, Rust, TypeScript, and Swift source.

Fix by adding EscapeCodeGenString() helper that re-escapes string content
before embedding, and applying it to all 7 affected injection points across
5 code generators (C++, Rust, TypeScript, Swift, FBS).

Resolves the TODO comments in idl_gen_cpp.cpp and idl_gen_rust.cpp.
2026-03-18 22:01:23 -04:00
26 changed files with 561 additions and 101 deletions

View File

@@ -319,7 +319,7 @@ jobs:
distribution: temurin
java-version: 17
- name: set up Gradle
uses: gradle/actions/setup-gradle@v5
uses: gradle/actions/setup-gradle@v6
- name: set up flatc
run: |
cmake -DFLATBUFFERS_BUILD_TESTS=OFF -DFLATBUFFERS_BUILD_FLATLIB=OFF -DFLATBUFFERS_BUILD_FLATHASH=OFF -DFLATBUFFERS_STRICT_MODE=ON .
@@ -399,7 +399,7 @@ jobs:
distribution: temurin
java-version: 17
- name: set up Gradle
uses: gradle/actions/setup-gradle@v5
uses: gradle/actions/setup-gradle@v6
- name: Build flatc
run: |
cmake -DFLATBUFFERS_BUILD_TESTS=OFF -DFLATBUFFERS_BUILD_FLATLIB=OFF -DFLATBUFFERS_BUILD_FLATHASH=OFF .
@@ -422,7 +422,7 @@ jobs:
distribution: temurin
java-version: 17
- name: set up Gradle
uses: gradle/actions/setup-gradle@v5
uses: gradle/actions/setup-gradle@v6
- name: Build flatc
run: |
cmake -DFLATBUFFERS_BUILD_TESTS=OFF -DFLATBUFFERS_BUILD_FLATLIB=OFF -DFLATBUFFERS_BUILD_FLATHASH=OFF .
@@ -499,7 +499,7 @@ jobs:
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v6
- uses: swift-actions/setup-swift@v3
- uses: swift-actions/setup-swift@v2
with:
swift-version: ${{ matrix.swift }}
- name: Get swift version
@@ -523,7 +523,7 @@ jobs:
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v6
- uses: swift-actions/setup-swift@v3
- uses: swift-actions/setup-swift@v2
with:
swift-version: 6.2.1
- uses: bytecodealliance/actions/wasmtime/setup@v1
@@ -545,7 +545,7 @@ jobs:
# FIXME: make test script not rely on flatc
run: cmake -G "Unix Makefiles" -DCMAKE_BUILD_TYPE=Release -DFLATBUFFERS_BUILD_TESTS=OFF -DFLATBUFFERS_INSTALL=OFF -DFLATBUFFERS_BUILD_FLATLIB=OFF -DFLATBUFFERS_BUILD_FLATHASH=OFF . && make -j
- name: pnpm
run: npm install -g pnpm esbuild
run: npm install -g pnpm
- name: deps
run: pnpm i
- name: compile

View File

@@ -1207,11 +1207,20 @@ class Builder FLATBUFFERS_FINAL_CLASS {
String(str);
}
size_t AlignedBlob(const void* data, size_t len, BitWidth alignment) {
// The requested alignment must not be smaller than the one required to
// store the length.
return CreateAlignedBlob(data, len, 0, FBT_BLOB,
std::max(alignment, WidthU(len)));
}
size_t AlignedBlob(const std::vector<uint8_t>& v, BitWidth alignment) {
return AlignedBlob(v.data(), v.size(), alignment);
}
size_t Blob(const void* data, size_t len) {
return CreateBlob(data, len, 0, FBT_BLOB);
}
size_t Blob(const std::vector<uint8_t>& v) {
return CreateBlob(v.data(), v.size(), 0, FBT_BLOB);
return Blob(v.data(), v.size());
}
void Blob(const char* key, const void* data, size_t len) {
@@ -1693,11 +1702,16 @@ class Builder FLATBUFFERS_FINAL_CLASS {
size_t CreateBlob(const void* data, size_t len, size_t trailing, Type type) {
auto bit_width = WidthU(len);
auto byte_width = Align(bit_width);
return CreateAlignedBlob(data, len, trailing, type, bit_width);
}
size_t CreateAlignedBlob(const void* data, size_t len, size_t trailing,
Type type, BitWidth alignment) {
auto byte_width = Align(alignment);
Write<uint64_t>(len, byte_width);
auto sloc = buf_.size();
WriteBytes(data, len + trailing);
stack_.push_back(Value(static_cast<uint64_t>(sloc), type, bit_width));
stack_.push_back(Value(static_cast<uint64_t>(sloc), type, alignment));
return sloc;
}

View File

@@ -30,50 +30,52 @@ namespace flatbuffers {
// ------------------------- GETTERS -------------------------
inline bool IsScalar(reflection::BaseType t) {
constexpr bool IsScalar(reflection::BaseType t) {
return t >= reflection::UType && t <= reflection::Double;
}
inline bool IsInteger(reflection::BaseType t) {
constexpr bool IsInteger(reflection::BaseType t) {
return t >= reflection::UType && t <= reflection::ULong;
}
inline bool IsFloat(reflection::BaseType t) {
constexpr bool IsFloat(reflection::BaseType t) {
return t == reflection::Float || t == reflection::Double;
}
inline bool IsLong(reflection::BaseType t) {
constexpr bool IsLong(reflection::BaseType t) {
return t == reflection::Long || t == reflection::ULong;
}
// Size of a basic type, don't use with structs.
inline size_t GetTypeSize(reflection::BaseType base_type) {
// This needs to correspond to the BaseType enum.
static size_t sizes[] = {
0, // None
1, // UType
1, // Bool
1, // Byte
1, // UByte
2, // Short
2, // UShort
4, // Int
4, // UInt
8, // Long
8, // ULong
4, // Float
8, // Double
4, // String
4, // Vector
4, // Obj
4, // Union
0, // Array. Only used in structs. 0 was chosen to prevent out-of-bounds
// errors.
8, // Vector64
// This needs to correspond to the BaseType enum.
constexpr size_t kBaseTypeSize[] = {
0, // None
1, // UType
1, // Bool
1, // Byte
1, // UByte
2, // Short
2, // UShort
4, // Int
4, // UInt
8, // Long
8, // ULong
4, // Float
8, // Double
4, // String
4, // Vector
4, // Obj
4, // Union
0, // Array. Only used in structs. 0 was chosen to prevent out-of-bounds
// errors.
8, // Vector64
0 // MaxBaseType. This must be kept the last entry in this array.
};
static_assert(sizeof(sizes) / sizeof(size_t) == reflection::MaxBaseType + 1,
"Size of sizes[] array does not match the count of BaseType "
"enum values.");
return sizes[base_type];
0 // MaxBaseType. This must be kept the last entry in this array.
};
static_assert(sizeof(kBaseTypeSize) / sizeof(size_t) ==
reflection::MaxBaseType + 1,
"Size of sizes[] array does not match the count of BaseType "
"enum values.");
// Size of a basic type, don't use with structs.
constexpr size_t GetTypeSize(reflection::BaseType base_type) {
return kBaseTypeSize[base_type];
}
// Same as above, but now correctly returns the size of a struct if
@@ -420,7 +422,7 @@ pointer_inside_vector<T, U> piv(T* ptr, std::vector<U>& vec) {
return pointer_inside_vector<T, U>(ptr, vec);
}
inline const char* UnionTypeFieldSuffix() { return "_type"; }
constexpr const char* UnionTypeFieldSuffix() { return "_type"; }
// Helper to figure out the actual table type a union refers to.
inline const reflection::Object& GetUnionType(

14
pnpm-lock.yaml generated
View File

@@ -33,6 +33,12 @@ importers:
specifier: ^8.34.1
version: 8.34.1(eslint@9.29.0)(typescript@5.8.3)
tests/ts:
dependencies:
flatbuffers:
specifier: workspace:*
version: link:../..
packages:
'@esbuild/aix-ppc64@0.25.5':
@@ -483,8 +489,8 @@ packages:
resolution: {integrity: sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==}
engines: {node: '>=16'}
flatted@3.3.1:
resolution: {integrity: sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==}
flatted@3.4.2:
resolution: {integrity: sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==}
glob-parent@5.1.2:
resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==}
@@ -1146,10 +1152,10 @@ snapshots:
flat-cache@4.0.1:
dependencies:
flatted: 3.3.1
flatted: 3.4.2
keyv: 4.5.4
flatted@3.3.1: {}
flatted@3.4.2: {}
glob-parent@5.1.2:
dependencies:

2
pnpm-workspace.yaml Normal file
View File

@@ -0,0 +1,2 @@
packages:
- tests/ts

View File

@@ -422,6 +422,11 @@ flatc(
schema="nested_union_test.fbs",
)
flatc(
["--python", "--gen-object-api"],
schema="union_name_test.fbs",
)
flatc(
NO_INCL_OPTS + CPP_OPTS,
schema="default_vectors_strings_test.fbs",

View File

@@ -126,9 +126,7 @@ static BinarySection GenerateMissingSection(const uint64_t offset,
std::map<uint64_t, BinarySection> BinaryAnnotator::Annotate() {
if (bfbs_ != nullptr && bfbs_length_ != 0) {
flatbuffers::Verifier verifier(bfbs_, static_cast<size_t>(bfbs_length_));
if ((is_size_prefixed_ &&
!reflection::VerifySizePrefixedSchemaBuffer(verifier)) ||
!reflection::VerifySchemaBuffer(verifier)) {
if (!reflection::VerifySchemaBuffer(verifier)) {
return {};
}
}

View File

@@ -2779,15 +2779,17 @@ class CppGenerator : public BaseGenerator {
get_call += ">(" + offset_str + ");";
code_ += get_call;
} else if (IsString(type) && field.value.constant != "0") {
// TODO: Add logic to always convert the string to a valid C++ string
// literal by handling string escapes.
std::string escaped;
flatbuffers::EscapeString(field.value.constant.c_str(),
field.value.constant.length(), &escaped,
true, false);
code_ += " auto* ptr = {{FIELD_VALUE}};";
code_ += " if (ptr) return ptr;";
code_ += " static const struct { uint32_t len; const char s[" +
NumToString(field.value.constant.length() + 1) +
"]; } bfbs_string = { " +
NumToString(field.value.constant.length()) + ", \"" +
field.value.constant + "\" };";
NumToString(field.value.constant.length()) + ", " +
escaped + " };";
code_ +=
" return reinterpret_cast<const ::flatbuffers::String "
" *>(&bfbs_string);";
@@ -3417,11 +3419,15 @@ class CppGenerator : public BaseGenerator {
code_.SetValue("CREATE_STRING", "CreateSharedString");
}
if (field->value.constant != "0") {
std::string escaped;
flatbuffers::EscapeString(field->value.constant.c_str(),
field->value.constant.length(), &escaped,
true, false);
code_ +=
" auto {{FIELD_NAME}}__ = {{FIELD_NAME}} ? "
"_fbb.{{CREATE_STRING}}({{FIELD_NAME}}) : "
"_fbb.{{CREATE_STRING}}(\"" +
field->value.constant + "\");";
"_fbb.{{CREATE_STRING}}(" +
escaped + ");";
} else {
code_ +=
" auto {{FIELD_NAME}}__ = {{FIELD_NAME}} ? "

View File

@@ -368,7 +368,17 @@ static std::string GenerateFBS(const Parser& parser,
if (field.value.type.base_type != BASE_TYPE_UTYPE) {
GenComment(field.doc_comment, &schema, nullptr, " ");
schema += " " + field.name + ":" + GenType(field.value.type);
if (field.value.constant != "0") schema += " = " + field.value.constant;
if (field.value.constant != "0") {
if (IsString(field.value.type)) {
std::string escaped;
flatbuffers::EscapeString(field.value.constant.c_str(),
field.value.constant.length(), &escaped,
true, false);
schema += " = " + escaped;
} else {
schema += " = " + field.value.constant;
}
}
std::vector<std::string> attributes;
if (field.IsRequired()) attributes.push_back("required");
if (field.key) attributes.push_back("key");

View File

@@ -2095,12 +2095,12 @@ class PythonGenerator : public BaseGenerator {
const auto field_method = namer_.Method(field);
const auto struct_var = namer_.Variable(struct_def);
const EnumDef& enum_def = *field.value.type.enum_def;
auto union_type = namer_.Type(enum_def);
auto union_fn = namer_.Function(enum_def);
if (parser_.opts.include_dependence_headers) {
union_type = namer_.NamespacedType(enum_def) + "." + union_type;
union_fn = namer_.NamespacedType(enum_def) + "." + union_fn;
}
code += GenIndents(2) + "self." + field_field + " = " + union_type +
code += GenIndents(2) + "self." + field_field + " = " + union_fn +
"Creator(" + "self." + field_field + "Type, " + struct_var + "." +
field_method + "())";
}

View File

@@ -1138,9 +1138,14 @@ class RustGenerator : public BaseGenerator {
// need one for Rust's Default trait so we use empty string. The usual
// value of field.value.constant is `0`, which is non-sensical except
// maybe to c++ (nullptr == 0).
// TODO: Escape strings?
const std::string defval =
field.IsRequired() ? "\"\"" : "\"" + field.value.constant + "\"";
std::string defval;
if (field.IsRequired()) {
defval = "\"\"";
} else {
flatbuffers::EscapeString(field.value.constant.c_str(),
field.value.constant.length(), &defval,
true, false);
}
if (context == kObject) {
return "alloc::string::ToString::to_string(" + defval + ")";
}

View File

@@ -859,7 +859,10 @@ class SwiftGenerator : public BaseGenerator {
break;
case BASE_TYPE_STRING: {
const auto default_string = "\"" + SwiftConstant(field) + "\"";
const auto sc = SwiftConstant(field);
std::string default_string;
flatbuffers::EscapeString(sc.c_str(), sc.length(), &default_string,
true, false);
code_.SetValue("VALUETYPE", GenType(field.value.type));
code_.SetValue("CONSTANT", field.IsDefault() ? default_string : "nil");
code_ += GenReaderMainBody(is_required) + GenOffset() +
@@ -1649,15 +1652,23 @@ class SwiftGenerator : public BaseGenerator {
buffer_constructor.push_back(field_var + " = _t." + field_field);
if (field.IsRequired()) {
std::string default_value =
field.IsDefault() ? SwiftConstant(field) : "";
base_constructor.push_back(field_var + " = \"" + default_value +
"\"");
std::string default_value;
if (field.IsDefault()) {
const auto sc = SwiftConstant(field);
flatbuffers::EscapeString(sc.c_str(), sc.length(), &default_value,
true, false);
} else {
default_value = "\"\"";
}
base_constructor.push_back(field_var + " = " + default_value);
break;
}
if (field.IsDefault() && !field.IsRequired()) {
std::string value = field.IsDefault() ? SwiftConstant(field) : "nil";
base_constructor.push_back(field_var + " = \"" + value + "\"");
const auto sc = SwiftConstant(field);
std::string value;
flatbuffers::EscapeString(sc.c_str(), sc.length(), &value,
true, false);
base_constructor.push_back(field_var + " = " + value);
}
break;
}

View File

@@ -529,7 +529,11 @@ class TsGenerator : public BaseGenerator {
if (value.constant == "0" || value.constant == "null") {
return "null";
} else {
return "\"" + value.constant + "\"";
std::string escaped;
flatbuffers::EscapeString(value.constant.c_str(),
value.constant.length(), &escaped,
true, false);
return escaped;
}
}
case BASE_TYPE_UNION:

View File

@@ -641,7 +641,10 @@ const uint8_t* AddFlatBuffer(std::vector<uint8_t>& flatbuf,
const uint8_t* newbuf, size_t newlen) {
// Align to sizeof(uoffset_t) past sizeof(largest_scalar_t) since we're
// going to chop off the root offset.
if (!newbuf || newlen < sizeof(uoffset_t)) return nullptr;
FLATBUFFERS_ASSERT(newlen >= sizeof(uoffset_t));
auto root = ReadScalar<uoffset_t>(newbuf);
if (root < sizeof(uoffset_t) || root >= newlen) return nullptr;
while ((flatbuf.size() & (sizeof(uoffset_t) - 1)) ||
!(flatbuf.size() & (sizeof(largest_scalar_t) - 1))) {
flatbuf.push_back(0);
@@ -649,7 +652,7 @@ const uint8_t* AddFlatBuffer(std::vector<uint8_t>& flatbuf,
auto insertion_point = static_cast<uoffset_t>(flatbuf.size());
// Insert the entire FlatBuffer minus the root pointer.
flatbuf.insert(flatbuf.end(), newbuf + sizeof(uoffset_t), newbuf + newlen);
auto root_offset = ReadScalar<uoffset_t>(newbuf) - sizeof(uoffset_t);
auto root_offset = root - sizeof(uoffset_t);
return flatbuf.data() + insertion_point + root_offset;
}

View File

@@ -28,6 +28,7 @@ ${test_dir}/../flatc -p -o ${gen_code_path} -I include_test monster_extra.fbs --
${test_dir}/../flatc -p -o ${gen_code_path} -I include_test arrays_test.fbs --gen-object-api --python-typing
${test_dir}/../flatc -p -o ${gen_code_path} -I include_test nested_union_test.fbs --gen-object-api --python-typing --python-decode-obj-api-strings
${test_dir}/../flatc -p -o ${gen_code_path} -I include_test service_test.fbs --grpc --grpc-python-typed-handlers --python-typing --no-python-gen-numpy --gen-onefile
${test_dir}/../flatc -p -o ${gen_code_path} union_name_test.fbs --gen-object-api
# Syntax: run_tests <interpreter> <benchmark vtable dedupes>
# <benchmark read count> <benchmark build count>

View File

@@ -1,5 +1,6 @@
#include "flexbuffers_test.h"
#include <memory>
#include <limits>
#include "flatbuffers/flexbuffers.h"
@@ -13,6 +14,13 @@ namespace tests {
// Shortcuts for the infinity.
static const auto infinity_d = std::numeric_limits<double>::infinity();
static bool IsAligned(const void* ptr, std::size_t alignment) {
void* p = const_cast<void*>(ptr);
std::size_t space = 2 * alignment;
void* q = std::align(alignment, alignment, p, space);
return q != nullptr && p == ptr && space == 2 * alignment;
}
void FlexBuffersTest() {
flexbuffers::Builder slb(512,
flexbuffers::BUILDER_FLAG_SHARE_KEYS_AND_STRINGS);
@@ -29,7 +37,10 @@ void FlexBuffersTest() {
slb.IndirectFloat(4.0f);
auto i_f = slb.LastValue();
uint8_t blob[] = {77};
slb.Blob(blob, 1);
uint32_t aligned_blob[] = {88, 99};
slb.Blob(blob, sizeof blob);
slb.AlignedBlob(aligned_blob, sizeof aligned_blob,
flexbuffers::BIT_WIDTH_32);
slb += false;
slb.ReuseValue(i_f);
});
@@ -62,7 +73,7 @@ void FlexBuffersTest() {
auto map = flexbuffers::GetRoot(slb.GetBuffer()).AsMap();
TEST_EQ(map.size(), 7);
auto vec = map["vec"].AsVector();
TEST_EQ(vec.size(), 6);
TEST_EQ(vec.size(), 7);
TEST_EQ(vec[0].AsInt64(), -100);
TEST_EQ_STR(vec[1].AsString().c_str(), "Fred");
TEST_EQ(vec[1].AsInt64(), 0); // Number parsing failed.
@@ -80,9 +91,15 @@ void FlexBuffersTest() {
auto blob = vec[3].AsBlob();
TEST_EQ(blob.size(), 1);
TEST_EQ(blob.data()[0], 77);
TEST_EQ(vec[4].IsBool(), true); // Check if type is a bool
TEST_EQ(vec[4].AsBool(), false); // Check if value is false
TEST_EQ(vec[5].AsDouble(), 4.0); // This is shared with vec[2] !
TEST_EQ(vec[4].IsBlob(), true);
auto aligned_blob = vec[4].AsBlob();
TEST_EQ(aligned_blob.size(), 8);
TEST_EQ(reinterpret_cast<const uint32_t*>(aligned_blob.data())[0], 88);
TEST_EQ(reinterpret_cast<const uint32_t*>(aligned_blob.data())[1], 99);
TEST_EQ(IsAligned(aligned_blob.data(), 4), true);
TEST_EQ(vec[5].IsBool(), true); // Check if type is a bool
TEST_EQ(vec[5].AsBool(), false); // Check if value is false
TEST_EQ(vec[6].AsDouble(), 4.0); // This is shared with vec[2] !
auto tvec = map["bar"].AsTypedVector();
TEST_EQ(tvec.size(), 3);
TEST_EQ(tvec[2].AsInt8(), 3);
@@ -107,9 +124,9 @@ void FlexBuffersTest() {
TEST_EQ(vec[2].MutateFloat(2.0f), true);
TEST_EQ(vec[2].AsFloat(), 2.0f);
TEST_EQ(vec[2].MutateFloat(3.14159), false); // Double does not fit in float.
TEST_EQ(vec[4].AsBool(), false); // Is false before change
TEST_EQ(vec[4].MutateBool(true), true); // Can change a bool
TEST_EQ(vec[4].AsBool(), true); // Changed bool is now true
TEST_EQ(vec[5].AsBool(), false); // Is false before change
TEST_EQ(vec[5].MutateBool(true), true); // Can change a bool
TEST_EQ(vec[5].AsBool(), true); // Changed bool is now true
// Parse from JSON:
flatbuffers::Parser parser;

View File

@@ -55,6 +55,10 @@ import MyGame.Example.NestedUnion.Color # refers to generated code
import monster_test_generated # the one-file version
import optional_scalars
import optional_scalars.ScalarStuff
import union_name_test.Container # refers to generated code
import union_name_test.Foo # refers to generated code
import union_name_test.Bar # refers to generated code
import union_name_test.my_test_union # refers to generated code
def create_namespace_shortcut(is_onefile):
@@ -3020,6 +3024,50 @@ class TestNestedUnionTables(unittest.TestCase):
)
class TestUnionCreatorNaming(unittest.TestCase):
"""Tests that union creator functions use consistent naming (issue #8843).
Uses a schema with a snake_case union name (my_test_union) to verify that
the generated creator function name matches between definition and call site.
"""
def test_union_creator_pack_unpack(self):
"""Pack and UnPack a table with a non-UpperCamel union name."""
containerT = union_name_test.Container.ContainerT()
containerT.uType = union_name_test.my_test_union.my_test_union.Foo
containerT.u = union_name_test.Foo.FooT()
containerT.u.val = 42
b = flatbuffers.Builder(0)
b.Finish(containerT.Pack(b))
container = union_name_test.Container.Container.GetRootAs(
b.Bytes, b.Head()
)
containerT2 = union_name_test.Container.ContainerT.InitFromObj(container)
self.assertEqual(containerT2.uType, union_name_test.my_test_union.my_test_union.Foo)
self.assertEqual(containerT2.u.val, 42)
def test_union_creator_with_bar(self):
"""Test the other union variant to ensure all branches work."""
containerT = union_name_test.Container.ContainerT()
containerT.uType = union_name_test.my_test_union.my_test_union.Bar
containerT.u = union_name_test.Bar.BarT()
containerT.u.name = "hello"
b = flatbuffers.Builder(0)
b.Finish(containerT.Pack(b))
container = union_name_test.Container.Container.GetRootAs(
b.Bytes, b.Head()
)
containerT2 = union_name_test.Container.ContainerT.InitFromObj(container)
self.assertEqual(containerT2.uType, union_name_test.my_test_union.my_test_union.Bar)
self.assertEqual(containerT2.u.name, b"hello")
class TestBuilderClear(unittest.TestCase):
def test_consistency(self):

View File

@@ -62,16 +62,10 @@ def flatc(
# Execute esbuild with the specified parameters
def esbuild(input, output):
cmd = ["esbuild", input, "--outfile=" + output]
cmd = ["../../node_modules/.bin/esbuild", input, "--outfile=" + output]
cmd += ["--format=cjs", "--bundle", "--external:flatbuffers"]
check_call(cmd)
print("Removing node_modules/ directory...")
shutil.rmtree(Path(tests_path, "node_modules"), ignore_errors=True)
check_call(["npm", "install", "--silent"])
flatc(
options=[
"--ts",
@@ -228,12 +222,12 @@ flatc(
)
print("Running TypeScript Compiler...")
check_call(["tsc"])
check_call(["../../node_modules/.bin/tsc"])
print(
"Running TypeScript Compiler in old node resolution mode for"
" no_import_ext..."
)
check_call(["tsc", "-p", "./tsconfig.node.json"])
check_call(["../../node_modules/.bin/tsc", "-p", "./tsconfig.node.json"])
NODE_CMD = ["node"]

View File

@@ -1,6 +1,6 @@
{
"type": "module",
"dependencies": {
"flatbuffers": "../../"
"flatbuffers": "workspace:*"
}
}

View File

@@ -1,10 +0,0 @@
lockfileVersion: '6.0'
settings:
autoInstallPeers: true
excludeLinksFromLockfile: false
dependencies:
flatbuffers:
specifier: ../../
version: link:../..

21
tests/union_name_test.fbs Normal file
View File

@@ -0,0 +1,21 @@
// Test for union creator naming consistency (issue #8843).
// Uses non-UpperCamel union name to verify that Function() naming
// is used consistently for both definition and call site.
namespace union_name_test;
table Foo {
val: int;
}
table Bar {
name: string;
}
union my_test_union { Foo, Bar }
table Container {
u: my_test_union;
}
root_type Container;

View File

@@ -0,0 +1,93 @@
# automatically generated by the FlatBuffers compiler, do not modify
# namespace: union_name_test
import flatbuffers
from flatbuffers.compat import import_numpy
np = import_numpy()
class Bar(object):
__slots__ = ['_tab']
@classmethod
def GetRootAs(cls, buf, offset=0):
n = flatbuffers.encode.Get(flatbuffers.packer.uoffset, buf, offset)
x = Bar()
x.Init(buf, n + offset)
return x
@classmethod
def GetRootAsBar(cls, buf, offset=0):
"""This method is deprecated. Please switch to GetRootAs."""
return cls.GetRootAs(buf, offset)
# Bar
def Init(self, buf, pos):
self._tab = flatbuffers.table.Table(buf, pos)
# Bar
def Name(self):
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(4))
if o != 0:
return self._tab.String(o + self._tab.Pos)
return None
def BarStart(builder):
builder.StartObject(1)
def Start(builder):
BarStart(builder)
def BarAddName(builder, name):
builder.PrependUOffsetTRelativeSlot(0, flatbuffers.number_types.UOffsetTFlags.py_type(name), 0)
def AddName(builder, name):
BarAddName(builder, name)
def BarEnd(builder):
return builder.EndObject()
def End(builder):
return BarEnd(builder)
class BarT(object):
# BarT
def __init__(
self,
name = None,
):
self.name = name # type: Optional[str]
@classmethod
def InitFromBuf(cls, buf, pos):
bar = Bar()
bar.Init(buf, pos)
return cls.InitFromObj(bar)
@classmethod
def InitFromPackedBuf(cls, buf, pos=0):
n = flatbuffers.encode.Get(flatbuffers.packer.uoffset, buf, pos)
return cls.InitFromBuf(buf, pos+n)
@classmethod
def InitFromObj(cls, bar):
x = BarT()
x._UnPack(bar)
return x
# BarT
def _UnPack(self, bar):
if bar is None:
return
self.name = bar.Name()
# BarT
def Pack(self, builder):
if self.name is not None:
name = builder.CreateString(self.name)
BarStart(builder)
if self.name is not None:
BarAddName(builder, name)
bar = BarEnd(builder)
return bar

View File

@@ -0,0 +1,120 @@
# automatically generated by the FlatBuffers compiler, do not modify
# namespace: union_name_test
import flatbuffers
from flatbuffers.compat import import_numpy
np = import_numpy()
class Container(object):
__slots__ = ['_tab']
@classmethod
def GetRootAs(cls, buf, offset=0):
n = flatbuffers.encode.Get(flatbuffers.packer.uoffset, buf, offset)
x = Container()
x.Init(buf, n + offset)
return x
@classmethod
def GetRootAsContainer(cls, buf, offset=0):
"""This method is deprecated. Please switch to GetRootAs."""
return cls.GetRootAs(buf, offset)
# Container
def Init(self, buf, pos):
self._tab = flatbuffers.table.Table(buf, pos)
# Container
def UType(self):
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(4))
if o != 0:
return self._tab.Get(flatbuffers.number_types.Uint8Flags, o + self._tab.Pos)
return 0
# Container
def U(self):
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(6))
if o != 0:
from flatbuffers.table import Table
obj = Table(bytearray(), 0)
self._tab.Union(obj, o)
return obj
return None
def ContainerStart(builder):
builder.StartObject(2)
def Start(builder):
ContainerStart(builder)
def ContainerAddUType(builder, uType):
builder.PrependUint8Slot(0, uType, 0)
def AddUType(builder, uType):
ContainerAddUType(builder, uType)
def ContainerAddU(builder, u):
builder.PrependUOffsetTRelativeSlot(1, flatbuffers.number_types.UOffsetTFlags.py_type(u), 0)
def AddU(builder, u):
ContainerAddU(builder, u)
def ContainerEnd(builder):
return builder.EndObject()
def End(builder):
return ContainerEnd(builder)
import union_name_test.Bar
import union_name_test.Foo
import union_name_test.my_test_union
try:
from typing import Union
except:
pass
class ContainerT(object):
# ContainerT
def __init__(
self,
uType = 0,
u = None,
):
self.uType = uType # type: int
self.u = u # type: Union[None, 'union_name_test.Foo.FooT', 'union_name_test.Bar.BarT']
@classmethod
def InitFromBuf(cls, buf, pos):
container = Container()
container.Init(buf, pos)
return cls.InitFromObj(container)
@classmethod
def InitFromPackedBuf(cls, buf, pos=0):
n = flatbuffers.encode.Get(flatbuffers.packer.uoffset, buf, pos)
return cls.InitFromBuf(buf, pos+n)
@classmethod
def InitFromObj(cls, container):
x = ContainerT()
x._UnPack(container)
return x
# ContainerT
def _UnPack(self, container):
if container is None:
return
self.uType = container.UType()
self.u = union_name_test.my_test_union.MyTestUnionCreator(self.uType, container.U())
# ContainerT
def Pack(self, builder):
if self.u is not None:
u = self.u.Pack(builder)
ContainerStart(builder)
ContainerAddUType(builder, self.uType)
if self.u is not None:
ContainerAddU(builder, u)
container = ContainerEnd(builder)
return container

View File

@@ -0,0 +1,90 @@
# automatically generated by the FlatBuffers compiler, do not modify
# namespace: union_name_test
import flatbuffers
from flatbuffers.compat import import_numpy
np = import_numpy()
class Foo(object):
__slots__ = ['_tab']
@classmethod
def GetRootAs(cls, buf, offset=0):
n = flatbuffers.encode.Get(flatbuffers.packer.uoffset, buf, offset)
x = Foo()
x.Init(buf, n + offset)
return x
@classmethod
def GetRootAsFoo(cls, buf, offset=0):
"""This method is deprecated. Please switch to GetRootAs."""
return cls.GetRootAs(buf, offset)
# Foo
def Init(self, buf, pos):
self._tab = flatbuffers.table.Table(buf, pos)
# Foo
def Val(self):
o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(4))
if o != 0:
return self._tab.Get(flatbuffers.number_types.Int32Flags, o + self._tab.Pos)
return 0
def FooStart(builder):
builder.StartObject(1)
def Start(builder):
FooStart(builder)
def FooAddVal(builder, val):
builder.PrependInt32Slot(0, val, 0)
def AddVal(builder, val):
FooAddVal(builder, val)
def FooEnd(builder):
return builder.EndObject()
def End(builder):
return FooEnd(builder)
class FooT(object):
# FooT
def __init__(
self,
val = 0,
):
self.val = val # type: int
@classmethod
def InitFromBuf(cls, buf, pos):
foo = Foo()
foo.Init(buf, pos)
return cls.InitFromObj(foo)
@classmethod
def InitFromPackedBuf(cls, buf, pos=0):
n = flatbuffers.encode.Get(flatbuffers.packer.uoffset, buf, pos)
return cls.InitFromBuf(buf, pos+n)
@classmethod
def InitFromObj(cls, foo):
x = FooT()
x._UnPack(foo)
return x
# FooT
def _UnPack(self, foo):
if foo is None:
return
self.val = foo.Val()
# FooT
def Pack(self, builder):
FooStart(builder)
FooAddVal(builder, self.val)
foo = FooEnd(builder)
return foo

View File

View File

@@ -0,0 +1,20 @@
# automatically generated by the FlatBuffers compiler, do not modify
# namespace: union_name_test
class my_test_union(object):
NONE = 0
Foo = 1
Bar = 2
def MyTestUnionCreator(unionType, table):
from flatbuffers.table import Table
if not isinstance(table, Table):
return None
if unionType == my_test_union.Foo:
import union_name_test.Foo
return union_name_test.Foo.FooT.InitFromBuf(table.Bytes, table.Pos)
if unionType == my_test_union.Bar:
import union_name_test.Bar
return union_name_test.Bar.BarT.InitFromBuf(table.Bytes, table.Pos)
return None