lib/std/lang/il/binary/decodeTests.rad 14.9 KiB raw
1
//! Package reconstruction, malformed input, and arena rollback tests.
2
3
use std::testing;
4
use std::lang::alloc;
5
use std::lang::il;
6
use std::lang::il::binary;
7
use std::lang::il::binary::reader;
8
use std::lang::il::binary::program;
9
use std::lang::il::binary::collect;
10
11
/// Decode arena backing storage. Tests reset it before each use.
12
static MEMORY: [u8; 2048] = [0; 2048];
13
14
/// Structural limits for small test programs.
15
constant LIMITS: binary::Limits = binary::Limits { registers: 16, blocks: 4 };
16
17
/// Check a package round trip and failure at every truncated byte length.
18
unsafe fn roundTrip(package: &binary::Package) throws (testing::TestError) {
19
    let mut buffer: [u8; 2048] = [0; 2048];
20
    let length = try program::encode(&mut buffer[..], package) catch {
21
        throw testing::TestError::Failed;
22
    };
23
    let memory = &mut MEMORY[..2048];
24
    let mut arena = alloc::new(&mut memory[..]);
25
    set arena.offset = 8;
26
    let decoded = try program::decode(&buffer[..length], &mut arena, LIMITS) catch {
27
        throw testing::TestError::Failed;
28
    };
29
    try testing::expectBytesEq(decoded.name, package.name);
30
    try testing::expect(decoded.dependencies.len == package.dependencies.len);
31
    try testing::expect(decoded.exports.len == package.exports.len);
32
    try testing::expect(decoded.program.fns.len == package.program.fns.len);
33
    let mut encoded: [u8; 2048] = [0; 2048];
34
    let repeated = try program::encode(&mut encoded[..], &decoded) catch {
35
        throw testing::TestError::Failed;
36
    };
37
    try testing::expectBytesEq(&buffer[..length], &encoded[..repeated]);
38
    // Decoded names and string initializers retain arena ownership.
39
    for i in 0..length {
40
        set buffer[i] = 0;
41
    }
42
    let copied = try program::encode(&mut buffer[..], &decoded) catch {
43
        throw testing::TestError::Failed;
44
    };
45
    try testing::expectBytesEq(&buffer[..copied], &encoded[..repeated]);
46
    for end in 0..length {
47
        set arena.offset = 8;
48
        let mut failed = false;
49
        try program::decode(&buffer[..end], &mut arena, LIMITS) catch err {
50
            try testing::expect(err == binary::Error::Truncated);
51
            set failed = true;
52
        };
53
        try testing::expect(failed);
54
        try testing::expect(arena.offset == 8);
55
    }
56
    set arena.offset = 8;
57
    let mut input = reader::new(&[], &mut arena, &[]);
58
    let mut exhausted = false;
59
    try reader::storage(&mut input, 0xffffffff, 8, 0xffffffff) catch err {
60
        try testing::expect(err == binary::Error::Storage);
61
        set exhausted = true;
62
    };
63
    try testing::expect(exhausted);
64
    try testing::expect(arena.offset == 8);
65
    // Exercise every smaller arena extent, including alignment boundaries.
66
    let mut usedArena = alloc::new(&mut memory[..]);
67
    let _ = try program::decode(&buffer[..length], &mut usedArena, LIMITS) catch {
68
        throw testing::TestError::Failed;
69
    };
70
    let required = usedArena.offset;
71
    for capacity in 0..required {
72
        let mut short = alloc::new(&mut MEMORY[..capacity]);
73
        let mut failed = false;
74
        try program::decode(&buffer[..length], &mut short, LIMITS) catch err {
75
            try testing::expect(err == binary::Error::Storage);
76
            set failed = true;
77
        };
78
        try testing::expect(failed);
79
        try testing::expect(short.offset == 0);
80
    }
81
}
82
83
/// Check an empty program with a package identity.
84
@test unsafe fn emptyPackage() throws (testing::TestError) {
85
    let package = binary::Package {
86
        symbols: &["p"], name: "p", dependencies: &[], exports: &[], entry: nil,
87
        program: il::Program { data: &[], fns: &[] },
88
    };
89
    try roundTrip(&package);
90
    let mut buffer: [u8; 64] = [0; 64];
91
    let length = try program::encode(&mut buffer[..], &package) catch {
92
        throw testing::TestError::Failed;
93
    };
94
    try testing::expectBytesEq(&buffer[..length], &[
95
        82, 73, 76, 0, 1, 0, 0, 0, 1, 0, 0, 0,
96
        1, 0, 0, 0, 112, 0, 0, 0, 0,
97
        0, 0, 0, 0, 0, 0, 0, 0, 0,
98
        0, 0, 0, 0, 0, 0, 0, 0,
99
    ]);
100
}
101
102
/// Check functions, dependencies, exports, initializers, and block metadata.
103
@test unsafe fn fullPackage() throws (testing::TestError) {
104
    let params: *unsafe [il::Param] = &[il::Param { value: il::Reg { n: 1 }, type: il::Type::W64 }];
105
    let mut instrs = [
106
        il::Instr::Call {
107
            retTy: il::Type::W64, dst: il::Reg { n: 2 }, func: il::Val::FnAddr("dep::fn"),
108
            args: &[il::Val::Reg(il::Reg { n: 1 })],
109
        },
110
        il::Instr::Ret { val: il::Val::Reg(il::Reg { n: 2 }) },
111
    ];
112
    let func = il::Fn {
113
        name: "p::main", params, returnType: il::Type::W64, isExtern: false, isLeaf: false,
114
        blocks: &[il::Block {
115
            label: "entry", params: &[], instrs: &mut instrs[..], locs: &[], preds: &[0], loopDepth: 2,
116
        }],
117
    };
118
    let external = il::Fn {
119
        name: "dep::fn", params, returnType: il::Type::W64, isExtern: true, isLeaf: true, blocks: &[],
120
    };
121
    let data = il::Data {
122
        name: "p::data", size: 64, alignment: 8, readOnly: false, isZeroInit: false,
123
        values: &[
124
            il::DataValue { item: il::DataItem::Val { typ: il::Type::W8, val: -1 }, count: 2 },
125
            il::DataValue { item: il::DataItem::Val { typ: il::Type::W16, val: -2 }, count: 1 },
126
            il::DataValue { item: il::DataItem::Val { typ: il::Type::W32, val: -3 }, count: 1 },
127
            il::DataValue { item: il::DataItem::Val { typ: il::Type::W64, val: -4 }, count: 1 },
128
            il::DataValue { item: il::DataItem::Sym("p::data"), count: 1 },
129
            il::DataValue { item: il::DataItem::Fn("dep::fn"), count: 1 },
130
            il::DataValue { item: il::DataItem::Str("bytes"), count: 2 },
131
            il::DataValue { item: il::DataItem::Str(""), count: 0 },
132
            il::DataValue { item: il::DataItem::Undef, count: 1 },
133
        ],
134
    };
135
    let zero = il::Data {
136
        name: "p::zero", size: 4096, alignment: 4096, readOnly: false, isZeroInit: true, values: &[],
137
    };
138
    let package = binary::Package {
139
        symbols: &["p", "dep", "p::main", "dep::fn", "p::data", "p::zero"], name: "p",
140
        dependencies: &["dep"],
141
        exports: &[
142
            binary::Export { name: "p::main", kind: binary::ExportKind::Function },
143
            binary::Export { name: "p::data", kind: binary::ExportKind::Data },
144
        ],
145
        entry: "p::main", program: il::Program { data: retainData(&[data, zero]), fns: &[&func, &external] },
146
    };
147
    try roundTrip(&package);
148
}
149
150
/// Reject an invalid encoded instruction without a panic.
151
unsafe fn badInstruction(bytes: *[u8], expected: binary::Error) throws (testing::TestError) {
152
    let memory = &mut MEMORY[..64];
153
    let mut arena = alloc::new(&mut memory[..]);
154
    let mut input = reader::new(bytes, &mut arena, &["p"]);
155
    set input.registers = 2;
156
    set input.blocks = 1;
157
    let mut failed = false;
158
    try reader::instr(&mut input) catch err {
159
        try testing::expect(err == expected);
160
        set failed = true;
161
    };
162
    try testing::expect(failed);
163
}
164
165
/// Check instruction tags, operand tags, types, optional flags, and indices.
166
@test unsafe fn malformedInstructions() throws (testing::TestError) {
167
    try badInstruction(&[255], binary::Error::Invalid);
168
    try badInstruction(&[5, 0, 0, 0, 0, 255], binary::Error::Invalid);
169
    try badInstruction(&[1, 3], binary::Error::Invalid);
170
    try badInstruction(&[6, 255], binary::Error::Invalid);
171
    try badInstruction(&[7, 255], binary::Error::Invalid);
172
    try badInstruction(&[13, 255], binary::Error::Invalid);
173
    try badInstruction(&[11, 2], binary::Error::Invalid);
174
    try badInstruction(&[10, 8, 2], binary::Error::Invalid);
175
    try badInstruction(&[5, 2, 0, 0, 0], binary::Error::Invalid);
176
    try badInstruction(&[5, 0, 0, 0, 0, 0, 2, 0, 0, 0], binary::Error::Invalid);
177
    try badInstruction(&[12, 1, 0, 0, 0], binary::Error::Invalid);
178
    try badInstruction(&[5, 0, 0, 0, 0, 2, 1, 0, 0, 0], binary::Error::Symbol);
179
    try badInstruction(&[12, 0, 0, 0, 0, 255, 255, 255, 255], binary::Error::Truncated);
180
}
181
182
/// Check header validation and package symbol indices with arena rollback.
183
@test unsafe fn malformedPackages() throws (testing::TestError) {
184
    let package = binary::Package {
185
        symbols: &["p"], name: "p", dependencies: &[], exports: &[], entry: nil,
186
        program: il::Program { data: &[], fns: &[] },
187
    };
188
    let mut buffer: [u8; 64] = [0; 64];
189
    let length = try program::encode(&mut buffer[..], &package) catch {
190
        throw testing::TestError::Failed;
191
    };
192
    let memory = &mut MEMORY[..64];
193
    let mut arena = alloc::new(&mut memory[..]);
194
    for offset in &[0, 4, 17, 29] {
195
        let original = buffer[offset];
196
        set buffer[offset] = 255;
197
        set arena.offset = 8;
198
        let mut failed = false;
199
        try program::decode(&buffer[..length], &mut arena, LIMITS) catch {
200
            set failed = true;
201
        };
202
        try testing::expect(failed);
203
        try testing::expect(arena.offset == 8);
204
        set buffer[offset] = original;
205
    }
206
    let mut trailing = false;
207
    try program::decode(&buffer[..length + 1], &mut arena, LIMITS) catch err {
208
        try testing::expect(err == binary::Error::Invalid);
209
        set trailing = true;
210
    };
211
    try testing::expect(trailing);
212
    try testing::expect(arena.offset == 8);
213
}
214
215
/// Encode a malformed package and check rejection with arena rollback.
216
unsafe fn rejected(package: &binary::Package, limits: binary::Limits) throws (testing::TestError) {
217
    let mut buffer: [u8; 512] = [0; 512];
218
    let length = try program::encode(&mut buffer[..], package) catch {
219
        throw testing::TestError::Failed;
220
    };
221
    let memory = &mut MEMORY[..256];
222
    let mut arena = alloc::new(&mut memory[..]);
223
    set arena.offset = 8;
224
    let mut failed = false;
225
    try program::decode(&buffer[..length], &mut arena, limits) catch err {
226
        try testing::expect(err == binary::Error::Invalid);
227
        set failed = true;
228
    };
229
    try testing::expect(failed);
230
    try testing::expect(arena.offset == 8);
231
}
232
233
/// Check initializer extent arithmetic, alignments, and duplicate symbols.
234
@test unsafe fn invalidData() throws (testing::TestError) {
235
    let mut item = il::Data {
236
        name: "p", size: 0xffffffff, alignment: 8, readOnly: false, isZeroInit: false,
237
        values: &[
238
            il::DataValue { item: il::DataItem::Val { typ: il::Type::W64, val: 0 }, count: 0xffffffff },
239
        ],
240
    };
241
    let mut package = binary::Package {
242
        symbols: &["p"], name: "p", dependencies: &[], exports: &[], entry: nil,
243
        program: il::Program { data: retainData(&[item]), fns: &[] },
244
    };
245
    try rejected(&package, LIMITS);
246
    set item.size = 1;
247
    set item.values = &[
248
        il::DataValue { item: il::DataItem::Undef, count: 1 },
249
        il::DataValue { item: il::DataItem::Undef, count: 1 },
250
    ];
251
    set package.program = il::Program { data: retainData(&[item]), fns: &[] };
252
    try rejected(&package, LIMITS);
253
    set item.values = &[];
254
    for alignment in &[0, 3] {
255
        set item.alignment = alignment;
256
        set package.program = il::Program { data: retainData(&[item]), fns: &[] };
257
        try rejected(&package, LIMITS);
258
    }
259
    set package.program = il::Program { data: &[], fns: &[] };
260
    set package.symbols = &["p", "p"];
261
    try rejected(&package, LIMITS);
262
    set package.symbols = &["p", ""];
263
    try rejected(&package, LIMITS);
264
}
265
266
/// Check function block limits, parameter indices, and predecessor indices.
267
@test unsafe fn invalidFunctions() throws (testing::TestError) {
268
    let mut instrs = [il::Instr::Ret { val: nil }];
269
    let mut block = il::Block {
270
        label: "entry", params: &[], instrs: &mut instrs[..], locs: &[], preds: &[], loopDepth: 0,
271
    };
272
    let mut func = il::Fn {
273
        name: "p", params: &[], returnType: il::Type::W64,
274
        isExtern: false, isLeaf: true, blocks: &[block],
275
    };
276
    let package = binary::Package {
277
        symbols: &["p"], name: "p", dependencies: &[], exports: &[], entry: nil,
278
        program: il::Program { data: &[], fns: &[&func] },
279
    };
280
    try rejected(&package, binary::Limits { registers: 16, blocks: 0 });
281
    set func.isExtern = true;
282
    try rejected(&package, LIMITS);
283
    set func.isExtern = false;
284
    set func.params = &[il::Param { value: il::Reg { n: 16 }, type: il::Type::W64 }];
285
    try rejected(&package, LIMITS);
286
    set func.params = &[];
287
    set block.preds = &[1];
288
    set func.blocks = &[block];
289
    try rejected(&package, LIMITS);
290
}
291
292
/// Reject unknown initializer tags and widths before reading their payloads.
293
@test unsafe fn malformedInitializers() throws (testing::TestError) {
294
    let memory = &mut MEMORY[..8];
295
    let mut arena = alloc::new(&mut memory[..]);
296
    let fixtures: [*[u8]; 2] = [&[255], &[0, 3]];
297
    for bytes in &fixtures[..] {
298
        let mut input = reader::new(bytes, &mut arena, &[]);
299
        let mut failed = false;
300
        try reader::dataValue(&mut input) catch err {
301
            try testing::expect(err == binary::Error::Invalid);
302
            set failed = true;
303
        };
304
        try testing::expect(failed);
305
    }
306
}
307
308
/// Check symbol collection capacity and package ownership failures.
309
@test unsafe fn collectionBounds() throws (testing::TestError) {
310
    let mut symbols: [*[u8]; 8] = undefined;
311
    let mut dependencies: [*[u8]; 1] = undefined;
312
    let empty = il::Program { data: &[], fns: &[] };
313
    let mut names = collect::new(&mut symbols[..0], &mut dependencies[..]);
314
    let mut failed = false;
315
    try collect::package(&mut names, "p", empty, &[], nil) catch err {
316
        try testing::expect(err == binary::Error::Capacity);
317
        set failed = true;
318
    };
319
    try testing::expect(failed);
320
    let item = il::Data {
321
        name: "p::data", size: 8, alignment: 8, readOnly: false, isZeroInit: false,
322
        values: &[il::DataValue { item: il::DataItem::Fn("dep::fn"), count: 1 }],
323
    };
324
    let local = il::Program { data: retainData(&[item]), fns: &[] };
325
    set names = collect::new(&mut symbols[..], &mut dependencies[..0]);
326
    set failed = false;
327
    try collect::package(&mut names, "p", local, &[], nil) catch err {
328
        try testing::expect(err == binary::Error::Capacity);
329
        set failed = true;
330
    };
331
    try testing::expect(failed);
332
    set names = collect::new(&mut symbols[..], &mut dependencies[..]);
333
    set failed = false;
334
    try collect::package(&mut names, "other", local, &[], nil) catch err {
335
        try testing::expect(err == binary::Error::Invalid);
336
        set failed = true;
337
    };
338
    try testing::expect(failed);
339
    set names = collect::new(&mut symbols[..], &mut dependencies[..]);
340
    let package = try collect::package(&mut names, "p", local, &[], nil) catch {
341
        throw testing::TestError::Failed;
342
    };
343
    try testing::expect(package.dependencies.len == 1);
344
    try testing::expectBytesEq(package.dependencies[0], "dep");
345
    try testing::expect(package.symbols.len == 4);
346
}
347
348
/// Copy data fixtures into stable storage for the package descriptor.
349
unsafe fn retainData(items: &[il::Data]) -> *[il::Data] {
350
    unsafe static DATA: [il::Data; 2] = undefined;
351
    assert items.len <= DATA.len;
352
    for item, i in items { set DATA[i] = item; }
353
    return &DATA[..items.len];
354
}