7 #include <mono/metadata/image.h>
8 #include <mono/metadata/metadata.h>
9 #include <mono/metadata/assembly.h>
10 #include <mono/metadata/marshal.h>
11 #include <mono/metadata/class-internals.h>
12 #include <mono/metadata/metadata-internals.h>
15 #define DEBUG_PARSER(stmt) do { stmt; } while (0)
17 #define DEBUG_PARSER(stmt)
21 #define DEBUG_SCANNER(stmt) do { stmt; } while (0)
22 #define SCANNER_DEBUG 1
24 #define DEBUG_SCANNER(stmt)
32 identifier ::= ([a-z] | [A-Z]) ([a-z] | [A-Z] | [0-9] | [_-.])*
33 hexa_digit = [0-9] | [a-f] | [A-F]
34 number ::= hexadecimal | decimal
35 hexadecimal ::= (+-)?('0' [xX])? hexa_digit+
44 identifier '{' assembly_directive test_entry* '}'
50 validity patch (',' patch)*
53 'valid' | 'invalid' | 'badrt'
62 ('set-byte' | 'set-ushort' | 'set-uint' | 'set-bit' | 'or-byte' | 'or-ushort' | 'or-uint' | 'truncate' ) expression
68 number | variable | function_call
71 fun_name '(' arg_list ')'
85 expression ',' arg_list
97 TODO For the sake of a simple implementation, tokens are space delimited.
113 INVALID_VALIDITY_TEST,
119 INVALID_VARIABLE_NAME,
120 INVALID_FUNCTION_NAME,
155 typedef struct _expression expression_t;
159 int start, end; /*stream range text is in [start, end[*/
189 expression_t *expression;
195 expression_t *expression;
199 patch_selector_t *selector;
200 patch_effect_t *effect;
216 GSList *patches; /*of test_patch_t*/
219 test_set_t *test_set;
224 /*******************************************************************************************************/
225 static guint32 expression_eval (expression_t *exp, test_entry_t *entry);
226 static expression_t* parse_expression (scanner_t *scanner);
229 test_validity_name (int validity)
232 case TEST_TYPE_VALID:
234 case TEST_TYPE_INVALID:
236 case TEST_TYPE_BADRT:
239 printf ("Invalid test type %d\n", validity);
240 exit (INVALID_VALIDITY_TEST);
245 read_whole_file_and_close (const char *name, int *file_size)
247 FILE *file = fopen (name, "ro");
252 printf ("Could not open file %s\n", name);
253 exit (INVALID_FILE_NAME);
256 fseek (file, 0, SEEK_END);
257 fsize = ftell (file);
258 fseek (file, 0, SEEK_SET);
260 res = g_malloc (fsize + 1);
262 fread (res, fsize, 1, file);
269 init_test_set (test_set_t *test_set)
271 MonoImageOpenStatus status;
274 test_set->assembly_data = read_whole_file_and_close (test_set->assembly, &test_set->assembly_size);
275 test_set->image = mono_image_open_from_data (test_set->assembly_data, test_set->assembly_size, FALSE, &status);
276 if (!test_set->image || status != MONO_IMAGE_OK) {
277 printf ("Could not parse image %s\n", test_set->assembly);
278 exit (INVALID_BAD_FILE);
285 make_test_name (test_entry_t *entry, test_set_t *test_set)
287 return g_strdup_printf ("%s-%s-%d.exe", test_validity_name (entry->validity), test_set->name, test_set->count++);
290 #define READ_VAR(KIND, PTR) GUINT32_FROM_LE((guint32)*((KIND*)(PTR)))
291 #define SET_VAR(KIND, PTR, VAL) do { *((KIND*)(PTR)) = GUINT32_TO_LE ((KIND)VAL); } while (0)
293 #define READ_BIT(PTR,OFF) ((((guint8*)(PTR))[(OFF / 8)] & (1 << ((OFF) % 8))) != 0)
294 #define SET_BIT(PTR,OFF) do { ((guint8*)(PTR))[(OFF / 8)] |= (1 << ((OFF) % 8)); } while (0)
297 get_pe_header (test_entry_t *entry)
299 return READ_VAR (guint32, entry->data + 0x3c) + 4;
303 translate_rva (test_entry_t *entry, guint32 rva)
305 guint32 pe_header = get_pe_header (entry);
306 guint32 sectionCount = READ_VAR (guint16, entry->data + pe_header + 2);
307 guint32 idx = pe_header + 244;
309 while (sectionCount-- > 0) {
310 guint32 size = READ_VAR (guint32, entry->data + idx + 8);
311 guint32 base = READ_VAR (guint32, entry->data + idx + 12);
312 guint32 offset = READ_VAR (guint32, entry->data + idx + 20);
314 if (rva >= base && rva <= base + size)
315 return (rva - base) + offset;
318 printf ("Could not translate RVA %x\n", rva);
323 get_cli_header (test_entry_t *entry)
325 guint32 offset = get_pe_header (entry) + 20; /*pe-optional-header*/
326 offset += 208; /*cli header entry offset in the pe-optional-header*/
327 return translate_rva (entry, READ_VAR (guint32, entry->data + offset));
331 get_cli_metadata_root (test_entry_t *entry)
333 guint32 offset = get_cli_header (entry);
334 offset += 8; /*metadata rva offset*/
335 return translate_rva (entry, READ_VAR (guint32, entry->data + offset));
339 pad4 (guint32 offset)
342 offset += 4 - (offset % 4);
347 get_metadata_stream_header (test_entry_t *entry, guint32 idx)
351 offset = get_cli_metadata_root (entry);
352 offset = pad4 (offset + 16 + READ_VAR (guint32, entry->data + offset + 12));
360 for (i = 0; i < 32; ++i) {
361 if (!READ_VAR (guint8, entry->data + offset++))
364 offset = pad4 (offset);
370 lookup_var (test_entry_t *entry, const char *name)
372 if (!strcmp ("file-size", name))
373 return entry->data_size;
374 if (!strcmp ("pe-signature", name))
375 return get_pe_header (entry) - 4;
376 if (!strcmp ("pe-header", name))
377 return get_pe_header (entry);
378 if (!strcmp ("pe-optional-header", name))
379 return get_pe_header (entry) + 20;
380 if (!strcmp ("section-table", name))
381 return get_pe_header (entry) + 244;
382 if (!strcmp ("cli-header", name))
383 return get_cli_header (entry);
384 if (!strcmp ("cli-metadata", name))
385 return get_cli_metadata_root (entry);
386 if (!strcmp ("tables-header", name)) {
387 guint32 metadata_root = get_cli_metadata_root (entry);
388 guint32 tilde_stream = get_metadata_stream_header (entry, 0);
389 guint32 offset = READ_VAR (guint32, entry->data + tilde_stream);
390 return metadata_root + offset;
393 printf ("Unknown variable in expression %s\n", name);
394 exit (INVALID_VARIABLE_NAME);
398 call_func (test_entry_t *entry, const char *name, GSList *args)
400 if (!strcmp ("read.byte", name)) {
402 if (g_slist_length (args) != 1) {
403 printf ("Invalid number of args to read.ushort %d\n", g_slist_length (args));
404 exit (INVALID_ARG_COUNT);
406 offset = expression_eval (args->data, entry);
407 return READ_VAR (guint8, entry->data + offset);
409 if (!strcmp ("read.ushort", name)) {
411 if (g_slist_length (args) != 1) {
412 printf ("Invalid number of args to read.ushort %d\n", g_slist_length (args));
413 exit (INVALID_ARG_COUNT);
415 offset = expression_eval (args->data, entry);
416 return READ_VAR (guint16, entry->data + offset);
418 if (!strcmp ("read.uint", name)) {
420 if (g_slist_length (args) != 1) {
421 printf ("Invalid number of args to read.uint %d\n", g_slist_length (args));
422 exit (INVALID_ARG_COUNT);
424 offset = expression_eval (args->data, entry);
425 return READ_VAR (guint32, entry->data + offset);
427 if (!strcmp ("translate.rva", name)) {
429 if (g_slist_length (args) != 1) {
430 printf ("Invalid number of args to translate.rva %d\n", g_slist_length (args));
431 exit (INVALID_ARG_COUNT);
433 rva = expression_eval (args->data, entry);
434 return translate_rva (entry, rva);
436 if (!strcmp ("translate.rva.ind", name)) {
438 if (g_slist_length (args) != 1) {
439 printf ("Invalid number of args to translate.rva.ind %d\n", g_slist_length (args));
440 exit (INVALID_ARG_COUNT);
442 rva = expression_eval (args->data, entry);
443 rva = READ_VAR (guint32, entry->data + rva);
444 return translate_rva (entry, rva);
446 if (!strcmp ("stream-header", name)) {
448 if (g_slist_length (args) != 1) {
449 printf ("Invalid number of args to stream-header %d\n", g_slist_length (args));
450 exit (INVALID_ARG_COUNT);
452 idx = expression_eval (args->data, entry);
453 return get_metadata_stream_header (entry, idx);
455 if (!strcmp ("table-row", name)) {
458 const MonoTableInfo *info;
459 if (g_slist_length (args) != 2) {
460 printf ("Invalid number of args to table-row %d\n", g_slist_length (args));
461 exit (INVALID_ARG_COUNT);
463 table = expression_eval (args->data, entry);
464 row = expression_eval (args->next->data, entry);
465 info = mono_image_get_table_info (entry->test_set->image, table);
466 data = info->base + row * info->row_size;
467 return data - entry->test_set->assembly_data;
469 if (!strcmp ("blob.i", name)) {
470 guint32 offset, base;
471 MonoStreamHeader blob = entry->test_set->image->heap_blob;
472 if (g_slist_length (args) != 1) {
473 printf ("Invalid number of args to blob %d\n", g_slist_length (args));
474 exit (INVALID_ARG_COUNT);
476 base = blob.data - entry->test_set->image->raw_data;
477 offset = expression_eval (args->data, entry);
478 offset = READ_VAR (guint16, entry->data + offset);
479 return base + offset;
482 printf ("Unknown function %s\n", name);
483 exit (INVALID_FUNCTION_NAME);
488 expression_eval (expression_t *exp, test_entry_t *entry)
491 case EXPRESSION_CONSTANT:
492 return exp->data.constant;
493 case EXPRESSION_VARIABLE:
494 return lookup_var (entry, exp->data.name);
496 return expression_eval (exp->data.bin.left, entry) + expression_eval (exp->data.bin.right, entry);
498 return expression_eval (exp->data.bin.left, entry) - expression_eval (exp->data.bin.right, entry);
499 case EXPRESSION_FUNC:
500 return call_func (entry, exp->data.func.name, exp->data.func.args);
502 printf ("Invalid expression type %d\n", exp->type);
503 exit (INVALID_EXPRESSION);
508 apply_selector (patch_selector_t *selector, test_entry_t *entry)
511 if (selector->expression)
512 value = expression_eval (selector->expression, entry);
513 switch (selector->type) {
514 case SELECTOR_ABS_OFFSET:
515 DEBUG_PARSER (printf("\tabsolute offset selector [%04x]\n", value));
518 printf ("Invalid selector type %d\n", selector->type);
519 exit (INVALID_SELECTOR);
524 apply_effect (patch_effect_t *effect, test_entry_t *entry, guint32 offset)
527 char *ptr = entry->data + offset;
528 if (effect->expression)
529 value = expression_eval (effect->expression, entry);
531 switch (effect->type) {
532 case EFFECT_SET_BYTE:
533 DEBUG_PARSER (printf("\tset-byte effect old value [%x] new value [%x]\n", READ_VAR (guint8, ptr), value));
534 SET_VAR (guint8, ptr, value);
536 case EFFECT_SET_USHORT:
537 DEBUG_PARSER (printf("\tset-ushort effect old value [%x] new value [%x]\n", READ_VAR (guint16, ptr), value));
538 SET_VAR (guint16, ptr, value);
540 case EFFECT_SET_UINT:
541 DEBUG_PARSER (printf("\tset-uint effect old value [%x] new value [%x]\n", READ_VAR (guint32, ptr), value));
542 SET_VAR (guint32, ptr, value);
544 case EFFECT_SET_TRUNC:
545 DEBUG_PARSER (printf("\ttrunc effect [%d]\n", offset));
546 entry->data_size = offset;
549 DEBUG_PARSER (printf("\tset-bit effect bit %d old value [%x]\n", value, READ_BIT (ptr, value)));
550 SET_BIT (ptr, value);
553 DEBUG_PARSER (printf("\tor-byte effect old value [%x] new value [%x]\n", READ_VAR (guint8, ptr), value));
554 SET_VAR (guint8, ptr, READ_VAR (guint8, ptr) | value);
556 case EFFECT_OR_USHORT:
557 DEBUG_PARSER (printf("\tor-ushort effect old value [%x] new value [%x]\n", READ_VAR (guint16, ptr), value));
558 SET_VAR (guint16, ptr, READ_VAR (guint16, ptr) | value);
561 DEBUG_PARSER (printf("\tor-uint effect old value [%x] new value [%x]\n", READ_VAR (guint32, ptr), value));
562 SET_VAR (guint32, ptr, READ_VAR (guint32, ptr) | value);
565 printf ("Invalid effect type %d\n", effect->type);
566 exit (INVALID_EFFECT);
571 apply_patch (test_entry_t *entry, test_patch_t *patch)
573 guint32 offset = apply_selector (patch->selector, entry);
574 apply_effect (patch->effect, entry, offset);
578 process_test_entry (test_set_t *test_set, test_entry_t *entry)
584 init_test_set (test_set);
585 entry->data = g_memdup (test_set->assembly_data, test_set->assembly_size);
586 entry->data_size = test_set->assembly_size;
587 entry->test_set = test_set;
589 DEBUG_PARSER (printf("(%d)%s\n", test_set->count, test_validity_name (entry->validity)));
590 for (tmp = entry->patches; tmp; tmp = tmp->next)
591 apply_patch (entry, tmp->data);
593 file_name = make_test_name (entry, test_set);
595 f = fopen (file_name, "wo");
596 fwrite (entry->data, entry->data_size, 1, f);
602 /*******************************************************************************************************/
605 patch_free (test_patch_t *patch)
607 free (patch->selector);
608 free (patch->effect);
613 test_set_free (test_set_t *set)
616 free (set->assembly);
617 free (set->assembly_data);
619 mono_image_close (set->image);
623 test_entry_free (test_entry_t *entry)
628 for (tmp = entry->patches; tmp; tmp = tmp->next)
629 patch_free (tmp->data);
630 g_slist_free (entry->patches);
634 /*******************************************************************************************************/
636 token_type_name (int type)
646 return "punctuation";
648 return "end of file";
650 return "unknown token type";
653 #define CUR_CHAR (scanner->input [scanner->idx])
656 is_eof (scanner_t *scanner)
658 return scanner->idx >= scanner->size;
664 return c == '{' || c == '}' || c == ',';
668 skip_spaces (scanner_t *scanner)
671 while (!is_eof (scanner) && isspace (CUR_CHAR)) {
672 if (CUR_CHAR == '\n')
676 if (CUR_CHAR == '#') {
677 while (!is_eof (scanner) && CUR_CHAR != '\n') {
685 token_text_dup (scanner_t *scanner, token_t *token)
687 int len = token->end - token->start;
689 char *str = g_memdup (scanner->input + token->start, len + 1);
696 dump_token (scanner_t *scanner, token_t *token)
698 char *str = token_text_dup (scanner, token);
700 printf ("token '%s' of type '%s' at line %d\n", str, token_type_name (token->type), token->line);
707 is_special_char (char c)
722 next_token (scanner_t *scanner)
724 int start, end, type;
726 skip_spaces (scanner);
727 start = scanner->idx;
728 while (!is_eof (scanner) && !isspace (CUR_CHAR)) {
729 if (scanner->idx == start) {
730 if (is_special_char (CUR_CHAR)) {
734 } else if (is_special_char (CUR_CHAR))
740 c = scanner->input [start];
741 if (start >= scanner->size)
743 else if (isdigit (c) || c == '\'')
745 else if (ispunct_char (c))
749 scanner->current.start = start;
750 scanner->current.end = end;
751 scanner->current.type = type;
752 scanner->current.line = scanner->line;
754 DEBUG_SCANNER (dump_token (scanner, &scanner->current));
758 scanner_new (const char *file_name)
762 res = g_new0 (scanner_t, 1);
763 res->input = read_whole_file_and_close (file_name, &res->size);
772 scanner_free (scanner_t *scanner)
774 free (scanner->input);
779 scanner_get_current_token (scanner_t *scanner)
781 return &scanner->current;
785 scanner_get_type (scanner_t *scanner)
787 return scanner_get_current_token (scanner)->type;
791 scanner_get_line (scanner_t *scanner)
793 return scanner_get_current_token (scanner)->line;
797 scanner_text_dup (scanner_t *scanner)
799 return token_text_dup (scanner, scanner_get_current_token (scanner));
803 scanner_text_parse_number (scanner_t *scanner, long *res)
805 char *text = scanner_text_dup (scanner);
808 if (text [0] == '\'') {
809 ok = strlen (text) != 3 || text [2] != '\'';
813 *res = strtol (text, &end, 0);
822 match_current_type (scanner_t *scanner, int type)
824 return scanner_get_type (scanner) == type;
828 match_current_text (scanner_t *scanner, const char *text)
830 token_t *t = scanner_get_current_token (scanner);
831 return !strncmp (scanner->input + t->start, text, t->end - t->start);
835 match_current_type_and_text (scanner_t *scanner, int type, const char *text)
837 return match_current_type (scanner, type) && match_current_text (scanner, text);
840 /*******************************************************************************************************/
841 #define FAIL(MSG, REASON) do { \
842 printf ("%s at line %d for rule %s\n", MSG, scanner_get_line (scanner), __FUNCTION__); \
846 #define EXPECT_TOKEN(TYPE) do { \
847 if (scanner_get_type (scanner) != TYPE) { \
848 printf ("Expected %s but got %s '%s' at line %d for rule %s\n", token_type_name (TYPE), token_type_name (scanner_get_type (scanner)), scanner_text_dup (scanner), scanner_get_line (scanner), __FUNCTION__); \
849 exit (INVALID_TOKEN_TYPE); \
853 #define CONSUME_SPECIFIC_PUNCT(TEXT) do { \
854 EXPECT_TOKEN (TOKEN_PUNC); \
855 if (!match_current_text (scanner, TEXT)) { \
856 char *__tmp = scanner_text_dup (scanner); \
857 printf ("Expected '%s' but got '%s' at line %d for rule %s\n", TEXT, __tmp, scanner_get_line (scanner), __FUNCTION__); \
859 exit (INVALID_PUNC_TEXT); \
861 next_token (scanner); \
864 #define CONSUME_IDENTIFIER(DEST) do { \
865 EXPECT_TOKEN (TOKEN_ID); \
866 DEST = scanner_text_dup (scanner); \
867 next_token (scanner); \
870 #define CONSUME_SPECIFIC_IDENTIFIER(TEXT) do { \
871 EXPECT_TOKEN (TOKEN_ID); \
872 if (!match_current_text (scanner, TEXT)) { \
873 char *__tmp = scanner_text_dup (scanner); \
874 printf ("Expected '%s' but got '%s' at line %d for rule %s\n", TEXT, __tmp, scanner_get_line (scanner), __FUNCTION__); \
876 exit (INVALID_ID_TEXT); \
878 next_token (scanner); \
881 #define CONSUME_NUMBER(DEST) do { \
883 EXPECT_TOKEN (TOKEN_NUM); \
884 if (scanner_text_parse_number (scanner, &__tmp_num)) { \
885 char *__tmp = scanner_text_dup (scanner); \
886 printf ("Expected a number but got '%s' at line %d for rule %s\n", __tmp, scanner_get_line (scanner), __FUNCTION__); \
888 exit (INVALID_NUMBER); \
891 next_token (scanner); \
894 #define LA_ID(TEXT) (scanner_get_type (scanner) == TOKEN_ID && match_current_text (scanner, TEXT))
895 #define LA_PUNCT(TEXT) (scanner_get_type (scanner) == TOKEN_PUNC && match_current_text (scanner, TEXT))
897 /*******************************************************************************************************/
900 parse_atom (scanner_t *scanner)
902 expression_t *atom = g_new0 (expression_t, 1);
903 if (scanner_get_type (scanner) == TOKEN_NUM) {
904 atom->type = EXPRESSION_CONSTANT;
905 CONSUME_NUMBER (atom->data.constant);
908 CONSUME_IDENTIFIER (name);
910 atom->data.func.name = name;
911 atom->type = EXPRESSION_FUNC;
912 CONSUME_SPECIFIC_IDENTIFIER ("(");
914 while (!LA_ID (")") && !match_current_type (scanner, TOKEN_EOF))
915 atom->data.func.args = g_slist_append (atom->data.func.args, parse_expression (scanner));
917 CONSUME_SPECIFIC_IDENTIFIER (")");
919 atom->data.name = name;
920 atom->type = EXPRESSION_VARIABLE;
928 parse_expression (scanner_t *scanner)
930 expression_t *exp = parse_atom (scanner);
932 while (LA_ID ("-") || LA_ID ("+")) {
934 CONSUME_IDENTIFIER (text);
935 expression_t *left = exp;
936 exp = g_new0 (expression_t, 1);
937 exp->type = !strcmp ("+", text) ? EXPRESSION_ADD: EXPRESSION_SUB;
938 exp->data.bin.left = left;
939 exp->data.bin.right = parse_atom (scanner);
945 static patch_selector_t*
946 parse_selector (scanner_t *scanner)
948 patch_selector_t *selector;
950 CONSUME_SPECIFIC_IDENTIFIER ("offset");
952 selector = g_new0 (patch_selector_t, 1);
953 selector->type = SELECTOR_ABS_OFFSET;
954 selector->expression = parse_expression (scanner);
958 static patch_effect_t*
959 parse_effect (scanner_t *scanner)
961 patch_effect_t *effect;
965 CONSUME_IDENTIFIER(name);
967 if (!strcmp ("set-byte", name))
968 type = EFFECT_SET_BYTE;
969 else if (!strcmp ("set-ushort", name))
970 type = EFFECT_SET_USHORT;
971 else if (!strcmp ("set-uint", name))
972 type = EFFECT_SET_UINT;
973 else if (!strcmp ("set-bit", name))
974 type = EFFECT_SET_BIT;
975 else if (!strcmp ("truncate", name))
976 type = EFFECT_SET_TRUNC;
977 else if (!strcmp ("or-byte", name))
978 type = EFFECT_OR_BYTE;
979 else if (!strcmp ("or-ushort", name))
980 type = EFFECT_OR_USHORT;
981 else if (!strcmp ("or-uint", name))
982 type = EFFECT_OR_UINT;
984 FAIL(g_strdup_printf ("Invalid effect kind, expected one of: (set-byte set-ushort set-uint set-bit or-byte or-ushort or-uint truncate) but got %s",name), INVALID_ID_TEXT);
986 effect = g_new0 (patch_effect_t, 1);
988 if (type != EFFECT_SET_TRUNC)
989 effect->expression = parse_expression (scanner);
994 parse_patch (scanner_t *scanner)
998 patch = g_new0 (test_patch_t, 1);
999 patch->selector = parse_selector (scanner);
1000 patch->effect = parse_effect (scanner);
1005 parse_validity (scanner_t *scanner)
1009 CONSUME_IDENTIFIER (name);
1011 if (!strcmp (name, "valid"))
1012 validity = TEST_TYPE_VALID;
1013 else if (!strcmp (name, "invalid"))
1014 validity = TEST_TYPE_INVALID;
1015 else if (!strcmp (name, "badrt"))
1016 validity = TEST_TYPE_BADRT;
1018 printf ("Expected either 'valid', 'invalid' or 'badtr' but got '%s' at the begining of a test entry at line %d\n", name, scanner_get_line (scanner));
1019 exit (INVALID_VALIDITY_TEST);
1027 parse_test_entry (scanner_t *scanner, test_set_t *test_set)
1029 test_entry_t entry = { 0 };
1031 entry.validity = parse_validity (scanner);
1035 CONSUME_SPECIFIC_PUNCT (",");
1036 entry.patches = g_slist_append (entry.patches, parse_patch (scanner));
1037 } while (match_current_type_and_text (scanner, TOKEN_PUNC, ","));
1039 process_test_entry (test_set, &entry);
1041 test_entry_free (&entry);
1045 parse_test (scanner_t *scanner)
1047 test_set_t set = { 0 };
1049 CONSUME_IDENTIFIER (set.name);
1050 CONSUME_SPECIFIC_PUNCT ("{");
1051 CONSUME_SPECIFIC_IDENTIFIER ("assembly");
1052 CONSUME_IDENTIFIER (set.assembly);
1054 DEBUG_PARSER (printf ("RULE %s using assembly %s\n", set.name, set.assembly));
1056 while (!match_current_type (scanner, TOKEN_EOF) && !match_current_type_and_text (scanner, TOKEN_PUNC, "}"))
1057 parse_test_entry (scanner, &set);
1059 CONSUME_SPECIFIC_PUNCT ("}");
1061 test_set_free (&set);
1066 parse_program (scanner_t *scanner)
1068 while (!match_current_type (scanner, TOKEN_EOF))
1069 parse_test (scanner);
1074 digest_file (const char *file)
1076 scanner_t *scanner = scanner_new (file);
1077 parse_program (scanner);
1078 scanner_free (scanner);
1082 main (int argc, char **argv)
1085 printf ("usage: gen-md.test file_to_process\n");
1089 mono_init_version ("gen-md-test", "v2.0.50727");
1090 mono_marshal_init ();
1092 digest_file (argv [1]);