Skip to content

Commit 1318f99

Browse files
authored
Stabilize Dart (#50)
## Summary Fixes #49 - Added tests for Dart, just like Golang - Made bool check more strict - Fixed some parts that were reading container length as Int64, not Uint64: This wasn't a problem when the number is small(0x00000000~0x01111111), but was one of the problems that were causing the tests to fail - Made serialization/deserialization consider max bytes length - Made serialization/deserialization consider container depth - Implemented `sortMapEntries` for BCS - Bumped Dart versions in the CI and package config to 3.5.3(>=3.0.0, <4.0.0) ## Test Plan Since the tests were already defined in Golang, I've written some extra Dart tests that follows the way Golang does.
1 parent 8f003c4 commit 1318f99

File tree

10 files changed

+319
-91
lines changed

10 files changed

+319
-91
lines changed

.github/workflows/main.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ jobs:
9090
- name: Setup Additional Languages (dart)
9191
uses: dart-lang/setup-dart@v1
9292
with:
93-
sdk: 2.14.0
93+
sdk: 3.5.3
9494

9595
- name: Setup Additional Languages (swift)
9696
# TODO: Use the release version again once

serde-generate/runtime/dart/bcs/bcs_deserializer.dart

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,18 @@
33

44
part of bcs;
55

6+
// Maximum length allowed for sequences (vectors, bytes, strings) and maps.
7+
const maxSequenceLength = (1 << 31) - 1;
8+
9+
// Maximum number of nested structs and enum variants.
10+
const maxContainerDepth = 500;
11+
612
class BcsDeserializer extends BinaryDeserializer {
7-
BcsDeserializer(Uint8List input) : super(input);
13+
BcsDeserializer(Uint8List input)
14+
: super(
15+
input: input,
16+
containerDepthBudget: maxContainerDepth,
17+
);
818

919
int deserializeUleb128AsUint32() {
1020
var value = 0;
@@ -27,7 +37,11 @@ class BcsDeserializer extends BinaryDeserializer {
2737

2838
@override
2939
int deserializeLength() {
30-
return deserializeUleb128AsUint32();
40+
final length = deserializeUleb128AsUint32();
41+
if (length > maxSequenceLength) {
42+
throw Exception("length is too large");
43+
}
44+
return length;
3145
}
3246

3347
@override

serde-generate/runtime/dart/bcs/bcs_serializer.dart

Lines changed: 45 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,11 @@
44
part of bcs;
55

66
class BcsSerializer extends BinarySerializer {
7+
BcsSerializer()
8+
: super(
9+
containerDepthBudget: maxContainerDepth,
10+
);
11+
712
void serializeUint32AsUleb128(int value) {
813
while (((value & 0xFFFFFFFF) >> 7) != 0) {
914
output.add((value & 0x7f) | 0x80);
@@ -22,7 +27,45 @@ class BcsSerializer extends BinarySerializer {
2227
serializeUint32AsUleb128(value);
2328
}
2429

25-
void sortMapEntries(Uint8List offsets) {
26-
// TODO(#120)
30+
void sortMapEntries(List<int> offsets) {
31+
if (offsets.isEmpty) {
32+
return;
33+
}
34+
35+
// Prepare a list of slices
36+
final data = Uint8List.fromList(output);
37+
List<Uint8List> slices = [];
38+
39+
// Collect slices
40+
for (int i = 0; i < offsets.length; i++) {
41+
final int startOffset = offsets[i];
42+
final int cutOffset;
43+
if (i + 1 < offsets.length) {
44+
cutOffset = offsets[i + 1];
45+
} else {
46+
cutOffset = data.length;
47+
}
48+
slices.add(data.sublist(startOffset, cutOffset));
49+
}
50+
51+
// Sort slices using lexicographic comparison
52+
slices.sort((a, b) {
53+
for (int i = 0; i < a.length && i < b.length; i++) {
54+
if (a[i] != b[i]) {
55+
return a[i].compareTo(b[i]);
56+
}
57+
}
58+
return a.length.compareTo(b.length);
59+
});
60+
61+
// Write sorted slices back to output
62+
int writePosition = offsets[0];
63+
for (final slice in slices) {
64+
output.setRange(writePosition, writePosition + slice.length, slice);
65+
writePosition += slice.length;
66+
}
67+
68+
// Ensure the final length is correct
69+
assert(offsets.last == output.length);
2770
}
2871
}

serde-generate/runtime/dart/bincode/bincode_deserializer.dart

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,16 @@
33

44
part of bincode;
55

6+
// Maximum number of nested structs and enum variants.
7+
const maxContainerDepth = (1 << 31) - 1;
8+
69
class BincodeDeserializer extends BinaryDeserializer {
7-
BincodeDeserializer(Uint8List input) : super(input);
10+
BincodeDeserializer(Uint8List input)
11+
: super(input: input, containerDepthBudget: maxContainerDepth);
812

913
@override
1014
int deserializeLength() {
11-
// bincode sends this as a u64 but since transferred data length should never exceed the upper
12-
// bounds of an i64 (9223372036854775807 bytes is 9k petabytes) still deserialize to a Dart int
13-
return deserializeInt64();
15+
return deserializeUint64().toInt();
1416
}
1517

1618
@override

serde-generate/runtime/dart/bincode/bincode_serializer.dart

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,19 +4,22 @@
44
part of bincode;
55

66
class BincodeSerializer extends BinarySerializer {
7+
BincodeSerializer()
8+
: super(
9+
containerDepthBudget: maxContainerDepth,
10+
);
11+
712
@override
813
void serializeLength(int value) {
9-
// bincode expects a u64 but since the capacity of a Dart int is less than that
10-
// we can safely serialize as int to simplify over constructing Uint8 bytes
11-
return serializeInt64(value);
14+
serializeUint64(Uint64(BigInt.from(value)));
1215
}
1316

1417
@override
1518
void serializeVariantIndex(int value) {
1619
serializeUint32(value);
1720
}
1821

19-
void sortMapEntries(Int32List offsets) {
22+
void sortMapEntries(List<int> offsets) {
2023
// Not required by the format.
2124
}
2225
}
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
name: serde
22

33
environment:
4-
sdk: '>=2.14.0 <3.0.0'
4+
sdk: '>=3.0.0 <4.0.0'
55

66
dependencies:
77
meta: ^1.0.0
88
tuple: ^2.0.0
9-
dev_dependencies:
9+
dev_dependencies:
1010
test: ^1.19.3

serde-generate/runtime/dart/serde/binary_deserializer.dart

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,20 +4,32 @@
44
part of serde;
55

66
abstract class BinaryDeserializer {
7-
BinaryDeserializer(Uint8List input) : input = ByteData.view(input.buffer);
7+
BinaryDeserializer({
8+
required Uint8List input,
9+
required this.containerDepthBudget,
10+
}) : input = ByteData.view(input.buffer);
811

912
@protected
1013
final ByteData input;
1114
int _offset = 0;
15+
int containerDepthBudget;
1216

1317
int get offset {
1418
return _offset;
1519
}
1620

1721
bool deserializeBool() {
18-
final result = input.getUint8(_offset) != 0;
22+
final result = input.getUint8(_offset);
1923
_offset += 1;
20-
return result;
24+
if (result == 0) {
25+
return false;
26+
} else if (result == 1) {
27+
return true;
28+
} else {
29+
throw Exception(
30+
'Invalid boolean: expected 0 or 1, but got ${result}',
31+
);
32+
}
2133
}
2234

2335
Unit deserializeUnit() {
@@ -146,4 +158,15 @@ abstract class BinaryDeserializer {
146158
return number.toUnsigned(byteLength * 8);
147159
}
148160
}
161+
162+
void increaseContainerDepth() {
163+
if (containerDepthBudget == 0) {
164+
throw Exception('exceeded maximum container depth');
165+
}
166+
containerDepthBudget -= 1;
167+
}
168+
169+
void decreaseContainerDepth() {
170+
containerDepthBudget += 1;
171+
}
149172
}

serde-generate/runtime/dart/serde/binary_serializer.dart

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,11 @@
44
part of serde;
55

66
abstract class BinarySerializer {
7+
BinarySerializer({
8+
required this.containerDepthBudget,
9+
});
10+
11+
int containerDepthBudget;
712
final List<int> output = List<int>.empty(growable: true);
813

914
Uint8List get bytes {
@@ -129,12 +134,25 @@ abstract class BinarySerializer {
129134
void serializeLength(int len);
130135

131136
void serializeInt128(Int128 value) {
132-
serializeInt64(value.low.toInt());
133-
serializeInt64(value.high.toInt());
137+
serializeUint64(Uint64(value.low));
138+
serializeUint64(Uint64(value.high));
134139
}
135140

136141
void serializeUint128(Uint128 value) {
137142
serializeUint64(Uint64(value.low));
138143
serializeUint64(Uint64(value.high));
139144
}
145+
146+
void increaseContainerDepth() {
147+
if (containerDepthBudget == 0) {
148+
throw Exception('exceeded maximum container depth');
149+
}
150+
containerDepthBudget -= 1;
151+
}
152+
153+
void decreaseContainerDepth() {
154+
containerDepthBudget += 1;
155+
}
156+
157+
void sortMapEntries(List<int> offsets);
140158
}

serde-generate/src/dart.rs

Lines changed: 25 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ impl<'a> CodeGenerator<'a> {
8686
r#"name: {}
8787
8888
environment:
89-
sdk: '>=2.14.0 <3.0.0'
89+
sdk: '>=3.0.0 <4.0.0'
9090
9191
dependencies:
9292
meta: ^1.0.0
@@ -670,53 +670,43 @@ return obj;
670670
}
671671

672672
if self.generator.config.serialization {
673-
// a struct (UnitStruct) with zero fields
674-
if variant_index.is_none() && fields.is_empty() {
675-
writeln!(
676-
self.out,
677-
"\n{}.deserialize(BinaryDeserializer deserializer);",
678-
self.quote_qualified_name(name)
679-
)?;
680673
// Deserialize (struct) or Load (variant)
681-
} else if variant_index.is_none() {
682-
writeln!(
683-
self.out,
684-
"\n{}.deserialize(BinaryDeserializer deserializer) :",
685-
self.quote_qualified_name(name)
686-
)?;
687-
} else if !fields.is_empty() {
674+
if variant_index.is_none() {
688675
writeln!(
689676
self.out,
690-
"\n{}.load(BinaryDeserializer deserializer) :",
677+
"\nstatic {} deserialize(BinaryDeserializer deserializer) {{",
691678
self.quote_qualified_name(name)
692679
)?;
693680
} else {
694681
writeln!(
695682
self.out,
696-
"\n{}.load(BinaryDeserializer deserializer);",
683+
"\nstatic {} load(BinaryDeserializer deserializer) {{",
697684
self.quote_qualified_name(name)
698685
)?;
699686
}
700687

701688
self.out.indent();
702-
for (index, field) in fields.iter().enumerate() {
703-
if index == field_count - 1 {
704-
writeln!(
705-
self.out,
706-
"{} = {};",
707-
self.quote_field(&field.name.to_mixed_case()),
708-
self.quote_deserialize(&field.value)
709-
)?;
710-
} else {
711-
writeln!(
712-
self.out,
713-
"{} = {},",
714-
self.quote_field(&field.name.to_mixed_case()),
715-
self.quote_deserialize(&field.value)
716-
)?;
717-
}
689+
writeln!(self.out, "deserializer.increaseContainerDepth();")?;
690+
writeln!(
691+
self.out,
692+
"final instance = {}(",
693+
self.quote_qualified_name(name)
694+
)?;
695+
self.out.indent();
696+
for field in fields {
697+
writeln!(
698+
self.out,
699+
"{}: {},",
700+
self.quote_field(&field.name.to_mixed_case()),
701+
self.quote_deserialize(&field.value)
702+
)?;
718703
}
719704
self.out.unindent();
705+
writeln!(self.out, ");")?;
706+
writeln!(self.out, "deserializer.decreaseContainerDepth();")?;
707+
writeln!(self.out, "return instance;")?;
708+
self.out.unindent();
709+
writeln!(self.out, "}}")?;
720710

721711
if variant_index.is_none() {
722712
for encoding in &self.generator.config.encodings {
@@ -780,6 +770,7 @@ return obj;
780770
if self.generator.config.serialization {
781771
writeln!(self.out, "\nvoid serialize(BinarySerializer serializer) {{",)?;
782772
self.out.indent();
773+
writeln!(self.out, "serializer.increaseContainerDepth();")?;
783774
if let Some(index) = variant_index {
784775
writeln!(self.out, "serializer.serializeVariantIndex({});", index)?;
785776
}
@@ -793,6 +784,7 @@ return obj;
793784
)
794785
)?;
795786
}
787+
writeln!(self.out, "serializer.decreaseContainerDepth();")?;
796788
self.out.unindent();
797789
writeln!(self.out, "}}")?;
798790

0 commit comments

Comments
 (0)