/************************************************************** * * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. * *************************************************************/ /* All Java Virtual Machine Specs are from * "The Java Virtual Machine Specification", T. Lindholm, F. Yellin * (JVMS) */ #include #include #include #include #include #include #include #if defined(UNX) || defined(OS2) #include #include /* ntohl(), ntohs() */ #elif defined(WNT) #include #define access _access #define vsnprintf _vsnprintf #define CDECL _cdecl #define F_OK 00 #define PATH_MAX _MAX_PATH #define ntohl(x) ((((x) & 0xff000000) >> 24) | (((x) & 0x00ff0000) >> 8) | \ (((x) & 0x0000ff00) << 8) | (((x) & 0x000000ff) << 24)) #define ntohs(x) ((((x) >> 8) & 0xff) | (((x) & 0xff) << 8)) #endif #if defined(OS2) #define CDECL #endif /* max. length of line in response file */ #define RES_FILE_BUF 65536 struct file { char *pname; FILE *pfs; }; struct growable { int ncur; int nmax; char **parray; }; typedef struct file file_t; typedef unsigned char uint8; typedef unsigned short uint16; typedef unsigned int uint32; struct utf8 { uint16 nlen; void *pdata; }; typedef struct utf8 utf8_t; /* The contents of the Constant_pool is described in JVMS p. 93 */ enum { CONSTANT_Class = 7, CONSTANT_Fieldref = 9, CONSTANT_Methodref = 10, CONSTANT_InterfaceMethodref = 11, CONSTANT_String = 8, CONSTANT_Integer = 3, CONSTANT_Float = 4, CONSTANT_Long = 5, CONSTANT_Double = 6, CONSTANT_NameAndType = 12, CONSTANT_Utf8 = 1, CONSTANT_MethodHandle = 15, CONSTANT_MethodType = 16, CONSTANT_InvokeDynamic = 18 }; enum { NGROW_INIT = 10, NGROW = 2 }; static char *pprogname = "javadep"; static char csep = ';'; #if defined (UNX) || defined(OS2) #define CDECL static char cpathsep = '/'; #elif defined (WNT) || defined(OS2) static char cpathsep = '\\'; #endif static FILE *pfsout = NULL; static char *pout_file = NULL; /* prototypes */ uint8 read_uint8(const file_t *pfile); uint16 read_uint16(const file_t *pfile); uint32 read_uint32(const file_t *pfile); void skip_bytes(const file_t *pfile, const size_t nnum); char *escape_slash(const char *pstr); int is_inner(const char *pstr); void print_dependencies(const struct growable *pdep, const char* pclass_file); void process_class_file(const char *pfilenamem, const struct growable *pfilt); char *utf8tolatin1(const utf8_t a_utf8); void *xmalloc(size_t size); void *xcalloc(size_t nmemb, size_t size); void *xrealloc(void *ptr, size_t size); void grow_if_needed (struct growable *pgrow); int append_to_growable(struct growable *, char *); struct growable *allocate_growable(void); void free_growable(struct growable *pgrowvoid); void create_filters(struct growable *pfilt, const struct growable *pinc); void usage(void); void err_quit(const char *, ...); void silent_quit(void); #ifdef WNT /* poor man's getopt() */ int simple_getopt(char *pargv[], const char *poptstring); char *optarg = NULL; int optind = 1; int optopt = 0; int opterr = 0; #endif uint8 read_uint8(const file_t *pfile) { /* read a byte from classfile */ int nread; uint8 ndata; nread = fread(&ndata, sizeof(uint8), 1, pfile->pfs); if ( !nread ) { fclose(pfile->pfs); err_quit("%s: truncated class file", pfile->pname); } return ndata; } uint16 read_uint16(const file_t *pfile) { /* read a short from classfile and convert it to host format */ int nread; uint16 ndata; nread = fread(&ndata, sizeof(uint16), 1, pfile->pfs); if ( !nread ) { fclose(pfile->pfs); err_quit("%s: truncated class file", pfile->pname); } ndata = ntohs(ndata); return ndata; } uint32 read_uint32(const file_t *pfile) { /* read an int from classfile and convert it to host format */ int nread; uint32 ndata; nread = fread(&ndata, sizeof(uint32), 1, pfile->pfs); if ( !nread ) { fclose(pfile->pfs); err_quit("%s: truncated class file", pfile->pname); } ndata = ntohl(ndata); return ndata; } utf8_t read_utf8(const file_t *pfile) { /* Read a java utf-8-string with uint16 length prependend * from class file. Returns utf8 struct * with fresh allocated datablock, * caller is responsible for freeing. * Data is still in network byteorder */ utf8_t a_utf8; int nread; a_utf8.pdata = NULL; a_utf8.nlen = read_uint16(pfile); if (a_utf8.nlen > 0) { a_utf8.pdata = xmalloc(a_utf8.nlen*sizeof(char)); nread = fread(a_utf8.pdata, a_utf8.nlen*sizeof(char), 1, pfile->pfs); if ( !nread ) { fclose(pfile->pfs); err_quit("%s: truncated class file", pfile->pname); } } return a_utf8; } char *utf8tolatin1(const utf8_t a_utf8) { /* function returns fresh allocated zero terminated string, * caller is responsible for freeing */ /* JVMS p. 101: the null byte is encoded using a two byte format, * Java Virtual Machine Utf8 strings differ in this respect from * standard UTF-8 strings */ /* Multibyte data is in network byte order */ char *p; char *pp; char *pstr; pstr = pp = xmalloc((a_utf8.nlen+1) * sizeof(char)); for ( p = (char*)a_utf8.pdata; p < (char*)a_utf8.pdata+a_utf8.nlen; p++ ) { if ( *p & 0x80 ) { err_quit("sorry, real UTF8 decoding not yet implemented\n"); } else { *pp++ = *p; } } *pp = '\0'; return pstr; } void skip_bytes(const file_t *pfile, const size_t nnumber) { /* skip a nnumber of bytes in classfile */ if ( fseek(pfile->pfs, nnumber, SEEK_CUR) == -1 ) err_quit("%s: %s", pfile->pname, strerror(errno)); } void add_to_dependencies(struct growable *pdep, const struct growable *pfilt, char *pdepstr, const char *pclass_file) { /* create dependencies */ int i; int nlen_filt, nlen_str, nlen_pdepstr; char *pstr, *ptrunc; char path[PATH_MAX+1]; char cnp_class_file[PATH_MAX+1]; char cnp_str[PATH_MAX+1]; nlen_pdepstr = strlen(pdepstr); pstr = xmalloc((nlen_pdepstr+6+1)*sizeof(char)); memcpy(pstr, pdepstr, nlen_pdepstr+1); strncat(pstr, ".class", 6); if ( pfilt->ncur == 0 ) { /* no filters */ if ( access(pstr, F_OK) == 0 ) { append_to_growable(pdep, strdup(pstr)); } } else { nlen_str = strlen(pstr); for ( i = 0; i < pfilt->ncur; i++ ) { nlen_filt = strlen(pfilt->parray[i]); if ( nlen_filt + 1 + nlen_str > PATH_MAX ) err_quit("path to long"); memcpy(path, pfilt->parray[i], nlen_filt); path[nlen_filt] = '/'; memcpy( path+nlen_filt+1, pstr, nlen_str+1); if ( access(path, F_OK) != 0 ) { free(pstr); pstr = NULL; return; /* path doesn't represent a real file, don't bother */ } /* get the canonical path */ #if defined (UNX) || defined(OS2) if ( !(realpath(pclass_file, cnp_class_file) && realpath(path, cnp_str) ) ) { err_quit("can't get the canonical path"); } #else if ( !(_fullpath(cnp_class_file, pclass_file, sizeof(cnp_class_file)) && _fullpath(cnp_str, path, sizeof(cnp_str)) ) ) { err_quit("can't get the canonical path"); } #endif /* truncate so that only the package prefix remains */ ptrunc = strrchr(cnp_str, cpathsep); *ptrunc = '\0'; ptrunc = strrchr(cnp_class_file, cpathsep); *ptrunc = '\0'; if ( !strcmp(cnp_str, cnp_class_file) ) { free(pstr); pstr = NULL; return; /* identical, don't bother with this one */ } append_to_growable(pdep, strdup(path)); } } free(pstr); return; } char * escape_slash(const char *pstr) { /* returns a fresh allocated string with all cpathsep escaped exchanged * with "$/" * * caller is responsible for freeing */ const char *pp = pstr; char *p, *pnp; char *pnew_str; int nlen_pnp, nlen_pp; int i = 0; while ( (p=strchr(pp, cpathsep)) != NULL ) { ++i; pp = ++p; } nlen_pnp = strlen(pstr) + i; pnp = pnew_str = xmalloc((nlen_pnp+1) * sizeof(char)); pp = pstr; if ( i > 0 ) { while ( (p=strchr(pp, cpathsep)) != NULL ) { memcpy(pnp, pp, p-pp); pnp += p-pp; *pnp++ = '$'; *pnp++ = '/'; pp = ++p; } } nlen_pp = strlen(pp); memcpy(pnp, pp, nlen_pp+1); return pnew_str; } void print_dependencies(const struct growable *pdep, const char* pclass_file) { char *pstr; int i; pstr = escape_slash(pclass_file); fprintf(pfsout, "%s:", pstr); free(pstr); for( i=0; incur; ++i) { fprintf(pfsout, " \\\n"); pstr=escape_slash(pdep->parray[i]); fprintf(pfsout, "\t%s", pstr); free(pstr); } fprintf(pfsout,"\n\n"); return; } int is_inner(const char *pstr) { /* return true if character '$' is found in classname */ /* * note that a '$' in a classname is not an exact indicator * for an inner class. Java identifier may legally contain * this character, and so may classnames. In the context * of javadep this doesn't matter since the makefile system * can't cope with classfiles with '$'s in the filename * anyway. * */ if ( strchr(pstr, '$') != NULL ) return 1; return 0; } void process_class_file(const char *pfilename, const struct growable *pfilt) { /* read class file and extract object information * java class files are in bigendian data format * (JVMS, p. 83) */ int i; uint32 nmagic; uint16 nminor, nmajor; uint16 ncnt; uint16 nclass_cnt; utf8_t* pc_pool; uint16* pc_class; file_t file; struct growable *pdepen; file.pname = (char*)pfilename; file.pfs = fopen(file.pname,"rb"); if ( !file.pfs ) silent_quit(); nmagic = read_uint32(&file); if ( nmagic != 0xCAFEBABE ) { fclose(file.pfs); err_quit("%s: invalid magic", file.pname); } nminor = read_uint16(&file); nmajor = read_uint16(&file); /* get number of entries in constant pool */ ncnt = read_uint16(&file); #ifdef DEBUG printf("Magic: %p\n", (void*)nmagic); printf("Major %d, Minor %d\n", nmajor, nminor); printf("Const_pool_count %d\n", ncnt); #endif /* There can be ncount entries in the constant_pool table * so at most ncount-1 of them can be of type CONSTANT_Class * (at leat one CONSTANT_Utf8 entry must exist). * Usually way less CONSTANT_Class entries exists, of course */ pc_pool = xcalloc(ncnt,sizeof(utf8_t)); pc_class = xmalloc((ncnt-1)*sizeof(uint16)); /* pc_pool[0] is reserved to the java virtual machine and does * not exist in the class file */ nclass_cnt = 0; for (i = 1; i < ncnt; i++) { uint8 ntag; uint16 nindex; utf8_t a_utf8; ntag = read_uint8(&file); /* we are only interested in CONSTANT_Class entries and * Utf8 string entries, because they might belong to * CONSTANT_Class entries */ switch(ntag) { case CONSTANT_Class: nindex = read_uint16(&file); pc_class[nclass_cnt++] = nindex; break; case CONSTANT_Fieldref: case CONSTANT_Methodref: case CONSTANT_InterfaceMethodref: skip_bytes(&file, 4L); break; case CONSTANT_String: skip_bytes(&file, 2L); break; case CONSTANT_Integer: case CONSTANT_Float: skip_bytes(&file, 4L); break; case CONSTANT_Long: case CONSTANT_Double: skip_bytes(&file, 8L); /* Long and Doubles take 2(!) * entries in constant_pool_table */ i++; break; case CONSTANT_NameAndType: skip_bytes(&file, 4L); break; case CONSTANT_Utf8: a_utf8 = read_utf8(&file); pc_pool[i] = a_utf8; break; case CONSTANT_MethodHandle: skip_bytes(&file, 3); break; case CONSTANT_MethodType: skip_bytes(&file, 2); break; case CONSTANT_InvokeDynamic: skip_bytes(&file, 4); break; default: /* Unknown Constant_pool entry, this means we are * in trouble */ err_quit("corrupted class file\n"); break; } } fclose(file.pfs); pdepen = allocate_growable(); for (i = 0; i < nclass_cnt; i++) { char *pstr, *ptmpstr; pstr = ptmpstr = utf8tolatin1(pc_pool[pc_class[i]]); /* we are not interested in inner classes */ if ( is_inner(pstr) ) { free(pstr); pstr = NULL; continue; } /* strip off evt. array indicators */ if ( *ptmpstr == '[' ) { while ( *ptmpstr == '[' ) ptmpstr++; /* we only interested in obj. arrays, which are marked with 'L' */ if ( *ptmpstr == 'L' ) { char *p = pstr; pstr = strdup(++ptmpstr); /* remove final ';' from object array name */ pstr[strlen(pstr)-1] = '\0'; free(p); } else { free(pstr); pstr = NULL; } } if (pstr) { add_to_dependencies(pdepen, pfilt, pstr, file.pname); free(pstr); } } print_dependencies(pdepen, file.pname); free_growable(pdepen); pdepen = NULL; for (i = 0; i < ncnt; i++) free(pc_pool[i].pdata); free(pc_class); free(pc_pool); } void * xmalloc(size_t size) { void *ptr; ptr = malloc(size); if ( !ptr ) err_quit("out of memory"); return ptr; } void * xcalloc(size_t nmemb, size_t size) { void *ptr; ptr = calloc(nmemb, size); if ( !ptr ) err_quit("out of memory"); return ptr; } void * xrealloc(void *ptr, size_t size) { ptr = realloc(ptr, size); if ( !ptr ) err_quit("out of memory"); return ptr; } void err_quit(const char* fmt, ...) { /* No dependency file must be generated for any error condition, * just print message and exit. */ va_list args; char buffer[PATH_MAX]; va_start(args, fmt); if ( pprogname ) fprintf(stderr, "%s: ", pprogname); vsnprintf(buffer, sizeof(buffer), fmt, args); fputs(buffer, stderr); fputc('\n', stderr); va_end(args); /* clean up */ if ( pfsout && pfsout != stdout ) { fclose(pfsout); unlink(pout_file); } exit(1); } void silent_quit() { /* In some cases we should just do a silent exit */ /* clean up */ if ( pfsout && pfsout != stdout ) { fclose(pfsout); unlink(pout_file); } exit(0); } int append_to_growable(struct growable *pgrow, char *pstr) { /* append an element pstr to pgrow, * return new number of elements */ grow_if_needed(pgrow); pgrow->parray[pgrow->ncur++] = pstr; return pgrow->ncur; } void grow_if_needed(struct growable *pgrow) { /* grow growable arrays */ if ( pgrow->ncur >= pgrow->nmax ) { pgrow->parray = xrealloc(pgrow->parray, (NGROW*pgrow->nmax)*sizeof(char*)); pgrow->nmax *= NGROW; } return; } struct growable *allocate_growable(void) { /* allocate an growable array, * initialize with NGROW_INIT elements */ struct growable *pgrow; pgrow = xmalloc(sizeof(struct growable)); pgrow->parray = xmalloc(NGROW_INIT*sizeof(char *)); pgrow->nmax = NGROW_INIT; pgrow->ncur = 0; return pgrow; } void free_growable(struct growable *pgrow) { int i; for( i = 0; i < pgrow->ncur; i++ ) free(pgrow->parray[i]); free(pgrow->parray); free(pgrow); } void create_filters(struct growable *pfilt, const struct growable *pinc) { char *p, *pp, *pstr; int i, nlen, nlen_pstr; /* break up includes into filter list */ for ( i = 0; i < pinc->ncur; i++ ) { pp = pinc->parray[i]; while ( (p = strchr(pp, csep)) != NULL) { nlen = p - pp; pstr = xmalloc((nlen+1)*sizeof(char*)); memcpy(pstr, pp, nlen); pstr[nlen] = '\0'; append_to_growable(pfilt, pstr); pp = p + 1; } nlen_pstr = strlen(pp); pstr = xmalloc((nlen_pstr+1)*sizeof(char*)); memcpy(pstr, pp, nlen_pstr+1); append_to_growable(pfilt, pstr); } } void usage() { fprintf(stderr, "usage: %s [-i|-I includepath ... -s|-S seperator " "-o|-O outpath -v|-V -h|-H] ....\n", pprogname); } #ifdef WNT /* my very simple minded implementation of getopt() * it's to sad that getopt() is not available everywhere * note: this is not a full POSIX conforming getopt() */ int simple_getopt(char *pargv[], const char *poptstring) { char *parg = pargv[optind]; /* skip all response file arguments */ if ( parg ) { while ( *parg == '@' ) parg = pargv[++optind]; if ( parg[0] == '-' && parg[1] != '\0' ) { char *popt; int c = parg[1]; if ( (popt = strchr(poptstring, c)) == NULL ) { optopt = c; if ( opterr ) fprintf(stderr, "Unknown option character `\\x%x'.\n", optopt); return '?'; } if ( *(++popt) == ':') { if ( parg[2] != '\0' ) { optarg = ++parg; } else { optarg = pargv[++optind]; } } else { optarg = NULL; } ++optind; return c; } } return -1; } #endif int CDECL main(int argc, char *argv[]) { int bv_flag = 0; struct growable *presp, *pincs, *pfilters; int c, i, nall_argc; char **pall_argv; presp = allocate_growable(); /* FIXME: cleanup the option parsing */ /* search for response file, read it */ for ( i = 1; i < argc; i++ ) { char *parg = argv[i]; char buffer[RES_FILE_BUF]; if ( *parg == '@' ) { FILE *pfile = fopen(++parg, "r"); if ( !pfile ) err_quit("%s: %s", parg, strerror(errno)); while ( !feof(pfile) ) { char *p, *token; if ( fgets(buffer, RES_FILE_BUF, pfile) ) {; p = buffer; while ( (token = strtok(p, " \t\n")) != NULL ) { p = NULL; append_to_growable(presp, strdup(token)); } } } fclose(pfile); } } /* copy all arguments incl. response file in one array * for parsing with getopt */ nall_argc = argc + presp->ncur; pall_argv = xmalloc((nall_argc+1)*sizeof(char *)); memcpy(pall_argv, argv, argc*sizeof(char *)); memcpy(pall_argv+argc, presp->parray, presp->ncur*sizeof(char *)); *(pall_argv+argc+presp->ncur) = '\0'; /* terminate */ opterr = 0; pincs = allocate_growable(); #ifdef WNT while( (c = simple_getopt(pall_argv, ":i:I:s:S:o:OhHvV")) != -1 ) { #else while( (c = getopt(nall_argc, pall_argv, ":i:I:s:S:o:OhHvV")) != -1 ) { #endif switch(c) { case 'i': case 'I': append_to_growable(pincs, strdup(optarg)); break; case 's': case 'S': csep = optarg[0]; break; case 'o': case 'O': pout_file = optarg; break; case 'h': case 'H': usage(); return 0; break; case 'v': case 'V': bv_flag = 1; break; case '?': if (isprint (optopt)) fprintf (stderr, "Unknown option `-%c'.\n", optopt); else fprintf (stderr, "Unknown option character `\\x%x'.\n", optopt); usage(); return 1; break; case ':': fprintf(stderr, "Missing parameter.\n"); usage(); return 1; break; default: usage(); return 1; break; } } pfilters = allocate_growable(); create_filters(pfilters, pincs); free_growable(pincs); pincs = NULL; if ( pout_file ) { pfsout = fopen(pout_file, "w"); if ( !pfsout ) err_quit("%s: %s", pout_file, strerror(errno)); } else { pfsout = stdout; } /* the remaining arguments are either class file * names or response files, ignore response file * since they have already been included */ for ( i = optind; i < nall_argc; i++ ) { char *parg = pall_argv[i]; if ( *parg != '@' ) { process_class_file(parg, pfilters); if ( pfsout != stdout ) { if ( bv_flag ) printf("Processed %s ...\n", parg); } } } free_growable(pfilters); pfilters = NULL; free(pall_argv); pall_argv = NULL; free_growable(presp); presp = NULL; fclose(pfsout); exit(0); }