From c0456daf3a2888ae41e7130300fd5b314c70613a Mon Sep 17 00:00:00 2001 From: Pieter De Baets Date: Wed, 1 Jul 2026 06:37:46 -0700 Subject: [PATCH 1/2] Skip memcpy when source vector is empty in MapBufferBuilder::build (#57398) Summary: `buckets_.data()` and `dynamicData_.data()` return `nullptr` when the vector is empty, and glibc marks `memcpy`'s src argument as `nonnull`. Passing `nullptr` is UB even with a size of 0, which trips UBSan halt-on-error on any MapBuffer that has no buckets (`EMPTY()`) or no dynamic-data entries (scalar-only maps). Guard both memcpys with an empty check. Changelog: [General][Fixed] - Avoid `memcpy(_, nullptr, 0)` UB in `MapBufferBuilder::build` for empty / scalar-only MapBuffers Reviewed By: cortinico Differential Revision: D110316404 --- .../renderer/mapbuffer/MapBufferBuilder.cpp | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/packages/react-native/ReactCommon/react/renderer/mapbuffer/MapBufferBuilder.cpp b/packages/react-native/ReactCommon/react/renderer/mapbuffer/MapBufferBuilder.cpp index eb6e859fec2..244f1065b52 100644 --- a/packages/react-native/ReactCommon/react/renderer/mapbuffer/MapBufferBuilder.cpp +++ b/packages/react-native/ReactCommon/react/renderer/mapbuffer/MapBufferBuilder.cpp @@ -183,11 +183,19 @@ MapBuffer MapBufferBuilder::build() { std::vector buffer(bufferSize); memcpy(buffer.data(), &header_, headerSize); - memcpy(buffer.data() + headerSize, buckets_.data(), bucketSize); - memcpy( - buffer.data() + headerSize + bucketSize, - dynamicData_.data(), - dynamicData_.size()); + // buckets_.data() / dynamicData_.data() return nullptr when the vector is + // empty; passing nullptr to memcpy is UB even with size 0 (glibc marks the + // src argument nonnull) and trips UBSan halt-on-error on empty / scalar-only + // MapBuffers. + if (!buckets_.empty()) { + memcpy(buffer.data() + headerSize, buckets_.data(), bucketSize); + } + if (!dynamicData_.empty()) { + memcpy( + buffer.data() + headerSize + bucketSize, + dynamicData_.data(), + dynamicData_.size()); + } return MapBuffer(std::move(buffer)); } From 28be5baacb08e961c02e789ea6f913f98873bfbe Mon Sep 17 00:00:00 2001 From: Pieter De Baets Date: Wed, 1 Jul 2026 06:37:46 -0700 Subject: [PATCH 2/2] Add IntBuffer and DoubleBuffer entry types to MapBuffer (#57359) Summary: Adds two new MapBuffer entry types, `IntBuffer` and `DoubleBuffer`, for storing homogeneous arrays of ints and doubles compactly in the dynamic data section. Unlike `Map` / map lists, these carry no per-element key/type overhead: a batch of N values costs ~N*elementSize bytes plus a single 4-byte count prefix instead of N 12-byte buckets. The bucket value holds the offset of the array within the dynamic data section. Covers the full surface: the C++ reader (`MapBuffer::getIntBuffer` / `getDoubleBuffer`), the C++ builder (`MapBufferBuilder::putIntBuffer` / `putDoubleBuffer`), and the Kotlin reader API (`MapBuffer.getIntBuffer` / `getDoubleBuffer`, `Entry.intBufferValue` / `doubleBufferValue`). The `DataType` enum gains `IntBuffer = 6` and `DoubleBuffer = 7`, kept in sync across C++ and Kotlin. Changelog: [General][Added] - Add `IntBuffer` and `DoubleBuffer` entry types to MapBuffer for compact homogeneous int/double arrays Reviewed By: zeyap Differential Revision: D109848476 --- .../ReactAndroid/api/ReactAndroid.api | 8 ++ .../react/common/mapbuffer/MapBuffer.kt | 40 ++++++++ .../common/mapbuffer/ReadableMapBuffer.kt | 34 +++++++ .../common/mapbuffer/WritableMapBuffer.kt | 10 ++ .../react/renderer/mapbuffer/MapBuffer.cpp | 40 ++++++++ .../react/renderer/mapbuffer/MapBuffer.h | 16 +++- .../renderer/mapbuffer/MapBufferBuilder.cpp | 46 +++++++++ .../renderer/mapbuffer/MapBufferBuilder.h | 4 + .../mapbuffer/tests/MapBufferTest.cpp | 95 +++++++++++++++++++ .../api-snapshots/ReactAndroidDebugCxx.api | 6 ++ .../api-snapshots/ReactAndroidNewarchCxx.api | 6 ++ .../api-snapshots/ReactAndroidReleaseCxx.api | 6 ++ .../api-snapshots/ReactAppleDebugCxx.api | 6 ++ .../api-snapshots/ReactAppleNewarchCxx.api | 6 ++ .../api-snapshots/ReactAppleReleaseCxx.api | 6 ++ .../api-snapshots/ReactCommonDebugCxx.api | 6 ++ .../api-snapshots/ReactCommonNewarchCxx.api | 6 ++ .../api-snapshots/ReactCommonReleaseCxx.api | 6 ++ 18 files changed, 345 insertions(+), 2 deletions(-) diff --git a/packages/react-native/ReactAndroid/api/ReactAndroid.api b/packages/react-native/ReactAndroid/api/ReactAndroid.api index 699c267339a..3eae29cea63 100644 --- a/packages/react-native/ReactAndroid/api/ReactAndroid.api +++ b/packages/react-native/ReactAndroid/api/ReactAndroid.api @@ -1665,7 +1665,9 @@ public abstract interface class com/facebook/react/common/mapbuffer/MapBuffer : public abstract fun getBoolean (I)Z public abstract fun getCount ()I public abstract fun getDouble (I)D + public abstract fun getDoubleBuffer (I)[D public abstract fun getInt (I)I + public abstract fun getIntBuffer (I)[I public abstract fun getKeyOffset (I)I public abstract fun getLong (I)J public abstract fun getMapBuffer (I)Lcom/facebook/react/common/mapbuffer/MapBuffer; @@ -1680,7 +1682,9 @@ public final class com/facebook/react/common/mapbuffer/MapBuffer$Companion { public final class com/facebook/react/common/mapbuffer/MapBuffer$DataType : java/lang/Enum { public static final field BOOL Lcom/facebook/react/common/mapbuffer/MapBuffer$DataType; public static final field DOUBLE Lcom/facebook/react/common/mapbuffer/MapBuffer$DataType; + public static final field DOUBLE_BUFFER Lcom/facebook/react/common/mapbuffer/MapBuffer$DataType; public static final field INT Lcom/facebook/react/common/mapbuffer/MapBuffer$DataType; + public static final field INT_BUFFER Lcom/facebook/react/common/mapbuffer/MapBuffer$DataType; public static final field LONG Lcom/facebook/react/common/mapbuffer/MapBuffer$DataType; public static final field MAP Lcom/facebook/react/common/mapbuffer/MapBuffer$DataType; public static final field STRING Lcom/facebook/react/common/mapbuffer/MapBuffer$DataType; @@ -1691,7 +1695,9 @@ public final class com/facebook/react/common/mapbuffer/MapBuffer$DataType : java public abstract interface class com/facebook/react/common/mapbuffer/MapBuffer$Entry { public abstract fun getBooleanValue ()Z + public abstract fun getDoubleBufferValue ()[D public abstract fun getDoubleValue ()D + public abstract fun getIntBufferValue ()[I public abstract fun getIntValue ()I public abstract fun getKey ()I public abstract fun getLongValue ()J @@ -1708,7 +1714,9 @@ public final class com/facebook/react/common/mapbuffer/ReadableMapBuffer : com/f public fun getBoolean (I)Z public fun getCount ()I public fun getDouble (I)D + public fun getDoubleBuffer (I)[D public fun getInt (I)I + public fun getIntBuffer (I)[I public fun getKeyOffset (I)I public fun getLong (I)J public synthetic fun getMapBuffer (I)Lcom/facebook/react/common/mapbuffer/MapBuffer; diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/common/mapbuffer/MapBuffer.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/common/mapbuffer/MapBuffer.kt index 44af587bd9f..17db9cd7a1d 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/common/mapbuffer/MapBuffer.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/common/mapbuffer/MapBuffer.kt @@ -46,6 +46,8 @@ public interface MapBuffer : Iterable { STRING, MAP, LONG, + INT_BUFFER, + DOUBLE_BUFFER, } /** @@ -161,6 +163,30 @@ public interface MapBuffer : Iterable { */ public fun getMapBufferList(key: Int): List + /** + * Provides parsed [IntArray] value if the entry for given key exists with [DataType.INT_BUFFER] + * type. This is a compact representation of a homogeneous list of ints with no per-element + * key/type overhead. + * + * @param key key to lookup the [IntArray] value for + * @return value associated with the requested key + * @throws IllegalArgumentException if the key doesn't exist + * @throws IllegalStateException if the data type doesn't match + */ + public fun getIntBuffer(key: Int): IntArray + + /** + * Provides parsed [DoubleArray] value if the entry for given key exists with + * [DataType.DOUBLE_BUFFER] type. This is a compact representation of a homogeneous list of + * doubles with no per-element key/type overhead. + * + * @param key key to lookup the [DoubleArray] value for + * @return value associated with the requested key + * @throws IllegalArgumentException if the key doesn't exist + * @throws IllegalStateException if the data type doesn't match + */ + public fun getDoubleBuffer(key: Int): DoubleArray + /** Iterable entry representing parsed MapBuffer values */ public interface Entry { /** @@ -213,5 +239,19 @@ public interface MapBuffer : Iterable { * @throws IllegalStateException if the data type doesn't match [DataType.MAP] */ public val mapBufferValue: MapBuffer + + /** + * Entry value represented as [IntArray] + * + * @throws IllegalStateException if the data type doesn't match [DataType.INT_BUFFER] + */ + public val intBufferValue: IntArray + + /** + * Entry value represented as [DoubleArray] + * + * @throws IllegalStateException if the data type doesn't match [DataType.DOUBLE_BUFFER] + */ + public val doubleBufferValue: DoubleArray } } diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/common/mapbuffer/ReadableMapBuffer.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/common/mapbuffer/ReadableMapBuffer.kt index c73ccb6da1d..79d701fadd0 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/common/mapbuffer/ReadableMapBuffer.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/common/mapbuffer/ReadableMapBuffer.kt @@ -152,6 +152,20 @@ private constructor( return readMapBufferList } + private fun readIntBufferValue(bufferPosition: Int): IntArray { + var offset = offsetForDynamicData + buffer.getInt(bufferPosition) + val count = buffer.getInt(offset) + offset += Int.SIZE_BYTES + return IntArray(count) { i -> buffer.getInt(offset + i * Int.SIZE_BYTES) } + } + + private fun readDoubleBufferValue(bufferPosition: Int): DoubleArray { + var offset = offsetForDynamicData + buffer.getInt(bufferPosition) + val count = buffer.getInt(offset) + offset += Int.SIZE_BYTES + return DoubleArray(count) { i -> buffer.getDouble(offset + i * Double.SIZE_BYTES) } + } + private fun getKeyOffsetForBucketIndex(bucketIndex: Int): Int { return offsetToMapBuffer + HEADER_SIZE + BUCKET_SIZE * bucketIndex } @@ -193,6 +207,12 @@ private constructor( override fun getMapBufferList(key: Int): List = readMapBufferListValue(getTypedValueOffsetForKey(key, MapBuffer.DataType.MAP)) + override fun getIntBuffer(key: Int): IntArray = + readIntBufferValue(getTypedValueOffsetForKey(key, MapBuffer.DataType.INT_BUFFER)) + + override fun getDoubleBuffer(key: Int): DoubleArray = + readDoubleBufferValue(getTypedValueOffsetForKey(key, MapBuffer.DataType.DOUBLE_BUFFER)) + override fun hashCode(): Int { buffer.rewind() return buffer.hashCode() @@ -229,6 +249,8 @@ private constructor( append('"') } MapBuffer.DataType.MAP -> append(entry.mapBufferValue.toString()) + MapBuffer.DataType.INT_BUFFER -> append(entry.intBufferValue.contentToString()) + MapBuffer.DataType.DOUBLE_BUFFER -> append(entry.doubleBufferValue.contentToString()) } } } @@ -311,6 +333,18 @@ private constructor( assertType(MapBuffer.DataType.MAP) return readMapBufferValue(bucketOffset + VALUE_OFFSET) } + + override val intBufferValue: IntArray + get() { + assertType(MapBuffer.DataType.INT_BUFFER) + return readIntBufferValue(bucketOffset + VALUE_OFFSET) + } + + override val doubleBufferValue: DoubleArray + get() { + assertType(MapBuffer.DataType.DOUBLE_BUFFER) + return readDoubleBufferValue(bucketOffset + VALUE_OFFSET) + } } public companion object { diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/common/mapbuffer/WritableMapBuffer.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/common/mapbuffer/WritableMapBuffer.kt index d75e8a39a8a..d02283bb54f 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/common/mapbuffer/WritableMapBuffer.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/common/mapbuffer/WritableMapBuffer.kt @@ -126,6 +126,10 @@ internal class WritableMapBuffer : MapBuffer { override fun getMapBufferList(key: Int): List = verifyValue(key, values.get(key)) + override fun getIntBuffer(key: Int): IntArray = verifyValue(key, values.get(key)) + + override fun getDoubleBuffer(key: Int): DoubleArray = verifyValue(key, values.get(key)) + /** Generalizes verification of the value types based on the requested type. */ private inline fun verifyValue(key: Int, value: Any?): T { require(value != null) { "Key not found: $key" } @@ -176,6 +180,12 @@ internal class WritableMapBuffer : MapBuffer { override val mapBufferValue: MapBuffer get() = verifyValue(key, values.valueAt(index)) + + override val intBufferValue: IntArray + get() = verifyValue(key, values.valueAt(index)) + + override val doubleBufferValue: DoubleArray + get() = verifyValue(key, values.valueAt(index)) } /* diff --git a/packages/react-native/ReactCommon/react/renderer/mapbuffer/MapBuffer.cpp b/packages/react-native/ReactCommon/react/renderer/mapbuffer/MapBuffer.cpp index 401a6670d03..e7c0a97ffe5 100644 --- a/packages/react-native/ReactCommon/react/renderer/mapbuffer/MapBuffer.cpp +++ b/packages/react-native/ReactCommon/react/renderer/mapbuffer/MapBuffer.cpp @@ -163,6 +163,46 @@ std::vector MapBuffer::getMapBufferList(MapBuffer::Key key) const { return mapBufferList; } +std::vector MapBuffer::getIntBuffer(MapBuffer::Key key) const { + auto bucketIndex = getKeyBucket(key); + react_native_assert(bucketIndex != -1 && "Key not found in MapBuffer"); + if (bucketIndex == -1) { + return {}; + } + + int32_t offset = getDynamicDataOffset() + getIntAtBucket(bucketIndex); + int32_t count = *reinterpret_cast(bytes_.data() + offset); + + std::vector result(count); + if (count > 0) { + memcpy( + result.data(), + bytes_.data() + offset + sizeof(int32_t), + static_cast(count) * sizeof(int32_t)); + } + return result; +} + +std::vector MapBuffer::getDoubleBuffer(MapBuffer::Key key) const { + auto bucketIndex = getKeyBucket(key); + react_native_assert(bucketIndex != -1 && "Key not found in MapBuffer"); + if (bucketIndex == -1) { + return {}; + } + + int32_t offset = getDynamicDataOffset() + getIntAtBucket(bucketIndex); + int32_t count = *reinterpret_cast(bytes_.data() + offset); + + std::vector result(count); + if (count > 0) { + memcpy( + result.data(), + bytes_.data() + offset + sizeof(int32_t), + static_cast(count) * sizeof(double)); + } + return result; +} + size_t MapBuffer::size() const { return bytes_.size(); } diff --git a/packages/react-native/ReactCommon/react/renderer/mapbuffer/MapBuffer.h b/packages/react-native/ReactCommon/react/renderer/mapbuffer/MapBuffer.h index 1ae3595368c..2d920af86e8 100644 --- a/packages/react-native/ReactCommon/react/renderer/mapbuffer/MapBuffer.h +++ b/packages/react-native/ReactCommon/react/renderer/mapbuffer/MapBuffer.h @@ -94,8 +94,9 @@ class MapBuffer { /** * Data types available for serialization in MapBuffer - * Keep in sync with `DataType` enum in `JReadableMapBuffer.java`, which - * expects the same values after reading them through JNI. + * Keep in sync with the `DataType` enum in `MapBuffer.kt` + * (packages/react-native/ReactAndroid/.../common/mapbuffer/MapBuffer.kt), + * which is ordinal-indexed on the JVM side, so the order must match exactly. */ enum DataType : uint16_t { Boolean = 0, @@ -104,6 +105,13 @@ class MapBuffer { String = 3, Map = 4, Long = 5, + // Homogeneous, length-prefixed arrays stored contiguously in the dynamic + // data section. Unlike Map, they carry no per-element key/type overhead, so + // a batch of N values costs ~N*elementSize bytes plus a single 4-byte count + // prefix instead of N*12-byte buckets. The bucket value is the offset of the + // array within the dynamic data section. + IntBuffer = 6, + DoubleBuffer = 7, }; explicit MapBuffer(std::vector data); @@ -131,6 +139,10 @@ class MapBuffer { std::vector getMapBufferList(MapBuffer::Key key) const; + std::vector getIntBuffer(MapBuffer::Key key) const; + + std::vector getDoubleBuffer(MapBuffer::Key key) const; + size_t size() const; const uint8_t *data() const; diff --git a/packages/react-native/ReactCommon/react/renderer/mapbuffer/MapBufferBuilder.cpp b/packages/react-native/ReactCommon/react/renderer/mapbuffer/MapBufferBuilder.cpp index 244f1065b52..c0e77bdbb5d 100644 --- a/packages/react-native/ReactCommon/react/renderer/mapbuffer/MapBufferBuilder.cpp +++ b/packages/react-native/ReactCommon/react/renderer/mapbuffer/MapBufferBuilder.cpp @@ -161,6 +161,52 @@ void MapBufferBuilder::putMapBufferList( INT_SIZE); } +void MapBufferBuilder::putIntBuffer( + MapBuffer::Key key, + const std::vector& value) { + // Wire format: [element count (int32)] + [count * int32]. The count is the + // number of elements, not bytes; see MapBuffer::getIntBuffer. + auto count = static_cast(value.size()); + auto payloadSize = static_cast(value.size() * sizeof(int32_t)); + + auto offset = static_cast(dynamicData_.size()); + dynamicData_.resize(offset + INT_SIZE + payloadSize, 0); + memcpy(dynamicData_.data() + offset, &count, INT_SIZE); + if (payloadSize > 0) { + memcpy(dynamicData_.data() + offset + INT_SIZE, value.data(), payloadSize); + } + + storeKeyValue( + key, + MapBuffer::DataType::IntBuffer, + reinterpret_cast(&offset), + INT_SIZE); +} + +void MapBufferBuilder::putDoubleBuffer( + MapBuffer::Key key, + const std::vector& value) { + // Wire format: [element count (int32)] + [count * double]. Doubles are copied + // byte-for-byte; the reader uses memcpy, so the payload needs no special + // alignment for correctness. A consumer that wants a zero-copy typed view on + // the JVM (ByteBuffer::asDoubleBuffer) must ensure 8-byte alignment itself. + auto count = static_cast(value.size()); + auto payloadSize = static_cast(value.size() * sizeof(double)); + + auto offset = static_cast(dynamicData_.size()); + dynamicData_.resize(offset + INT_SIZE + payloadSize, 0); + memcpy(dynamicData_.data() + offset, &count, INT_SIZE); + if (payloadSize > 0) { + memcpy(dynamicData_.data() + offset + INT_SIZE, value.data(), payloadSize); + } + + storeKeyValue( + key, + MapBuffer::DataType::DoubleBuffer, + reinterpret_cast(&offset), + INT_SIZE); +} + static inline bool compareBuckets( const MapBuffer::Bucket& a, const MapBuffer::Bucket& b) { diff --git a/packages/react-native/ReactCommon/react/renderer/mapbuffer/MapBufferBuilder.h b/packages/react-native/ReactCommon/react/renderer/mapbuffer/MapBufferBuilder.h index af054da5578..2963c77a9b3 100644 --- a/packages/react-native/ReactCommon/react/renderer/mapbuffer/MapBufferBuilder.h +++ b/packages/react-native/ReactCommon/react/renderer/mapbuffer/MapBufferBuilder.h @@ -39,6 +39,10 @@ class MapBufferBuilder { void putMapBufferList(MapBuffer::Key key, const std::vector &mapBufferList); + void putIntBuffer(MapBuffer::Key key, const std::vector &value); + + void putDoubleBuffer(MapBuffer::Key key, const std::vector &value); + MapBuffer build(); private: diff --git a/packages/react-native/ReactCommon/react/renderer/mapbuffer/tests/MapBufferTest.cpp b/packages/react-native/ReactCommon/react/renderer/mapbuffer/tests/MapBufferTest.cpp index 3cc169563fc..ce4fd6237c0 100644 --- a/packages/react-native/ReactCommon/react/renderer/mapbuffer/tests/MapBufferTest.cpp +++ b/packages/react-native/ReactCommon/react/renderer/mapbuffer/tests/MapBufferTest.cpp @@ -205,6 +205,101 @@ TEST(MapBufferTest, testMapListEntries) { EXPECT_EQ(mapBufferList2[1].getDouble(3), 908.1); } +TEST(MapBufferTest, testEmptyMapBufferList) { + auto builder = MapBufferBuilder(); + + builder.putMapBufferList(0, {}); + auto map = builder.build(); + + EXPECT_EQ(map.getMapBufferList(0).size(), 0); +} + +// Place the list behind another dynamic-data entry so its offset is non-zero, +// exercising `getDynamicDataOffset() + getIntAtBucket(...)` against a non-zero +// base rather than the zero-offset path testMapListEntries covers. +TEST(MapBufferTest, testMapListEntriesAtNonZeroOffset) { + std::vector mapBufferList; + auto inner = MapBufferBuilder(); + inner.putString(0, "inner"); + inner.putInt(1, 42); + mapBufferList.push_back(inner.build()); + + auto builder = MapBufferBuilder(); + builder.putString(0, "prefix"); + builder.putMapBufferList(1, mapBufferList); + auto map = builder.build(); + + EXPECT_EQ(map.getString(0), "prefix"); + std::vector readList = map.getMapBufferList(1); + EXPECT_EQ(readList.size(), 1); + EXPECT_EQ(readList[0].getString(0), "inner"); + EXPECT_EQ(readList[0].getInt(1), 42); +} + +TEST(MapBufferTest, testIntBufferEntries) { + auto builder = MapBufferBuilder(); + + std::vector values{ + 1, + -2, + 3, + std::numeric_limits::min(), + std::numeric_limits::max()}; + builder.putIntBuffer(0, values); + auto map = builder.build(); + + EXPECT_EQ(map.count(), 1); + EXPECT_EQ(map.getIntBuffer(0), values); +} + +TEST(MapBufferTest, testEmptyIntBuffer) { + auto builder = MapBufferBuilder(); + + builder.putIntBuffer(0, {}); + auto map = builder.build(); + + EXPECT_EQ(map.getIntBuffer(0).size(), 0); +} + +TEST(MapBufferTest, testDoubleBufferEntries) { + auto builder = MapBufferBuilder(); + + std::vector values{0.0, -1.5, 3.14159, 1e300, -1e-300}; + builder.putDoubleBuffer(0, values); + auto map = builder.build(); + + EXPECT_EQ(map.count(), 1); + EXPECT_EQ(map.getDoubleBuffer(0), values); +} + +TEST(MapBufferTest, testEmptyDoubleBuffer) { + auto builder = MapBufferBuilder(); + + builder.putDoubleBuffer(0, {}); + auto map = builder.build(); + + EXPECT_EQ(map.getDoubleBuffer(0).size(), 0); +} + +// Mirrors the batched-animated-props use case: a pair of typed streams plus +// some scalar metadata, with keys inserted out of order to exercise both the +// dynamic-data section and the bucket sort path. +TEST(MapBufferTest, testIntAndDoubleBuffersAlongsideScalars) { + std::vector intStream{1, 100, 1, 2, 4, 15, 4}; + std::vector doubleStream{0.5, 12.0, 0.25}; + + auto builder = MapBufferBuilder(); + builder.putDoubleBuffer(2, doubleStream); + builder.putInt(0, 7); + builder.putIntBuffer(1, intStream); + auto map = builder.build(); + + EXPECT_EQ(map.count(), 3); + EXPECT_EQ(map.getInt(0), 7); + EXPECT_EQ(map.getIntBuffer(1), intStream); + EXPECT_EQ(map.getDoubleBuffer(2), doubleStream); +} + TEST(MapBufferTest, testMapRandomAccess) { auto builder = MapBufferBuilder(); builder.putInt(1234, 4321); diff --git a/scripts/cxx-api/api-snapshots/ReactAndroidDebugCxx.api b/scripts/cxx-api/api-snapshots/ReactAndroidDebugCxx.api index 9a5ce2066c8..9a073133f87 100644 --- a/scripts/cxx-api/api-snapshots/ReactAndroidDebugCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactAndroidDebugCxx.api @@ -3254,7 +3254,9 @@ class facebook::react::MapBuffer { public size_t size() const; public static constexpr uint16_t HEADER_ALIGNMENT; public std::string getString(facebook::react::MapBuffer::Key key) const; + public std::vector getDoubleBuffer(facebook::react::MapBuffer::Key key) const; public std::vector getMapBufferList(facebook::react::MapBuffer::Key key) const; + public std::vector getIntBuffer(facebook::react::MapBuffer::Key key) const; public uint16_t count() const; public using Key = uint16_t; } @@ -3262,7 +3264,9 @@ class facebook::react::MapBuffer { enum facebook::react::MapBuffer::DataType : uint16_t { Boolean, Double, + DoubleBuffer, Int, + IntBuffer, Long, Map, String, @@ -3287,7 +3291,9 @@ class facebook::react::MapBufferBuilder { public static facebook::react::MapBuffer EMPTY(); public void putBool(facebook::react::MapBuffer::Key key, bool value); public void putDouble(facebook::react::MapBuffer::Key key, double value); + public void putDoubleBuffer(facebook::react::MapBuffer::Key key, const std::vector& value); public void putInt(facebook::react::MapBuffer::Key key, int32_t value); + public void putIntBuffer(facebook::react::MapBuffer::Key key, const std::vector& value); public void putLong(facebook::react::MapBuffer::Key key, int64_t value); public void putMapBuffer(facebook::react::MapBuffer::Key key, const facebook::react::MapBuffer& map); public void putMapBufferList(facebook::react::MapBuffer::Key key, const std::vector& mapBufferList); diff --git a/scripts/cxx-api/api-snapshots/ReactAndroidNewarchCxx.api b/scripts/cxx-api/api-snapshots/ReactAndroidNewarchCxx.api index ff685621148..b5c182e4a2a 100644 --- a/scripts/cxx-api/api-snapshots/ReactAndroidNewarchCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactAndroidNewarchCxx.api @@ -3163,7 +3163,9 @@ class facebook::react::MapBuffer { public size_t size() const; public static constexpr uint16_t HEADER_ALIGNMENT; public std::string getString(facebook::react::MapBuffer::Key key) const; + public std::vector getDoubleBuffer(facebook::react::MapBuffer::Key key) const; public std::vector getMapBufferList(facebook::react::MapBuffer::Key key) const; + public std::vector getIntBuffer(facebook::react::MapBuffer::Key key) const; public uint16_t count() const; public using Key = uint16_t; } @@ -3171,7 +3173,9 @@ class facebook::react::MapBuffer { enum facebook::react::MapBuffer::DataType : uint16_t { Boolean, Double, + DoubleBuffer, Int, + IntBuffer, Long, Map, String, @@ -3196,7 +3200,9 @@ class facebook::react::MapBufferBuilder { public static facebook::react::MapBuffer EMPTY(); public void putBool(facebook::react::MapBuffer::Key key, bool value); public void putDouble(facebook::react::MapBuffer::Key key, double value); + public void putDoubleBuffer(facebook::react::MapBuffer::Key key, const std::vector& value); public void putInt(facebook::react::MapBuffer::Key key, int32_t value); + public void putIntBuffer(facebook::react::MapBuffer::Key key, const std::vector& value); public void putLong(facebook::react::MapBuffer::Key key, int64_t value); public void putMapBuffer(facebook::react::MapBuffer::Key key, const facebook::react::MapBuffer& map); public void putMapBufferList(facebook::react::MapBuffer::Key key, const std::vector& mapBufferList); diff --git a/scripts/cxx-api/api-snapshots/ReactAndroidReleaseCxx.api b/scripts/cxx-api/api-snapshots/ReactAndroidReleaseCxx.api index 25e566d2466..c65773c3069 100644 --- a/scripts/cxx-api/api-snapshots/ReactAndroidReleaseCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactAndroidReleaseCxx.api @@ -3251,7 +3251,9 @@ class facebook::react::MapBuffer { public size_t size() const; public static constexpr uint16_t HEADER_ALIGNMENT; public std::string getString(facebook::react::MapBuffer::Key key) const; + public std::vector getDoubleBuffer(facebook::react::MapBuffer::Key key) const; public std::vector getMapBufferList(facebook::react::MapBuffer::Key key) const; + public std::vector getIntBuffer(facebook::react::MapBuffer::Key key) const; public uint16_t count() const; public using Key = uint16_t; } @@ -3259,7 +3261,9 @@ class facebook::react::MapBuffer { enum facebook::react::MapBuffer::DataType : uint16_t { Boolean, Double, + DoubleBuffer, Int, + IntBuffer, Long, Map, String, @@ -3284,7 +3288,9 @@ class facebook::react::MapBufferBuilder { public static facebook::react::MapBuffer EMPTY(); public void putBool(facebook::react::MapBuffer::Key key, bool value); public void putDouble(facebook::react::MapBuffer::Key key, double value); + public void putDoubleBuffer(facebook::react::MapBuffer::Key key, const std::vector& value); public void putInt(facebook::react::MapBuffer::Key key, int32_t value); + public void putIntBuffer(facebook::react::MapBuffer::Key key, const std::vector& value); public void putLong(facebook::react::MapBuffer::Key key, int64_t value); public void putMapBuffer(facebook::react::MapBuffer::Key key, const facebook::react::MapBuffer& map); public void putMapBufferList(facebook::react::MapBuffer::Key key, const std::vector& mapBufferList); diff --git a/scripts/cxx-api/api-snapshots/ReactAppleDebugCxx.api b/scripts/cxx-api/api-snapshots/ReactAppleDebugCxx.api index 14984a9b86d..f532e5b1a25 100644 --- a/scripts/cxx-api/api-snapshots/ReactAppleDebugCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactAppleDebugCxx.api @@ -5484,7 +5484,9 @@ class facebook::react::MapBuffer { public size_t size() const; public static constexpr uint16_t HEADER_ALIGNMENT; public std::string getString(facebook::react::MapBuffer::Key key) const; + public std::vector getDoubleBuffer(facebook::react::MapBuffer::Key key) const; public std::vector getMapBufferList(facebook::react::MapBuffer::Key key) const; + public std::vector getIntBuffer(facebook::react::MapBuffer::Key key) const; public uint16_t count() const; public using Key = uint16_t; } @@ -5492,7 +5494,9 @@ class facebook::react::MapBuffer { enum facebook::react::MapBuffer::DataType : uint16_t { Boolean, Double, + DoubleBuffer, Int, + IntBuffer, Long, Map, String, @@ -5517,7 +5521,9 @@ class facebook::react::MapBufferBuilder { public static facebook::react::MapBuffer EMPTY(); public void putBool(facebook::react::MapBuffer::Key key, bool value); public void putDouble(facebook::react::MapBuffer::Key key, double value); + public void putDoubleBuffer(facebook::react::MapBuffer::Key key, const std::vector& value); public void putInt(facebook::react::MapBuffer::Key key, int32_t value); + public void putIntBuffer(facebook::react::MapBuffer::Key key, const std::vector& value); public void putLong(facebook::react::MapBuffer::Key key, int64_t value); public void putMapBuffer(facebook::react::MapBuffer::Key key, const facebook::react::MapBuffer& map); public void putMapBufferList(facebook::react::MapBuffer::Key key, const std::vector& mapBufferList); diff --git a/scripts/cxx-api/api-snapshots/ReactAppleNewarchCxx.api b/scripts/cxx-api/api-snapshots/ReactAppleNewarchCxx.api index ec05c677501..0d6be3e03d3 100644 --- a/scripts/cxx-api/api-snapshots/ReactAppleNewarchCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactAppleNewarchCxx.api @@ -5408,7 +5408,9 @@ class facebook::react::MapBuffer { public size_t size() const; public static constexpr uint16_t HEADER_ALIGNMENT; public std::string getString(facebook::react::MapBuffer::Key key) const; + public std::vector getDoubleBuffer(facebook::react::MapBuffer::Key key) const; public std::vector getMapBufferList(facebook::react::MapBuffer::Key key) const; + public std::vector getIntBuffer(facebook::react::MapBuffer::Key key) const; public uint16_t count() const; public using Key = uint16_t; } @@ -5416,7 +5418,9 @@ class facebook::react::MapBuffer { enum facebook::react::MapBuffer::DataType : uint16_t { Boolean, Double, + DoubleBuffer, Int, + IntBuffer, Long, Map, String, @@ -5441,7 +5445,9 @@ class facebook::react::MapBufferBuilder { public static facebook::react::MapBuffer EMPTY(); public void putBool(facebook::react::MapBuffer::Key key, bool value); public void putDouble(facebook::react::MapBuffer::Key key, double value); + public void putDoubleBuffer(facebook::react::MapBuffer::Key key, const std::vector& value); public void putInt(facebook::react::MapBuffer::Key key, int32_t value); + public void putIntBuffer(facebook::react::MapBuffer::Key key, const std::vector& value); public void putLong(facebook::react::MapBuffer::Key key, int64_t value); public void putMapBuffer(facebook::react::MapBuffer::Key key, const facebook::react::MapBuffer& map); public void putMapBufferList(facebook::react::MapBuffer::Key key, const std::vector& mapBufferList); diff --git a/scripts/cxx-api/api-snapshots/ReactAppleReleaseCxx.api b/scripts/cxx-api/api-snapshots/ReactAppleReleaseCxx.api index ca2d1156795..27acb0ef68b 100644 --- a/scripts/cxx-api/api-snapshots/ReactAppleReleaseCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactAppleReleaseCxx.api @@ -5481,7 +5481,9 @@ class facebook::react::MapBuffer { public size_t size() const; public static constexpr uint16_t HEADER_ALIGNMENT; public std::string getString(facebook::react::MapBuffer::Key key) const; + public std::vector getDoubleBuffer(facebook::react::MapBuffer::Key key) const; public std::vector getMapBufferList(facebook::react::MapBuffer::Key key) const; + public std::vector getIntBuffer(facebook::react::MapBuffer::Key key) const; public uint16_t count() const; public using Key = uint16_t; } @@ -5489,7 +5491,9 @@ class facebook::react::MapBuffer { enum facebook::react::MapBuffer::DataType : uint16_t { Boolean, Double, + DoubleBuffer, Int, + IntBuffer, Long, Map, String, @@ -5514,7 +5518,9 @@ class facebook::react::MapBufferBuilder { public static facebook::react::MapBuffer EMPTY(); public void putBool(facebook::react::MapBuffer::Key key, bool value); public void putDouble(facebook::react::MapBuffer::Key key, double value); + public void putDoubleBuffer(facebook::react::MapBuffer::Key key, const std::vector& value); public void putInt(facebook::react::MapBuffer::Key key, int32_t value); + public void putIntBuffer(facebook::react::MapBuffer::Key key, const std::vector& value); public void putLong(facebook::react::MapBuffer::Key key, int64_t value); public void putMapBuffer(facebook::react::MapBuffer::Key key, const facebook::react::MapBuffer& map); public void putMapBufferList(facebook::react::MapBuffer::Key key, const std::vector& mapBufferList); diff --git a/scripts/cxx-api/api-snapshots/ReactCommonDebugCxx.api b/scripts/cxx-api/api-snapshots/ReactCommonDebugCxx.api index 4f98056312e..fdc2fcf781f 100644 --- a/scripts/cxx-api/api-snapshots/ReactCommonDebugCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactCommonDebugCxx.api @@ -2169,7 +2169,9 @@ class facebook::react::MapBuffer { public size_t size() const; public static constexpr uint16_t HEADER_ALIGNMENT; public std::string getString(facebook::react::MapBuffer::Key key) const; + public std::vector getDoubleBuffer(facebook::react::MapBuffer::Key key) const; public std::vector getMapBufferList(facebook::react::MapBuffer::Key key) const; + public std::vector getIntBuffer(facebook::react::MapBuffer::Key key) const; public uint16_t count() const; public using Key = uint16_t; } @@ -2177,7 +2179,9 @@ class facebook::react::MapBuffer { enum facebook::react::MapBuffer::DataType : uint16_t { Boolean, Double, + DoubleBuffer, Int, + IntBuffer, Long, Map, String, @@ -2202,7 +2206,9 @@ class facebook::react::MapBufferBuilder { public static facebook::react::MapBuffer EMPTY(); public void putBool(facebook::react::MapBuffer::Key key, bool value); public void putDouble(facebook::react::MapBuffer::Key key, double value); + public void putDoubleBuffer(facebook::react::MapBuffer::Key key, const std::vector& value); public void putInt(facebook::react::MapBuffer::Key key, int32_t value); + public void putIntBuffer(facebook::react::MapBuffer::Key key, const std::vector& value); public void putLong(facebook::react::MapBuffer::Key key, int64_t value); public void putMapBuffer(facebook::react::MapBuffer::Key key, const facebook::react::MapBuffer& map); public void putMapBufferList(facebook::react::MapBuffer::Key key, const std::vector& mapBufferList); diff --git a/scripts/cxx-api/api-snapshots/ReactCommonNewarchCxx.api b/scripts/cxx-api/api-snapshots/ReactCommonNewarchCxx.api index 16d1eb0c64a..080a39d18fd 100644 --- a/scripts/cxx-api/api-snapshots/ReactCommonNewarchCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactCommonNewarchCxx.api @@ -2105,7 +2105,9 @@ class facebook::react::MapBuffer { public size_t size() const; public static constexpr uint16_t HEADER_ALIGNMENT; public std::string getString(facebook::react::MapBuffer::Key key) const; + public std::vector getDoubleBuffer(facebook::react::MapBuffer::Key key) const; public std::vector getMapBufferList(facebook::react::MapBuffer::Key key) const; + public std::vector getIntBuffer(facebook::react::MapBuffer::Key key) const; public uint16_t count() const; public using Key = uint16_t; } @@ -2113,7 +2115,9 @@ class facebook::react::MapBuffer { enum facebook::react::MapBuffer::DataType : uint16_t { Boolean, Double, + DoubleBuffer, Int, + IntBuffer, Long, Map, String, @@ -2138,7 +2142,9 @@ class facebook::react::MapBufferBuilder { public static facebook::react::MapBuffer EMPTY(); public void putBool(facebook::react::MapBuffer::Key key, bool value); public void putDouble(facebook::react::MapBuffer::Key key, double value); + public void putDoubleBuffer(facebook::react::MapBuffer::Key key, const std::vector& value); public void putInt(facebook::react::MapBuffer::Key key, int32_t value); + public void putIntBuffer(facebook::react::MapBuffer::Key key, const std::vector& value); public void putLong(facebook::react::MapBuffer::Key key, int64_t value); public void putMapBuffer(facebook::react::MapBuffer::Key key, const facebook::react::MapBuffer& map); public void putMapBufferList(facebook::react::MapBuffer::Key key, const std::vector& mapBufferList); diff --git a/scripts/cxx-api/api-snapshots/ReactCommonReleaseCxx.api b/scripts/cxx-api/api-snapshots/ReactCommonReleaseCxx.api index 23e2b116a3f..ed14fdb1415 100644 --- a/scripts/cxx-api/api-snapshots/ReactCommonReleaseCxx.api +++ b/scripts/cxx-api/api-snapshots/ReactCommonReleaseCxx.api @@ -2166,7 +2166,9 @@ class facebook::react::MapBuffer { public size_t size() const; public static constexpr uint16_t HEADER_ALIGNMENT; public std::string getString(facebook::react::MapBuffer::Key key) const; + public std::vector getDoubleBuffer(facebook::react::MapBuffer::Key key) const; public std::vector getMapBufferList(facebook::react::MapBuffer::Key key) const; + public std::vector getIntBuffer(facebook::react::MapBuffer::Key key) const; public uint16_t count() const; public using Key = uint16_t; } @@ -2174,7 +2176,9 @@ class facebook::react::MapBuffer { enum facebook::react::MapBuffer::DataType : uint16_t { Boolean, Double, + DoubleBuffer, Int, + IntBuffer, Long, Map, String, @@ -2199,7 +2203,9 @@ class facebook::react::MapBufferBuilder { public static facebook::react::MapBuffer EMPTY(); public void putBool(facebook::react::MapBuffer::Key key, bool value); public void putDouble(facebook::react::MapBuffer::Key key, double value); + public void putDoubleBuffer(facebook::react::MapBuffer::Key key, const std::vector& value); public void putInt(facebook::react::MapBuffer::Key key, int32_t value); + public void putIntBuffer(facebook::react::MapBuffer::Key key, const std::vector& value); public void putLong(facebook::react::MapBuffer::Key key, int64_t value); public void putMapBuffer(facebook::react::MapBuffer::Key key, const facebook::react::MapBuffer& map); public void putMapBufferList(facebook::react::MapBuffer::Key key, const std::vector& mapBufferList);