| /* Scan linker error messages for missing template instantiations and provide |
| them. |
| |
| Copyright (C) 1995, 1998 Free Software Foundation, Inc. |
| Contributed by Jason Merrill (jason@cygnus.com). |
| |
| This file is part of GNU CC. |
| |
| GNU CC is free software; you can redistribute it and/or modify |
| it under the terms of the GNU General Public License as published by |
| the Free Software Foundation; either version 2, or (at your option) |
| any later version. |
| |
| GNU CC is distributed in the hope that it will be useful, |
| but WITHOUT ANY WARRANTY; without even the implied warranty of |
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| GNU General Public License for more details. |
| |
| You should have received a copy of the GNU General Public License |
| along with GNU CC; see the file COPYING. If not, write to |
| the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. */ |
| |
| #include "config.h" |
| #include "system.h" |
| #include "hash.h" |
| #include "demangle.h" |
| #include "toplev.h" |
| |
| #define MAX_ITERATIONS 17 |
| |
| /* Obstack allocation and deallocation routines. */ |
| #define obstack_chunk_alloc xmalloc |
| #define obstack_chunk_free free |
| |
| extern char * xmalloc PARAMS((unsigned)); |
| |
| /* Defined in collect2.c. */ |
| extern int vflag, debug; |
| extern char *ldout; |
| extern char *c_file_name; |
| extern struct obstack temporary_obstack; |
| extern struct obstack permanent_obstack; |
| extern char * temporary_firstobj; |
| |
| /* Defined in the automatically-generated underscore.c. */ |
| extern int prepends_underscore; |
| |
| static int tlink_verbose; |
| |
| /* Hash table code. */ |
| |
| typedef struct symbol_hash_entry |
| { |
| struct hash_entry root; |
| struct file_hash_entry *file; |
| int chosen; |
| int tweaking; |
| int tweaked; |
| } symbol; |
| |
| typedef struct file_hash_entry |
| { |
| struct hash_entry root; |
| const char *args; |
| const char *dir; |
| const char *main; |
| int tweaking; |
| } file; |
| |
| typedef struct demangled_hash_entry |
| { |
| struct hash_entry root; |
| const char *mangled; |
| } demangled; |
| |
| static struct hash_table symbol_table; |
| |
| static struct hash_entry * |
| symbol_hash_newfunc (entry, table, string) |
| struct hash_entry *entry; |
| struct hash_table *table; |
| const char *string; |
| { |
| struct symbol_hash_entry *ret = (struct symbol_hash_entry *) entry; |
| if (ret == NULL) |
| { |
| ret = ((struct symbol_hash_entry *) |
| hash_allocate (table, sizeof (struct symbol_hash_entry))); |
| if (ret == NULL) |
| return NULL; |
| } |
| ret = ((struct symbol_hash_entry *) |
| hash_newfunc ((struct hash_entry *) ret, table, string)); |
| ret->file = NULL; |
| ret->chosen = 0; |
| ret->tweaking = 0; |
| ret->tweaked = 0; |
| return (struct hash_entry *) ret; |
| } |
| |
| static struct symbol_hash_entry * |
| symbol_hash_lookup (string, create) |
| const char *string; |
| boolean create; |
| { |
| return ((struct symbol_hash_entry *) |
| hash_lookup (&symbol_table, string, create, true)); |
| } |
| |
| static struct hash_table file_table; |
| |
| static struct hash_entry * |
| file_hash_newfunc (entry, table, string) |
| struct hash_entry *entry; |
| struct hash_table *table; |
| const char *string; |
| { |
| struct file_hash_entry *ret = (struct file_hash_entry *) entry; |
| if (ret == NULL) |
| { |
| ret = ((struct file_hash_entry *) |
| hash_allocate (table, sizeof (struct file_hash_entry))); |
| if (ret == NULL) |
| return NULL; |
| } |
| ret = ((struct file_hash_entry *) |
| hash_newfunc ((struct hash_entry *) ret, table, string)); |
| ret->args = NULL; |
| ret->dir = NULL; |
| ret->main = NULL; |
| ret->tweaking = 0; |
| return (struct hash_entry *) ret; |
| } |
| |
| static struct file_hash_entry * |
| file_hash_lookup (string) |
| const char *string; |
| { |
| return ((struct file_hash_entry *) |
| hash_lookup (&file_table, string, true, true)); |
| } |
| |
| static struct hash_table demangled_table; |
| |
| static struct hash_entry * |
| demangled_hash_newfunc (entry, table, string) |
| struct hash_entry *entry; |
| struct hash_table *table; |
| const char *string; |
| { |
| struct demangled_hash_entry *ret = (struct demangled_hash_entry *) entry; |
| if (ret == NULL) |
| { |
| ret = ((struct demangled_hash_entry *) |
| hash_allocate (table, sizeof (struct demangled_hash_entry))); |
| if (ret == NULL) |
| return NULL; |
| } |
| ret = ((struct demangled_hash_entry *) |
| hash_newfunc ((struct hash_entry *) ret, table, string)); |
| ret->mangled = NULL; |
| return (struct hash_entry *) ret; |
| } |
| |
| static struct demangled_hash_entry * |
| demangled_hash_lookup (string, create) |
| const char *string; |
| boolean create; |
| { |
| return ((struct demangled_hash_entry *) |
| hash_lookup (&demangled_table, string, create, true)); |
| } |
| |
| /* Stack code. */ |
| |
| struct symbol_stack_entry |
| { |
| symbol *value; |
| struct symbol_stack_entry *next; |
| }; |
| struct obstack symbol_stack_obstack; |
| struct symbol_stack_entry *symbol_stack; |
| |
| struct file_stack_entry |
| { |
| file *value; |
| struct file_stack_entry *next; |
| }; |
| struct obstack file_stack_obstack; |
| struct file_stack_entry *file_stack; |
| |
| static void |
| symbol_push (p) |
| symbol *p; |
| { |
| struct symbol_stack_entry *ep = (struct symbol_stack_entry *) obstack_alloc |
| (&symbol_stack_obstack, sizeof (struct symbol_stack_entry)); |
| ep->value = p; |
| ep->next = symbol_stack; |
| symbol_stack = ep; |
| } |
| |
| static symbol * |
| symbol_pop () |
| { |
| struct symbol_stack_entry *ep = symbol_stack; |
| symbol *p; |
| if (ep == NULL) |
| return NULL; |
| p = ep->value; |
| symbol_stack = ep->next; |
| obstack_free (&symbol_stack_obstack, ep); |
| return p; |
| } |
| |
| static void |
| file_push (p) |
| file *p; |
| { |
| struct file_stack_entry *ep; |
| |
| if (p->tweaking) |
| return; |
| |
| ep = (struct file_stack_entry *) obstack_alloc |
| (&file_stack_obstack, sizeof (struct file_stack_entry)); |
| ep->value = p; |
| ep->next = file_stack; |
| file_stack = ep; |
| p->tweaking = 1; |
| } |
| |
| static file * |
| file_pop () |
| { |
| struct file_stack_entry *ep = file_stack; |
| file *p; |
| if (ep == NULL) |
| return NULL; |
| p = ep->value; |
| file_stack = ep->next; |
| obstack_free (&file_stack_obstack, ep); |
| p->tweaking = 0; |
| return p; |
| } |
| |
| /* Other machinery. */ |
| |
| static void |
| tlink_init () |
| { |
| char *p; |
| |
| hash_table_init (&symbol_table, symbol_hash_newfunc); |
| hash_table_init (&file_table, file_hash_newfunc); |
| hash_table_init (&demangled_table, demangled_hash_newfunc); |
| obstack_begin (&symbol_stack_obstack, 0); |
| obstack_begin (&file_stack_obstack, 0); |
| |
| p = getenv ("TLINK_VERBOSE"); |
| if (p) |
| tlink_verbose = atoi (p); |
| else |
| { |
| tlink_verbose = 1; |
| if (vflag) |
| tlink_verbose = 2; |
| if (debug) |
| tlink_verbose = 3; |
| } |
| } |
| |
| static int |
| tlink_execute (prog, argv, redir) |
| char *prog; |
| char **argv; |
| char *redir; |
| { |
| collect_execute (prog, argv, redir); |
| return collect_wait (prog); |
| } |
| |
| static char * |
| frob_extension (s, ext) |
| char *s, *ext; |
| { |
| char *p = rindex (s, '/'); |
| if (! p) |
| p = s; |
| p = rindex (p, '.'); |
| if (! p) |
| p = s + strlen (s); |
| |
| obstack_grow (&temporary_obstack, s, p - s); |
| return obstack_copy0 (&temporary_obstack, ext, strlen (ext)); |
| } |
| |
| static char * |
| obstack_fgets (stream, ob) |
| FILE *stream; |
| struct obstack *ob; |
| { |
| int c; |
| while ((c = getc (stream)) != EOF && c != '\n') |
| obstack_1grow (ob, c); |
| if (obstack_object_size (ob) == 0) |
| return NULL; |
| obstack_1grow (ob, '\0'); |
| return obstack_finish (ob); |
| } |
| |
| static char * |
| tfgets (stream) |
| FILE *stream; |
| { |
| return obstack_fgets (stream, &temporary_obstack); |
| } |
| |
| static char * |
| pfgets (stream) |
| FILE *stream; |
| { |
| return obstack_fgets (stream, &permanent_obstack); |
| } |
| |
| /* Real tlink code. */ |
| |
| static void |
| freadsym (stream, f, chosen) |
| FILE *stream; |
| file *f; |
| int chosen; |
| { |
| symbol *sym; |
| |
| { |
| char *name = tfgets (stream); |
| sym = symbol_hash_lookup (name, true); |
| } |
| |
| if (sym->file == NULL) |
| { |
| symbol_push (sym); |
| sym->file = f; |
| sym->chosen = chosen; |
| } |
| else if (chosen) |
| { |
| if (sym->chosen && sym->file != f) |
| { |
| if (sym->chosen == 1) |
| file_push (sym->file); |
| else |
| { |
| file_push (f); |
| f = sym->file; |
| chosen = sym->chosen; |
| } |
| } |
| sym->file = f; |
| sym->chosen = chosen; |
| } |
| } |
| |
| static void |
| read_repo_file (f) |
| file *f; |
| { |
| char c; |
| FILE *stream = fopen (f->root.string, "r"); |
| |
| if (tlink_verbose >= 2) |
| fprintf (stderr, "collect: reading %s\n", f->root.string); |
| |
| while (fscanf (stream, "%c ", &c) == 1) |
| { |
| switch (c) |
| { |
| case 'A': |
| f->args = pfgets (stream); |
| break; |
| case 'D': |
| f->dir = pfgets (stream); |
| break; |
| case 'M': |
| f->main = pfgets (stream); |
| break; |
| case 'P': |
| freadsym (stream, f, 2); |
| break; |
| case 'C': |
| freadsym (stream, f, 1); |
| break; |
| case 'O': |
| freadsym (stream, f, 0); |
| break; |
| } |
| obstack_free (&temporary_obstack, temporary_firstobj); |
| } |
| fclose (stream); |
| if (f->args == NULL) |
| f->args = getenv ("COLLECT_GCC_OPTIONS"); |
| if (f->dir == NULL) |
| f->dir = "."; |
| } |
| |
| static void |
| maybe_tweak (line, f) |
| char *line; |
| file *f; |
| { |
| symbol *sym = symbol_hash_lookup (line + 2, false); |
| |
| if ((sym->file == f && sym->tweaking) |
| || (sym->file != f && line[0] == 'C')) |
| { |
| sym->tweaking = 0; |
| sym->tweaked = 1; |
| |
| if (line[0] == 'O') |
| line[0] = 'C'; |
| else |
| line[0] = 'O'; |
| } |
| } |
| |
| static int |
| recompile_files () |
| { |
| file *f; |
| |
| while ((f = file_pop ()) != NULL) |
| { |
| char *line, *command; |
| FILE *stream = fopen (f->root.string, "r"); |
| char *outname = frob_extension (f->root.string, ".rnw"); |
| FILE *output = fopen (outname, "w"); |
| |
| while ((line = tfgets (stream)) != NULL) |
| { |
| switch (line[0]) |
| { |
| case 'C': |
| case 'O': |
| maybe_tweak (line, f); |
| } |
| fprintf (output, "%s\n", line); |
| } |
| fclose (stream); |
| fclose (output); |
| rename (outname, f->root.string); |
| |
| obstack_grow (&temporary_obstack, "cd ", 3); |
| obstack_grow (&temporary_obstack, f->dir, strlen (f->dir)); |
| obstack_grow (&temporary_obstack, "; ", 2); |
| obstack_grow (&temporary_obstack, c_file_name, strlen (c_file_name)); |
| obstack_1grow (&temporary_obstack, ' '); |
| obstack_grow (&temporary_obstack, f->args, strlen (f->args)); |
| obstack_1grow (&temporary_obstack, ' '); |
| command = obstack_copy0 (&temporary_obstack, f->main, strlen (f->main)); |
| |
| if (tlink_verbose) |
| fprintf (stderr, "collect: recompiling %s\n", f->main); |
| if (tlink_verbose >= 3) |
| fprintf (stderr, "%s\n", command); |
| |
| if (system (command) != 0) |
| return 0; |
| |
| read_repo_file (f); |
| |
| obstack_free (&temporary_obstack, temporary_firstobj); |
| } |
| return 1; |
| } |
| |
| static int |
| read_repo_files (object_lst) |
| char **object_lst; |
| { |
| char **object = object_lst; |
| |
| for (; *object; object++) |
| { |
| char *p = frob_extension (*object, ".rpo"); |
| file *f; |
| |
| if (! file_exists (p)) |
| continue; |
| |
| f = file_hash_lookup (p); |
| |
| read_repo_file (f); |
| } |
| |
| if (file_stack != NULL && ! recompile_files ()) |
| return 0; |
| |
| return (symbol_stack != NULL); |
| } |
| |
| static void |
| demangle_new_symbols () |
| { |
| symbol *sym; |
| |
| while ((sym = symbol_pop ()) != NULL) |
| { |
| demangled *dem; |
| char *p = cplus_demangle (sym->root.string, DMGL_PARAMS | DMGL_ANSI); |
| |
| if (! p) |
| continue; |
| |
| dem = demangled_hash_lookup (p, true); |
| dem->mangled = sym->root.string; |
| } |
| } |
| |
| static int |
| scan_linker_output (fname) |
| char *fname; |
| { |
| FILE *stream = fopen (fname, "r"); |
| char *line; |
| |
| while ((line = tfgets (stream)) != NULL) |
| { |
| char *p = line, *q; |
| symbol *sym; |
| int end; |
| |
| while (*p && ISSPACE (*p)) |
| ++p; |
| |
| if (! *p) |
| continue; |
| |
| for (q = p; *q && ! ISSPACE (*q); ++q) |
| ; |
| |
| /* Try the first word on the line. */ |
| if (*p == '.') |
| ++p; |
| if (*p == '_' && prepends_underscore) |
| ++p; |
| |
| end = ! *q; |
| *q = 0; |
| sym = symbol_hash_lookup (p, false); |
| |
| if (! sym && ! end) |
| /* Try a mangled name in `quotes'. */ |
| { |
| demangled *dem = 0; |
| p = index (q+1, '`'); |
| q = 0; |
| |
| #define MUL "multiple definition of " |
| #define UND "undefined reference to " |
| |
| if (p && (p - line > sizeof (MUL))) |
| { |
| char *beg = p - sizeof (MUL) + 1; |
| *p = 0; |
| if (!strcmp (beg, MUL) || !strcmp (beg, UND)) |
| p++, q = index (p, '\''); |
| } |
| if (q) |
| *q = 0, dem = demangled_hash_lookup (p, false); |
| if (dem) |
| sym = symbol_hash_lookup (dem->mangled, false); |
| } |
| |
| if (sym && sym->tweaked) |
| { |
| fclose (stream); |
| return 0; |
| } |
| if (sym && !sym->tweaking) |
| { |
| if (tlink_verbose >= 2) |
| fprintf (stderr, "collect: tweaking %s in %s\n", |
| sym->root.string, sym->file->root.string); |
| sym->tweaking = 1; |
| file_push (sym->file); |
| } |
| |
| obstack_free (&temporary_obstack, temporary_firstobj); |
| } |
| |
| fclose (stream); |
| return (file_stack != NULL); |
| } |
| |
| void |
| do_tlink (ld_argv, object_lst) |
| char **ld_argv, **object_lst; |
| { |
| int exit = tlink_execute ("ld", ld_argv, ldout); |
| |
| tlink_init (); |
| |
| if (exit) |
| { |
| int i = 0; |
| |
| /* Until collect does a better job of figuring out which are object |
| files, assume that everything on the command line could be. */ |
| if (read_repo_files (ld_argv)) |
| while (exit && i++ < MAX_ITERATIONS) |
| { |
| if (tlink_verbose >= 3) |
| dump_file (ldout); |
| demangle_new_symbols (); |
| if (! scan_linker_output (ldout)) |
| break; |
| if (! recompile_files ()) |
| break; |
| if (tlink_verbose) |
| fprintf (stderr, "collect: relinking\n"); |
| exit = tlink_execute ("ld", ld_argv, ldout); |
| } |
| } |
| |
| dump_file (ldout); |
| unlink (ldout); |
| if (exit) |
| { |
| error ("ld returned %d exit status", exit); |
| collect_exit (exit); |
| } |
| } |