OSDN Git Service

Update FSF address.
[pf3gnuchains/gcc-fork.git] / gcc / java / jcf-io.c
1 /* Utility routines for finding and reading Java(TM) .class files.
2    Copyright (C) 1996, 1997, 1998, 1999, 2000, 2002, 2003, 2004, 2005
3    Free Software Foundation, Inc.
4
5 This file is part of GCC.
6
7 GCC is free software; you can redistribute it and/or modify
8 it under the terms of the GNU General Public License as published by
9 the Free Software Foundation; either version 2, or (at your option)
10 any later version.
11
12 GCC is distributed in the hope that it will be useful,
13 but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15 GNU General Public License for more details.
16
17 You should have received a copy of the GNU General Public License
18 along with GCC; see the file COPYING.  If not, write to
19 the Free Software Foundation, 51 Franklin Street, Fifth Floor,
20 Boston, MA 02110-1301, USA.  
21
22 Java and all Java-based marks are trademarks or registered trademarks
23 of Sun Microsystems, Inc. in the United States and other countries.
24 The Free Software Foundation is independent of Sun Microsystems, Inc.  */
25
26 /* Written by Per Bothner <bothner@cygnus.com>, February 1996. */
27
28 #include "config.h"
29 #include "system.h"
30 #include "coretypes.h"
31 #include "tm.h"
32
33 #include "jcf.h"
34 #include "tree.h"
35 #include "toplev.h"
36 #include "java-tree.h"
37 #include "hashtab.h"
38 #if JCF_USE_SCANDIR
39 #include <dirent.h>
40 #include <fnmatch.h>
41 #endif
42
43 #include "zlib.h"
44
45 /* DOS brain-damage */
46 #ifndef O_BINARY
47 #define O_BINARY 0 /* MS-DOS brain-damage */
48 #endif
49
50 int
51 jcf_unexpected_eof (JCF *jcf, int count ATTRIBUTE_UNUSED)
52 {
53   if (jcf->filename)
54     fprintf (stderr, "Premature end of .class file %s.\n", jcf->filename);
55   else
56     fprintf (stderr, "Premature end of .class file <stdin>.\n");
57   exit (-1);
58 }
59
60 void
61 jcf_trim_old_input (JCF *jcf)
62 {
63   int count = jcf->read_ptr - jcf->buffer;
64   if (count > 0)
65     {
66       memmove (jcf->buffer, jcf->read_ptr, jcf->read_end - jcf->read_ptr);
67       jcf->read_ptr -= count;
68       jcf->read_end -= count;
69     }
70 }
71
72 int
73 jcf_filbuf_from_stdio (JCF *jcf, int count)
74 {
75   FILE *file = (FILE*) (jcf->read_state);
76   if (count > jcf->buffer_end - jcf->read_ptr)
77     {
78       JCF_u4 old_read_ptr = jcf->read_ptr - jcf->buffer;
79       JCF_u4 old_read_end = jcf->read_end - jcf->buffer;
80       JCF_u4 old_size = jcf->buffer_end - jcf->buffer;
81       JCF_u4 new_size = (old_size == 0 ? 2000 : 2 * old_size) + count;
82       unsigned char *new_buffer = jcf->buffer == NULL ? ALLOC (new_size)
83         : REALLOC (jcf->buffer, new_size);
84       jcf->buffer = new_buffer;
85       jcf->buffer_end = new_buffer + new_size;
86       jcf->read_ptr = new_buffer + old_read_ptr;
87       jcf->read_end = new_buffer + old_read_end;
88     }
89   count -= jcf->read_end - jcf->read_ptr;
90   if (count <= 0)
91     return 0;
92   if ((int) fread (jcf->read_end, 1, count, file) != count)
93     jcf_unexpected_eof (jcf, count);
94   jcf->read_end += count;
95   return 0;
96 }
97
98 #include "zipfile.h"
99
100 struct ZipFile *SeenZipFiles = NULL;
101
102 /* Open a zip file with the given name, and cache directory and file
103    descriptor.  If the file is missing, treat it as an empty archive.
104    Return NULL if the .zip file is malformed.
105 */
106
107 ZipFile *
108 opendir_in_zip (const char *zipfile, int is_system)
109 {
110   struct ZipFile* zipf;
111   char magic [4];
112   int fd;
113   for (zipf = SeenZipFiles;  zipf != NULL;  zipf = zipf->next)
114     {
115       if (strcmp (zipf->name, zipfile) == 0)
116         return zipf;
117     }
118
119   zipf = ALLOC (sizeof (struct ZipFile) + strlen (zipfile) + 1);
120   zipf->next = SeenZipFiles;
121   zipf->name = (char*)(zipf+1);
122   strcpy (zipf->name, zipfile);
123   fd = open (zipfile, O_RDONLY | O_BINARY);
124   zipf->fd = fd;
125   if (fd < 0)
126     {
127       /* A missing zip file is not considered an error.
128        We may want to re-consider that.  FIXME. */
129       zipf->count = 0;
130       zipf->dir_size = 0;
131       zipf->central_directory = NULL;
132     }
133   else
134     {
135       jcf_dependency_add_file (zipfile, is_system);
136       if (read (fd, magic, 4) != 4 || GET_u4 (magic) != (JCF_u4)ZIPMAGIC)
137         return NULL;
138       lseek (fd, 0L, SEEK_SET);
139       if (read_zip_archive (zipf) != 0)
140         return NULL;
141     }
142
143   SeenZipFiles = zipf;  
144   return zipf;
145 }
146
147 /* Returns:
148    0:  OK - zipmember found.
149    -1: Not found.
150    -2: Malformed archive.
151 */
152
153 int
154 open_in_zip (JCF *jcf, const char *zipfile, const char *zipmember,
155              int is_system)
156 {
157   ZipDirectory *zipd;
158   int i, len;
159   ZipFile *zipf = opendir_in_zip (zipfile, is_system);
160
161   if (zipf == NULL)
162     return -2;
163
164   if (!zipmember)
165     return 0;
166
167   len = strlen (zipmember);
168   
169   zipd = (struct ZipDirectory*) zipf->central_directory;
170   for (i = 0; i < zipf->count; i++, zipd = ZIPDIR_NEXT (zipd))
171     {
172       if (len == zipd->filename_length &&
173           strncmp (ZIPDIR_FILENAME (zipd), zipmember, len) == 0)
174         {
175           JCF_ZERO (jcf);
176
177           jcf->filename = xstrdup (zipfile);
178           jcf->classname = xstrdup (zipmember);
179           return read_zip_member(jcf, zipd, zipf);
180         }
181     }
182   return -1;
183 }
184
185 /* Read data from zip archive member. */
186
187 int
188 read_zip_member (JCF *jcf,  ZipDirectory *zipd, ZipFile *zipf)
189 {
190   jcf->filbuf = jcf_unexpected_eof;
191   jcf->zipd = (void *)zipd;
192
193   if (zipd->compression_method == Z_NO_COMPRESSION)
194     {
195       jcf->buffer = ALLOC (zipd->size);
196       jcf->buffer_end = jcf->buffer + zipd->size;
197       jcf->read_ptr = jcf->buffer;
198       jcf->read_end = jcf->buffer_end;
199       if (lseek (zipf->fd, zipd->filestart, 0) < 0
200           || read (zipf->fd, jcf->buffer, zipd->size) != (long) zipd->size)
201         return -2;
202     }
203   else
204     {
205       char *buffer;
206       z_stream d_stream; /* decompression stream */
207       d_stream.zalloc = (alloc_func) 0;
208       d_stream.zfree = (free_func) 0;
209       d_stream.opaque = (voidpf) 0;
210
211       jcf->buffer = ALLOC (zipd->uncompressed_size);
212       d_stream.next_out = jcf->buffer;
213       d_stream.avail_out = zipd->uncompressed_size;
214       jcf->buffer_end = jcf->buffer + zipd->uncompressed_size;
215       jcf->read_ptr = jcf->buffer;
216       jcf->read_end = jcf->buffer_end;
217       buffer = ALLOC (zipd->size);
218       d_stream.next_in = (unsigned char *) buffer;
219       d_stream.avail_in = zipd->size;
220       if (lseek (zipf->fd, zipd->filestart, 0) < 0
221           || read (zipf->fd, buffer, zipd->size) != (long) zipd->size)
222         return -2;
223       /* Handle NO_HEADER using undocumented zlib feature.
224          This is a very common hack.  */
225       inflateInit2 (&d_stream, -MAX_WBITS);
226       inflate (&d_stream, Z_NO_FLUSH);
227       inflateEnd (&d_stream);
228       FREE (buffer);
229     }
230
231   return 0;
232 }
233
234 const char *
235 open_class (const char *filename, JCF *jcf, int fd, const char *dep_name)
236 {
237   if (jcf)
238     {
239       struct stat stat_buf;
240       if (fstat (fd, &stat_buf) != 0
241           || ! S_ISREG (stat_buf.st_mode))
242         {
243           perror ("Could not figure length of .class file");
244           return NULL;
245         }
246       if (dep_name != NULL)
247         jcf_dependency_add_file (dep_name, 0);
248       JCF_ZERO (jcf);
249       jcf->buffer = ALLOC (stat_buf.st_size);
250       jcf->buffer_end = jcf->buffer + stat_buf.st_size;
251       jcf->read_ptr = jcf->buffer;
252       jcf->read_end = jcf->buffer_end;
253       jcf->read_state = NULL;
254       jcf->filename = filename;
255       if (read (fd, jcf->buffer, stat_buf.st_size) != stat_buf.st_size)
256         {
257           perror ("Failed to read .class file");
258           return NULL;
259         }
260       close (fd);
261       jcf->filbuf = jcf_unexpected_eof;
262     }
263   else
264     close (fd);
265   return filename;
266 }
267
268
269 const char *
270 find_classfile (char *filename, JCF *jcf, const char *dep_name)
271 {
272   int fd = open (filename, O_RDONLY | O_BINARY);
273   if (fd < 0)
274     return NULL;
275   return open_class (filename, jcf, fd, dep_name);
276 }
277
278 #if JCF_USE_SCANDIR
279
280 /* A comparison function (as for qsort) that compares KEY (a char *
281    giving the basename of a file) with the name stored in ENTRY (a
282    dirent **).  */
283
284 static int
285 compare_path (const void *key, const void *entry)
286 {
287   return strcmp ((const char *) key, 
288                  (*((const struct dirent **) entry))->d_name);
289 }
290
291 /* Returns nonzero if ENTRY names a .java or .class file.  */
292
293 static int
294 java_or_class_file (const struct dirent *entry)
295 {
296   const char *base = lbasename (entry->d_name);
297   return (fnmatch ("*.java", base, 0) == 0 || 
298           fnmatch ("*.class", base, 0) == 0);
299 }
300
301 /* Information about the files present in a particular directory.  */
302 typedef struct memoized_dirlist_entry 
303 {
304   /* The name of the directory.  */
305   const char *dir;
306   /* The number of .java and .class files present, or -1 if we could
307      not, for some reason, obtain the list.  */
308   int num_files;
309   /* The .java and .class files in the directory, in alphabetical
310      order.  */
311   struct dirent **files;
312 } memoized_dirlist_entry;
313
314 /* Returns true if ENTRY (a memoized_dirlist_entry *) corresponds to
315    the directory given by KEY (a char *) giving the directory 
316    name.  */
317
318 static int
319 memoized_dirlist_lookup_eq (const void *entry, const void *key)
320 {
321   return strcmp ((const char *) key,
322                  ((const memoized_dirlist_entry *) entry)->dir) == 0;
323 }
324
325 /* A hash table mapping directory names to the lists of .java and
326    .class files in that directory.  */
327
328 static htab_t memoized_dirlists;
329
330 #endif
331
332 /* Like stat, but avoids actually making the stat system call if we
333    know that it cannot succeed.  FILENAME and BUF are as for stat.  */
334
335 static int
336 caching_stat (char *filename, struct stat *buf)
337 {
338 #if JCF_USE_SCANDIR
339   char *sep;
340   char origsep = 0;
341   char *base;
342   memoized_dirlist_entry *dent;
343   void **slot;
344   
345   /* If the hashtable has not already been created, create it now.  */
346   if (!memoized_dirlists)
347     memoized_dirlists = htab_create (37,
348                                      htab_hash_string,
349                                      memoized_dirlist_lookup_eq,
350                                      NULL);
351
352   /* Get the name of the directory.  */
353   sep = strrchr (filename, DIR_SEPARATOR);
354 #ifdef DIR_SEPARATOR_2
355   if (! sep)
356     sep = strrchr (filename, DIR_SEPARATOR_2);
357 #endif
358   if (sep)
359     {
360       origsep = *sep;
361       *sep = '\0';
362       base = sep + 1;
363     }
364   else
365     base = filename;
366
367   /* Obtain the entry for this directory from the hash table.  */
368   slot = htab_find_slot (memoized_dirlists, filename, INSERT);
369   if (!*slot)
370     {
371       /* We have not already scanned this directory; scan it now.  */
372       dent = ((memoized_dirlist_entry *) 
373               ALLOC (sizeof (memoized_dirlist_entry)));
374       dent->dir = xstrdup (filename);
375       /* Unfortunately, scandir is not fully standardized.  In
376          particular, the type of the function pointer passed as the
377          third argument sometimes takes a "const struct dirent *"
378          parameter, and sometimes just a "struct dirent *".  We cast
379          to (void *) and use __extension__ so that either way it is
380          quietly accepted.  FIXME: scandir is not in POSIX.  */
381       dent->num_files = __extension__ scandir (filename, &dent->files, 
382                                                (void *) java_or_class_file, 
383                                                alphasort);
384       *slot = dent;
385     }
386   else
387     dent = *((memoized_dirlist_entry **) slot);
388
389   /* Put the separator back.  */
390   if (sep)
391     *sep = origsep;
392
393   /* If the file is not in the list, there is no need to stat it; it
394      does not exist.  */
395   if (dent->num_files != -1
396       && !bsearch (base, dent->files, dent->num_files,
397                    sizeof (struct dirent *), compare_path))
398     return -1;
399 #endif
400   
401   return stat (filename, buf);
402 }
403
404 /* Returns 1 if the CLASSNAME (really a char *) matches the name
405    stored in TABLE_ENTRY (also a char *).  */
406
407 static int
408 memoized_class_lookup_eq (const void *table_entry, const void *classname)
409 {
410   return strcmp ((const char *)classname, (const char *)table_entry) == 0;
411 }
412
413 /* A hash table keeping track of class names that were not found
414    during class lookup.  (There is no need to cache the values
415    associated with names that were found; they are saved in
416    IDENTIFIER_CLASS_VALUE.)  */
417 static htab_t memoized_class_lookups;
418
419 /* Returns a freshly malloc'd string with the fully qualified pathname
420    of the .class file for the class CLASSNAME.  CLASSNAME must be
421    allocated in permanent storage; this function may retain a pointer
422    to it.  Returns NULL on failure.  If JCF != NULL, it is suitably
423    initialized.  SOURCE_OK is true if we should also look for .java
424    file. */
425
426 const char *
427 find_class (const char *classname, int classname_length, JCF *jcf,
428             int source_ok)
429 {
430   int fd;
431   int i, k, java = -1, class = -1;
432   struct stat java_buf, class_buf;
433   char *dep_file;
434   void *entry;
435   char *java_buffer;
436   int buflen;
437   char *buffer;
438   hashval_t hash;
439
440   /* Create the hash table, if it does not already exist.  */
441   if (!memoized_class_lookups)
442     memoized_class_lookups = htab_create (37, 
443                                           htab_hash_string, 
444                                           memoized_class_lookup_eq,
445                                           NULL);
446
447   /* Loop for this class in the hashtable.  If it is present, we've
448      already looked for this class and failed to find it.  */
449   hash = htab_hash_string (classname);
450   if (htab_find_with_hash (memoized_class_lookups, classname, hash))
451     return NULL;
452
453   /* Allocate and zero out the buffer, since we don't explicitly put a
454      null pointer when we're copying it below.  */
455   buflen = jcf_path_max_len () + classname_length + 10;
456   buffer = ALLOC (buflen);
457   memset (buffer, 0, buflen);
458
459   java_buffer = alloca (buflen);
460
461   jcf->java_source = 0;
462
463   for (entry = jcf_path_start (); entry != NULL; entry = jcf_path_next (entry))
464     {
465       const char *path_name = jcf_path_name (entry);
466       if (class != 0)
467         {
468           int dir_len;
469
470           strcpy (buffer, path_name);
471           i = strlen (buffer);
472
473           /* This is right because we know that `.zip' entries will have a
474              trailing slash.  See jcf-path.c.  */
475           dir_len = i - 1;
476
477           for (k = 0; k < classname_length; k++, i++)
478             {
479               char ch = classname[k];
480               buffer[i] = ch == '.' ? '/' : ch;
481             }
482           strcpy (buffer+i, ".class");
483
484           if (jcf_path_is_zipfile (entry))
485             {
486               int err_code;
487               JCF _jcf;
488               buffer[dir_len] = '\0';
489               SOURCE_FRONTEND_DEBUG 
490                 (("Trying [...%s]:%s", 
491                   &buffer[dir_len-(dir_len > 15 ? 15 : dir_len)], 
492                   buffer+dir_len+1));
493               if (jcf == NULL)
494                 jcf = &_jcf;
495               err_code = open_in_zip (jcf, buffer, buffer+dir_len+1,
496                                       jcf_path_is_system (entry));
497               if (err_code == 0)
498                 {
499                   /* Should we check if .zip is out-of-date wrt .java? */
500                   buffer[dir_len] = '(';
501                   strcpy (buffer+i, ".class)");
502                   if (jcf == &_jcf)
503                     JCF_FINISH (jcf);
504                   return buffer;
505                 }
506               else
507                 continue;
508             }
509           class = caching_stat(buffer, &class_buf);
510         }
511
512       if (source_ok)
513         {
514           /* Compute name of .java file.  */
515           int l, m;
516           strcpy (java_buffer, path_name);
517           l = strlen (java_buffer);
518           for (m = 0; m < classname_length; ++m)
519             java_buffer[m + l] = (classname[m] == '.'
520                                   ? DIR_SEPARATOR : classname[m]);
521           strcpy (java_buffer + m + l, ".java");
522           java = caching_stat (java_buffer, &java_buf);
523           if (java == 0)
524             break;
525         }
526     }
527
528   /* We preferably pick a class file if we have a chance. If the source
529      file is newer than the class file, we issue a warning and parse the
530      source file instead.
531      There should be a flag to allow people have the class file picked
532      up no matter what. FIXME. */
533   if (! java && ! class && java_buf.st_mtime > class_buf.st_mtime)
534     {
535       if (flag_newer)
536         warning (0, "source file for class %qs is newer than its matching class file.  Source file %qs used instead", classname, java_buffer);
537       class = -1;
538     }
539
540   if (! java)
541     dep_file = java_buffer;
542   else
543     dep_file = buffer;
544   if (!class)
545     {
546       SOURCE_FRONTEND_DEBUG ((stderr, "[Class selected: %s]\n",
547                               classname+classname_length-
548                               (classname_length <= 30 ? 
549                                classname_length : 30)));
550       fd = JCF_OPEN_EXACT_CASE (buffer, O_RDONLY | O_BINARY);
551       if (fd >= 0)
552         goto found;
553     }
554   /* Give .java a try, if necessary */
555   if (!java)
556     {
557       strcpy (buffer, java_buffer);
558       SOURCE_FRONTEND_DEBUG ((stderr, "[Source selected: %s]\n",
559                               classname+classname_length-
560                               (classname_length <= 30 ? 
561                                classname_length : 30)));
562       fd = JCF_OPEN_EXACT_CASE (buffer, O_RDONLY);
563       if (fd >= 0)
564         {
565           jcf->java_source = 1;
566           goto found;
567         }
568     }
569
570   free (buffer);
571
572   /* Remember that this class could not be found so that we do not
573      have to look again.  */
574   *htab_find_slot_with_hash (memoized_class_lookups, classname, hash, INSERT) 
575     = (void *) classname;
576
577   return NULL;
578  found:
579   if (jcf->java_source)
580     {
581       JCF_ZERO (jcf);           /* JCF_FINISH relies on this */
582       jcf->java_source = 1;
583       jcf->filename = xstrdup (buffer);
584       close (fd);               /* We use STDIO for source file */
585     }
586   else
587     buffer = (char *) open_class (buffer, jcf, fd, dep_file);
588   jcf->classname = xstrdup (classname);
589   return buffer;
590 }
591
592 void
593 jcf_print_char (FILE *stream, int ch)
594 {
595   switch (ch)
596     {
597     case '\'':
598     case '\\':
599     case '\"':
600       fprintf (stream, "\\%c", ch);
601       break;
602     case '\n':
603       fprintf (stream, "\\n");
604       break;
605     case '\t':
606       fprintf (stream, "\\t");
607       break;
608     case '\r':
609       fprintf (stream, "\\r");
610       break;
611     default:
612       if (ch >= ' ' && ch < 127)
613         putc (ch, stream);
614       else if (ch < 256)
615         fprintf (stream, "\\%03x", ch);
616       else
617         fprintf (stream, "\\u%04x", ch);
618     }
619 }
620
621 /* Print UTF8 string at STR of length LENGTH bytes to STREAM. */
622
623 void
624 jcf_print_utf8 (FILE *stream, const unsigned char *str, int length)
625 {
626   const unsigned char * limit = str + length;
627   while (str < limit)
628     {
629       int ch = UTF8_GET (str, limit);
630       if (ch < 0)
631         {
632           fprintf (stream, "\\<invalid>");
633           return;
634         }
635       jcf_print_char (stream, ch);
636     }
637 }
638
639 /* Same as jcf_print_utf8, but print IN_CHAR as OUT_CHAR. */
640
641 void
642 jcf_print_utf8_replace (FILE *stream, const unsigned char *str, int length,
643                         int in_char, int out_char)
644 {
645   const unsigned char *limit = str + length;
646   while (str < limit)
647     {
648       int ch = UTF8_GET (str, limit);
649       if (ch < 0)
650         {
651           fprintf (stream, "\\<invalid>");
652           return;
653         }
654       jcf_print_char (stream, ch == in_char ? out_char : ch);
655     }
656 }
657
658 /* Check that all the cross-references in the constant pool are
659    valid.  Returns 0 on success.
660    Otherwise, returns the index of the (first) invalid entry.
661    Only checks internal consistency, but does not check that
662    any classes, fields, or methods are valid.*/
663
664 int
665 verify_constant_pool (JCF *jcf)
666 {
667   int i, n;
668   for (i = 1; i < JPOOL_SIZE (jcf); i++)
669     {
670       switch (JPOOL_TAG (jcf, i))
671         {
672         case CONSTANT_NameAndType:
673           n = JPOOL_USHORT2 (jcf, i);
674           if (n <= 0 || n >= JPOOL_SIZE(jcf)
675               || JPOOL_TAG (jcf, n) != CONSTANT_Utf8)
676             return i;
677           /* ... fall through ... */
678         case CONSTANT_Class:
679         case CONSTANT_String:
680           n = JPOOL_USHORT1 (jcf, i);
681           if (n <= 0 || n >= JPOOL_SIZE(jcf)
682               || JPOOL_TAG (jcf, n) != CONSTANT_Utf8)
683             return i;
684           break;
685         case CONSTANT_Fieldref:
686         case CONSTANT_Methodref:
687         case CONSTANT_InterfaceMethodref:
688           n = JPOOL_USHORT1 (jcf, i);
689           if (n <= 0 || n >= JPOOL_SIZE(jcf)
690               || JPOOL_TAG (jcf, n) != CONSTANT_Class)
691             return i;
692           n = JPOOL_USHORT2 (jcf, i);
693           if (n <= 0 || n >= JPOOL_SIZE(jcf)
694               || JPOOL_TAG (jcf, n) != CONSTANT_NameAndType)
695             return i;
696           break;
697         case CONSTANT_Long:
698         case CONSTANT_Double:
699           i++;
700           break;
701         case CONSTANT_Float:
702         case CONSTANT_Integer:
703         case CONSTANT_Utf8:
704         case CONSTANT_Unicode:
705           break;
706         default:
707           return i;
708         }
709     }
710   return 0;
711 }
712
713 void
714 format_uint (char *buffer, uint64 value, int base)
715 {
716 #define WRITE_BUF_SIZE (4 + sizeof(uint64) * 8)
717   char buf[WRITE_BUF_SIZE];
718   char *buf_ptr = buf+WRITE_BUF_SIZE; /* End of buf. */
719   int chars_written;
720   int i;
721
722   /* Now do the actual conversion, placing the result at the *end* of buf. */
723   /* Note this code does not pretend to be optimized. */
724   do {
725     int digit = value % base;
726     static const char digit_chars[] = "0123456789abcdefghijklmnopqrstuvwxyz";
727     *--buf_ptr = digit_chars[digit];
728     value /= base;
729   } while (value != 0);
730
731   chars_written = buf+WRITE_BUF_SIZE - buf_ptr;
732   for (i = 0; i < chars_written; i++)
733     buffer[i] = *buf_ptr++;
734   buffer[i] = 0;
735 }
736
737 void
738 format_int (char *buffer, jlong value, int base)
739 {
740   uint64 abs_value;
741   if (value < 0)
742     {
743       abs_value = -(uint64)value;
744       *buffer++ = '-';
745     }
746   else
747     abs_value = (uint64) value;
748   format_uint (buffer, abs_value, base);
749 }