master
1const std = @import("std");
2
3pub const ModuleDefinitionType = enum {
4 mingw,
5};
6
7pub const ModuleDefinition = struct {
8 exports: std.ArrayList(Export) = .empty,
9 name: ?[]const u8 = null,
10 base_address: usize = 0,
11 arena: std.heap.ArenaAllocator,
12 type: ModuleDefinitionType,
13
14 pub const Export = struct {
15 /// This may lack mangling, such as underscore prefixing and stdcall suffixing.
16 /// In a .def file, this is `foo` in `foo` or `bar` in `foo = bar`.
17 name: []const u8,
18 /// Note: This is currently only set by `fixupForImportLibraryGeneration`
19 mangled_symbol_name: ?[]const u8,
20 /// The external, exported name.
21 /// In a .def file, this is `foo` in `foo = bar`.
22 ext_name: ?[]const u8,
23 /// In a .def file, this is `bar` in `foo == bar`.
24 import_name: ?[]const u8,
25 /// In a .def file, this is `bar` in `foo EXPORTAS bar`.
26 export_as: ?[]const u8,
27 no_name: bool,
28 ordinal: u16,
29 type: std.coff.ImportType,
30 private: bool,
31 };
32
33 /// Modifies `exports` such that import library generation will
34 /// behave as expected. Based on LLVM's dlltool driver.
35 pub fn fixupForImportLibraryGeneration(self: *ModuleDefinition, machine_type: std.coff.IMAGE.FILE.MACHINE) void {
36 const kill_at = true;
37 for (self.exports.items) |*e| {
38 // If ExtName is set (if the "ExtName = Name" syntax was used), overwrite
39 // Name with ExtName and clear ExtName. When only creating an import
40 // library and not linking, the internal name is irrelevant. This avoids
41 // cases where writeImportLibrary tries to transplant decoration from
42 // symbol decoration onto ExtName.
43 if (e.ext_name) |ext_name| {
44 e.name = ext_name;
45 e.ext_name = null;
46 }
47
48 if (kill_at) {
49 if (e.import_name != null or std.mem.startsWith(u8, e.name, "?"))
50 continue;
51
52 if (machine_type == .I386) {
53 // By making sure E.SymbolName != E.Name for decorated symbols,
54 // writeImportLibrary writes these symbols with the type
55 // IMPORT_NAME_UNDECORATE.
56 e.mangled_symbol_name = e.name;
57 }
58 // Trim off the trailing decoration. Symbols will always have a
59 // starting prefix here (either _ for cdecl/stdcall, @ for fastcall
60 // or ? for C++ functions). Vectorcall functions won't have any
61 // fixed prefix, but the function base name will still be at least
62 // one char.
63 const name_len_without_at_suffix = std.mem.indexOfScalarPos(u8, e.name, 1, '@') orelse e.name.len;
64 e.name = e.name[0..name_len_without_at_suffix];
65 }
66 }
67 }
68
69 pub fn deinit(self: *const ModuleDefinition) void {
70 self.arena.deinit();
71 }
72};
73
74pub const Diagnostics = struct {
75 err: Error,
76 token: Token,
77 extra: Extra = .{ .none = {} },
78
79 pub const Extra = union {
80 none: void,
81 expected: Token.Tag,
82 };
83
84 pub const Error = enum {
85 invalid_byte,
86 unfinished_quoted_identifier,
87 /// `expected` is populated
88 expected_token,
89 expected_integer,
90 unknown_statement,
91 unimplemented,
92 };
93
94 fn formatToken(ctx: TokenFormatContext, writer: *std.Io.Writer) std.Io.Writer.Error!void {
95 switch (ctx.token.tag) {
96 .eof, .invalid => return writer.writeAll(ctx.token.tag.nameForErrorDisplay()),
97 else => return writer.writeAll(ctx.token.slice(ctx.source)),
98 }
99 }
100
101 const TokenFormatContext = struct {
102 token: Token,
103 source: []const u8,
104 };
105
106 fn fmtToken(self: Diagnostics, source: []const u8) std.fmt.Alt(TokenFormatContext, formatToken) {
107 return .{ .data = .{
108 .token = self.token,
109 .source = source,
110 } };
111 }
112
113 pub fn writeMsg(self: Diagnostics, writer: *std.Io.Writer, source: []const u8) !void {
114 switch (self.err) {
115 .invalid_byte => {
116 return writer.print("invalid byte '{f}'", .{std.ascii.hexEscape(self.token.slice(source), .upper)});
117 },
118 .unfinished_quoted_identifier => {
119 return writer.print("unfinished quoted identifier at '{f}', expected closing '\"'", .{self.fmtToken(source)});
120 },
121 .expected_token => {
122 return writer.print("expected '{s}', got '{f}'", .{ self.extra.expected.nameForErrorDisplay(), self.fmtToken(source) });
123 },
124 .expected_integer => {
125 return writer.print("expected integer, got '{f}'", .{self.fmtToken(source)});
126 },
127 .unimplemented => {
128 return writer.print("support for '{f}' has not yet been implemented", .{self.fmtToken(source)});
129 },
130 .unknown_statement => {
131 return writer.print("unknown/invalid statement syntax beginning with '{f}'", .{self.fmtToken(source)});
132 },
133 }
134 }
135};
136
137pub fn parse(
138 allocator: std.mem.Allocator,
139 source: [:0]const u8,
140 machine_type: std.coff.IMAGE.FILE.MACHINE,
141 module_definition_type: ModuleDefinitionType,
142 diagnostics: *Diagnostics,
143) !ModuleDefinition {
144 var tokenizer = Tokenizer.init(source);
145 var parser = Parser.init(&tokenizer, machine_type, module_definition_type, diagnostics);
146
147 return parser.parse(allocator);
148}
149
150const Token = struct {
151 tag: Tag,
152 start: usize,
153 end: usize,
154
155 pub const keywords = std.StaticStringMap(Tag).initComptime(.{
156 .{ "BASE", .keyword_base },
157 .{ "CONSTANT", .keyword_constant },
158 .{ "DATA", .keyword_data },
159 .{ "EXPORTS", .keyword_exports },
160 .{ "EXPORTAS", .keyword_exportas },
161 .{ "HEAPSIZE", .keyword_heapsize },
162 .{ "LIBRARY", .keyword_library },
163 .{ "NAME", .keyword_name },
164 .{ "NONAME", .keyword_noname },
165 .{ "PRIVATE", .keyword_private },
166 .{ "STACKSIZE", .keyword_stacksize },
167 .{ "VERSION", .keyword_version },
168 });
169
170 pub const Tag = enum {
171 invalid,
172 eof,
173 identifier,
174 comma,
175 equal,
176 equal_equal,
177 keyword_base,
178 keyword_constant,
179 keyword_data,
180 keyword_exports,
181 keyword_exportas,
182 keyword_heapsize,
183 keyword_library,
184 keyword_name,
185 keyword_noname,
186 keyword_private,
187 keyword_stacksize,
188 keyword_version,
189
190 pub fn nameForErrorDisplay(self: Tag) []const u8 {
191 return switch (self) {
192 .invalid => "<invalid>",
193 .eof => "<eof>",
194 .identifier => "<identifier>",
195 .comma => ",",
196 .equal => "=",
197 .equal_equal => "==",
198 .keyword_base => "BASE",
199 .keyword_constant => "CONSTANT",
200 .keyword_data => "DATA",
201 .keyword_exports => "EXPORTS",
202 .keyword_exportas => "EXPORTAS",
203 .keyword_heapsize => "HEAPSIZE",
204 .keyword_library => "LIBRARY",
205 .keyword_name => "NAME",
206 .keyword_noname => "NONAME",
207 .keyword_private => "PRIVATE",
208 .keyword_stacksize => "STACKSIZE",
209 .keyword_version => "VERSION",
210 };
211 }
212 };
213
214 /// Returns a useful slice of the token, e.g. for quoted identifiers, this
215 /// will return a slice without the quotes included.
216 pub fn slice(self: Token, source: []const u8) []const u8 {
217 return source[self.start..self.end];
218 }
219};
220
221const Tokenizer = struct {
222 source: [:0]const u8,
223 index: usize,
224 error_context_token: ?Token = null,
225
226 pub fn init(source: [:0]const u8) Tokenizer {
227 return .{
228 .source = source,
229 .index = 0,
230 };
231 }
232
233 const State = enum {
234 start,
235 identifier_or_keyword,
236 quoted_identifier,
237 comment,
238 equal,
239 eof_or_invalid,
240 };
241
242 pub const Error = error{
243 InvalidByte,
244 UnfinishedQuotedIdentifier,
245 };
246
247 pub fn next(self: *Tokenizer) Error!Token {
248 var result: Token = .{
249 .tag = undefined,
250 .start = self.index,
251 .end = undefined,
252 };
253 state: switch (State.start) {
254 .start => switch (self.source[self.index]) {
255 0 => continue :state .eof_or_invalid,
256 '\r', '\n', ' ', '\t', '\x0B' => {
257 self.index += 1;
258 result.start = self.index;
259 continue :state .start;
260 },
261 ';' => continue :state .comment,
262 '=' => continue :state .equal,
263 ',' => {
264 result.tag = .comma;
265 self.index += 1;
266 },
267 '"' => continue :state .quoted_identifier,
268 else => continue :state .identifier_or_keyword,
269 },
270 .comment => {
271 self.index += 1;
272 switch (self.source[self.index]) {
273 0 => continue :state .eof_or_invalid,
274 '\n' => {
275 self.index += 1;
276 result.start = self.index;
277 continue :state .start;
278 },
279 else => continue :state .comment,
280 }
281 },
282 .equal => {
283 self.index += 1;
284 switch (self.source[self.index]) {
285 '=' => {
286 result.tag = .equal_equal;
287 self.index += 1;
288 },
289 else => result.tag = .equal,
290 }
291 },
292 .quoted_identifier => {
293 self.index += 1;
294 switch (self.source[self.index]) {
295 0 => {
296 self.error_context_token = .{
297 .tag = .eof,
298 .start = self.index,
299 .end = self.index,
300 };
301 return error.UnfinishedQuotedIdentifier;
302 },
303 '"' => {
304 result.tag = .identifier;
305 self.index += 1;
306
307 // Return the token unquoted
308 return .{
309 .tag = result.tag,
310 .start = result.start + 1,
311 .end = self.index - 1,
312 };
313 },
314 else => continue :state .quoted_identifier,
315 }
316 },
317 .identifier_or_keyword => {
318 self.index += 1;
319 switch (self.source[self.index]) {
320 0, '=', ',', ';', '\r', '\n', ' ', '\t', '\x0B' => {
321 const keyword = Token.keywords.get(self.source[result.start..self.index]);
322 result.tag = keyword orelse .identifier;
323 },
324 else => continue :state .identifier_or_keyword,
325 }
326 },
327 .eof_or_invalid => {
328 if (self.index == self.source.len) {
329 return .{
330 .tag = .eof,
331 .start = self.index,
332 .end = self.index,
333 };
334 }
335 self.error_context_token = .{
336 .tag = .invalid,
337 .start = self.index,
338 .end = self.index + 1,
339 };
340 return error.InvalidByte;
341 },
342 }
343
344 result.end = self.index;
345 return result;
346 }
347};
348
349test Tokenizer {
350 try testTokenizer(
351 \\foo
352 \\; hello
353 \\BASE
354 \\"bar"
355 \\
356 , &.{
357 .identifier,
358 .keyword_base,
359 .identifier,
360 });
361}
362
363fn testTokenizer(source: [:0]const u8, expected: []const Token.Tag) !void {
364 var tokenizer = Tokenizer.init(source);
365 for (expected) |expected_tag| {
366 const token = try tokenizer.next();
367 try std.testing.expectEqual(expected_tag, token.tag);
368 }
369 const last_token = try tokenizer.next();
370 try std.testing.expectEqual(.eof, last_token.tag);
371}
372
373pub const Parser = struct {
374 tokenizer: *Tokenizer,
375 diagnostics: *Diagnostics,
376 lookahead_tokenizer: Tokenizer,
377 machine_type: std.coff.IMAGE.FILE.MACHINE,
378 module_definition_type: ModuleDefinitionType,
379
380 pub fn init(
381 tokenizer: *Tokenizer,
382 machine_type: std.coff.IMAGE.FILE.MACHINE,
383 module_definition_type: ModuleDefinitionType,
384 diagnostics: *Diagnostics,
385 ) Parser {
386 return .{
387 .tokenizer = tokenizer,
388 .machine_type = machine_type,
389 .module_definition_type = module_definition_type,
390 .diagnostics = diagnostics,
391 .lookahead_tokenizer = undefined,
392 };
393 }
394
395 pub const Error = error{ParseError} || std.mem.Allocator.Error;
396
397 pub fn parse(self: *Parser, allocator: std.mem.Allocator) Error!ModuleDefinition {
398 var module: ModuleDefinition = .{
399 .arena = .init(allocator),
400 .type = self.module_definition_type,
401 };
402 const arena = module.arena.allocator();
403 errdefer module.deinit();
404 while (true) {
405 const tok = try self.nextToken();
406 switch (tok.tag) {
407 .eof => break,
408 .keyword_library, .keyword_name => {
409 const is_library = tok.tag == .keyword_library;
410
411 const name = try self.lookaheadToken();
412 if (name.tag != .identifier) continue;
413 self.commitLookahead();
414
415 const base_tok = try self.lookaheadToken();
416 if (base_tok.tag == .keyword_base) {
417 self.commitLookahead();
418
419 _ = try self.expectToken(.equal);
420
421 module.base_address = try self.expectInteger(usize);
422 }
423
424 // Append .dll/.exe if there's no extension
425 const name_slice = name.slice(self.tokenizer.source);
426 module.name = if (std.fs.path.extension(name_slice).len == 0)
427 try std.mem.concat(arena, u8, &.{ name_slice, if (is_library) ".dll" else ".exe" })
428 else
429 try arena.dupe(u8, name_slice);
430 },
431 .keyword_exports => {
432 while (true) {
433 var name_tok = try self.lookaheadToken();
434 if (name_tok.tag != .identifier) break;
435 self.commitLookahead();
436
437 const ext_name_tok = ext_name: {
438 const equal = try self.lookaheadToken();
439 if (equal.tag != .equal) break :ext_name null;
440 self.commitLookahead();
441
442 // The syntax is `<ext_name> = <name>`, so we need to
443 // swap the current name token over to ext_name and use
444 // this token as the name.
445 const ext_name_tok = name_tok;
446 name_tok = try self.expectToken(.identifier);
447 break :ext_name ext_name_tok;
448 };
449
450 var name_needs_underscore = false;
451 var ext_name_needs_underscore = false;
452 if (self.machine_type == .I386) {
453 const is_decorated = isDecorated(name_tok.slice(self.tokenizer.source), self.module_definition_type);
454 const is_forward_target = ext_name_tok != null and std.mem.indexOfScalar(u8, name_tok.slice(self.tokenizer.source), '.') != null;
455 name_needs_underscore = !is_decorated and !is_forward_target;
456
457 if (ext_name_tok) |ext_name| {
458 ext_name_needs_underscore = !isDecorated(ext_name.slice(self.tokenizer.source), self.module_definition_type);
459 }
460 }
461
462 var import_name_tok: ?Token = null;
463 var export_as_tok: ?Token = null;
464 var ordinal: ?u16 = null;
465 var import_type: std.coff.ImportType = .CODE;
466 var private: bool = false;
467 var no_name: bool = false;
468 while (true) {
469 const arg_tok = try self.lookaheadToken();
470 switch (arg_tok.tag) {
471 .identifier => {
472 const slice = arg_tok.slice(self.tokenizer.source);
473 if (slice[0] != '@') break;
474
475 // foo @ 10
476 if (slice.len == 1) {
477 self.commitLookahead();
478 ordinal = try self.expectInteger(u16);
479 continue;
480 }
481 // foo @10
482 ordinal = std.fmt.parseUnsigned(u16, slice[1..], 0) catch {
483 // e.g. foo @bar, the @bar is presumed to be the start of a separate
484 // export (and there could be a newline between them)
485 break;
486 };
487 // finally safe to commit to consuming the token
488 self.commitLookahead();
489
490 const noname_tok = try self.lookaheadToken();
491 if (noname_tok.tag == .keyword_noname) {
492 self.commitLookahead();
493 no_name = true;
494 }
495 },
496 .equal_equal => {
497 self.commitLookahead();
498 import_name_tok = try self.expectToken(.identifier);
499 },
500 .keyword_data => {
501 self.commitLookahead();
502 import_type = .DATA;
503 },
504 .keyword_constant => {
505 self.commitLookahead();
506 import_type = .CONST;
507 },
508 .keyword_private => {
509 self.commitLookahead();
510 private = true;
511 },
512 .keyword_exportas => {
513 self.commitLookahead();
514 export_as_tok = try self.expectToken(.identifier);
515 },
516 else => break,
517 }
518 }
519
520 const name = if (name_needs_underscore)
521 try std.mem.concat(arena, u8, &.{ "_", name_tok.slice(self.tokenizer.source) })
522 else
523 try arena.dupe(u8, name_tok.slice(self.tokenizer.source));
524
525 const ext_name: ?[]const u8 = if (ext_name_tok) |ext_name| if (name_needs_underscore)
526 try std.mem.concat(arena, u8, &.{ "_", ext_name.slice(self.tokenizer.source) })
527 else
528 try arena.dupe(u8, ext_name.slice(self.tokenizer.source)) else null;
529
530 try module.exports.append(arena, .{
531 .name = name,
532 .mangled_symbol_name = null,
533 .ext_name = ext_name,
534 .import_name = if (import_name_tok) |imp_name| try arena.dupe(u8, imp_name.slice(self.tokenizer.source)) else null,
535 .export_as = if (export_as_tok) |export_as| try arena.dupe(u8, export_as.slice(self.tokenizer.source)) else null,
536 .no_name = no_name,
537 .ordinal = ordinal orelse 0,
538 .type = import_type,
539 .private = private,
540 });
541 }
542 },
543 .keyword_heapsize,
544 .keyword_stacksize,
545 .keyword_version,
546 => return self.unimplemented(tok),
547 else => {
548 self.diagnostics.* = .{
549 .err = .unknown_statement,
550 .token = tok,
551 };
552 return error.ParseError;
553 },
554 }
555 }
556 return module;
557 }
558
559 fn isDecorated(symbol: []const u8, module_definition_type: ModuleDefinitionType) bool {
560 // In def files, the symbols can either be listed decorated or undecorated.
561 //
562 // - For cdecl symbols, only the undecorated form is allowed.
563 // - For fastcall and vectorcall symbols, both fully decorated or
564 // undecorated forms can be present.
565 // - For stdcall symbols in non-MinGW environments, the decorated form is
566 // fully decorated with leading underscore and trailing stack argument
567 // size - like "_Func@0".
568 // - In MinGW def files, a decorated stdcall symbol does not include the
569 // leading underscore though, like "Func@0".
570
571 // This function controls whether a leading underscore should be added to
572 // the given symbol name or not. For MinGW, treat a stdcall symbol name such
573 // as "Func@0" as undecorated, i.e. a leading underscore must be added.
574 // For non-MinGW, look for '@' in the whole string and consider "_Func@0"
575 // as decorated, i.e. don't add any more leading underscores.
576 // We can't check for a leading underscore here, since function names
577 // themselves can start with an underscore, while a second one still needs
578 // to be added.
579 if (std.mem.startsWith(u8, symbol, "@")) return true;
580 if (std.mem.indexOf(u8, symbol, "@@") != null) return true;
581 if (std.mem.startsWith(u8, symbol, "?")) return true;
582 if (module_definition_type != .mingw and std.mem.indexOfScalar(u8, symbol, '@') != null) return true;
583 return false;
584 }
585
586 fn expectInteger(self: *Parser, T: type) Error!T {
587 const tok = try self.nextToken();
588 blk: {
589 if (tok.tag != .identifier) break :blk;
590 return std.fmt.parseUnsigned(T, tok.slice(self.tokenizer.source), 0) catch break :blk;
591 }
592 self.diagnostics.* = .{
593 .err = .expected_integer,
594 .token = tok,
595 };
596 return error.ParseError;
597 }
598
599 fn unimplemented(self: *Parser, tok: Token) Error {
600 self.diagnostics.* = .{
601 .err = .unimplemented,
602 .token = tok,
603 };
604 return error.ParseError;
605 }
606
607 fn expectToken(self: *Parser, tag: Token.Tag) Error!Token {
608 const tok = try self.nextToken();
609 if (tok.tag != tag) {
610 self.diagnostics.* = .{
611 .err = .expected_token,
612 .token = tok,
613 .extra = .{ .expected = tag },
614 };
615 return error.ParseError;
616 }
617 return tok;
618 }
619
620 fn nextToken(self: *Parser) Error!Token {
621 return self.nextFromTokenizer(self.tokenizer);
622 }
623
624 fn lookaheadToken(self: *Parser) Error!Token {
625 self.lookahead_tokenizer = self.tokenizer.*;
626 return self.nextFromTokenizer(&self.lookahead_tokenizer);
627 }
628
629 fn commitLookahead(self: *Parser) void {
630 self.tokenizer.* = self.lookahead_tokenizer;
631 }
632
633 fn nextFromTokenizer(
634 self: *Parser,
635 tokenizer: *Tokenizer,
636 ) Error!Token {
637 return tokenizer.next() catch |err| {
638 self.diagnostics.* = .{
639 .err = switch (err) {
640 error.InvalidByte => .invalid_byte,
641 error.UnfinishedQuotedIdentifier => .unfinished_quoted_identifier,
642 },
643 .token = tokenizer.error_context_token.?,
644 };
645 return error.ParseError;
646 };
647 }
648};
649
650test parse {
651 const source =
652 \\LIBRARY "foo"
653 \\; hello
654 \\EXPORTS
655 \\foo @ 10
656 \\bar @104
657 \\baz@4
658 \\foo == bar
659 \\alias = function
660 \\
661 \\data DATA
662 \\constant CONSTANT
663 \\
664 ;
665
666 try testParse(.AMD64, source, "foo.dll", &[_]ModuleDefinition.Export{
667 .{
668 .name = "foo",
669 .mangled_symbol_name = null,
670 .ext_name = null,
671 .import_name = null,
672 .export_as = null,
673 .no_name = false,
674 .ordinal = 10,
675 .type = .CODE,
676 .private = false,
677 },
678 .{
679 .name = "bar",
680 .mangled_symbol_name = null,
681 .ext_name = null,
682 .import_name = null,
683 .export_as = null,
684 .no_name = false,
685 .ordinal = 104,
686 .type = .CODE,
687 .private = false,
688 },
689 .{
690 .name = "baz@4",
691 .mangled_symbol_name = null,
692 .ext_name = null,
693 .import_name = null,
694 .export_as = null,
695 .no_name = false,
696 .ordinal = 0,
697 .type = .CODE,
698 .private = false,
699 },
700 .{
701 .name = "foo",
702 .mangled_symbol_name = null,
703 .ext_name = null,
704 .import_name = "bar",
705 .export_as = null,
706 .no_name = false,
707 .ordinal = 0,
708 .type = .CODE,
709 .private = false,
710 },
711 .{
712 .name = "function",
713 .mangled_symbol_name = null,
714 .ext_name = "alias",
715 .import_name = null,
716 .export_as = null,
717 .no_name = false,
718 .ordinal = 0,
719 .type = .CODE,
720 .private = false,
721 },
722 .{
723 .name = "data",
724 .mangled_symbol_name = null,
725 .ext_name = null,
726 .import_name = null,
727 .export_as = null,
728 .no_name = false,
729 .ordinal = 0,
730 .type = .DATA,
731 .private = false,
732 },
733 .{
734 .name = "constant",
735 .mangled_symbol_name = null,
736 .ext_name = null,
737 .import_name = null,
738 .export_as = null,
739 .no_name = false,
740 .ordinal = 0,
741 .type = .CONST,
742 .private = false,
743 },
744 });
745
746 try testParse(.I386, source, "foo.dll", &[_]ModuleDefinition.Export{
747 .{
748 .name = "_foo",
749 .mangled_symbol_name = null,
750 .ext_name = null,
751 .import_name = null,
752 .export_as = null,
753 .no_name = false,
754 .ordinal = 10,
755 .type = .CODE,
756 .private = false,
757 },
758 .{
759 .name = "_bar",
760 .mangled_symbol_name = null,
761 .ext_name = null,
762 .import_name = null,
763 .export_as = null,
764 .no_name = false,
765 .ordinal = 104,
766 .type = .CODE,
767 .private = false,
768 },
769 .{
770 .name = "_baz@4",
771 .mangled_symbol_name = null,
772 .ext_name = null,
773 .import_name = null,
774 .export_as = null,
775 .no_name = false,
776 .ordinal = 0,
777 .type = .CODE,
778 .private = false,
779 },
780 .{
781 .name = "_foo",
782 .mangled_symbol_name = null,
783 .ext_name = null,
784 .import_name = "bar",
785 .export_as = null,
786 .no_name = false,
787 .ordinal = 0,
788 .type = .CODE,
789 .private = false,
790 },
791 .{
792 .name = "_function",
793 .mangled_symbol_name = null,
794 .ext_name = "_alias",
795 .import_name = null,
796 .export_as = null,
797 .no_name = false,
798 .ordinal = 0,
799 .type = .CODE,
800 .private = false,
801 },
802 .{
803 .name = "_data",
804 .mangled_symbol_name = null,
805 .ext_name = null,
806 .import_name = null,
807 .export_as = null,
808 .no_name = false,
809 .ordinal = 0,
810 .type = .DATA,
811 .private = false,
812 },
813 .{
814 .name = "_constant",
815 .mangled_symbol_name = null,
816 .ext_name = null,
817 .import_name = null,
818 .export_as = null,
819 .no_name = false,
820 .ordinal = 0,
821 .type = .CONST,
822 .private = false,
823 },
824 });
825
826 try testParse(.ARMNT, source, "foo.dll", &[_]ModuleDefinition.Export{
827 .{
828 .name = "foo",
829 .mangled_symbol_name = null,
830 .ext_name = null,
831 .import_name = null,
832 .export_as = null,
833 .no_name = false,
834 .ordinal = 10,
835 .type = .CODE,
836 .private = false,
837 },
838 .{
839 .name = "bar",
840 .mangled_symbol_name = null,
841 .ext_name = null,
842 .import_name = null,
843 .export_as = null,
844 .no_name = false,
845 .ordinal = 104,
846 .type = .CODE,
847 .private = false,
848 },
849 .{
850 .name = "baz@4",
851 .mangled_symbol_name = null,
852 .ext_name = null,
853 .import_name = null,
854 .export_as = null,
855 .no_name = false,
856 .ordinal = 0,
857 .type = .CODE,
858 .private = false,
859 },
860 .{
861 .name = "foo",
862 .mangled_symbol_name = null,
863 .ext_name = null,
864 .import_name = "bar",
865 .export_as = null,
866 .no_name = false,
867 .ordinal = 0,
868 .type = .CODE,
869 .private = false,
870 },
871 .{
872 .name = "function",
873 .mangled_symbol_name = null,
874 .ext_name = "alias",
875 .import_name = null,
876 .export_as = null,
877 .no_name = false,
878 .ordinal = 0,
879 .type = .CODE,
880 .private = false,
881 },
882 .{
883 .name = "data",
884 .mangled_symbol_name = null,
885 .ext_name = null,
886 .import_name = null,
887 .export_as = null,
888 .no_name = false,
889 .ordinal = 0,
890 .type = .DATA,
891 .private = false,
892 },
893 .{
894 .name = "constant",
895 .mangled_symbol_name = null,
896 .ext_name = null,
897 .import_name = null,
898 .export_as = null,
899 .no_name = false,
900 .ordinal = 0,
901 .type = .CONST,
902 .private = false,
903 },
904 });
905
906 try testParse(.ARM64, source, "foo.dll", &[_]ModuleDefinition.Export{
907 .{
908 .name = "foo",
909 .mangled_symbol_name = null,
910 .ext_name = null,
911 .import_name = null,
912 .export_as = null,
913 .no_name = false,
914 .ordinal = 10,
915 .type = .CODE,
916 .private = false,
917 },
918 .{
919 .name = "bar",
920 .mangled_symbol_name = null,
921 .ext_name = null,
922 .import_name = null,
923 .export_as = null,
924 .no_name = false,
925 .ordinal = 104,
926 .type = .CODE,
927 .private = false,
928 },
929 .{
930 .name = "baz@4",
931 .mangled_symbol_name = null,
932 .ext_name = null,
933 .import_name = null,
934 .export_as = null,
935 .no_name = false,
936 .ordinal = 0,
937 .type = .CODE,
938 .private = false,
939 },
940 .{
941 .name = "foo",
942 .mangled_symbol_name = null,
943 .ext_name = null,
944 .import_name = "bar",
945 .export_as = null,
946 .no_name = false,
947 .ordinal = 0,
948 .type = .CODE,
949 .private = false,
950 },
951 .{
952 .name = "function",
953 .mangled_symbol_name = null,
954 .ext_name = "alias",
955 .import_name = null,
956 .export_as = null,
957 .no_name = false,
958 .ordinal = 0,
959 .type = .CODE,
960 .private = false,
961 },
962 .{
963 .name = "data",
964 .mangled_symbol_name = null,
965 .ext_name = null,
966 .import_name = null,
967 .export_as = null,
968 .no_name = false,
969 .ordinal = 0,
970 .type = .DATA,
971 .private = false,
972 },
973 .{
974 .name = "constant",
975 .mangled_symbol_name = null,
976 .ext_name = null,
977 .import_name = null,
978 .export_as = null,
979 .no_name = false,
980 .ordinal = 0,
981 .type = .CONST,
982 .private = false,
983 },
984 });
985}
986
987test "ntdll" {
988 const source =
989 \\;
990 \\; Definition file of ntdll.dll
991 \\; Automatic generated by gendef
992 \\; written by Kai Tietz 2008
993 \\;
994 \\LIBRARY "ntdll.dll"
995 \\EXPORTS
996 \\RtlDispatchAPC@12
997 \\RtlActivateActivationContextUnsafeFast@0
998 ;
999
1000 try testParse(.AMD64, source, "ntdll.dll", &[_]ModuleDefinition.Export{
1001 .{
1002 .name = "RtlDispatchAPC@12",
1003 .mangled_symbol_name = null,
1004 .ext_name = null,
1005 .import_name = null,
1006 .export_as = null,
1007 .no_name = false,
1008 .ordinal = 0,
1009 .type = .CODE,
1010 .private = false,
1011 },
1012 .{
1013 .name = "RtlActivateActivationContextUnsafeFast@0",
1014 .mangled_symbol_name = null,
1015 .ext_name = null,
1016 .import_name = null,
1017 .export_as = null,
1018 .no_name = false,
1019 .ordinal = 0,
1020 .type = .CODE,
1021 .private = false,
1022 },
1023 });
1024}
1025
1026fn testParse(machine_type: std.coff.IMAGE.FILE.MACHINE, source: [:0]const u8, expected_module_name: []const u8, expected_exports: []const ModuleDefinition.Export) !void {
1027 var diagnostics: Diagnostics = undefined;
1028 const module = parse(std.testing.allocator, source, machine_type, .mingw, &diagnostics) catch |err| switch (err) {
1029 error.OutOfMemory => |e| return e,
1030 error.ParseError => {
1031 const stderr, _ = std.debug.lockStderrWriter(&.{});
1032 defer std.debug.unlockStderrWriter();
1033 try diagnostics.writeMsg(stderr, source);
1034 try stderr.writeByte('\n');
1035 return err;
1036 },
1037 };
1038 defer module.deinit();
1039
1040 try std.testing.expectEqualStrings(expected_module_name, module.name orelse "");
1041 try std.testing.expectEqual(expected_exports.len, module.exports.items.len);
1042 for (expected_exports, module.exports.items) |expected, actual| {
1043 try std.testing.expectEqualStrings(expected.name, actual.name);
1044 try std.testing.expectEqualStrings(expected.export_as orelse "", actual.export_as orelse "");
1045 try std.testing.expectEqualStrings(expected.ext_name orelse "", actual.ext_name orelse "");
1046 try std.testing.expectEqualStrings(expected.import_name orelse "", actual.import_name orelse "");
1047 try std.testing.expectEqualStrings(expected.mangled_symbol_name orelse "", actual.mangled_symbol_name orelse "");
1048 try std.testing.expectEqual(expected.ordinal, actual.ordinal);
1049 try std.testing.expectEqual(expected.no_name, actual.no_name);
1050 try std.testing.expectEqual(expected.private, actual.private);
1051 try std.testing.expectEqual(expected.type, actual.type);
1052 }
1053}
1054
1055test "parse errors" {
1056 for (&[_]std.coff.IMAGE.FILE.MACHINE{ .AMD64, .I386, .ARMNT, .ARM64 }) |machine_type| {
1057 try testParseErrorMsg("invalid byte '\\x00'", machine_type, "LIBRARY \x00");
1058 try testParseErrorMsg("unfinished quoted identifier at '<eof>', expected closing '\"'", machine_type, "LIBRARY \"foo");
1059 try testParseErrorMsg("expected '=', got 'foo'", machine_type, "LIBRARY foo BASE foo");
1060 try testParseErrorMsg("expected integer, got 'foo'", machine_type, "EXPORTS foo @ foo");
1061 try testParseErrorMsg("support for 'HEAPSIZE' has not yet been implemented", machine_type, "HEAPSIZE");
1062 try testParseErrorMsg("unknown/invalid statement syntax beginning with 'LIB'", machine_type, "LIB");
1063 }
1064}
1065
1066fn testParseErrorMsg(expected_msg: []const u8, machine_type: std.coff.IMAGE.FILE.MACHINE, source: [:0]const u8) !void {
1067 var diagnostics: Diagnostics = undefined;
1068 _ = parse(std.testing.allocator, source, machine_type, .mingw, &diagnostics) catch |err| switch (err) {
1069 error.OutOfMemory => |e| return e,
1070 error.ParseError => {
1071 var buf: [256]u8 = undefined;
1072 var writer: std.Io.Writer = .fixed(&buf);
1073 try diagnostics.writeMsg(&writer, source);
1074 try std.testing.expectEqualStrings(expected_msg, writer.buffered());
1075 return;
1076 },
1077 };
1078 return error.UnexpectedSuccess;
1079}