Compare commits

...

14 Commits

Author SHA1 Message Date
dependabot[bot]
9b68adde0f Bump the npm_and_yarn group across 2 directories with 3 updates
Bumps the npm_and_yarn group with 1 update in the / directory: [brace-expansion](https://github.com/juliangruber/brace-expansion).
Bumps the npm_and_yarn group with 1 update in the /tests/ts/bazel_repository_test_dir directory: [lodash](https://github.com/lodash/lodash).


Updates `brace-expansion` from 1.1.12 to 1.1.13
- [Release notes](https://github.com/juliangruber/brace-expansion/releases)
- [Commits](https://github.com/juliangruber/brace-expansion/compare/v1.1.12...v1.1.13)

Updates `picomatch` from 2.3.1 to 4.0.4
- [Release notes](https://github.com/micromatch/picomatch/releases)
- [Changelog](https://github.com/micromatch/picomatch/blob/master/CHANGELOG.md)
- [Commits](https://github.com/micromatch/picomatch/compare/2.3.1...4.0.4)

Updates `lodash` from 4.17.23 to 4.18.1
- [Release notes](https://github.com/lodash/lodash/releases)
- [Commits](https://github.com/lodash/lodash/compare/4.17.23...4.18.1)

---
updated-dependencies:
- dependency-name: brace-expansion
  dependency-version: 1.1.13
  dependency-type: indirect
  dependency-group: npm_and_yarn
- dependency-name: picomatch
  dependency-version: 4.0.4
  dependency-type: indirect
  dependency-group: npm_and_yarn
- dependency-name: lodash
  dependency-version: 4.18.1
  dependency-type: direct:development
  dependency-group: npm_and_yarn
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-04-10 03:25:46 +00:00
Felix
e223d69b36 [Python] Extend GRPC Typing (#9007)
Extend function calls with optional type infos for checking
and discovering.

e838ba8a71/src/python/grpcio/grpc/__init__.py (L680)
2026-04-03 14:12:08 +00:00
Tulgaaaaaaaa
05cc7a2eff fix: correct operator precedence in ForAllFields reverse iteration (#8991)
* fix: correct operator precedence in ForAllFields reverse iteration

The expression `size() - i + 1` evaluates as `(size() - i) + 1` due to
left-to-right associativity, producing an out-of-bounds index when
reverse=true. For a vector of size N, the first iteration (i=0) accesses
index N+1, which is 2 past the last valid index.

Changed to `size() - (i + 1)` to match the correct implementation
already present in bfbs_gen.h:192.

Bug: CWE-125 (Out-of-bounds Read), CWE-783 (Operator Precedence Error)

* test: add ForAllFieldsReverseTest for reverse iteration correctness

Verify that ForAllFields with reverse=true iterates fields in
descending ID order. Tests both Stat (3 fields) and Monster
(many fields with non-sequential definition order) tables.

---------

Co-authored-by: Tulgaa <tulgaa.kek@gmail.com>
2026-04-02 10:14:27 +00:00
Noam ismach moshe
8a12183c3b Fix out-of-bounds vector access in StructDef::Deserialize (#8988)
* Fix out-of-bounds vector access in StructDef::Deserialize

* Fix syntax: use error_ instead of error()
2026-04-02 08:03:03 +00:00
Renzo
21b706b62d fix: swapped argument order in new_inconsistent_union calls (#9001) (#9010) 2026-04-02 07:05:58 +00:00
Tomasz Andrzejak
c5f151ab33 Add fallible try_* API for rust FlatBufferBuilder (#8918)
* Add fallible try_* API for FlatBufferBuilder

This is to support error propagation from Allocator trait. The Allocator
grow_downwards() method returns Result<(), Self::Error>, but
FlatBufferBuilder panics via .expect() when allocation fails instead of
propagating the error.

* Add rust fallible API docs
2026-04-02 06:49:51 +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
37 changed files with 2044 additions and 1131 deletions

View File

@@ -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

@@ -97,6 +97,61 @@ convenient accessors for all fields, e.g. `hp()`, `mana()`, etc:
*Note: That we never stored a `mana` value, so it will return the default.*
## Fallible API and Custom Allocators
Every `FlatBufferBuilder` method that may allocate has a `try_*` counterpart
(e.g. `try_create_string`, `try_push`, `try_finish`) that returns
`Result<T, A::Error>` instead of panicking. This is useful when allocation
failures must be handled gracefully: for example in `no_std` environments or
with fixed-capacity buffers.
The existing panicking methods are unchanged and remain the simplest option
when using the default allocator.
#### Custom allocators
Implement the `Allocator` trait and pass it to `FlatBufferBuilder::new_in()`:
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~{.rs}
use flatbuffers::{Allocator, FlatBufferBuilder};
struct MyAllocator { /* ... */ }
unsafe impl Allocator for MyAllocator {
type Error = MyError;
fn grow_downwards(&mut self) -> Result<(), Self::Error> { /* ... */ }
fn len(&self) -> usize { /* ... */ }
}
let alloc = MyAllocator::new(/* ... */);
let mut builder = FlatBufferBuilder::new_in(alloc);
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The built-in `DefaultAllocator` uses `Vec<u8>` and sets `Error = Infallible`,
so the `try_*` methods on a default builder can never fail.
#### Example: building a buffer with error propagation
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~{.rs}
fn build<A: flatbuffers::Allocator>(
builder: &mut FlatBufferBuilder<A>,
) -> Result<(), A::Error> {
let name = builder.try_create_string("Orc")?;
let inventory = builder.try_create_vector(&[0u8, 1, 2, 3, 4])?;
let table_start = builder.start_table();
builder.try_push_slot_always(Monster::VT_NAME, name)?;
builder.try_push_slot_always(Monster::VT_INVENTORY, inventory)?;
builder.try_push_slot(Monster::VT_HP, 80i16, 100)?;
let root = builder.try_end_table(table_start)?;
builder.try_finish(root, None)?;
Ok(())
}
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
See the `FlatBufferBuilder` rustdoc for the full list of `try_*` methods.
## Direct memory access
As you can see from the above examples, all elements in a buffer are

View File

@@ -172,6 +172,7 @@ class StubGenerator : public BaseGenerator {
<< " def __init__(self, channel: grpc.Channel) -> None: ...\n";
for (const RPCCall* method : service->calls.vec) {
imports->Import("typing");
std::string request = "bytes";
std::string response = "bytes";
@@ -183,14 +184,22 @@ class StubGenerator : public BaseGenerator {
imports->Import(ModuleFor(method->response), response);
}
ss << " def " << method->name << "(self, ";
ss << " def " << method->name << "(\n";
ss << " self,\n";
if (ClientStreaming(method)) {
imports->Import("typing");
ss << "request_iterator: typing.Iterator[" << request << "]";
ss << " request_iterator: typing.Iterator[" << request << "]\n";
} else {
ss << "request: " << request;
ss << " request: " << request << ",\n";
}
ss << ") -> ";
ss << " timeout: float | None = None,\n";
// https://github.com/python/typeshed/blob/ccf9411fb1f5bee2a8e3d278889de17a08f7bbe3/stubs/grpcio/grpc/__init__.pyi#L37
ss << " metadata: typing.Sequence[tuple[str, typing.Union[str, bytes]]] | None = None,\n";
ss << " credentials: grpc.CallCredentials | None = None,\n";
ss << " wait_for_ready: bool | None = None,\n";
ss << " compression: grpc.Compression | None = None\n";
ss << " ) -> ";
if (ServerStreaming(method)) {
imports->Import("typing");
ss << "typing.Iterator[" << response << "]";

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(

1612
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

2
pnpm-workspace.yaml Normal file
View File

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

View File

@@ -337,22 +337,43 @@ impl<'fbb, A: Allocator> FlatBufferBuilder<'fbb, A> {
self.strings_pool.clear();
}
/// Push a Push'able value onto the front of the in-progress data.
///
/// This function uses traits to provide a unified API for writing
/// scalars, tables, vectors, and WIPOffsets.
/// Fallible version of [`push`](Self::push).
#[inline]
pub fn push<P: Push>(&mut self, x: P) -> WIPOffset<P::Output> {
pub fn try_push<P: Push>(&mut self, x: P) -> Result<WIPOffset<P::Output>, A::Error> {
let sz = P::size();
self.align(sz, P::alignment());
self.make_space(sz);
self.align(sz, P::alignment())?;
self.make_space(sz)?;
{
let (dst, rest) = self.allocator[self.head.range_to_end()].split_at_mut(sz);
// Safety:
// Called make_space above
unsafe { x.push(dst, rest.len()) };
}
WIPOffset::new(self.used_space() as UOffsetT)
Ok(WIPOffset::new(self.used_space() as UOffsetT))
}
/// Push a Push'able value onto the front of the in-progress data.
///
/// This function uses traits to provide a unified API for writing
/// scalars, tables, vectors, and WIPOffsets.
#[inline]
pub fn push<P: Push>(&mut self, x: P) -> WIPOffset<P::Output> {
self.try_push(x).expect("Flatbuffer allocation failure")
}
/// Fallible version of [`push_slot`](Self::push_slot).
#[inline]
pub fn try_push_slot<X: Push + PartialEq>(
&mut self,
slotoff: VOffsetT,
x: X,
default: X,
) -> Result<(), A::Error> {
self.assert_nested("push_slot");
if x != default || self.force_defaults {
self.try_push_slot_always(slotoff, x)?;
}
Ok(())
}
/// Push a Push'able value onto the front of the in-progress data, and
@@ -360,19 +381,29 @@ impl<'fbb, A: Allocator> FlatBufferBuilder<'fbb, A> {
/// the default, then this is a no-op.
#[inline]
pub fn push_slot<X: Push + PartialEq>(&mut self, slotoff: VOffsetT, x: X, default: X) {
self.assert_nested("push_slot");
if x != default || self.force_defaults {
self.push_slot_always(slotoff, x);
}
self.try_push_slot(slotoff, x, default)
.expect("Flatbuffer allocation failure")
}
/// Fallible version of [`push_slot_always`](Self::push_slot_always).
#[inline]
pub fn try_push_slot_always<X: Push>(
&mut self,
slotoff: VOffsetT,
x: X,
) -> Result<(), A::Error> {
self.assert_nested("push_slot_always");
let off = self.try_push(x)?;
self.track_field(slotoff, off.value());
Ok(())
}
/// Push a Push'able value onto the front of the in-progress data, and
/// store a reference to it in the in-progress vtable.
#[inline]
pub fn push_slot_always<X: Push>(&mut self, slotoff: VOffsetT, x: X) {
self.assert_nested("push_slot_always");
let off = self.push(x);
self.track_field(slotoff, off.value());
self.try_push_slot_always(slotoff, x)
.expect("Flatbuffer allocation failure")
}
/// Retrieve the number of vtables that have been serialized into the
@@ -397,6 +428,22 @@ impl<'fbb, A: Allocator> FlatBufferBuilder<'fbb, A> {
WIPOffset::new(self.used_space() as UOffsetT)
}
/// Fallible version of [`end_table`](Self::end_table).
#[inline]
pub fn try_end_table(
&mut self,
off: WIPOffset<TableUnfinishedWIPOffset>,
) -> Result<WIPOffset<TableFinishedWIPOffset>, A::Error> {
self.assert_nested("end_table");
let o = self.write_vtable(off)?;
self.nested = false;
self.field_locs.clear();
Ok(WIPOffset::new(o.value()))
}
/// End a Table write.
///
/// Asserts that the builder is in a nested state.
@@ -405,14 +452,19 @@ impl<'fbb, A: Allocator> FlatBufferBuilder<'fbb, A> {
&mut self,
off: WIPOffset<TableUnfinishedWIPOffset>,
) -> WIPOffset<TableFinishedWIPOffset> {
self.assert_nested("end_table");
self.try_end_table(off)
.expect("Flatbuffer allocation failure")
}
let o = self.write_vtable(off);
self.nested = false;
self.field_locs.clear();
WIPOffset::new(o.value())
/// Fallible version of [`start_vector`](Self::start_vector).
#[inline]
pub fn try_start_vector<T: Push>(&mut self, num_items: usize) -> Result<(), A::Error> {
self.assert_not_nested(
"start_vector can not be called when a table or vector is under construction",
);
self.align(num_items * T::size(), T::alignment().max_of(SIZE_UOFFSET))?;
self.nested = true;
Ok(())
}
/// Start a Vector write.
@@ -424,11 +476,20 @@ impl<'fbb, A: Allocator> FlatBufferBuilder<'fbb, A> {
/// function will want to use `push` to add values.
#[inline]
pub fn start_vector<T: Push>(&mut self, num_items: usize) {
self.assert_not_nested(
"start_vector can not be called when a table or vector is under construction",
);
self.nested = true;
self.align(num_items * T::size(), T::alignment().max_of(SIZE_UOFFSET));
self.try_start_vector::<T>(num_items)
.expect("Flatbuffer allocation failure")
}
/// Fallible version of [`end_vector`](Self::end_vector).
#[inline]
pub fn try_end_vector<T: Push>(
&mut self,
num_elems: usize,
) -> Result<WIPOffset<Vector<'fbb, T>>, A::Error> {
self.assert_nested("end_vector");
let o = self.try_push::<UOffsetT>(num_elems as UOffsetT)?;
self.nested = false;
Ok(WIPOffset::new(o.value()))
}
/// End a Vector write.
@@ -439,10 +500,31 @@ impl<'fbb, A: Allocator> FlatBufferBuilder<'fbb, A> {
/// Asserts that the builder is in a nested state.
#[inline]
pub fn end_vector<T: Push>(&mut self, num_elems: usize) -> WIPOffset<Vector<'fbb, T>> {
self.assert_nested("end_vector");
self.nested = false;
let o = self.push::<UOffsetT>(num_elems as UOffsetT);
WIPOffset::new(o.value())
self.try_end_vector::<T>(num_elems)
.expect("Flatbuffer allocation failure")
}
/// Fallible version of [`create_shared_string`](Self::create_shared_string).
///
/// Uses a HashMap to track previously written strings, providing O(1)
/// amortized lookup and insertion.
#[cfg(feature = "std")]
#[inline]
pub fn try_create_shared_string<'a: 'b, 'b>(
&'a mut self,
s: &'b str,
) -> Result<WIPOffset<&'fbb str>, A::Error> {
self.assert_not_nested(
"create_shared_string can not be called when a table or vector is under construction",
);
if let Some(&offset) = self.strings_pool.get(s) {
return Ok(offset);
}
let address = WIPOffset::new(self.try_create_byte_string(s.as_bytes())?.value());
self.strings_pool.insert(s.to_owned(), address);
Ok(address)
}
/// Create a utf8 string, and de-duplicate if already created.
@@ -452,26 +534,20 @@ impl<'fbb, A: Allocator> FlatBufferBuilder<'fbb, A> {
#[cfg(feature = "std")]
#[inline]
pub fn create_shared_string<'a: 'b, 'b>(&'a mut self, s: &'b str) -> WIPOffset<&'fbb str> {
self.assert_not_nested(
"create_shared_string can not be called when a table or vector is under construction",
);
if let Some(&offset) = self.strings_pool.get(s) {
return offset;
}
let address = WIPOffset::new(self.create_byte_string(s.as_bytes()).value());
self.strings_pool.insert(s.to_owned(), address);
address
self.try_create_shared_string(s)
.expect("Flatbuffer allocation failure")
}
/// Create a utf8 string, and de-duplicate if already created.
/// Fallible version of [`create_shared_string`](Self::create_shared_string).
///
/// Uses a sorted Vec with binary search to track previously written
/// strings when in `no_std` mode.
#[cfg(not(feature = "std"))]
#[inline]
pub fn create_shared_string<'a: 'b, 'b>(&'a mut self, s: &'b str) -> WIPOffset<&'fbb str> {
pub fn try_create_shared_string<'a: 'b, 'b>(
&'a mut self,
s: &'b str,
) -> Result<WIPOffset<&'fbb str>, A::Error> {
self.assert_not_nested(
"create_shared_string can not be called when a table or vector is under construction",
);
@@ -494,52 +570,83 @@ impl<'fbb, A: Allocator> FlatBufferBuilder<'fbb, A> {
});
match found {
Ok(index) => self.strings_pool[index],
Ok(index) => Ok(self.strings_pool[index]),
Err(index) => {
let address = WIPOffset::new(self.create_byte_string(s.as_bytes()).value());
let address =
WIPOffset::new(self.try_create_byte_string(s.as_bytes())?.value());
self.strings_pool.insert(index, address);
address
Ok(address)
}
}
}
/// Create a utf8 string, and de-duplicate if already created.
///
/// Uses a sorted Vec with binary search to track previously written
/// strings when in `no_std` mode.
#[cfg(not(feature = "std"))]
#[inline]
pub fn create_shared_string<'a: 'b, 'b>(&'a mut self, s: &'b str) -> WIPOffset<&'fbb str> {
self.try_create_shared_string(s)
.expect("Flatbuffer allocation failure")
}
/// Fallible version of [`create_string`](Self::create_string).
#[inline]
pub fn try_create_string<'a: 'b, 'b>(
&'a mut self,
s: &'b str,
) -> Result<WIPOffset<&'fbb str>, A::Error> {
self.assert_not_nested(
"create_string can not be called when a table or vector is under construction",
);
Ok(WIPOffset::new(
self.try_create_byte_string(s.as_bytes())?.value(),
))
}
/// Create a utf8 string.
///
/// The wire format represents this as a zero-terminated byte vector.
#[inline]
pub fn create_string<'a: 'b, 'b>(&'a mut self, s: &'b str) -> WIPOffset<&'fbb str> {
self.try_create_string(s)
.expect("Flatbuffer allocation failure")
}
/// Fallible version of [`create_byte_string`](Self::create_byte_string).
#[inline]
pub fn try_create_byte_string(
&mut self,
data: &[u8],
) -> Result<WIPOffset<&'fbb [u8]>, A::Error> {
self.assert_not_nested(
"create_string can not be called when a table or vector is under construction",
"create_byte_string can not be called when a table or vector is under construction",
);
WIPOffset::new(self.create_byte_string(s.as_bytes()).value())
self.align(data.len() + 1, PushAlignment::new(SIZE_UOFFSET))?;
self.try_push(0u8)?;
self.push_bytes_unprefixed(data)?;
self.try_push(data.len() as UOffsetT)?;
Ok(WIPOffset::new(self.used_space() as UOffsetT))
}
/// Create a zero-terminated byte vector.
#[inline]
pub fn create_byte_string(&mut self, data: &[u8]) -> WIPOffset<&'fbb [u8]> {
self.assert_not_nested(
"create_byte_string can not be called when a table or vector is under construction",
);
self.align(data.len() + 1, PushAlignment::new(SIZE_UOFFSET));
self.push(0u8);
self.push_bytes_unprefixed(data);
self.push(data.len() as UOffsetT);
WIPOffset::new(self.used_space() as UOffsetT)
self.try_create_byte_string(data)
.expect("Flatbuffer allocation failure")
}
/// Create a vector of Push-able objects.
///
/// Speed-sensitive users may wish to reduce memory usage by creating the
/// vector manually: use `start_vector`, `push`, and `end_vector`.
/// Fallible version of [`create_vector`](Self::create_vector).
#[inline]
pub fn create_vector<'a: 'b, 'b, T: Push + 'b>(
pub fn try_create_vector<'a: 'b, 'b, T: Push + 'b>(
&'a mut self,
items: &'b [T],
) -> WIPOffset<Vector<'fbb, T::Output>> {
) -> Result<WIPOffset<Vector<'fbb, T::Output>>, A::Error> {
let elem_size = T::size();
let slice_size = items.len() * elem_size;
self.align(slice_size, T::alignment().max_of(SIZE_UOFFSET));
self.ensure_capacity(slice_size + UOffsetT::size());
self.align(slice_size, T::alignment().max_of(SIZE_UOFFSET))?;
self.ensure_capacity(slice_size + UOffsetT::size())?;
self.head -= slice_size;
let mut written_len = self.head.distance_to_end();
@@ -553,7 +660,38 @@ impl<'fbb, A: Allocator> FlatBufferBuilder<'fbb, A> {
unsafe { item.push(out, written_len) };
}
WIPOffset::new(self.push::<UOffsetT>(items.len() as UOffsetT).value())
Ok(WIPOffset::new(
self.try_push::<UOffsetT>(items.len() as UOffsetT)?.value(),
))
}
/// Create a vector of Push-able objects.
///
/// Speed-sensitive users may wish to reduce memory usage by creating the
/// vector manually: use `start_vector`, `push`, and `end_vector`.
#[inline]
pub fn create_vector<'a: 'b, 'b, T: Push + 'b>(
&'a mut self,
items: &'b [T],
) -> WIPOffset<Vector<'fbb, T::Output>> {
self.try_create_vector(items)
.expect("Flatbuffer allocation failure")
}
/// Fallible version of [`create_vector_from_iter`](Self::create_vector_from_iter).
#[inline]
pub fn try_create_vector_from_iter<T: Push>(
&mut self,
items: impl ExactSizeIterator<Item = T> + DoubleEndedIterator,
) -> Result<WIPOffset<Vector<'fbb, T::Output>>, A::Error> {
let elem_size = T::size();
self.align(items.len() * elem_size, T::alignment().max_of(SIZE_UOFFSET))?;
let mut actual = 0;
for item in items.rev() {
self.try_push(item)?;
actual += 1;
}
Ok(WIPOffset::new(self.try_push::<UOffsetT>(actual)?.value()))
}
/// Create a vector of Push-able objects.
@@ -565,14 +703,8 @@ impl<'fbb, A: Allocator> FlatBufferBuilder<'fbb, A> {
&mut self,
items: impl ExactSizeIterator<Item = T> + DoubleEndedIterator,
) -> WIPOffset<Vector<'fbb, T::Output>> {
let elem_size = T::size();
self.align(items.len() * elem_size, T::alignment().max_of(SIZE_UOFFSET));
let mut actual = 0;
for item in items.rev() {
self.push(item);
actual += 1;
}
WIPOffset::new(self.push::<UOffsetT>(actual).value())
self.try_create_vector_from_iter(items)
.expect("Flatbuffer allocation failure")
}
/// Set whether default values are stored.
@@ -635,13 +767,34 @@ impl<'fbb, A: Allocator> FlatBufferBuilder<'fbb, A> {
assert!(o != 0, "missing required field {}", assert_msg_name);
}
/// Fallible version of [`finish_size_prefixed`](Self::finish_size_prefixed).
#[inline]
pub fn try_finish_size_prefixed<T>(
&mut self,
root: WIPOffset<T>,
file_identifier: Option<&str>,
) -> Result<(), A::Error> {
self.finish_with_opts(root, file_identifier, true)
}
/// Finalize the FlatBuffer by: aligning it, pushing an optional file
/// identifier on to it, pushing a size prefix on to it, and marking the
/// internal state of the FlatBufferBuilder as `finished`. Afterwards,
/// users can call `finished_data` to get the resulting data.
#[inline]
pub fn finish_size_prefixed<T>(&mut self, root: WIPOffset<T>, file_identifier: Option<&str>) {
self.finish_with_opts(root, file_identifier, true);
self.try_finish_size_prefixed(root, file_identifier)
.expect("Flatbuffer allocation failure")
}
/// Fallible version of [`finish`](Self::finish).
#[inline]
pub fn try_finish<T>(
&mut self,
root: WIPOffset<T>,
file_identifier: Option<&str>,
) -> Result<(), A::Error> {
self.finish_with_opts(root, file_identifier, false)
}
/// Finalize the FlatBuffer by: aligning it, pushing an optional file
@@ -650,7 +803,14 @@ impl<'fbb, A: Allocator> FlatBufferBuilder<'fbb, A> {
/// `finished_data` to get the resulting data.
#[inline]
pub fn finish<T>(&mut self, root: WIPOffset<T>, file_identifier: Option<&str>) {
self.finish_with_opts(root, file_identifier, false);
self.try_finish(root, file_identifier)
.expect("Flatbuffer allocation failure")
}
/// Fallible version of [`finish_minimal`](Self::finish_minimal).
#[inline]
pub fn try_finish_minimal<T>(&mut self, root: WIPOffset<T>) -> Result<(), A::Error> {
self.finish_with_opts(root, None, false)
}
/// Finalize the FlatBuffer by: aligning it and marking the internal state
@@ -658,7 +818,8 @@ impl<'fbb, A: Allocator> FlatBufferBuilder<'fbb, A> {
/// `finished_data` to get the resulting data.
#[inline]
pub fn finish_minimal<T>(&mut self, root: WIPOffset<T>) {
self.finish_with_opts(root, None, false);
self.try_finish_minimal(root)
.expect("Flatbuffer allocation failure")
}
#[inline]
@@ -676,13 +837,13 @@ impl<'fbb, A: Allocator> FlatBufferBuilder<'fbb, A> {
fn write_vtable(
&mut self,
table_tail_revloc: WIPOffset<TableUnfinishedWIPOffset>,
) -> WIPOffset<VTableWIPOffset> {
) -> Result<WIPOffset<VTableWIPOffset>, A::Error> {
self.assert_nested("write_vtable");
// Write the vtable offset, which is the start of any Table.
// We fill its value later.
let object_revloc_to_vtable: WIPOffset<VTableWIPOffset> =
WIPOffset::new(self.push::<UOffsetT>(0xF0F0_F0F0).value());
WIPOffset::new(self.try_push::<UOffsetT>(0xF0F0_F0F0)?.value());
// Layout of the data this function will create when a new vtable is
// needed.
@@ -725,7 +886,7 @@ impl<'fbb, A: Allocator> FlatBufferBuilder<'fbb, A> {
// fill the WIP vtable with zeros:
let vtable_byte_len = get_vtable_byte_len(&self.field_locs);
self.make_space(vtable_byte_len);
self.make_space(vtable_byte_len)?;
// compute the length of the table (not vtable!) in bytes:
let table_object_size = object_revloc_to_vtable.value() - table_tail_revloc.value();
@@ -750,13 +911,15 @@ impl<'fbb, A: Allocator> FlatBufferBuilder<'fbb, A> {
}
}
let new_vt_bytes = &self.allocator[vt_start_pos.range_to(vt_end_pos)];
let found = self.written_vtable_revpos.binary_search_by(|old_vtable_revpos: &UOffsetT| {
let old_vtable_pos = self.allocator.len() - *old_vtable_revpos as usize;
// Safety:
// Already written vtables are valid by construction
let old_vtable = unsafe { VTable::init(&self.allocator, old_vtable_pos) };
new_vt_bytes.cmp(old_vtable.as_bytes())
});
let found = self
.written_vtable_revpos
.binary_search_by(|old_vtable_revpos: &UOffsetT| {
let old_vtable_pos = self.allocator.len() - *old_vtable_revpos as usize;
// Safety:
// Already written vtables are valid by construction
let old_vtable = unsafe { VTable::init(&self.allocator, old_vtable_pos) };
new_vt_bytes.cmp(old_vtable.as_bytes())
});
let final_vtable_revpos = match found {
Ok(i) => {
// The new vtable is a duplicate so clear it.
@@ -794,17 +957,18 @@ impl<'fbb, A: Allocator> FlatBufferBuilder<'fbb, A> {
self.field_locs.clear();
object_revloc_to_vtable
Ok(object_revloc_to_vtable)
}
// Only call this when you know it is safe to double the size of the buffer.
#[inline]
fn grow_allocator(&mut self) {
fn grow_allocator(&mut self) -> Result<(), A::Error> {
let starting_active_size = self.used_space();
self.allocator.grow_downwards().expect("Flatbuffer allocation failure");
self.allocator.grow_downwards()?;
let ending_active_size = self.used_space();
debug_assert_eq!(starting_active_size, ending_active_size);
Ok(())
}
// with or without a size prefix changes how we load the data, so finish*
@@ -814,7 +978,7 @@ impl<'fbb, A: Allocator> FlatBufferBuilder<'fbb, A> {
root: WIPOffset<T>,
file_identifier: Option<&str>,
size_prefixed: bool,
) {
) -> Result<(), A::Error> {
self.assert_not_finished("buffer cannot be finished when it is already finished");
self.assert_not_nested(
"buffer cannot be finished when a table or vector is under construction",
@@ -827,34 +991,40 @@ impl<'fbb, A: Allocator> FlatBufferBuilder<'fbb, A> {
// for the size prefix:
let b = if size_prefixed { SIZE_UOFFSET } else { 0 };
// for the file identifier (a string that is not zero-terminated):
let c = if file_identifier.is_some() { FILE_IDENTIFIER_LENGTH } else { 0 };
let c = if file_identifier.is_some() {
FILE_IDENTIFIER_LENGTH
} else {
0
};
a + b + c
};
{
let ma = PushAlignment::new(self.min_align);
self.align(to_align, ma);
self.align(to_align, ma)?;
}
if let Some(ident) = file_identifier {
debug_assert_eq!(ident.len(), FILE_IDENTIFIER_LENGTH);
self.push_bytes_unprefixed(ident.as_bytes());
self.push_bytes_unprefixed(ident.as_bytes())?;
}
self.push(root);
self.try_push(root)?;
if size_prefixed {
let sz = self.used_space() as UOffsetT;
self.push::<UOffsetT>(sz);
self.try_push::<UOffsetT>(sz)?;
}
self.finished = true;
Ok(())
}
#[inline]
fn align(&mut self, len: usize, alignment: PushAlignment) {
fn align(&mut self, len: usize, alignment: PushAlignment) -> Result<(), A::Error> {
self.track_min_align(alignment.value());
let s = self.used_space() as usize;
self.make_space(padding_bytes(s + len, alignment.value()));
self.make_space(padding_bytes(s + len, alignment.value()))?;
Ok(())
}
#[inline]
@@ -863,31 +1033,34 @@ impl<'fbb, A: Allocator> FlatBufferBuilder<'fbb, A> {
}
#[inline]
fn push_bytes_unprefixed(&mut self, x: &[u8]) -> UOffsetT {
let n = self.make_space(x.len());
fn push_bytes_unprefixed(&mut self, x: &[u8]) -> Result<UOffsetT, A::Error> {
let n = self.make_space(x.len())?;
self.allocator[n.range_to(n + x.len())].copy_from_slice(x);
n.to_forward_index(&self.allocator) as UOffsetT
Ok(n.to_forward_index(&self.allocator) as UOffsetT)
}
#[inline]
fn make_space(&mut self, want: usize) -> ReverseIndex {
self.ensure_capacity(want);
fn make_space(&mut self, want: usize) -> Result<ReverseIndex, A::Error> {
self.ensure_capacity(want)?;
self.head -= want;
self.head
Ok(self.head)
}
#[inline]
fn ensure_capacity(&mut self, want: usize) -> usize {
fn ensure_capacity(&mut self, want: usize) -> Result<usize, A::Error> {
if self.unused_ready_space() >= want {
return want;
return Ok(want);
}
assert!(want <= FLATBUFFERS_MAX_BUFFER_SIZE, "cannot grow buffer beyond 2 gigabytes");
assert!(
want <= FLATBUFFERS_MAX_BUFFER_SIZE,
"cannot grow buffer beyond 2 gigabytes"
);
while self.unused_ready_space() < want {
self.grow_allocator();
self.grow_allocator()?;
}
want
Ok(want)
}
#[inline]
fn unused_ready_space(&self) -> usize {
@@ -1129,4 +1302,111 @@ mod tests {
allocs
);
}
/// A test allocator that fails after a specified number of grow operations.
struct FailingAllocator {
inner: DefaultAllocator,
grows_remaining: usize,
}
#[derive(Debug, Clone, PartialEq, Eq)]
struct AllocationError;
impl core::fmt::Display for AllocationError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, "allocation failed")
}
}
impl FailingAllocator {
fn new(initial_size: usize, max_grows: usize) -> Self {
Self {
inner: DefaultAllocator::from_vec(vec![0u8; initial_size]),
grows_remaining: max_grows,
}
}
}
impl Deref for FailingAllocator {
type Target = [u8];
fn deref(&self) -> &Self::Target {
&self.inner
}
}
impl DerefMut for FailingAllocator {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.inner
}
}
unsafe impl Allocator for FailingAllocator {
type Error = AllocationError;
fn grow_downwards(&mut self) -> Result<(), Self::Error> {
if self.grows_remaining == 0 {
return Err(AllocationError);
}
self.grows_remaining -= 1;
// DefaultAllocator returns Infallible, so unwrap is safe
self.inner.grow_downwards().unwrap();
Ok(())
}
fn len(&self) -> usize {
self.inner.len()
}
}
#[test]
fn try_push_propagates_allocation_error() {
let allocator = FailingAllocator::new(1, 0);
let mut builder = FlatBufferBuilder::new_in(allocator);
let result = builder.try_push::<u64>(0x1234567890ABCDEF);
assert!(result.is_err());
}
#[test]
fn try_create_string_propagates_allocation_error() {
let allocator = FailingAllocator::new(1, 0);
let mut builder = FlatBufferBuilder::new_in(allocator);
let result = builder.try_create_string("hello world");
assert!(result.is_err());
}
#[test]
fn try_create_vector_propagates_allocation_error() {
let allocator = FailingAllocator::new(1, 0);
let mut builder = FlatBufferBuilder::new_in(allocator);
let result = builder.try_create_vector(&[1u32, 2, 3, 4, 5]);
assert!(result.is_err());
}
#[test]
fn try_methods_succeed_with_sufficient_capacity() {
let allocator = FailingAllocator::new(1, 10);
let mut builder = FlatBufferBuilder::new_in(allocator);
let result = builder.try_create_string("hello");
assert!(result.is_ok());
let result = builder.try_create_vector(&[1u32, 2, 3]);
assert!(result.is_ok());
}
#[test]
fn try_finish_propagates_allocation_error() {
let allocator = FailingAllocator::new(1, 3);
let mut builder = FlatBufferBuilder::new_in(allocator);
let start = builder.start_table();
let table = builder
.try_end_table(start)
.expect("end_table should succeed with 3 grows");
let result = builder.try_finish_minimal(table);
assert!(result.is_err(), "finish should fail after grows exhausted");
}
}

View File

@@ -492,8 +492,8 @@ impl<'ver, 'opts, 'buf> TableVerifier<'ver, 'opts, 'buf> {
Ok(self)
}
_ => InvalidFlatbuffer::new_inconsistent_union(
key_field_name.into(),
val_field_name.into(),
key_field_name.into(),
),
}
}

View File

@@ -397,8 +397,8 @@ fn verify_union<'a, 'b, 'c>(
}
} else {
return InvalidFlatbuffer::new_inconsistent_union(
format!("{}_type", field.name()),
field.name().to_string(),
format!("{}_type", field.name()),
)?;
}

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

@@ -4132,7 +4132,15 @@ bool StructDef::Deserialize(Parser& parser, const reflection::Object* object) {
sortbysize = attributes.Lookup("original_order") == nullptr && !fixed;
const auto& of = *(object->fields());
auto indexes = std::vector<uoffset_t>(of.size());
for (uoffset_t i = 0; i < of.size(); i++) indexes[of.Get(i)->id()] = i;
for (uoffset_t i = 0; i < of.size(); i++) {
uint16_t field_id = of.Get(i)->id();
if (field_id >= of.size()) {
parser.error_ = "Field ID " + std::to_string(field_id) +
" exceeds field count " + std::to_string(of.size());
return false;
}
indexes[field_id] = i;
}
size_t tmp_struct_size = 0;
for (size_t i = 0; i < indexes.size(); i++) {
auto field = of.Get(indexes[i]);

View File

@@ -389,7 +389,7 @@ void ForAllFields(const reflection::Object* object, bool reverse,
for (size_t i = 0; i < field_to_id_map.size(); ++i) {
func(object->fields()->Get(
field_to_id_map[reverse ? field_to_id_map.size() - i + 1 : i]));
field_to_id_map[reverse ? field_to_id_map.size() - (i + 1) : i]));
}
}
@@ -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

@@ -248,6 +248,93 @@ void ReflectionTest(const std::string& tests_data_path, uint8_t* flatbuf,
true);
}
// Test that ForAllFields with reverse=true iterates fields in descending
// ID order. This exercises a fix for an operator precedence bug where the
// expression `size() - i + 1` was evaluated as `(size() - i) + 1` instead
// of the correct `size() - (i + 1)`, causing an out-of-bounds read.
void ForAllFieldsReverseTest(const std::string& tests_data_path) {
// Load the binary schema.
std::string bfbsfile;
TEST_EQ(flatbuffers::LoadFile((tests_data_path + "monster_test.bfbs").c_str(),
true, &bfbsfile),
true);
// Verify the schema.
flatbuffers::Verifier verifier(
reinterpret_cast<const uint8_t*>(bfbsfile.c_str()), bfbsfile.length());
TEST_EQ(reflection::VerifySchemaBuffer(verifier), true);
auto& schema = *reflection::GetSchema(bfbsfile.c_str());
// Use the Stat table which has 3 fields with sequential IDs:
// id:string (id: 0)
// val:long (id: 1)
// count:ushort (id: 2)
auto stat_object = schema.objects()->LookupByKey("MyGame.Example.Stat");
TEST_NOTNULL(stat_object);
TEST_EQ(stat_object->fields()->size(), 3u);
// Test forward iteration: fields should come in ascending ID order (0,1,2).
{
std::vector<uint16_t> forward_ids;
flatbuffers::ForAllFields(
stat_object, /*reverse=*/false,
[&forward_ids](const reflection::Field* field) {
forward_ids.push_back(field->id());
});
TEST_EQ(forward_ids.size(), 3u);
TEST_EQ(forward_ids[0], 0); // id
TEST_EQ(forward_ids[1], 1); // val
TEST_EQ(forward_ids[2], 2); // count
}
// Test reverse iteration: fields should come in descending ID order (2,1,0).
// With the old buggy code `size() - i + 1`, at i=0 this would compute
// index 3 - 0 + 1 = 4, which is out of bounds for a size-3 vector.
{
std::vector<uint16_t> reverse_ids;
flatbuffers::ForAllFields(
stat_object, /*reverse=*/true,
[&reverse_ids](const reflection::Field* field) {
reverse_ids.push_back(field->id());
});
TEST_EQ(reverse_ids.size(), 3u);
TEST_EQ(reverse_ids[0], 2); // count (highest ID first)
TEST_EQ(reverse_ids[1], 1); // val
TEST_EQ(reverse_ids[2], 0); // id (lowest ID last)
}
// Also test with the root Monster table which has many fields and
// non-sequential definition order vs. ID order (e.g., pos=id:0, hp=id:2,
// mana=id:1). This ensures the ID-to-index mapping works correctly.
auto root_table = schema.root_table();
TEST_NOTNULL(root_table);
{
std::vector<uint16_t> forward_ids;
flatbuffers::ForAllFields(
root_table, /*reverse=*/false,
[&forward_ids](const reflection::Field* field) {
forward_ids.push_back(field->id());
});
// Verify ascending order.
for (size_t i = 1; i < forward_ids.size(); ++i) {
TEST_ASSERT(forward_ids[i - 1] < forward_ids[i]);
}
}
{
std::vector<uint16_t> reverse_ids;
flatbuffers::ForAllFields(
root_table, /*reverse=*/true,
[&reverse_ids](const reflection::Field* field) {
reverse_ids.push_back(field->id());
});
// Verify descending order.
for (size_t i = 1; i < reverse_ids.size(); ++i) {
TEST_ASSERT(reverse_ids[i - 1] > reverse_ids[i]);
}
}
}
void MiniReflectFlatBuffersTest(uint8_t* flatbuf) {
auto s =
flatbuffers::FlatBufferToString(flatbuf, Monster::MiniReflectTypeTable());

View File

@@ -10,6 +10,7 @@ namespace tests {
void ReflectionTest(const std::string& tests_data_path, uint8_t* flatbuf,
size_t length);
void ForAllFieldsReverseTest(const std::string& tests_data_path);
void MiniReflectFixedLengthArrayTest();
void MiniReflectFlatBuffersTest(uint8_t* flatbuf);

View File

@@ -3448,4 +3448,250 @@ fn test_shared_strings_pool_deduplication() {
}
} // mod flatbuffers_tests
#[cfg(test)]
mod try_api {
extern crate flatbuffers;
use alloc::vec::Vec;
use flatbuffers::Follow;
use super::my_game;
use super::serialized_example_is_accessible_and_correct;
#[test]
fn try_api_full_table_roundtrip() {
// Build a Monster using exclusively try_* API (mirrors library_code example).
let mut builder = flatbuffers::FlatBufferBuilder::new();
let nested_union_mon = {
let name = builder.try_create_string("Fred").unwrap();
let table_start = builder.start_table();
builder
.try_push_slot_always(my_game::example::Monster::VT_NAME, name)
.unwrap();
builder.try_end_table(table_start).unwrap()
};
let pos = my_game::example::Vec3::new(
1.0,
2.0,
3.0,
3.0,
my_game::example::Color::Green,
&my_game::example::Test::new(5i16, 6i8),
);
let inv = builder.try_create_vector(&[0u8, 1, 2, 3, 4]).unwrap();
let test4 = builder
.try_create_vector(
&[
my_game::example::Test::new(10, 20),
my_game::example::Test::new(30, 40),
][..],
)
.unwrap();
let name = builder.try_create_string("MyMonster").unwrap();
let test1 = builder.try_create_string("test1").unwrap();
let test2 = builder.try_create_string("test2").unwrap();
let testarrayofstring = builder.try_create_vector(&[test1, test2]).unwrap();
let table_start = builder.start_table();
builder
.try_push_slot(my_game::example::Monster::VT_HP, 80i16, 100)
.unwrap();
builder
.try_push_slot_always(my_game::example::Monster::VT_NAME, name)
.unwrap();
builder
.try_push_slot_always(my_game::example::Monster::VT_POS, &pos)
.unwrap();
builder
.try_push_slot(
my_game::example::Monster::VT_TEST_TYPE,
my_game::example::Any::Monster,
my_game::example::Any::NONE,
)
.unwrap();
builder
.try_push_slot_always(my_game::example::Monster::VT_TEST, nested_union_mon)
.unwrap();
builder
.try_push_slot_always(my_game::example::Monster::VT_INVENTORY, inv)
.unwrap();
builder
.try_push_slot_always(my_game::example::Monster::VT_TEST4, test4)
.unwrap();
builder
.try_push_slot_always(
my_game::example::Monster::VT_TESTARRAYOFSTRING,
testarrayofstring,
)
.unwrap();
let root = builder.try_end_table(table_start).unwrap();
builder
.try_finish(root, Some(my_game::example::MONSTER_IDENTIFIER))
.unwrap();
let buf = builder.finished_data();
serialized_example_is_accessible_and_correct(buf, true, false).unwrap();
}
#[test]
fn try_shared_string_deduplication() {
let mut builder = flatbuffers::FlatBufferBuilder::new();
let offset1 = builder
.try_create_shared_string("welcome to flatbuffers!!")
.unwrap();
let offset2 = builder.try_create_shared_string("welcome").unwrap();
let offset3 = builder
.try_create_shared_string("welcome to flatbuffers!!")
.unwrap();
assert_ne!(offset2.value(), offset3.value());
assert_eq!(offset1.value(), offset3.value());
builder.reset();
let offset4 = builder.try_create_shared_string("welcome").unwrap();
let offset5 = builder
.try_create_shared_string("welcome to flatbuffers!!")
.unwrap();
assert_ne!(offset2.value(), offset4.value());
assert_ne!(offset5.value(), offset1.value());
builder.reset();
// Shared strings work with an object in between writes
let name = builder.try_create_shared_string("foo").unwrap();
let enemy =
my_game::example::Monster::create(&mut builder, &my_game::example::MonsterArgs {
name: Some(name),
..Default::default()
});
let secondary_name = builder.try_create_shared_string("foo").unwrap();
assert_eq!(name.value(), secondary_name.value());
let args = my_game::example::MonsterArgs {
name: Some(secondary_name),
enemy: Some(enemy),
testarrayofstring: Some(builder.try_create_vector(&[name, secondary_name]).unwrap()),
..Default::default()
};
let main_monster = my_game::example::Monster::create(&mut builder, &args);
builder.try_finish(main_monster, None).unwrap();
let monster =
my_game::example::root_as_monster(builder.finished_data()).unwrap();
assert_eq!(monster.enemy().unwrap().name(), "foo");
let string_vector = monster.testarrayofstring().unwrap();
assert_eq!(string_vector.get(0), "foo");
assert_eq!(string_vector.get(1), "foo");
}
#[test]
fn try_vector_manual_build_roundtrip() {
// Build a vector of scalars manually using try_start_vector / try_push / try_end_vector.
let mut builder = flatbuffers::FlatBufferBuilder::new();
let items: Vec<u32> = vec![10, 20, 30, 40, 50];
builder
.try_start_vector::<u32>(items.len())
.unwrap();
for &v in items.iter().rev() {
builder.try_push::<u32>(v).unwrap();
}
let vecend = builder.try_end_vector::<u32>(items.len()).unwrap();
builder.try_finish_minimal(vecend).unwrap();
let buf = builder.finished_data();
let got = unsafe {
<flatbuffers::ForwardsUOffset<flatbuffers::Vector<u32>>>::follow(&buf[..], 0)
};
let result: Vec<u32> = got.iter().collect();
assert_eq!(result, items);
}
#[test]
fn try_create_vector_roundtrip() {
let mut builder = flatbuffers::FlatBufferBuilder::new();
let items: Vec<i64> = vec![-1, 0, 1, i64::MIN, i64::MAX];
let offset = builder.try_create_vector(&items).unwrap();
builder.try_finish_minimal(offset).unwrap();
let buf = builder.finished_data();
let got = unsafe {
<flatbuffers::ForwardsUOffset<flatbuffers::Vector<i64>>>::follow(&buf[..], 0)
};
let result: Vec<i64> = got.iter().collect();
assert_eq!(result, items);
}
#[test]
fn try_create_vector_from_iter_roundtrip() {
let mut builder = flatbuffers::FlatBufferBuilder::new();
let items: Vec<f64> = vec![1.0, 2.5, -3.14, 0.0];
let offset = builder
.try_create_vector_from_iter(items.iter().copied())
.unwrap();
builder.try_finish_minimal(offset).unwrap();
let buf = builder.finished_data();
let got = unsafe {
<flatbuffers::ForwardsUOffset<flatbuffers::Vector<f64>>>::follow(&buf[..], 0)
};
let result: Vec<f64> = got.iter().collect();
assert_eq!(result, items);
}
#[test]
fn try_create_byte_string_roundtrip() {
let mut builder = flatbuffers::FlatBufferBuilder::new();
let data = b"hello bytes";
let offset = builder.try_create_byte_string(data).unwrap();
builder.try_finish_minimal(offset).unwrap();
let buf = builder.finished_data();
let got = unsafe {
<flatbuffers::ForwardsUOffset<&[u8]>>::follow(&buf[..], 0)
};
assert_eq!(got, data);
}
#[test]
fn try_finish_size_prefixed_roundtrip() {
let mut builder = flatbuffers::FlatBufferBuilder::new();
let args = &my_game::example::MonsterArgs {
mana: 200,
hp: 300,
name: Some(builder.try_create_string("bob").unwrap()),
..Default::default()
};
let mon = my_game::example::Monster::create(&mut builder, &args);
builder.try_finish_size_prefixed(mon, None).unwrap();
let buf = builder.finished_data();
let m = flatbuffers::size_prefixed_root::<my_game::example::Monster>(buf).unwrap();
assert_eq!(m.mana(), 200);
assert_eq!(m.hp(), 300);
assert_eq!(m.name(), "bob");
}
#[test]
fn try_finish_with_file_identifier() {
let mut builder = flatbuffers::FlatBufferBuilder::new();
let name = builder.try_create_string("foo").unwrap();
let args = &my_game::example::MonsterArgs {
name: Some(name),
hp: 42,
..Default::default()
};
let mon = my_game::example::Monster::create(&mut builder, &args);
builder
.try_finish(mon, Some(my_game::example::MONSTER_IDENTIFIER))
.unwrap();
let buf = builder.finished_data();
assert!(my_game::example::monster_buffer_has_identifier(buf));
let m = my_game::example::root_as_monster(buf).unwrap();
assert_eq!(m.name(), "foo");
assert_eq!(m.hp(), 42);
}
}
}

View File

@@ -1774,6 +1774,7 @@ int FlatBufferTests(const std::string& tests_data_path) {
FixedLengthArrayJsonTest(tests_data_path, false);
FixedLengthArrayJsonTest(tests_data_path, true);
ReflectionTest(tests_data_path, flatbuf.data(), flatbuf.size());
ForAllFieldsReverseTest(tests_data_path);
ParseProtoTest(tests_data_path);
EvolutionTest(tests_data_path);
UnionDeprecationTest(tests_data_path);

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

@@ -3,6 +3,6 @@
"type": "module",
"private": true,
"devDependencies": {
"lodash": "4.17.23"
"lodash": "4.18.1"
}
}

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