[Rust] Add support for fixed size arrays (#6548)

* Add support for fixed size arrays

* clang-format

* Update rust image to 1.51 to support const generics

* Handle correctly big endian

* Add fuzz tests and clean code

* Add struct fuzz test and optimize struct arrays for api

* Bump flatbuffers crate version
This commit is contained in:
Eddie Linder
2021-04-16 18:15:59 +03:00
committed by GitHub
parent 151900ba96
commit da3bb64ef6
25 changed files with 1442 additions and 77 deletions

View File

@@ -1,6 +1,6 @@
[package]
name = "flatbuffers"
version = "0.8.3"
version = "0.8.4"
edition = "2018"
authors = ["Robert Winslow <hello@rwinslow.com>", "FlatBuffers Maintainers"]
license = "Apache-2.0"

View File

@@ -0,0 +1,136 @@
/*
* Copyright 2021 Google Inc. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
use crate::follow::Follow;
use crate::{
vector::{SafeSliceAccess, VectorIter},
EndianScalar,
};
use std::fmt::{Debug, Formatter, Result};
use std::marker::PhantomData;
use std::mem::size_of;
use std::slice::from_raw_parts;
#[derive(Copy, Clone)]
pub struct Array<'a, T: 'a, const N: usize>(&'a [u8], PhantomData<T>);
impl<'a, T: 'a, const N: usize> Debug for Array<'a, T, N>
where
T: 'a + Follow<'a>,
<T as Follow<'a>>::Inner: Debug,
{
fn fmt(&self, f: &mut Formatter) -> Result {
f.debug_list().entries(self.iter()).finish()
}
}
impl<'a, T: 'a, const N: usize> Array<'a, T, N> {
#[inline(always)]
pub fn new(buf: &'a [u8]) -> Self {
debug_assert!(size_of::<T>() * N == buf.len());
Array {
0: buf,
1: PhantomData,
}
}
#[inline(always)]
pub fn len(&self) -> usize {
N
}
}
impl<'a, T: Follow<'a> + 'a, const N: usize> Array<'a, T, N> {
#[inline(always)]
pub fn get(&self, idx: usize) -> T::Inner {
debug_assert!(idx < N);
let sz = size_of::<T>();
T::follow(self.0, sz * idx)
}
#[inline(always)]
pub fn iter(&self) -> VectorIter<'a, T> {
VectorIter::from_slice(self.0, self.len())
}
}
impl<'a, T: Follow<'a> + Debug, const N: usize> Into<[T::Inner; N]> for Array<'a, T, N> {
#[inline(always)]
fn into(self) -> [T::Inner; N] {
array_init(|i| self.get(i))
}
}
impl<'a, T: SafeSliceAccess + 'a, const N: usize> Array<'a, T, N> {
pub fn safe_slice(self) -> &'a [T] {
let sz = size_of::<T>();
debug_assert!(sz > 0);
let ptr = self.0.as_ptr() as *const T;
unsafe { from_raw_parts(ptr, N) }
}
}
/// Implement Follow for all possible Arrays that have Follow-able elements.
impl<'a, T: Follow<'a> + 'a, const N: usize> Follow<'a> for Array<'a, T, N> {
type Inner = Array<'a, T, N>;
#[inline(always)]
fn follow(buf: &'a [u8], loc: usize) -> Self::Inner {
Array::new(&buf[loc..loc + N * size_of::<T>()])
}
}
pub fn emplace_scalar_array<T: EndianScalar, const N: usize>(
buf: &mut [u8],
loc: usize,
src: &[T; N],
) {
let mut buf_ptr = buf[loc..].as_mut_ptr() as *mut T;
for item in src.iter() {
let item_le = item.to_little_endian();
unsafe {
buf_ptr.write(item_le);
buf_ptr = buf_ptr.add(1);
}
}
}
impl<'a, T: Follow<'a> + 'a, const N: usize> IntoIterator for Array<'a, T, N> {
type Item = T::Inner;
type IntoIter = VectorIter<'a, T>;
#[inline]
fn into_iter(self) -> Self::IntoIter {
self.iter()
}
}
#[inline]
pub fn array_init<F, T, const N: usize>(mut initializer: F) -> [T; N]
where
F: FnMut(usize) -> T,
{
let mut array: core::mem::MaybeUninit<[T; N]> = core::mem::MaybeUninit::uninit();
let mut ptr_i = array.as_mut_ptr() as *mut T;
unsafe {
for i in 0..N {
let value_i = initializer(i);
ptr_i.write(value_i);
ptr_i = ptr_i.add(1);
}
array.assume_init()
}
}

View File

@@ -253,12 +253,9 @@ impl<'fbb> FlatBufferBuilder<'fbb> {
// Gets The pointer to the size of the string
let str_memory = &buf[buf.len() - ptr..];
// Gets the size of the written string from buffer
let size = u32::from_le_bytes([
str_memory[0],
str_memory[1],
str_memory[2],
str_memory[3],
]) as usize;
let size =
u32::from_le_bytes([str_memory[0], str_memory[1], str_memory[2], str_memory[3]])
as usize;
// Size of the string size
let string_size: usize = 4;
// Fetches actual string bytes from index of string after string size
@@ -728,23 +725,21 @@ impl<'fbb> FlatBufferBuilder<'fbb> {
// could be empty (e.g. for empty tables, or for all-default values).
debug_assert!(
self.nested,
format!(
"incorrect FlatBufferBuilder usage: {} must be called while in a nested state",
fn_name
)
"incorrect FlatBufferBuilder usage: {} must be called while in a nested state",
fn_name
);
}
#[inline]
fn assert_not_nested(&self, msg: &'static str) {
debug_assert!(!self.nested, msg);
debug_assert!(!self.nested, "{}", msg);
}
#[inline]
fn assert_finished(&self, msg: &'static str) {
debug_assert!(self.finished, msg);
debug_assert!(self.finished, "{}", msg);
}
#[inline]
fn assert_not_finished(&self, msg: &'static str) {
debug_assert!(!self.finished, msg);
debug_assert!(!self.finished, "{}", msg);
}
}

View File

@@ -155,7 +155,7 @@ pub fn emplace_scalar<T: EndianScalar>(s: &mut [u8], x: T) {
core::ptr::copy_nonoverlapping(
&x_le as *const T as *const u8,
s.as_mut_ptr() as *mut u8,
size_of::<T>()
size_of::<T>(),
);
}
}
@@ -174,11 +174,7 @@ pub fn read_scalar<T: EndianScalar>(s: &[u8]) -> T {
let mut mem = core::mem::MaybeUninit::<T>::uninit();
// Since [u8] has alignment 1, we copy it into T which may have higher alignment.
let x = unsafe {
core::ptr::copy_nonoverlapping(
s.as_ptr(),
mem.as_mut_ptr() as *mut u8,
size_of::<T>()
);
core::ptr::copy_nonoverlapping(s.as_ptr(), mem.as_mut_ptr() as *mut u8, size_of::<T>());
mem.assume_init()
};
x.from_little_endian()

View File

@@ -28,6 +28,7 @@
//! At this time, to generate Rust code, you will need the latest `master` version of `flatc`, available from here: <https://github.com/google/flatbuffers>
//! (On OSX, you can install FlatBuffers from `HEAD` with the Homebrew package manager.)
mod array;
mod builder;
mod endian_scalar;
mod follow;
@@ -40,6 +41,7 @@ mod verifier;
mod vtable;
mod vtable_writer;
pub use crate::array::{array_init, emplace_scalar_array, Array};
pub use crate::builder::FlatBufferBuilder;
pub use crate::endian_scalar::{
byte_swap_f32, byte_swap_f64, emplace_scalar, read_scalar, read_scalar_at, EndianScalar,

View File

@@ -29,11 +29,15 @@ use crate::primitives::*;
pub struct Vector<'a, T: 'a>(&'a [u8], usize, PhantomData<T>);
impl<'a, T:'a> Default for Vector<'a, T> {
impl<'a, T: 'a> Default for Vector<'a, T> {
fn default() -> Self {
// Static, length 0 vector.
// Note that derived default causes UB due to issues in read_scalar_at /facepalm.
Self(&[0; core::mem::size_of::<UOffsetT>()], 0, Default::default())
Self(
&[0; core::mem::size_of::<UOffsetT>()],
0,
Default::default(),
)
}
}
@@ -88,7 +92,7 @@ impl<'a, T: Follow<'a> + 'a> Vector<'a, T> {
#[inline(always)]
pub fn iter(&self) -> VectorIter<'a, T> {
VectorIter::new(*self)
VectorIter::from_vector(*self)
}
}
@@ -185,7 +189,7 @@ pub struct VectorIter<'a, T: 'a> {
impl<'a, T: 'a> VectorIter<'a, T> {
#[inline]
pub fn new(inner: Vector<'a, T>) -> Self {
pub fn from_vector(inner: Vector<'a, T>) -> Self {
VectorIter {
buf: inner.0,
// inner.1 is the location of the data for the vector.
@@ -196,6 +200,16 @@ impl<'a, T: 'a> VectorIter<'a, T> {
phantom: PhantomData,
}
}
#[inline]
pub fn from_slice(buf: &'a [u8], items_num: usize) -> Self {
VectorIter {
buf,
loc: 0,
remaining: items_num,
phantom: PhantomData,
}
}
}
impl<'a, T: Follow<'a> + 'a> Clone for VectorIter<'a, T> {

View File

@@ -39,8 +39,10 @@ pub enum InvalidFlatbuffer {
required: &'static str,
error_trace: ErrorTrace,
},
#[error("Union exactly one of union discriminant (`{field_type}`) and value \
(`{field}`) are present.\n{error_trace}")]
#[error(
"Union exactly one of union discriminant (`{field_type}`) and value \
(`{field}`) are present.\n{error_trace}"
)]
InconsistentUnion {
field: &'static str,
field_type: &'static str,
@@ -70,8 +72,10 @@ pub enum InvalidFlatbuffer {
range: Range<usize>,
error_trace: ErrorTrace,
},
#[error("Signed offset at position {position} has value {soffset} which points out of bounds.\
\n{error_trace}")]
#[error(
"Signed offset at position {position} has value {soffset} which points out of bounds.\
\n{error_trace}"
)]
SignedOffsetOutOfBounds {
soffset: SOffsetT,
position: usize,