From 11a19887053534c43f73e74786b46a615ecbf28e Mon Sep 17 00:00:00 2001 From: mustiikhalil <26250654+mustiikhalil@users.noreply.github.com> Date: Sun, 5 Jun 2022 23:04:05 +0200 Subject: [PATCH] Started implementation for private flags in rust (#7269) Adds flag to the cpp to fix bad annotations in all the languages Addresses comments to the PR, and fixes logic for leaking annotations --- include/flatbuffers/idl.h | 5 + scripts/generate_code.py | 5 + src/flatc.cpp | 5 + src/idl_gen_rust.cpp | 32 ++-- src/idl_parser.cpp | 59 ++++++ tests/private_annotation_test.fbs | 18 ++ tests/private_annotation_test/ab_generated.rs | 97 ++++++++++ .../annotations_generated.rs | 137 ++++++++++++++ .../private_annotation_test/any_generated.rs | 174 ++++++++++++++++++ .../private_annotation_test/game_generated.rs | 137 ++++++++++++++ tests/private_annotation_test/mod.rs | 11 ++ .../object_generated.rs | 131 +++++++++++++ tests/test.cpp | 124 +++++++++++++ 13 files changed, 924 insertions(+), 11 deletions(-) create mode 100644 tests/private_annotation_test.fbs create mode 100644 tests/private_annotation_test/ab_generated.rs create mode 100644 tests/private_annotation_test/annotations_generated.rs create mode 100644 tests/private_annotation_test/any_generated.rs create mode 100644 tests/private_annotation_test/game_generated.rs create mode 100644 tests/private_annotation_test/mod.rs create mode 100644 tests/private_annotation_test/object_generated.rs diff --git a/include/flatbuffers/idl.h b/include/flatbuffers/idl.h index 2e74d7072..52e368f36 100644 --- a/include/flatbuffers/idl.h +++ b/include/flatbuffers/idl.h @@ -603,6 +603,7 @@ struct IDLOptions { bool json_nested_flexbuffers; bool json_nested_legacy_flatbuffers; bool ts_flat_file; + bool no_leak_private_annotations; // Possible options for the more general generator below. enum Language { @@ -702,6 +703,7 @@ struct IDLOptions { json_nested_flexbuffers(true), json_nested_legacy_flatbuffers(false), ts_flat_file(false), + no_leak_private_annotations(false), mini_reflect(IDLOptions::kNone), require_explicit_ids(false), rust_serialize(false), @@ -993,6 +995,9 @@ class Parser : public ParserState { FLATBUFFERS_CHECKED_ERROR ParseRoot(const char *_source, const char **include_paths, const char *source_filename); + FLATBUFFERS_CHECKED_ERROR CheckPrivateLeak(); + FLATBUFFERS_CHECKED_ERROR CheckPrivatelyLeakedFields( + const Definition &def, const Definition &value_type); FLATBUFFERS_CHECKED_ERROR DoParse(const char *_source, const char **include_paths, const char *source_filename, diff --git a/scripts/generate_code.py b/scripts/generate_code.py index 1ea0c2c58..73c858466 100755 --- a/scripts/generate_code.py +++ b/scripts/generate_code.py @@ -456,6 +456,11 @@ flatc( prefix=cpp_17_prefix, ) +# Private annotations +annotations_test_schema = "private_annotation_test.fbs" + +flatc(RUST_OPTS + ["--no-leak-private-annotation", "--gen-object-api"], prefix="private_annotation_test", schema=annotations_test_schema) + # Sample files samples_schema = "monster.fbs" flatc(BASE_OPTS + CPP_OPTS + LOBSTER_OPTS, schema=samples_schema, cwd=samples_path) diff --git a/src/flatc.cpp b/src/flatc.cpp index 990e77948..b1a2bcce5 100644 --- a/src/flatc.cpp +++ b/src/flatc.cpp @@ -221,6 +221,9 @@ const static FlatCOption options[] = { "Only generated one typescript file per .fbs file." }, { "", "annotate", "SCHEMA", "Annotate the provided BINARY_FILE with the specified SCHEMA file." }, + { "", "no-leak-private-annotation", "", + "Prevents multiple type of annotations within a Fbs SCHEMA file." + "Currently this is required to generate private types in Rust" }, }; static void AppendTextWrappedString(std::stringstream &ss, std::string &text, @@ -596,6 +599,8 @@ int FlatCompiler::Compile(int argc, const char **argv) { opts.json_nested_legacy_flatbuffers = true; } else if (arg == "--ts-flat-files") { opts.ts_flat_file = true; + } else if (arg == "--no-leak-private-annotation") { + opts.no_leak_private_annotations = true; } else if (arg == "--annotate") { if (++argi >= argc) Error("missing path following: " + arg, true); annotate_schema = flatbuffers::PosixPath(argv[argi]); diff --git a/src/idl_gen_rust.cpp b/src/idl_gen_rust.cpp index 4f3e368f3..a2d915faa 100644 --- a/src/idl_gen_rust.cpp +++ b/src/idl_gen_rust.cpp @@ -698,6 +698,9 @@ class RustGenerator : public BaseGenerator { // an enum match function, // and an enum array of values void GenEnum(const EnumDef &enum_def) { + const bool is_private = parser_.opts.no_leak_private_annotations && + (enum_def.attributes.Lookup("private") != nullptr); + code_.SetValue("ACCESS_TYPE", is_private ? "pub(crate)" : "pub"); code_.SetValue("ENUM_TY", namer_.Type(enum_def)); code_.SetValue("BASE_TYPE", GetEnumTypeForDecl(enum_def.underlying_type)); code_.SetValue("ENUM_NAMESPACE", namer_.Namespace(enum_def.name)); @@ -718,7 +721,7 @@ class RustGenerator : public BaseGenerator { code_ += " flatbuffers::bitflags::bitflags! {"; GenComment(enum_def.doc_comment, " "); code_ += " #[derive(Default)]"; - code_ += " pub struct {{ENUM_TY}}: {{BASE_TYPE}} {"; + code_ += " {{ACCESS_TYPE}} struct {{ENUM_TY}}: {{BASE_TYPE}} {"; ForAllEnumValues1(enum_def, [&](const EnumVal &ev) { this->GenComment(ev.doc_comment, " "); code_ += " const {{VARIANT}} = {{VALUE}};"; @@ -764,7 +767,7 @@ class RustGenerator : public BaseGenerator { "#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, " "Default)]"; code_ += "#[repr(transparent)]"; - code_ += "pub struct {{ENUM_TY}}(pub {{BASE_TYPE}});"; + code_ += "{{ACCESS_TYPE}} struct {{ENUM_TY}}(pub {{BASE_TYPE}});"; code_ += "#[allow(non_upper_case_globals)]"; code_ += "impl {{ENUM_TY}} {"; ForAllEnumValues1(enum_def, [&](const EnumVal &ev) { @@ -881,7 +884,7 @@ class RustGenerator : public BaseGenerator { if (enum_def.is_union) { // Generate typesafe offset(s) for unions code_.SetValue("UNION_TYPE", namer_.Type(enum_def)); - code_ += "pub struct {{UNION_TYPE}}UnionTableOffset {}"; + code_ += "{{ACCESS_TYPE}} struct {{UNION_TYPE}}UnionTableOffset {}"; code_ += ""; if (parser_.opts.generate_object_based_api) { GenUnionObject(enum_def); } } @@ -916,7 +919,7 @@ class RustGenerator : public BaseGenerator { // intended. code_ += "#[non_exhaustive]"; code_ += "#[derive(Debug, Clone, PartialEq)]"; - code_ += "pub enum {{ENUM_OTY}} {"; + code_ += "{{ACCESS_TYPE}} enum {{ENUM_OTY}} {"; code_ += " NONE,"; ForAllUnionObjectVariantsBesidesNone(enum_def, [&] { code_ += "{{NATIVE_VARIANT}}(Box<{{U_ELEMENT_TABLE_TYPE}}>),"; @@ -1620,18 +1623,22 @@ class RustGenerator : public BaseGenerator { // Generate an accessor struct, builder struct, and create function for a // table. void GenTable(const StructDef &struct_def) { + + const bool is_private = parser_.opts.no_leak_private_annotations && + (struct_def.attributes.Lookup("private") != nullptr); + code_.SetValue("ACCESS_TYPE", is_private ? "pub(crate)" : "pub"); code_.SetValue("STRUCT_TY", namer_.Type(struct_def)); code_.SetValue("STRUCT_FN", namer_.Function(struct_def)); // Generate an offset type, the base type, the Follow impl, and the // init_from_table impl. - code_ += "pub enum {{STRUCT_TY}}Offset {}"; + code_ += "{{ACCESS_TYPE}} enum {{STRUCT_TY}}Offset {}"; code_ += "#[derive(Copy, Clone, PartialEq)]"; code_ += ""; GenComment(struct_def.doc_comment); - code_ += "pub struct {{STRUCT_TY}}<'a> {"; + code_ += "{{ACCESS_TYPE}} struct {{STRUCT_TY}}<'a> {"; code_ += " pub _tab: flatbuffers::Table<'a>,"; code_ += "}"; code_ += ""; @@ -1965,7 +1972,7 @@ class RustGenerator : public BaseGenerator { // Generate an args struct: code_.SetValue("MAYBE_LT", TableBuilderArgsNeedsLifetime(struct_def) ? "<'a>" : ""); - code_ += "pub struct {{STRUCT_TY}}Args{{MAYBE_LT}} {"; + code_ += "{{ACCESS_TYPE}} struct {{STRUCT_TY}}Args{{MAYBE_LT}} {"; ForAllTableFields(struct_def, [&](const FieldDef &field) { code_.SetValue("PARAM_TYPE", TableBuilderArgsDefnType(field, "'a")); code_ += " pub {{FIELD}}: {{PARAM_TYPE}},"; @@ -2054,7 +2061,7 @@ class RustGenerator : public BaseGenerator { } // Generate a builder struct: - code_ += "pub struct {{STRUCT_TY}}Builder<'a: 'b, 'b> {"; + code_ += "{{ACCESS_TYPE}} struct {{STRUCT_TY}}Builder<'a: 'b, 'b> {"; code_ += " fbb_: &'b mut flatbuffers::FlatBufferBuilder<'a>,"; code_ += " start_: flatbuffers::WIPOffset<" @@ -2175,7 +2182,7 @@ class RustGenerator : public BaseGenerator { // Generate the native object. code_ += "#[non_exhaustive]"; code_ += "#[derive(Debug, Clone, PartialEq)]"; - code_ += "pub struct {{STRUCT_OTY}} {"; + code_ += "{{ACCESS_TYPE}} struct {{STRUCT_OTY}} {"; ForAllObjectTableFields(table, [&](const FieldDef &field) { // Union objects combine both the union discriminant and value, so we // skip making a field for the discriminant. @@ -2583,6 +2590,9 @@ class RustGenerator : public BaseGenerator { } // Generate an accessor struct with constructor for a flatbuffers struct. void GenStruct(const StructDef &struct_def) { + const bool is_private = parser_.opts.no_leak_private_annotations && + (struct_def.attributes.Lookup("private") != nullptr); + code_.SetValue("ACCESS_TYPE", is_private ? "pub(crate)" : "pub"); // Generates manual padding and alignment. // Variables are private because they contain little endian data on all // platforms. @@ -2600,7 +2610,7 @@ class RustGenerator : public BaseGenerator { code_ += "// struct {{STRUCT_TY}}, aligned to {{ALIGN}}"; code_ += "#[repr(transparent)]"; code_ += "#[derive(Clone, Copy, PartialEq)]"; - code_ += "pub struct {{STRUCT_TY}}(pub [u8; {{STRUCT_SIZE}}]);"; + code_ += "{{ACCESS_TYPE}} struct {{STRUCT_TY}}(pub [u8; {{STRUCT_SIZE}}]);"; code_ += "impl Default for {{STRUCT_TY}} { "; code_ += " fn default() -> Self { "; code_ += " Self([0; {{STRUCT_SIZE}}])"; @@ -2847,7 +2857,7 @@ class RustGenerator : public BaseGenerator { if (parser_.opts.generate_object_based_api) { // Struct declaration code_ += "#[derive(Debug, Clone, PartialEq, Default)]"; - code_ += "pub struct {{STRUCT_OTY}} {"; + code_ += "{{ACCESS_TYPE}} struct {{STRUCT_OTY}} {"; ForAllStructFields(struct_def, [&](const FieldDef &field) { (void)field; // unused. code_ += "pub {{FIELD}}: {{FIELD_OTY}},"; diff --git a/src/idl_parser.cpp b/src/idl_parser.cpp index 08642fdcd..323c6e5c2 100644 --- a/src/idl_parser.cpp +++ b/src/idl_parser.cpp @@ -3266,19 +3266,78 @@ CheckedError Parser::ParseRoot(const char *source, const char **include_paths, for (auto val_it = enum_def.Vals().begin(); val_it != enum_def.Vals().end(); ++val_it) { auto &val = **val_it; + if (!(opts.lang_to_generate != 0 && SupportsAdvancedUnionFeatures()) && (IsStruct(val.union_type) || IsString(val.union_type))) + return Error( "only tables can be union elements in the generated language: " + val.name); } } } + + auto err = CheckPrivateLeak(); + if (err.Check()) return err; + // Parse JSON object only if the scheme has been parsed. if (token_ == '{') { ECHECK(DoParseJson()); } return NoError(); } +CheckedError Parser::CheckPrivateLeak() { + if (!opts.no_leak_private_annotations) return NoError(); + // Iterate over all structs/tables to validate we arent leaking + // any private (structs/tables/enums) + for (auto it = structs_.vec.begin(); it != structs_.vec.end(); it++) { + auto &struct_def = **it; + for (auto fld_it = struct_def.fields.vec.begin(); + fld_it != struct_def.fields.vec.end(); ++fld_it) { + auto &field = **fld_it; + + if (field.value.type.enum_def) { + auto err = + CheckPrivatelyLeakedFields(struct_def, *field.value.type.enum_def); + if (err.Check()) { return err; } + } else if (field.value.type.struct_def) { + auto err = CheckPrivatelyLeakedFields(struct_def, + *field.value.type.struct_def); + if (err.Check()) { return err; } + } + } + } + // Iterate over all enums to validate we arent leaking + // any private (structs/tables) + for (auto it = enums_.vec.begin(); it != enums_.vec.end(); ++it) { + auto &enum_def = **it; + if (enum_def.is_union) { + for (auto val_it = enum_def.Vals().begin(); + val_it != enum_def.Vals().end(); ++val_it) { + auto &val = **val_it; + if (val.union_type.struct_def) { + auto err = + CheckPrivatelyLeakedFields(enum_def, *val.union_type.struct_def); + if (err.Check()) { return err; } + } + } + } + } + return NoError(); +} + +CheckedError Parser::CheckPrivatelyLeakedFields(const Definition &def, + const Definition &value_type) { + if (!opts.no_leak_private_annotations) return NoError(); + const auto is_private = def.attributes.Lookup("private"); + const auto is_field_private = value_type.attributes.Lookup("private"); + if (!is_private && is_field_private) { + return Error( + "Leaking private implementation, verify all objects have similar " + "annotations"); + } + return NoError(); +} + // Generate a unique hash for a file based on its name and contents (if any). static uint64_t HashFile(const char *source_filename, const char *source) { uint64_t hash = 0; diff --git a/tests/private_annotation_test.fbs b/tests/private_annotation_test.fbs new file mode 100644 index 000000000..434927b84 --- /dev/null +++ b/tests/private_annotation_test.fbs @@ -0,0 +1,18 @@ +enum AB:byte (private) { + A, + B, +} + +struct Object (private) { + value: int; +} + +union Any (private) { Game, Annotations } + +table Game (private) { + value: int; +} + +table Annotations (private) { + value: int; +} \ No newline at end of file diff --git a/tests/private_annotation_test/ab_generated.rs b/tests/private_annotation_test/ab_generated.rs new file mode 100644 index 000000000..7f6adf8df --- /dev/null +++ b/tests/private_annotation_test/ab_generated.rs @@ -0,0 +1,97 @@ +// automatically generated by the FlatBuffers compiler, do not modify +extern crate alloc; +extern crate flatbuffers; +use alloc::boxed::Box; +use alloc::string::{String, ToString}; +use alloc::vec::Vec; +use core::mem; +use core::cmp::Ordering; +use self::flatbuffers::{EndianScalar, Follow}; +use super::*; +#[deprecated(since = "2.0.0", note = "Use associated constants instead. This will no longer be generated in 2021.")] +pub const ENUM_MIN_AB: i8 = 0; +#[deprecated(since = "2.0.0", note = "Use associated constants instead. This will no longer be generated in 2021.")] +pub const ENUM_MAX_AB: i8 = 1; +#[deprecated(since = "2.0.0", note = "Use associated constants instead. This will no longer be generated in 2021.")] +#[allow(non_camel_case_types)] +pub const ENUM_VALUES_AB: [AB; 2] = [ + AB::A, + AB::B, +]; + +#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)] +#[repr(transparent)] +pub(crate) struct AB(pub i8); +#[allow(non_upper_case_globals)] +impl AB { + pub const A: Self = Self(0); + pub const B: Self = Self(1); + + pub const ENUM_MIN: i8 = 0; + pub const ENUM_MAX: i8 = 1; + pub const ENUM_VALUES: &'static [Self] = &[ + Self::A, + Self::B, + ]; + /// Returns the variant's name or "" if unknown. + pub fn variant_name(self) -> Option<&'static str> { + match self { + Self::A => Some("A"), + Self::B => Some("B"), + _ => None, + } + } +} +impl core::fmt::Debug for AB { + fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { + if let Some(name) = self.variant_name() { + f.write_str(name) + } else { + f.write_fmt(format_args!("", self.0)) + } + } +} +impl<'a> flatbuffers::Follow<'a> for AB { + type Inner = Self; + #[inline] + fn follow(buf: &'a [u8], loc: usize) -> Self::Inner { + let b = unsafe { + flatbuffers::read_scalar_at::(buf, loc) + }; + Self(b) + } +} + +impl flatbuffers::Push for AB { + type Output = AB; + #[inline] + fn push(&self, dst: &mut [u8], _rest: &[u8]) { + unsafe { flatbuffers::emplace_scalar::(dst, self.0); } + } +} + +impl flatbuffers::EndianScalar for AB { + #[inline] + fn to_little_endian(self) -> Self { + let b = i8::to_le(self.0); + Self(b) + } + #[inline] + #[allow(clippy::wrong_self_convention)] + fn from_little_endian(self) -> Self { + let b = i8::from_le(self.0); + Self(b) + } +} + +impl<'a> flatbuffers::Verifiable for AB { + #[inline] + fn run_verifier( + v: &mut flatbuffers::Verifier, pos: usize + ) -> Result<(), flatbuffers::InvalidFlatbuffer> { + use self::flatbuffers::Verifiable; + i8::run_verifier(v, pos) + } +} + +impl flatbuffers::SimpleToVerifyInSlice for AB {} diff --git a/tests/private_annotation_test/annotations_generated.rs b/tests/private_annotation_test/annotations_generated.rs new file mode 100644 index 000000000..aeaa5bf64 --- /dev/null +++ b/tests/private_annotation_test/annotations_generated.rs @@ -0,0 +1,137 @@ +// automatically generated by the FlatBuffers compiler, do not modify +extern crate alloc; +extern crate flatbuffers; +use alloc::boxed::Box; +use alloc::string::{String, ToString}; +use alloc::vec::Vec; +use core::mem; +use core::cmp::Ordering; +use self::flatbuffers::{EndianScalar, Follow}; +use super::*; +pub(crate) enum AnnotationsOffset {} +#[derive(Copy, Clone, PartialEq)] + +pub(crate) struct Annotations<'a> { + pub _tab: flatbuffers::Table<'a>, +} + +impl<'a> flatbuffers::Follow<'a> for Annotations<'a> { + type Inner = Annotations<'a>; + #[inline] + fn follow(buf: &'a [u8], loc: usize) -> Self::Inner { + Self { _tab: flatbuffers::Table { buf, loc } } + } +} + +impl<'a> Annotations<'a> { + pub const VT_VALUE: flatbuffers::VOffsetT = 4; + + pub const fn get_fully_qualified_name() -> &'static str { + "Annotations" + } + + #[inline] + pub fn init_from_table(table: flatbuffers::Table<'a>) -> Self { + Annotations { _tab: table } + } + #[allow(unused_mut)] + pub fn create<'bldr: 'args, 'args: 'mut_bldr, 'mut_bldr>( + _fbb: &'mut_bldr mut flatbuffers::FlatBufferBuilder<'bldr>, + args: &'args AnnotationsArgs + ) -> flatbuffers::WIPOffset> { + let mut builder = AnnotationsBuilder::new(_fbb); + builder.add_value(args.value); + builder.finish() + } + + pub fn unpack(&self) -> AnnotationsT { + let value = self.value(); + AnnotationsT { + value, + } + } + + #[inline] + pub fn value(&self) -> i32 { + self._tab.get::(Annotations::VT_VALUE, Some(0)).unwrap() + } +} + +impl flatbuffers::Verifiable for Annotations<'_> { + #[inline] + fn run_verifier( + v: &mut flatbuffers::Verifier, pos: usize + ) -> Result<(), flatbuffers::InvalidFlatbuffer> { + use self::flatbuffers::Verifiable; + v.visit_table(pos)? + .visit_field::("value", Self::VT_VALUE, false)? + .finish(); + Ok(()) + } +} +pub(crate) struct AnnotationsArgs { + pub value: i32, +} +impl<'a> Default for AnnotationsArgs { + #[inline] + fn default() -> Self { + AnnotationsArgs { + value: 0, + } + } +} + +pub(crate) struct AnnotationsBuilder<'a: 'b, 'b> { + fbb_: &'b mut flatbuffers::FlatBufferBuilder<'a>, + start_: flatbuffers::WIPOffset, +} +impl<'a: 'b, 'b> AnnotationsBuilder<'a, 'b> { + #[inline] + pub fn add_value(&mut self, value: i32) { + self.fbb_.push_slot::(Annotations::VT_VALUE, value, 0); + } + #[inline] + pub fn new(_fbb: &'b mut flatbuffers::FlatBufferBuilder<'a>) -> AnnotationsBuilder<'a, 'b> { + let start = _fbb.start_table(); + AnnotationsBuilder { + fbb_: _fbb, + start_: start, + } + } + #[inline] + pub fn finish(self) -> flatbuffers::WIPOffset> { + let o = self.fbb_.end_table(self.start_); + flatbuffers::WIPOffset::new(o.value()) + } +} + +impl core::fmt::Debug for Annotations<'_> { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + let mut ds = f.debug_struct("Annotations"); + ds.field("value", &self.value()); + ds.finish() + } +} +#[non_exhaustive] +#[derive(Debug, Clone, PartialEq)] +pub(crate) struct AnnotationsT { + pub value: i32, +} +impl Default for AnnotationsT { + fn default() -> Self { + Self { + value: 0, + } + } +} +impl AnnotationsT { + pub fn pack<'b>( + &self, + _fbb: &mut flatbuffers::FlatBufferBuilder<'b> + ) -> flatbuffers::WIPOffset> { + let value = self.value; + Annotations::create(_fbb, &AnnotationsArgs{ + value, + }) + } +} diff --git a/tests/private_annotation_test/any_generated.rs b/tests/private_annotation_test/any_generated.rs new file mode 100644 index 000000000..237f98a6d --- /dev/null +++ b/tests/private_annotation_test/any_generated.rs @@ -0,0 +1,174 @@ +// automatically generated by the FlatBuffers compiler, do not modify +extern crate alloc; +extern crate flatbuffers; +use alloc::boxed::Box; +use alloc::string::{String, ToString}; +use alloc::vec::Vec; +use core::mem; +use core::cmp::Ordering; +use self::flatbuffers::{EndianScalar, Follow}; +use super::*; +#[deprecated(since = "2.0.0", note = "Use associated constants instead. This will no longer be generated in 2021.")] +pub const ENUM_MIN_ANY: u8 = 0; +#[deprecated(since = "2.0.0", note = "Use associated constants instead. This will no longer be generated in 2021.")] +pub const ENUM_MAX_ANY: u8 = 2; +#[deprecated(since = "2.0.0", note = "Use associated constants instead. This will no longer be generated in 2021.")] +#[allow(non_camel_case_types)] +pub const ENUM_VALUES_ANY: [Any; 3] = [ + Any::NONE, + Any::Game, + Any::Annotations, +]; + +#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)] +#[repr(transparent)] +pub(crate) struct Any(pub u8); +#[allow(non_upper_case_globals)] +impl Any { + pub const NONE: Self = Self(0); + pub const Game: Self = Self(1); + pub const Annotations: Self = Self(2); + + pub const ENUM_MIN: u8 = 0; + pub const ENUM_MAX: u8 = 2; + pub const ENUM_VALUES: &'static [Self] = &[ + Self::NONE, + Self::Game, + Self::Annotations, + ]; + /// Returns the variant's name or "" if unknown. + pub fn variant_name(self) -> Option<&'static str> { + match self { + Self::NONE => Some("NONE"), + Self::Game => Some("Game"), + Self::Annotations => Some("Annotations"), + _ => None, + } + } +} +impl core::fmt::Debug for Any { + fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { + if let Some(name) = self.variant_name() { + f.write_str(name) + } else { + f.write_fmt(format_args!("", self.0)) + } + } +} +impl<'a> flatbuffers::Follow<'a> for Any { + type Inner = Self; + #[inline] + fn follow(buf: &'a [u8], loc: usize) -> Self::Inner { + let b = unsafe { + flatbuffers::read_scalar_at::(buf, loc) + }; + Self(b) + } +} + +impl flatbuffers::Push for Any { + type Output = Any; + #[inline] + fn push(&self, dst: &mut [u8], _rest: &[u8]) { + unsafe { flatbuffers::emplace_scalar::(dst, self.0); } + } +} + +impl flatbuffers::EndianScalar for Any { + #[inline] + fn to_little_endian(self) -> Self { + let b = u8::to_le(self.0); + Self(b) + } + #[inline] + #[allow(clippy::wrong_self_convention)] + fn from_little_endian(self) -> Self { + let b = u8::from_le(self.0); + Self(b) + } +} + +impl<'a> flatbuffers::Verifiable for Any { + #[inline] + fn run_verifier( + v: &mut flatbuffers::Verifier, pos: usize + ) -> Result<(), flatbuffers::InvalidFlatbuffer> { + use self::flatbuffers::Verifiable; + u8::run_verifier(v, pos) + } +} + +impl flatbuffers::SimpleToVerifyInSlice for Any {} +pub(crate) struct AnyUnionTableOffset {} + +#[allow(clippy::upper_case_acronyms)] +#[non_exhaustive] +#[derive(Debug, Clone, PartialEq)] +pub(crate) enum AnyT { + NONE, + Game(Box), + Annotations(Box), +} +impl Default for AnyT { + fn default() -> Self { + Self::NONE + } +} +impl AnyT { + pub fn any_type(&self) -> Any { + match self { + Self::NONE => Any::NONE, + Self::Game(_) => Any::Game, + Self::Annotations(_) => Any::Annotations, + } + } + pub fn pack(&self, fbb: &mut flatbuffers::FlatBufferBuilder) -> Option> { + match self { + Self::NONE => None, + Self::Game(v) => Some(v.pack(fbb).as_union_value()), + Self::Annotations(v) => Some(v.pack(fbb).as_union_value()), + } + } + /// If the union variant matches, return the owned GameT, setting the union to NONE. + pub fn take_game(&mut self) -> Option> { + if let Self::Game(_) = self { + let v = core::mem::replace(self, Self::NONE); + if let Self::Game(w) = v { + Some(w) + } else { + unreachable!() + } + } else { + None + } + } + /// If the union variant matches, return a reference to the GameT. + pub fn as_game(&self) -> Option<&GameT> { + if let Self::Game(v) = self { Some(v.as_ref()) } else { None } + } + /// If the union variant matches, return a mutable reference to the GameT. + pub fn as_game_mut(&mut self) -> Option<&mut GameT> { + if let Self::Game(v) = self { Some(v.as_mut()) } else { None } + } + /// If the union variant matches, return the owned AnnotationsT, setting the union to NONE. + pub fn take_annotations(&mut self) -> Option> { + if let Self::Annotations(_) = self { + let v = core::mem::replace(self, Self::NONE); + if let Self::Annotations(w) = v { + Some(w) + } else { + unreachable!() + } + } else { + None + } + } + /// If the union variant matches, return a reference to the AnnotationsT. + pub fn as_annotations(&self) -> Option<&AnnotationsT> { + if let Self::Annotations(v) = self { Some(v.as_ref()) } else { None } + } + /// If the union variant matches, return a mutable reference to the AnnotationsT. + pub fn as_annotations_mut(&mut self) -> Option<&mut AnnotationsT> { + if let Self::Annotations(v) = self { Some(v.as_mut()) } else { None } + } +} diff --git a/tests/private_annotation_test/game_generated.rs b/tests/private_annotation_test/game_generated.rs new file mode 100644 index 000000000..ff4a0fd18 --- /dev/null +++ b/tests/private_annotation_test/game_generated.rs @@ -0,0 +1,137 @@ +// automatically generated by the FlatBuffers compiler, do not modify +extern crate alloc; +extern crate flatbuffers; +use alloc::boxed::Box; +use alloc::string::{String, ToString}; +use alloc::vec::Vec; +use core::mem; +use core::cmp::Ordering; +use self::flatbuffers::{EndianScalar, Follow}; +use super::*; +pub(crate) enum GameOffset {} +#[derive(Copy, Clone, PartialEq)] + +pub(crate) struct Game<'a> { + pub _tab: flatbuffers::Table<'a>, +} + +impl<'a> flatbuffers::Follow<'a> for Game<'a> { + type Inner = Game<'a>; + #[inline] + fn follow(buf: &'a [u8], loc: usize) -> Self::Inner { + Self { _tab: flatbuffers::Table { buf, loc } } + } +} + +impl<'a> Game<'a> { + pub const VT_VALUE: flatbuffers::VOffsetT = 4; + + pub const fn get_fully_qualified_name() -> &'static str { + "Game" + } + + #[inline] + pub fn init_from_table(table: flatbuffers::Table<'a>) -> Self { + Game { _tab: table } + } + #[allow(unused_mut)] + pub fn create<'bldr: 'args, 'args: 'mut_bldr, 'mut_bldr>( + _fbb: &'mut_bldr mut flatbuffers::FlatBufferBuilder<'bldr>, + args: &'args GameArgs + ) -> flatbuffers::WIPOffset> { + let mut builder = GameBuilder::new(_fbb); + builder.add_value(args.value); + builder.finish() + } + + pub fn unpack(&self) -> GameT { + let value = self.value(); + GameT { + value, + } + } + + #[inline] + pub fn value(&self) -> i32 { + self._tab.get::(Game::VT_VALUE, Some(0)).unwrap() + } +} + +impl flatbuffers::Verifiable for Game<'_> { + #[inline] + fn run_verifier( + v: &mut flatbuffers::Verifier, pos: usize + ) -> Result<(), flatbuffers::InvalidFlatbuffer> { + use self::flatbuffers::Verifiable; + v.visit_table(pos)? + .visit_field::("value", Self::VT_VALUE, false)? + .finish(); + Ok(()) + } +} +pub(crate) struct GameArgs { + pub value: i32, +} +impl<'a> Default for GameArgs { + #[inline] + fn default() -> Self { + GameArgs { + value: 0, + } + } +} + +pub(crate) struct GameBuilder<'a: 'b, 'b> { + fbb_: &'b mut flatbuffers::FlatBufferBuilder<'a>, + start_: flatbuffers::WIPOffset, +} +impl<'a: 'b, 'b> GameBuilder<'a, 'b> { + #[inline] + pub fn add_value(&mut self, value: i32) { + self.fbb_.push_slot::(Game::VT_VALUE, value, 0); + } + #[inline] + pub fn new(_fbb: &'b mut flatbuffers::FlatBufferBuilder<'a>) -> GameBuilder<'a, 'b> { + let start = _fbb.start_table(); + GameBuilder { + fbb_: _fbb, + start_: start, + } + } + #[inline] + pub fn finish(self) -> flatbuffers::WIPOffset> { + let o = self.fbb_.end_table(self.start_); + flatbuffers::WIPOffset::new(o.value()) + } +} + +impl core::fmt::Debug for Game<'_> { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + let mut ds = f.debug_struct("Game"); + ds.field("value", &self.value()); + ds.finish() + } +} +#[non_exhaustive] +#[derive(Debug, Clone, PartialEq)] +pub(crate) struct GameT { + pub value: i32, +} +impl Default for GameT { + fn default() -> Self { + Self { + value: 0, + } + } +} +impl GameT { + pub fn pack<'b>( + &self, + _fbb: &mut flatbuffers::FlatBufferBuilder<'b> + ) -> flatbuffers::WIPOffset> { + let value = self.value; + Game::create(_fbb, &GameArgs{ + value, + }) + } +} diff --git a/tests/private_annotation_test/mod.rs b/tests/private_annotation_test/mod.rs new file mode 100644 index 000000000..01f5280cd --- /dev/null +++ b/tests/private_annotation_test/mod.rs @@ -0,0 +1,11 @@ +// Automatically generated by the Flatbuffers compiler. Do not modify. +mod ab_generated; +pub use self::ab_generated::*; +mod any_generated; +pub use self::any_generated::*; +mod object_generated; +pub use self::object_generated::*; +mod game_generated; +pub use self::game_generated::*; +mod annotations_generated; +pub use self::annotations_generated::*; diff --git a/tests/private_annotation_test/object_generated.rs b/tests/private_annotation_test/object_generated.rs new file mode 100644 index 000000000..b51564e85 --- /dev/null +++ b/tests/private_annotation_test/object_generated.rs @@ -0,0 +1,131 @@ +// automatically generated by the FlatBuffers compiler, do not modify +extern crate alloc; +extern crate flatbuffers; +use alloc::boxed::Box; +use alloc::string::{String, ToString}; +use alloc::vec::Vec; +use core::mem; +use core::cmp::Ordering; +use self::flatbuffers::{EndianScalar, Follow}; +use super::*; +// struct Object, aligned to 4 +#[repr(transparent)] +#[derive(Clone, Copy, PartialEq)] +pub(crate) struct Object(pub [u8; 4]); +impl Default for Object { + fn default() -> Self { + Self([0; 4]) + } +} +impl core::fmt::Debug for Object { + fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { + f.debug_struct("Object") + .field("value", &self.value()) + .finish() + } +} + +impl flatbuffers::SimpleToVerifyInSlice for Object {} +impl flatbuffers::SafeSliceAccess for Object {} +impl<'a> flatbuffers::Follow<'a> for Object { + type Inner = &'a Object; + #[inline] + fn follow(buf: &'a [u8], loc: usize) -> Self::Inner { + <&'a Object>::follow(buf, loc) + } +} +impl<'a> flatbuffers::Follow<'a> for &'a Object { + type Inner = &'a Object; + #[inline] + fn follow(buf: &'a [u8], loc: usize) -> Self::Inner { + flatbuffers::follow_cast_ref::(buf, loc) + } +} +impl<'b> flatbuffers::Push for Object { + type Output = Object; + #[inline] + fn push(&self, dst: &mut [u8], _rest: &[u8]) { + let src = unsafe { + ::core::slice::from_raw_parts(self as *const Object as *const u8, Self::size()) + }; + dst.copy_from_slice(src); + } +} +impl<'b> flatbuffers::Push for &'b Object { + type Output = Object; + + #[inline] + fn push(&self, dst: &mut [u8], _rest: &[u8]) { + let src = unsafe { + ::core::slice::from_raw_parts(*self as *const Object as *const u8, Self::size()) + }; + dst.copy_from_slice(src); + } +} + +impl<'a> flatbuffers::Verifiable for Object { + #[inline] + fn run_verifier( + v: &mut flatbuffers::Verifier, pos: usize + ) -> Result<(), flatbuffers::InvalidFlatbuffer> { + use self::flatbuffers::Verifiable; + v.in_buffer::(pos) + } +} + +impl<'a> Object { + #[allow(clippy::too_many_arguments)] + pub fn new( + value: i32, + ) -> Self { + let mut s = Self([0; 4]); + s.set_value(value); + s + } + + pub const fn get_fully_qualified_name() -> &'static str { + "Object" + } + + pub fn value(&self) -> i32 { + let mut mem = core::mem::MaybeUninit::::uninit(); + unsafe { + core::ptr::copy_nonoverlapping( + self.0[0..].as_ptr(), + mem.as_mut_ptr() as *mut u8, + core::mem::size_of::(), + ); + mem.assume_init() + }.from_little_endian() + } + + pub fn set_value(&mut self, x: i32) { + let x_le = x.to_little_endian(); + unsafe { + core::ptr::copy_nonoverlapping( + &x_le as *const i32 as *const u8, + self.0[0..].as_mut_ptr(), + core::mem::size_of::(), + ); + } + } + + pub fn unpack(&self) -> ObjectT { + ObjectT { + value: self.value(), + } + } +} + +#[derive(Debug, Clone, PartialEq, Default)] +pub(crate) struct ObjectT { + pub value: i32, +} +impl ObjectT { + pub fn pack(&self) -> Object { + Object::new( + self.value, + ) + } +} + diff --git a/tests/test.cpp b/tests/test.cpp index cfac0c332..e1141d9f3 100644 --- a/tests/test.cpp +++ b/tests/test.cpp @@ -4324,6 +4324,129 @@ void FlatbuffersIteratorsTest() { void FlatbuffersIteratorsTest() {} #endif +void PrivateAnnotationsLeaks() { + // Simple schemas and a "has optional scalar" sentinal. + std::vector schemas; + std::vector failure_schemas; + + // (private) (table/struct) + schemas.push_back( + "table Monster (private) { mana: int; }" + "struct ABC (private) { mana: int; }"); + + // (public) (table/struct) + schemas.push_back( + "table Monster { mana: int; }" + "struct ABC { mana: int; }"); + + // (private) (union) containing (private) (table/struct) + schemas.push_back( + "table Monster (private) { mana: int; } " + "struct ABC (private) { mana: int; } " + "union Any (private) { Monster, ABC } "); + + // (public) (union) containing (public) (table/struct) + schemas.push_back( + "table Monster { mana: int; }" + "struct ABC { mana: int; }" + "union Any { Monster, ABC }"); + + // (private) (table/struct/enum) + schemas.push_back( + "table Monster (private) { mana: int; }" + "struct ABC (private) { mana: int; }" + "enum Race:byte (private) { None = -1, Human = 0, }"); + + // (public) (table/struct/enum) + schemas.push_back( + "table Monster { mana: int; }" + "struct ABC { mana: int; }" + "enum Race:byte { None = -1, Human = 0, }"); + + // (private) (union) containing (private) (table/struct) + schemas.push_back( + "table Monster (private) { mana: int; }" + "struct ABC (private) { mana: int; }" + "enum Race:byte (private) { None = -1, Human = 0, }" + "union Any (private) { Monster, ABC }"); + + // (public) (union) containing (public) (table/struct) + schemas.push_back( + "table Monster { mana: int; }" + "struct ABC { mana: int; }" + "enum Race:byte { None = -1, Human = 0, }" + "union Any { Monster, ABC }"); + + // (private) (table), (public struct) + schemas.push_back( + "table Monster (private) { mana: int; }" + "struct ABC { mana: int; }"); + + // (private) (table), (public) (struct/enum) + schemas.push_back( + "table Monster (private) { mana: int; }" + "struct ABC { mana: int; }" + "enum Race:byte { None = -1, Human = 0, }"); + + // (public) (struct) containing (public) (enum) + schemas.push_back( + "enum Race:byte { None = -1, Human = 0, }" + "table Monster { mana: int; }" + "struct ABC { mana: int; type: Race; }"); + + // (public) (union) containing (private) (table) & (public) (struct) + failure_schemas.push_back( + "table Monster (private) { mana: int; }" + "struct ABC { mana: int; }" + "union Any { Monster, ABC }"); + + // (public) (union) containing (private) (table/struct) + failure_schemas.push_back( + "table Monster (private) { mana: int; }" + "struct ABC (private) { mana: int; }" + "enum Race:byte { None = -1, Human = 0, }" + "union Any { Monster, ABC }"); + + // (public) (table) containing (private) (struct) + failure_schemas.push_back( + "table Monster { mana: int; ab: ABC; }" + "struct ABC (private) { mana: int; }"); + + // (public) (struct) containing (private) (enum) + failure_schemas.push_back( + "enum Race:byte (private) { None = -1, Human = 0, }" + "table Monster { mana: int; }" + "struct ABC { mana: int; type: Race; }"); + + flatbuffers::IDLOptions opts; + opts.lang_to_generate = flatbuffers::IDLOptions::Language::kSwift; + opts.no_leak_private_annotations = true; + + for (auto schema = schemas.begin(); schema < schemas.end(); schema++) { + flatbuffers::Parser parser(opts); + TEST_ASSERT(parser.Parse(schema->c_str())); + } + + for (auto schema = failure_schemas.begin(); schema < failure_schemas.end(); + schema++) { + flatbuffers::Parser parser(opts); + TEST_EQ(false, parser.Parse(schema->c_str())); + } + + opts.no_leak_private_annotations = false; + + for (auto schema = schemas.begin(); schema < schemas.end(); schema++) { + flatbuffers::Parser parser(opts); + TEST_ASSERT(parser.Parse(schema->c_str())); + } + + for (auto schema = failure_schemas.begin(); schema < failure_schemas.end(); + schema++) { + flatbuffers::Parser parser(opts); + TEST_ASSERT(parser.Parse(schema->c_str())); + } +} + int FlatBufferTests() { // clang-format off @@ -4425,6 +4548,7 @@ int FlatBufferTests() { StructUnionTest(); WarningsAsErrorsTest(); NestedVerifierTest(); + PrivateAnnotationsLeaks(); return 0; }