1 module wasm_reader.reader;
2 
3 import wasm_reader.leb;
4 import std.typecons;
5 import std.meta;
6 import std.traits;
7 import std.range;
8 import std.algorithm : copy;
9 
10 version(unittest) {
11   import unit_threaded;
12 }
13 
14 struct encoding(T) { }
15 struct condition(string cond) { }
16 struct length(alias field) { }
17 struct positionMark(string name) { }
18 
19 struct header {
20   uint version_;
21 }
22 
23 struct Section {
24   static auto calcPayloadLength(ref Section section, size_t[string] marks) {
25     if (section.id != 0)
26       return section.payload_len;
27     return section.payload_len - (marks["b"] - marks["a"]);
28   }
29   @encoding!varuint7
30   uint id;
31   @encoding!varuint32
32   uint payload_len;
33   @condition!"id == 0" {
34     @positionMark!"a" @encoding!varuint32
35       uint name_len;
36     @length!"name_len"
37       string name;
38   }
39   @positionMark!"b" @length!calcPayloadLength
40   ubyte[] payload;
41   uint size() {
42     return 1 + sizeOf!(varuint32)(payload_len) + payload_len;
43   }
44 }
45 
46 struct import_section {
47   @encoding!varuint32
48   uint count;
49   @length!"count"
50   import_entry[] entries;
51 }
52 
53 enum external_kind : ubyte  {
54   Function = 0,
55     Table = 1,
56     Memory = 2,
57     Global = 3
58 }
59 
60 @encoding!varint7
61 enum value_type : byte {
62   i32 = -0x01,
63     i64 = -0x02,
64     f32 = -0x03,
65     f64 = -0x04,
66     anyfunc = -0x10,
67     func = -0x20,
68     empty = -0x40
69 }
70 
71 struct resizable_limits {
72   @encoding!varuint1 bool flags;
73   @encoding!varuint32 uint initial;
74   @condition!"flags == true" @varuint32 uint maximum;
75 }
76 @encoding!varint7
77 enum elem_type : byte {
78   anyfunc = -0x10
79 }
80 
81 struct global_type {
82   value_type content_type;
83   @encoding!varuint1 bool mutability;
84 }
85 
86 struct table_type {
87   elem_type element_type;
88   resizable_limits limits;
89 }
90 
91 struct memory_type {
92   resizable_limits limits;
93 }
94 struct import_entry {
95   @encoding!varuint32
96   uint module_len;
97   @length!"module_len"
98   string module_str;
99   @encoding!varuint32
100   uint field_len;
101   @length!"field_len"
102   string field_str;
103   external_kind kind;
104   @condition!"kind == external_kind.Function" @encoding!varuint32 uint functionType;
105   @condition!"kind == external_kind.Table" table_type tableType;
106   @condition!"kind == external_kind.Memory" memory_type memoryType;
107   @condition!"kind == external_kind.Global" global_type globalType;
108 }
109 
110 struct Sections(Range) {
111   private {
112     Range range;
113     bool initialized = false;
114     Section section;
115     void readSection() {
116       section = range.read!Section;
117       initialized = true;
118     }
119   }
120   this(ref Range range) {
121     this.range = range;
122   }
123   @property bool empty() {
124     return range.empty;
125   }
126   Section front() {
127     if (!initialized)
128       readSection();
129     return section;
130   }
131   void popFront() {
132     readSection();
133   }
134 }
135 
136 static assert(isInputRange!(Sections!(ubyte[])));
137 
138 auto readSections(Range)(auto ref Range range) {
139   return Sections!(Range)(range);
140 }
141 
142 @("file")
143 unittest {
144   import std.stdio;
145   import std.algorithm;
146   import std.range : take;
147   auto input = File("resource/example.wasm").byChunk(4096).joiner().drop(8);
148   auto sections = input.readSections.take(2).array();
149   sections.shouldEqual([Section(1, 29, 0, "", [6, 96, 0, 0, 96, 2, 127, 127, 1, 127, 96, 1, 127, 0, 96, 1, 127, 1, 127, 96, 1, 125, 1, 127, 96, 2, 127, 127, 0]), Section(2, 181, 0, "", [7, 3, 101, 110, 118, 26, 115, 112, 97, 115, 109, 95, 97, 100, 100, 80, 114, 105, 109, 105, 116, 105, 118, 101, 95, 95, 115, 116, 114, 105, 110, 103, 0, 1, 3, 101, 110, 118, 11, 99, 111, 110, 115, 111, 108, 101, 95, 108, 111, 103, 0, 2, 3, 101, 110, 118, 23, 115, 112, 97, 115, 109, 95, 97, 100, 100, 80, 114, 105, 109, 105, 116, 105, 118, 101, 95, 95, 105, 110, 116, 0, 3, 3, 101, 110, 118, 25, 115, 112, 97, 115, 109, 95, 97, 100, 100, 80, 114, 105, 109, 105, 116, 105, 118, 101, 95, 95, 102, 108, 111, 97, 116, 0, 4, 3, 101, 110, 118, 21, 68, 111, 99, 117, 109, 101, 110, 116, 95, 108, 111, 99, 97, 116, 105, 111, 110, 95, 71, 101, 116, 0, 5, 3, 101, 110, 118, 18, 115, 112, 97, 115, 109, 95, 114, 101, 109, 111, 118, 101, 79, 98, 106, 101, 99, 116, 0, 2, 3, 101, 110, 118, 6, 109, 101, 109, 111, 114, 121, 2, 0, 2])]);
150 }
151 
152 struct RangeWithPosition(Range) if (is(ElementType!Range == ubyte)) {
153   private { Range* range; size_t pos = 0; }
154   this(Range* range) {
155     this.range = range;
156   }
157   auto front() {
158     return (*range).front();
159   }
160   auto empty() {
161     return range.empty;
162   }
163   void popFront() {
164     (*range).popFront();
165     pos += 1;
166   }
167   auto getPosition() {
168     return pos;
169   }
170 }
171 
172 template read(T) {
173   import std.traits;
174   T read(Range)(ref Range range, size_t cnt) if (isArray!T) {
175     static if(is(T : U[], U)) {}
176     static if (isAggregateType!U) {
177       Unqual!U[] items = new Unqual!U[cnt];
178       foreach(idx; 0..cnt) {
179         items[idx] = .read!U(range);
180       }
181       return cast(T)items;
182     } else {
183       Unqual!U[] items = new Unqual!U[cnt];
184       foreach(idx; 0..cnt) {
185         items[idx] = range.front();
186         range.popFront();
187       }
188       return cast(T)items;
189     }
190   }
191   T read(Range)(auto ref Range plainRange) {
192     static if (isAggregateType!T) {
193       auto range = RangeWithPosition!Range(&plainRange);
194       T t;
195       size_t[string] marks;
196       assert(range.getPosition() == 0);
197       static foreach(field; T.tupleof) {{
198           static if (hasUDA!(field, positionMark)) {
199             alias positionMarks = getUDAs!(field, positionMark);
200             static foreach(marker; positionMarks) {{
201                 enum mark = TemplateArgsOf!(marker)[0];
202                 marks[mark] = range.getPosition();
203               }}
204           }
205           static if (hasUDA!(field, condition)) {
206             alias conditions = getUDAs!(field, condition);
207             static foreach(c; conditions) {{
208                 enum condition = TemplateArgsOf!(c)[0];
209                 mixin("if (t."~condition~") readMember!(field.stringof)(range, t, marks);");
210               }}
211           } else {
212             readMember!(field.stringof)(range, t, marks);
213           }
214         }}
215       return t;
216     } else static if (is(T == enum)) {
217       static if (hasUDA!(T, encoding)) {
218         alias encodingType = TemplateArgsOf!(getUDAs!(T, encoding)[0])[0];
219         return readEncoding!(encodingType, T)(plainRange);
220       } else {
221         T t;
222         ubyte[] mem = (cast(ubyte[])(&t)[0..1]);
223         foreach(idx; 0..mem.length) {
224           mem[idx] = plainRange.front;
225           plainRange.popFront();
226         }
227         return t;
228       }
229     } else {
230       T t;
231       ubyte[] mem = (cast(ubyte[])(&t)[0..1]);
232       foreach(idx; 0..mem.length) {
233         mem[idx] = plainRange.front;
234         plainRange.popFront();
235       }
236       return t;
237     }
238   }
239 }
240 
241 Type readEncoding(Encoding, Type, Range)(auto ref Range range) {
242   return cast(Type)Encoding.decode(range);
243 }
244 
245 template readMember(string name) {
246   void readMember(Range, T)(auto ref Range range, ref T t, ref size_t[string] marks) {
247     alias field = AliasSeq!(__traits(getMember, t, name))[0];
248     alias Field = typeof(field);
249     static if (is(Field : U[], U)) {
250       assert(hasUDA!(field, length));
251       alias lengths = getUDAs!(field, length);
252       static if (is(typeof(TemplateArgsOf!(lengths[0])[0]) == string)) {
253         enum length = TemplateArgsOf!(lengths[0])[0];
254         size_t len = __traits(getMember, t, length);
255         __traits(getMember, t, name) = read!(Field)(range, len);
256       } else {
257         alias lengthFun = TemplateArgsOf!(lengths[0])[0];
258         size_t len = lengthFun(t, marks);
259         __traits(getMember, t, name) = read!(Field)(range, len);
260       }
261     } else static if (hasUDA!(field, encoding)) {
262       alias EncodingType = TemplateArgsOf!(getUDAs!(field, encoding)[0])[0];
263       __traits(getMember, t, name) = readEncoding!(EncodingType, Field)(range);
264     } else {
265       __traits(getMember, t, name) = read!(Field)(range);
266     }
267   }
268 }
269 
270 unittest {
271   ubyte[] data = [0x01,'A',0x0,0x0,0x0,0x0,0x0,0x0,0x0];
272   data.read!import_entry.shouldEqual(import_entry(1,"A",0,"",external_kind.Function,0));
273 }
274 
275 unittest {
276   ubyte[] data = [0x01, 0x1d, 0x06, 0x60, 0x01, 0x7f, 0x00, 0x60, 0x02, 0x7f, 0x7f, 0x01, 0x7f, 0x60, 0x01, 0x7f, 0x01, 0x7f, 0x60, 0x01, 0x7d, 0x01, 0x7f, 0x60, 0x02, 0x7f, 0x7f, 0x00, 0x60, 0x00, 0x00];
277   data.read!Section.shouldEqual(Section(1, 29, 0, "", [6, 96, 1, 127, 0, 96, 2, 127, 127, 1, 127, 96, 1, 127, 1, 127, 96, 1, 125, 1, 127, 96, 2, 127, 127, 0, 96, 0, 0]));
278   data.length.shouldEqual(0);
279 }
280 
281 unittest {
282   ubyte[] data = [0x00, 0x1f, 0x01, 65, 0x06, 0x60, 0x01, 0x7f, 0x00, 0x60, 0x02, 0x7f, 0x7f, 0x01, 0x7f, 0x60, 0x01, 0x7f, 0x01, 0x7f, 0x60, 0x01, 0x7d, 0x01, 0x7f, 0x60, 0x02, 0x7f, 0x7f, 0x00, 0x60, 0x00, 0x00];
283   data.read!Section.shouldEqual(Section(0, 31, 1, "A", [6, 96, 1, 127, 0, 96, 2, 127, 127, 1, 127, 96, 1, 127, 1, 127, 96, 1, 125, 1, 127, 96, 2, 127, 127, 0, 96, 0, 0]));
284   data.length.shouldEqual(0);
285 }
286 
287 unittest {
288   ubyte[] data = [0x02, 0xb5, 0x01, 0x07, 0x03, 0x65, 0x6e, 0x76, 0x06, 0x6d, 0x65, 0x6d, 0x6f, 0x72, 0x79, 0x02, 0x00, 0x02, 0x03, 0x65, 0x6e, 0x76, 0x0b, 0x63, 0x6f, 0x6e, 0x73, 0x6f, 0x6c, 0x65, 0x5f, 0x6c, 0x6f, 0x67, 0x00, 0x00, 0x03, 0x65, 0x6e, 0x76, 0x1a, 0x73, 0x70, 0x61, 0x73, 0x6d, 0x5f, 0x61, 0x64, 0x64, 0x50, 0x72, 0x69, 0x6d, 0x69, 0x74, 0x69, 0x76, 0x65, 0x5f, 0x5f, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x00, 0x01, 0x03, 0x65, 0x6e, 0x76, 0x12, 0x73, 0x70, 0x61, 0x73, 0x6d, 0x5f, 0x72, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x00, 0x00, 0x03, 0x65, 0x6e, 0x76, 0x17, 0x73, 0x70, 0x61, 0x73, 0x6d, 0x5f, 0x61, 0x64, 0x64, 0x50, 0x72, 0x69, 0x6d, 0x69, 0x74, 0x69, 0x76, 0x65, 0x5f, 0x5f, 0x69, 0x6e, 0x74, 0x00, 0x02, 0x03, 0x65, 0x6e, 0x76, 0x19, 0x73, 0x70, 0x61, 0x73, 0x6d, 0x5f, 0x61, 0x64, 0x64, 0x50, 0x72, 0x69, 0x6d, 0x69, 0x74, 0x69, 0x76, 0x65, 0x5f, 0x5f, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x00, 0x03, 0x03, 0x65, 0x6e, 0x76, 0x15, 0x44, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x6c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x47, 0x65, 0x74, 0x00, 0x04];
289   data.read!Section.shouldEqual(Section(2, 181, 0, "", [7, 3, 101, 110, 118, 6, 109, 101, 109, 111, 114, 121, 2, 0, 2, 3, 101, 110, 118, 11, 99, 111, 110, 115, 111, 108, 101, 95, 108, 111, 103, 0, 0, 3, 101, 110, 118, 26, 115, 112, 97, 115, 109, 95, 97, 100, 100, 80, 114, 105, 109, 105, 116, 105, 118, 101, 95, 95, 115, 116, 114, 105, 110, 103, 0, 1, 3, 101, 110, 118, 18, 115, 112, 97, 115, 109, 95, 114, 101, 109, 111, 118, 101, 79, 98, 106, 101, 99, 116, 0, 0, 3, 101, 110, 118, 23, 115, 112, 97, 115, 109, 95, 97, 100, 100, 80, 114, 105, 109, 105, 116, 105, 118, 101, 95, 95, 105, 110, 116, 0, 2, 3, 101, 110, 118, 25, 115, 112, 97, 115, 109, 95, 97, 100, 100, 80, 114, 105, 109, 105, 116, 105, 118, 101, 95, 95, 102, 108, 111, 97, 116, 0, 3, 3, 101, 110, 118, 21, 68, 111, 99, 117, 109, 101, 110, 116, 95, 108, 111, 99, 97, 116, 105, 111, 110, 95, 71, 101, 116, 0, 4]));
290   data.length.shouldEqual(0);
291 }
292 
293 unittest {
294   ubyte[] data = [0x07, 0x03, 0x65, 0x6e, 0x76, 0x06, 0x6d, 0x65, 0x6d, 0x6f, 0x72, 0x79, 0x02, 0x00, 0x02, 0x03, 0x65, 0x6e, 0x76, 0x0b, 0x63, 0x6f, 0x6e, 0x73, 0x6f, 0x6c, 0x65, 0x5f, 0x6c, 0x6f, 0x67, 0x00, 0x00, 0x03, 0x65, 0x6e, 0x76, 0x1a, 0x73, 0x70, 0x61, 0x73, 0x6d, 0x5f, 0x61, 0x64, 0x64, 0x50, 0x72, 0x69, 0x6d, 0x69, 0x74, 0x69, 0x76, 0x65, 0x5f, 0x5f, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x00, 0x01, 0x03, 0x65, 0x6e, 0x76, 0x12, 0x73, 0x70, 0x61, 0x73, 0x6d, 0x5f, 0x72, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x00, 0x00, 0x03, 0x65, 0x6e, 0x76, 0x17, 0x73, 0x70, 0x61, 0x73, 0x6d, 0x5f, 0x61, 0x64, 0x64, 0x50, 0x72, 0x69, 0x6d, 0x69, 0x74, 0x69, 0x76, 0x65, 0x5f, 0x5f, 0x69, 0x6e, 0x74, 0x00, 0x02, 0x03, 0x65, 0x6e, 0x76, 0x19, 0x73, 0x70, 0x61, 0x73, 0x6d, 0x5f, 0x61, 0x64, 0x64, 0x50, 0x72, 0x69, 0x6d, 0x69, 0x74, 0x69, 0x76, 0x65, 0x5f, 0x5f, 0x66, 0x6c, 0x6f, 0x61, 0x74, 0x00, 0x03, 0x03, 0x65, 0x6e, 0x76, 0x15, 0x44, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x6c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x47, 0x65, 0x74, 0x00, 0x04];
295   data.read!import_section.shouldEqual(import_section(7, [import_entry(3, "env", 6, "memory", external_kind.Memory, 0, table_type(elem_type.anyfunc, resizable_limits(false, 0, 0)), memory_type(resizable_limits(false, 2, 0)), global_type(value_type.i32, false)), import_entry(3, "env", 11, "console_log", external_kind.Function, 0, table_type(elem_type.anyfunc, resizable_limits(false, 0, 0)), memory_type(resizable_limits(false, 0, 0)), global_type(value_type.i32, false)), import_entry(3, "env", 26, "spasm_addPrimitive__string", external_kind.Function, 1, table_type(elem_type.anyfunc, resizable_limits(false, 0, 0)), memory_type(resizable_limits(false, 0, 0)), global_type(value_type.i32, false)), import_entry(3, "env", 18, "spasm_removeObject", external_kind.Function, 0, table_type(elem_type.anyfunc, resizable_limits(false, 0, 0)), memory_type(resizable_limits(false, 0, 0)), global_type(value_type.i32, false)), import_entry(3, "env", 23, "spasm_addPrimitive__int", external_kind.Function, 2, table_type(elem_type.anyfunc, resizable_limits(false, 0, 0)), memory_type(resizable_limits(false, 0, 0)), global_type(value_type.i32, false)), import_entry(3, "env", 25, "spasm_addPrimitive__float", external_kind.Function, 3, table_type(elem_type.anyfunc, resizable_limits(false, 0, 0)), memory_type(resizable_limits(false, 0, 0)), global_type(value_type.i32, false)), import_entry(3, "env", 21, "Document_location_Get", external_kind.Function, 4, table_type(elem_type.anyfunc, resizable_limits(false, 0, 0)), memory_type(resizable_limits(false, 0, 0)), global_type(value_type.i32, false))]));
296   data.length.shouldEqual(0);
297 }