/* * Copyright (c) 1980 Regents of the University of California. * All rights reserved. The Berkeley software License Agreement * specifies the terms and conditions for redistribution. */ #ifndef lint char copyright[] = "@(#) Copyright (c) 1980 Regents of the University of California.\n\ All rights reserved.\n"; #endif not lint #ifndef lint static char sccsid[] = "@(#)pc3.c 5.1 (Berkeley) 6/5/85"; #endif not lint /* * Pc3 is a pass in the Berkeley Pascal compilation * process that is performed just prior to linking Pascal * object files. Its purpose is to enforce the rules of * separate compilation for Berkeley Pascal. Pc3 is called * with the same argument list of object files that is sent to * the loader. These checks are performed by pc3 by examining * the symbol tables of the object files: * (1) All .o files must be up to date with respect to the * runtime libraries. * (2) Each global Pascal symbol (label, constant, type, * variable, procedure, or function name) must be uniquely * declared, i.e. declared in only one included file or * source file. * (3) Each external function (or procedure) may be resolved * at most once in a source file which included the * external declaration of the function. * * The symbol table of each object file is scanned and * each global Pascal symbol is placed in a hashed symbol * table. The Pascal compiler has been modified to emit all * Pascal global symbols to the object file symbol table. The * information stored in the symbol table for each such symbol * is: * * - the name of the symbol; * - a subtype descriptor; * - the file which logically contains the declaration of * the symbol or which caused the inclusion of an include file. * - for included files: * - a checksum; * - for symbols: * - the file which textually contains the declaration of * the symbol (possibly an include file); * - the line number at which the symbol is declared; * - the file which contains the resolution of the symbol. * - the line number at which the symbol is resolved; * * If a symbol has been previously entered into the symbol * table, a check is made that the current declaration is of * the same type and from the same include file as the previous * one. Except for files and functions and procedures, it is * an error for a symbol declaration to be encountered more * than once, unless the re-declarations come from the same * included file as the original. * * As an include file symbol is encountered in a source * file, the symbol table entry of each symbol declared in that * include file is modified to reflect its new logical * inclusion in the source file. File symbols are also * encountered as an included file ends, signaling the * continuation of the enclosing file. * * Functions and procedures which have been declared * external may be resolved by declarations from source files * which included the external declaration of the function. * Functions and procedures may be resolved at most once across * a set of object files. The loader will complain if a * function is not resolved at least once. */ char program[] = "pc"; #include #include #include #include #include #include #include #include "pstab.h" #include "pc3.h" int errors = NONE; BOOL wflag = FALSE; /* * check each of the argument .o files (or archives of .o files). */ main( argc , argv ) int argc; char **argv; { struct fileinfo ofile; for ( argv++ ; *argv != 0 && **argv == '-' ; argv++ ) { (*argv)++; switch ( **argv ) { default: error( FATAL , "pc3: bad flag -%c\n" , **argv ); case 'w': wflag = TRUE; break; } } for ( /* void */ ; *argv != 0 ; argv++ ) { # ifdef DEBUG fprintf( stderr , "[main] *argv = %s\n" , *argv ); # endif DEBUG ofile.name = *argv; checkfile( &ofile ); } exit( errors ); } /* * check the namelist of a file, or all namelists of an archive. */ checkfile( ofilep ) struct fileinfo *ofilep; { union { char mag_armag[ SARMAG + 1 ]; struct exec mag_exec; } mag_un; int red; struct stat filestat; ofilep -> file = fopen( ofilep -> name , "r" ); if ( ofilep -> file == NULL ) { error( ERROR , "cannot open: %s" , ofilep -> name ); return; } fstat( fileno( ofilep -> file ) , &filestat ); red = fread( (char *) &mag_un , 1 , sizeof mag_un , ofilep -> file ); if ( red != sizeof mag_un ) { error( ERROR , "cannot read header: %s" , ofilep -> name ); return; } if ( mag_un.mag_exec.a_magic == OARMAG ) { error( WARNING , "old archive: %s" , ofilep -> name ); return; } if ( strncmp( mag_un.mag_armag , ARMAG , SARMAG ) == 0 ) { /* archive, iterate through elements */ # ifdef DEBUG fprintf( stderr , "[checkfile] archive %s\n" , ofilep -> name ); # endif DEBUG ofilep -> nextoffset = SARMAG; while ( nextelement( ofilep ) ) { checknl( ofilep ); } } else if ( N_BADMAG( mag_un.mag_exec ) ) { /* not a file.o */ error( ERROR , "bad format: %s" , ofilep -> name ); return; } else { /* a file.o */ # ifdef DEBUG fprintf( stderr , "[checkfile] .o file %s\n" , ofilep -> name ); # endif DEBUG fseek( ofilep -> file , 0L , 0 ); ofilep -> nextoffset = filestat.st_size; checknl( ofilep ); } fclose( ofilep -> file ); } /* * check the namelist of this file for conflicts with * previously entered symbols. */ checknl( ofilep ) register struct fileinfo *ofilep; { long red; struct exec oexec; off_t symoff; long numsyms; register struct nlist *nlp; register char *stringp; long strsize; long sym; red = fread( (char *) &oexec , 1 , sizeof oexec , ofilep -> file ); if ( red != sizeof oexec ) { error( ERROR , "error reading struct exec: %s" , ofilep -> name ); return; } if ( N_BADMAG( oexec ) ) { return; } symoff = N_SYMOFF( oexec ) - sizeof oexec; fseek( ofilep -> file , symoff , 1 ); numsyms = oexec.a_syms / sizeof ( struct nlist ); if ( numsyms == 0 ) { error( WARNING , "no name list: %s" , ofilep -> name ); return; } nlp = (struct nlist *) calloc( numsyms , sizeof ( struct nlist ) ); if ( nlp == 0 ) { error( FATAL , "no room for %d nlists" , numsyms ); } red = fread( ( char * ) nlp , numsyms , sizeof ( struct nlist ) , ofilep -> file ); if ( ftell( ofilep -> file ) + sizeof ( off_t ) >= ofilep -> nextoffset ) { error( WARNING , "no string table (old format .o?)" , ofilep -> name ); return; } red = fread( (char *) &strsize , sizeof strsize , 1 , ofilep -> file ); if ( red != 1 ) { error( WARNING , "no string table (old format .o?)" , ofilep -> name ); return; } stringp = ( char * ) malloc( strsize ); if ( stringp == 0 ) { error( FATAL , "no room for %d bytes of strings" , strsize ); } red = fread( stringp + sizeof strsize , strsize - sizeof ( strsize ) , 1 , ofilep -> file ); if ( red != 1 ) { error( WARNING , "error reading string table: %s" , ofilep -> name ); } # ifdef DEBUG fprintf( stderr , "[checknl] %s: %d symbols\n" , ofilep -> name , numsyms ); # endif DEBUG for ( sym = 0 ; sym < numsyms ; sym++) { if ( nlp[ sym ].n_un.n_strx ) { nlp[ sym ].n_un.n_name = stringp + nlp[ sym ].n_un.n_strx; } else { nlp[ sym ].n_un.n_name = ""; } checksymbol( &nlp[ sym ] , ofilep ); } if ( nlp ) { free( nlp ); } if ( stringp ) { free( stringp ); } } /* * check a symbol. * look it up in the hashed symbol table, * entering it if necessary. * this maintains a state of which .p and .i files * it is currently in the midst from the nlist entries * for source and included files. * if we are inside a .p but not a .i, pfilep == ifilep. */ checksymbol( nlp , ofilep ) struct nlist *nlp; struct fileinfo *ofilep; { static struct symbol *pfilep = NIL; static struct symbol *ifilep = NIL; register struct symbol *symbolp; int errtype; # ifdef DEBUG if ( pfilep && ifilep ) { fprintf( stderr , "[checksymbol] pfile %s ifile %s\n" , pfilep -> name , ifilep -> name ); } fprintf( stderr , "[checksymbol] ->name %s ->n_desc %x (%s)\n" , nlp -> n_un.n_name , nlp -> n_desc , classify( nlp -> n_desc ) ); # endif DEBUG if ( nlp -> n_type != N_PC ) { /* don't care about the others */ return; } symbolp = entersymbol( nlp -> n_un.n_name ); if ( symbolp -> lookup == NEW ) { # ifdef DEBUG fprintf( stderr , "[checksymbol] ->name %s is NEW\n" , symbolp -> name ); # endif DEBUG symbolp -> desc = nlp -> n_desc; symbolp -> fromp = pfilep; switch ( symbolp -> desc ) { default: error( FATAL , "panic: [checksymbol] NEW" ); case N_PGLABEL: case N_PGCONST: case N_PGTYPE: case N_PGVAR: case N_PGFUNC: case N_PGPROC: case N_PLDATA: case N_PLTEXT: symbolp -> sym_un.sym_str.rfilep = ifilep; symbolp -> sym_un.sym_str.rline = nlp -> n_value; symbolp -> sym_un.sym_str.fromi = ifilep; symbolp -> sym_un.sym_str.iline = nlp -> n_value; return; case N_PEFUNC: case N_PEPROC: symbolp -> sym_un.sym_str.rfilep = NIL; symbolp -> sym_un.sym_str.rline = 0; /* * functions can only be declared external * in included files. */ if ( pfilep == ifilep ) { error( WARNING , "%s, line %d: %s %s must be declared in included file" , pfilep -> name , nlp -> n_value , classify( symbolp -> desc ) , symbolp -> name ); } symbolp -> sym_un.sym_str.fromi = ifilep; symbolp -> sym_un.sym_str.iline = nlp -> n_value; return; case N_PSO: if ( nlp -> n_value < N_FLAGCHECKSUM ) { error( WARNING, "%s is out of date and should be recompiled", ofilep -> name ); } pfilep = symbolp; ifilep = symbolp; symbolp -> sym_un.checksum = N_FLAGCHECKSUM; return; case N_PSOL: ifilep = symbolp; symbolp -> sym_un.checksum = nlp -> n_value; return; } } else { # ifdef DEBUG fprintf( stderr , "[checksymbol] ->name %s is OLD\n" , symbolp -> name ); # endif DEBUG errtype = ERROR; switch ( symbolp -> desc ) { default: error( FATAL , "panic [checksymbol] OLD" ); return; case N_PSO: /* * finding a file again means you are back * in it after finishing an include file. */ if ( symbolp -> desc != nlp -> n_desc ) { error( FATAL , "panic [checksymbol] PSO" ); return; } pfilep = symbolp; ifilep = symbolp; return; case N_PSOL: /* * include files can be seen more than once, * but their checksums are checked if they are * greater than N_FLAGCHECKSUM. * PSOL's are seen with checksums as the * include file is entered, and with * N_FLAGCHECKSUM as we are back in an * included file from a nested include. */ if ( symbolp -> desc != nlp -> n_desc ) { error( FATAL , "panic [checksymbol] PSOL" ); return; } if ((unsigned) symbolp->sym_un.checksum > N_FLAGCHECKSUM && (unsigned) nlp -> n_value > N_FLAGCHECKSUM && symbolp -> sym_un.checksum != nlp -> n_value ) { error( ERROR, "%s included in %s differs from %s included in %s", symbolp -> name, pfilep -> name, symbolp -> name, symbolp -> fromp -> name ); } ifilep = symbolp; return; case N_PEFUNC: case N_PEPROC: /* * this might be the resolution of the external * has to match func/proc of external * and has to have included external * and has to not have been previously resolved. */ if ( ( ( symbolp -> desc == N_PEFUNC && nlp -> n_desc == N_PGFUNC ) || ( symbolp -> desc == N_PEPROC && nlp -> n_desc == N_PGPROC ) ) && ( symbolp -> fromp == pfilep ) && ( symbolp -> sym_un.sym_str.rfilep == NIL ) ) { /* * resolve external */ # ifdef DEBUG fprintf( stderr , "[checksymbol] resolving external\n" ); # endif DEBUG symbolp -> sym_un.sym_str.rfilep = ifilep; symbolp -> sym_un.sym_str.rline = nlp -> n_value; return; } /* * otherwise, it might be another external, * which is okay if it's * the same type and from the same include file */ if ( ( ( symbolp -> desc == N_PEFUNC && nlp -> n_desc == N_PEFUNC ) || ( symbolp -> desc == N_PEPROC && nlp -> n_desc == N_PEPROC ) ) && ( symbolp -> sym_un.sym_str.fromi == ifilep ) ) { /* * just another pretty external * make it look like it comes from here. */ # ifdef DEBUG fprintf( stderr , "[checksymbol] just another pretty external\n" ); # endif DEBUG symbolp -> fromp = pfilep; return; } /* * something is wrong * if it's not resolved, use the header file * otherwise, it's just a regular error */ if ( symbolp -> sym_un.sym_str.rfilep == NIL ) { error( ERROR , "%s, line %d: %s is already defined\n\t(%s, line %d)." , ifilep -> name , nlp -> n_value , nlp -> n_un.n_name , symbolp -> sym_un.sym_str.fromi -> name , symbolp -> sym_un.sym_str.iline ); return; } break; case N_PGFUNC: case N_PGPROC: /* * functions may not be seen more than once. * the loader will complain about * `multiply defined', but we can, too. */ break; case N_PGLABEL: case N_PGCONST: case N_PGTYPE: case N_PGVAR: /* * labels, constants, types, variables * and external declarations * may be seen as many times as they want, * as long as they come from the same include file. * make it look like they come from this .p file. */ included: if ( nlp -> n_desc != symbolp -> desc || symbolp -> sym_un.sym_str.fromi != ifilep ) { break; } symbolp -> fromp = pfilep; return; case N_PLDATA: case N_PLTEXT: switch ( nlp -> n_desc ) { default: error( FATAL , "pc3: unknown stab 0x%x" , nlp -> n_desc ); return; case N_PSO: case N_PSOL: case N_PGCONST: case N_PGTYPE: /* these won't conflict with library */ return; case N_PGLABEL: case N_PGVAR: case N_PGFUNC: case N_PGPROC: case N_PEFUNC: case N_PEPROC: case N_PLDATA: case N_PLTEXT: errtype = WARNING; break; } break; } /* * this is the breaks */ error( errtype , "%s, line %d: %s %s is already defined\n\t%s%s (%s, line %d)." , ifilep -> name , nlp -> n_value , classify( nlp -> n_desc ) , nlp -> n_un.n_name , ( symbolp -> desc == nlp -> n_desc ? "" : " as " ) , ( symbolp -> desc == nlp -> n_desc ? "" : article( symbolp -> desc ) ) , symbolp -> sym_un.sym_str.rfilep -> name , symbolp -> sym_un.sym_str.rline ); } } /* * quadratically hashed symbol table. * things are never deleted from the hash symbol table. * as more hash table is needed, * a new one is alloc'ed and chained to the end. * search is by rehashing within each table, * traversing chains to next table if unsuccessful. */ struct symbol * entersymbol( name ) char *name; { static struct symboltableinfo symboltable; char *enteredname; long hashindex; register struct symboltableinfo *tablep; register struct symbol **herep; register struct symbol **limitp; register long increment; enteredname = enterstring( name ); hashindex = SHORT_ABS( ( long ) enteredname ) % SYMBOLPRIME; for ( tablep = &symboltable ; /*return*/ ; tablep = tablep -> chain ) { if ( tablep == NIL ) { # ifdef SPACEDEBUG fprintf( stderr , "[entersymbol] calloc'ing table for %d symbols\n" , SYMBOLPRIME ); # endif SPACEDEBUG for ( tablep = &symboltable ; tablep->chain != NIL ; tablep = tablep->chain ) { continue; } tablep->chain = ( struct symboltableinfo * ) calloc( 1 , sizeof ( struct symboltableinfo ) ); if ( tablep->chain == NIL ) { error( FATAL , "ran out of memory (entersymbol)" ); } tablep = tablep->chain; } herep = &( tablep -> entry[ hashindex ] ); limitp = &( tablep -> entry[ SYMBOLPRIME ] ); increment = 1; do { if ( *herep == NIL ) { /* empty */ if ( tablep -> used > ( ( SYMBOLPRIME / 4 ) * 3 ) ) { /* too full, break for next table */ break; } tablep -> used++; *herep = symbolalloc(); ( *herep ) -> name = enteredname; ( *herep ) -> lookup = NEW; # ifdef HASHDEBUG fprintf( stderr , "[entersymbol] name %s NEW after %d\n" , enteredname , increment / 2 ); # endif HASHDEBUG return *herep; } /* a find? */ if ( ( *herep ) -> name == enteredname ) { ( *herep ) -> lookup = OLD; # ifdef HASHDEBUG fprintf( stderr , "[entersymbol] name %s OLD at %d\n" , enteredname , increment / 2 ); # endif HASHDEBUG return *herep; } herep += increment; if ( herep >= limitp ) { herep -= SYMBOLPRIME; } increment += 2; } while ( increment < SYMBOLPRIME ); # ifdef HASHDEBUG fprintf( stderr , "[entersymbol] next symboltable\n" ); # endif HASHDEBUG } } /* * allocate a symbol from the dynamically allocated symbol table. */ struct symbol * symbolalloc() { static struct symbol *nextsymbol = NIL; static long symbolsleft = 0; struct symbol *newsymbol; if ( symbolsleft <= 0 ) { # ifdef SPACEDEBUG fprintf( stderr , "[symbolalloc] malloc space for %d symbols\n" , SYMBOLALLOC / sizeof( struct symbol ) ); # endif SPACEDEBUG nextsymbol = ( struct symbol * ) malloc( SYMBOLALLOC ); if ( nextsymbol == 0 ) { error( FATAL , "ran out of memory (symbolalloc)" ); } symbolsleft = SYMBOLALLOC / sizeof( struct symbol ); } newsymbol = nextsymbol; nextsymbol++; symbolsleft--; return newsymbol; } /* * hash a string based on all of its characters. */ long hashstring( string ) char *string; { register char *cp; register long value; value = 0; for ( cp = string ; *cp ; cp++ ) { value = ( value * 2 ) + *cp; } return value; } /* * quadratically hashed string table. * things are never deleted from the hash string table. * as more hash table is needed, * a new one is alloc'ed and chained to the end. * search is by rehashing within each table, * traversing chains to next table if unsuccessful. */ char * enterstring( string ) char *string; { static struct stringtableinfo stringtable; long hashindex; register struct stringtableinfo *tablep; register char **herep; register char **limitp; register long increment; hashindex = SHORT_ABS( hashstring( string ) ) % STRINGPRIME; for ( tablep = &stringtable ; /*return*/ ; tablep = tablep -> chain ) { if ( tablep == NIL ) { # ifdef SPACEDEBUG fprintf( stderr , "[enterstring] calloc space for %d strings\n" , STRINGPRIME ); # endif SPACEDEBUG for ( tablep = &stringtable ; tablep->chain != NIL ; tablep = tablep->chain ) { continue; } tablep->chain = ( struct stringtableinfo * ) calloc( 1 , sizeof ( struct stringtableinfo ) ); if ( tablep->chain == NIL ) { error( FATAL , "ran out of memory (enterstring)" ); } tablep = tablep->chain; } herep = &( tablep -> entry[ hashindex ] ); limitp = &( tablep -> entry[ STRINGPRIME ] ); increment = 1; do { if ( *herep == NIL ) { /* empty */ if ( tablep -> used > ( ( STRINGPRIME / 4 ) * 3 ) ) { /* too full, break for next table */ break; } tablep -> used++; *herep = charalloc( strlen( string ) ); strcpy( *herep , string ); # ifdef HASHDEBUG fprintf( stderr , "[enterstring] string %s copied after %d\n" , *herep , increment / 2 ); # endif HASHDEBUG return *herep; } /* quick, check the first chars and then the rest */ if ( **herep == *string && strcmp( *herep , string ) == 0 ) { # ifdef HASHDEBUG fprintf( stderr , "[enterstring] string %s found after %d\n" , *herep , increment / 2 ); # endif HASHDEBUG return *herep; } herep += increment; if ( herep >= limitp ) { herep -= STRINGPRIME; } increment += 2; } while ( increment < STRINGPRIME ); # ifdef HASHDEBUG fprintf( stderr , "[enterstring] next stringtable\n" ); # endif HASHDEBUG } } /* * copy a string to the dynamically allocated character table. */ char * charalloc( length ) register long length; { static char *nextchar = NIL; static long charsleft = 0; register long lengthplus1 = length + 1; register long askfor; char *newstring; if ( charsleft < lengthplus1 ) { askfor = lengthplus1 > CHARALLOC ? lengthplus1 : CHARALLOC; # ifdef SPACEDEBUG fprintf( stderr , "[charalloc] malloc space for %d chars\n" , askfor ); # endif SPACEDEBUG nextchar = ( char * ) malloc( askfor ); if ( nextchar == 0 ) { error( FATAL , "no room for %d characters" , askfor ); } charsleft = askfor; } newstring = nextchar; nextchar += lengthplus1; charsleft -= lengthplus1; return newstring; } /* * read an archive header for the next element * and find the offset of the one after this. */ BOOL nextelement( ofilep ) struct fileinfo *ofilep; { register char *cp; register long red; register off_t arsize; struct ar_hdr archdr; fseek( ofilep -> file , ofilep -> nextoffset , 0 ); red = fread( (char *) &archdr , 1 , sizeof archdr , ofilep -> file ); if ( red != sizeof archdr ) { return FALSE; } /* null terminate the blank-padded name */ cp = &archdr.ar_name[ ( sizeof archdr.ar_name ) - 1 ]; *cp = '\0'; while ( *--cp == ' ' ) { *cp = '\0'; } /* set up the address of the beginning of next element */ arsize = atol( archdr.ar_size ); /* archive elements are aligned on 0 mod 2 boundaries */ if ( arsize & 1 ) { arsize += 1; } ofilep -> nextoffset = ftell( ofilep -> file ) + arsize; /* say we had one */ return TRUE; } /* * variable number of arguments to error, like printf. */ error( type , message , arg1 , arg2 , arg3 , arg4 , arg5 , arg6 , arg7 , arg8 ) int type; char *message; { errors = type > errors ? type : errors; if ( wflag && type == WARNING ) { return; } fprintf( stderr , "%s: " , program ); switch ( type ) { case WARNING: fprintf( stderr , "Warning: " ); break; case ERROR: fprintf( stderr , "Error: " ); break; case FATAL: fprintf( stderr , "Fatal: " ); break; default: fprintf( stderr , "Ooops: " ); break; } fprintf( stderr , message , arg1,arg2,arg3,arg4,arg5,arg6,arg7,arg8 ); fprintf( stderr , "\n" ); if ( type == FATAL ) { exit( FATAL ); } } char * classify( type ) unsigned char type; { switch ( type ) { case N_PSO: return "source file"; case N_PSOL: return "include file"; case N_PGLABEL: return "label"; case N_PGCONST: return "constant"; case N_PGTYPE: return "type"; case N_PGVAR: return "variable"; case N_PGFUNC: return "function"; case N_PGPROC: return "procedure"; case N_PEFUNC: return "external function"; case N_PEPROC: return "external procedure"; case N_PLDATA: return "library variable"; case N_PLTEXT: return "library routine"; default: return "unknown symbol"; } } char * article( type ) unsigned char type; { switch ( type ) { case N_PSO: return "a source file"; case N_PSOL: return "an include file"; case N_PGLABEL: return "a label"; case N_PGCONST: return "a constant"; case N_PGTYPE: return "a type"; case N_PGVAR: return "a variable"; case N_PGFUNC: return "a function"; case N_PGPROC: return "a procedure"; case N_PEFUNC: return "an external function"; case N_PEPROC: return "an external procedure"; case N_PLDATA: return "a library variable"; case N_PLTEXT: return "a library routine"; default: return "an unknown symbol"; } }