Skip to content

Commit 7bfaabc

Browse files
authored
[TS] Flexbuffers root vector fix (google#8847)
* fix and test * chatgpt help * consistent throws in reference.ts
1 parent e1407e4 commit 7bfaabc

File tree

2 files changed

+63
-14
lines changed

2 files changed

+63
-14
lines changed

tests/ts/JavaScriptFlexBuffersTest.js

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,31 @@ function main() {
1414
testRoundTripWithBuilder();
1515
testDeduplicationOff();
1616
testBugWhereOffestWereStoredAsIntInsteadOfUInt();
17+
testRootVector();
1718

1819
console.log('FlexBuffers test: completed successfully');
1920
}
2021

22+
function testRootVector() {
23+
// Root vector of strings
24+
const stringVec = ['a', 'b', 'c'];
25+
const bufStr = flexbuffers.encode(stringVec).buffer;
26+
const objStr = flexbuffers.toObject(bufStr);
27+
assert.deepStrictEqual(objStr, stringVec);
28+
29+
// Root vector of numbers
30+
const numVec = [1, 2, 3, 4];
31+
const bufNum = flexbuffers.encode(numVec).buffer;
32+
const objNum = flexbuffers.toObject(bufNum);
33+
assert.deepStrictEqual(objNum, numVec);
34+
35+
// Root vector of mixed types
36+
const mixedVec = ['x', 42, true, null];
37+
const bufMixed = flexbuffers.encode(mixedVec).buffer;
38+
const objMixed = flexbuffers.toObject(bufMixed);
39+
assert.deepStrictEqual(objMixed, mixedVec);
40+
}
41+
2142
function testSingleValueBuffers() {
2243
{
2344
const ref = flexbuffers.toReference(new Uint8Array([0, 0, 1]).buffer);

ts/flexbuffers/reference.ts

Lines changed: 42 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -26,14 +26,29 @@ export function toReference(buffer: ArrayBuffer): Reference {
2626
const len = buffer.byteLength;
2727

2828
if (len < 3) {
29-
throw 'Buffer needs to be bigger than 3';
29+
throw new Error('Buffer needs to be bigger than 3 bytes');
3030
}
3131

3232
const dataView = new DataView(buffer);
33-
const byteWidth = dataView.getUint8(len - 1);
33+
const rootByteWidth = dataView.getUint8(len - 1);
3434
const packedType = dataView.getUint8(len - 2);
35-
const parentWidth = fromByteWidth(byteWidth);
36-
const offset = len - byteWidth - 2;
35+
36+
let parentWidth = fromByteWidth(rootByteWidth);
37+
let offset = len - rootByteWidth - 2;
38+
39+
const rootValueType = packedType >> 2;
40+
if (
41+
rootValueType === ValueType.VECTOR ||
42+
rootValueType === ValueType.MAP ||
43+
rootValueType === ValueType.BLOB ||
44+
rootValueType === ValueType.STRING
45+
) {
46+
// Ensure parent width is wide enough to address the buffer
47+
let w = 1;
48+
while ((1 << (w * 8)) <= len && w < 8) w <<= 1;
49+
parentWidth = fromByteWidth(w);
50+
offset = len - w - 2; // no extra hacks
51+
}
3752

3853
return new Reference(dataView, offset, parentWidth, packedType, '/');
3954
}
@@ -182,20 +197,31 @@ export class Reference {
182197
const length = this.length();
183198
if (Number.isInteger(key) && isAVector(this.valueType)) {
184199
if (key >= length || key < 0) {
185-
throw `Key: [${key}] is not applicable on ${this.path} of ${this.valueType} length: ${length}`;
200+
throw new Error(`Key: [${key}] is not applicable on ${this.path} of ${this.valueType} length: ${length}`);
186201
}
187202
const _indirect = indirect(this.dataView, this.offset, this.parentWidth);
188203
const elementOffset = _indirect + key * this.byteWidth;
189-
let _packedType = this.dataView.getUint8(
190-
_indirect + length * this.byteWidth + key,
191-
);
204+
205+
let _packedType: ValueType;
206+
192207
if (isTypedVector(this.valueType)) {
193-
const _valueType = typedVectorElementType(this.valueType);
194-
_packedType = packedType(_valueType, BitWidth.WIDTH8);
195-
} else if (isFixedTypedVector(this.valueType)) {
196-
const _valueType = fixedTypedVectorElementType(this.valueType);
197-
_packedType = packedType(_valueType, BitWidth.WIDTH8);
208+
// Root typed vector: derive type instead of reading from buffer
209+
const _valueType = typedVectorElementType(this.valueType);
210+
_packedType = packedType(_valueType, BitWidth.WIDTH8);
211+
} else if (isFixedTypedVector(this.valueType)) {
212+
const _valueType = fixedTypedVectorElementType(this.valueType);
213+
_packedType = packedType(_valueType, BitWidth.WIDTH8);
214+
} else {
215+
// Only read packed type from buffer if it exists
216+
const typeOffset = _indirect + length * this.byteWidth + key;
217+
if (typeOffset < this.dataView.byteLength) {
218+
_packedType = this.dataView.getUint8(typeOffset);
219+
} else {
220+
// fallback for edge cases (e.g., root vectors)
221+
_packedType = this.packedType;
222+
}
198223
}
224+
199225
return new Reference(
200226
this.dataView,
201227
elementOffset,
@@ -204,6 +230,7 @@ export class Reference {
204230
`${this.path}[${key}]`,
205231
);
206232
}
233+
207234
if (typeof key === 'string') {
208235
const index = keyIndex(
209236
key,
@@ -226,7 +253,8 @@ export class Reference {
226253
);
227254
}
228255
}
229-
throw `Key [${key}] is not applicable on ${this.path} of ${this.valueType}`;
256+
257+
throw new Error(`Key [${key}] is not applicable on ${this.path} of ${this.valueType}`);
230258
}
231259

232260
length(): number {

0 commit comments

Comments
 (0)