2674 lines
69 KiB
C
2674 lines
69 KiB
C
/* Program to write C++-suitable header files from a Java(TM) .class
|
||
file. This is similar to SUN's javah.
|
||
|
||
Copyright (C) 1996, 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005
|
||
Free Software Foundation, Inc.
|
||
|
||
This file is part of GCC.
|
||
|
||
GCC 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.
|
||
|
||
GCC 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 GCC; see the file COPYING. If not, write to
|
||
the Free Software Foundation, 51 Franklin Street, Fifth Floor,
|
||
Boston, MA 02110-1301, USA.
|
||
|
||
Java and all Java-based marks are trademarks or registered trademarks
|
||
of Sun Microsystems, Inc. in the United States and other countries.
|
||
The Free Software Foundation is independent of Sun Microsystems, Inc. */
|
||
|
||
/* Written by Per Bothner <bothner@cygnus.com>, February 1996. */
|
||
|
||
#include "config.h"
|
||
#include "system.h"
|
||
#include "coretypes.h"
|
||
#include "tm.h"
|
||
#include <math.h>
|
||
|
||
#include "jcf.h"
|
||
#include "tree.h"
|
||
#include "version.h"
|
||
#include "javaop.h"
|
||
#include "java-tree.h"
|
||
#include "java-opcodes.h"
|
||
#include "ggc.h"
|
||
#include "hashtab.h"
|
||
#include "intl.h"
|
||
|
||
#include <getopt.h>
|
||
|
||
|
||
|
||
/* The output file. */
|
||
FILE *out = NULL;
|
||
|
||
/* Nonzero on failure. */
|
||
static int found_error = 0;
|
||
|
||
#ifdef JNI_DEFAULT
|
||
#define TOOLNAME "gjnih"
|
||
|
||
/* Nonzero if we're generating JNI output. */
|
||
int flag_jni = 1;
|
||
#else
|
||
#define TOOLNAME "gcjh"
|
||
|
||
int flag_jni = 0;
|
||
#endif
|
||
|
||
/* When nonzero, warn when source file is newer than matching class
|
||
file. */
|
||
int flag_newer = 1;
|
||
|
||
/* Directory to place resulting files in. Set by -d option. */
|
||
static const char *output_directory = "";
|
||
|
||
/* Directory to place temporary file. Set by -td option. Currently unused. */
|
||
static const char *temp_directory = "/tmp";
|
||
|
||
/* Number of friend functions we have to declare. */
|
||
static int friend_count;
|
||
|
||
/* A class can optionally have a `friend' function declared. If
|
||
non-NULL, this is that function. */
|
||
static char **friend_specs = NULL;
|
||
|
||
/* Number of lines we are prepending before the class. */
|
||
static int prepend_count;
|
||
|
||
/* We can prepend extra lines before the class's start. */
|
||
static char **prepend_specs = NULL;
|
||
|
||
/* Number of lines we are appending at the end of the class. */
|
||
static int add_count;
|
||
|
||
/* We can append extra lines just before the class's end. */
|
||
static char **add_specs = NULL;
|
||
|
||
/* Number of lines we are appending after the class. */
|
||
static int append_count;
|
||
|
||
/* We can append extra lines after the class's end. */
|
||
static char **append_specs = NULL;
|
||
|
||
int verbose = 0;
|
||
|
||
int stubs = 0;
|
||
|
||
struct JCF *current_jcf;
|
||
|
||
/* This holds access information for the last field we examined. They
|
||
let us generate "private:", "public:", and "protected:" properly.
|
||
If 0 then we haven't previously examined any field. */
|
||
static JCF_u2 last_access;
|
||
|
||
/* Pass this macro the flags for a class and for a method. It will
|
||
return true if the method should be considered `final'. */
|
||
#define METHOD_IS_FINAL(Class, Method) \
|
||
(((Class) & ACC_FINAL) || ((Method) & (ACC_FINAL | ACC_PRIVATE)))
|
||
|
||
/* Pass this macro the flags for a method. It will return true if the
|
||
method is native. */
|
||
#define METHOD_IS_NATIVE(Method) \
|
||
((Method) & ACC_NATIVE)
|
||
|
||
#define METHOD_IS_PRIVATE(Class, Method) \
|
||
(((Method) & ACC_PRIVATE) != 0)
|
||
|
||
/* We keep a linked list of all method names we have seen. This lets
|
||
us determine if a method name and a field name are in conflict. */
|
||
struct method_name
|
||
{
|
||
unsigned char *name;
|
||
int length;
|
||
unsigned char *signature;
|
||
int sig_length;
|
||
int is_native;
|
||
struct method_name *next;
|
||
};
|
||
|
||
/* List of method names we've seen. */
|
||
static struct method_name *method_name_list;
|
||
|
||
static void print_field_info (FILE*, JCF*, int, int, JCF_u2);
|
||
static void print_mangled_classname (FILE*, JCF*, const char*, int);
|
||
static int print_cxx_classname (FILE*, const char*, JCF*, int, int);
|
||
static void print_method_info (FILE*, JCF*, int, int, JCF_u2);
|
||
static void print_c_decl (FILE*, JCF*, int, int, int, const char *, int);
|
||
static void print_stub_or_jni (FILE*, JCF*, int, int, int, const char *, int);
|
||
static void print_full_cxx_name (FILE*, JCF*, int, int, int, const char *, int);
|
||
static void decompile_method (FILE*, JCF*, int) ATTRIBUTE_UNUSED;
|
||
static void add_class_decl (FILE*, JCF*, JCF_u2);
|
||
|
||
static void print_name (FILE *, JCF *, int);
|
||
static void print_base_classname (FILE *, JCF *, int);
|
||
static int utf8_cmp (const unsigned char *, int, const char *);
|
||
static char *cxx_keyword_subst (const unsigned char *, int);
|
||
static void generate_access (FILE *, JCF_u2);
|
||
static int name_is_method_p (const unsigned char *, int);
|
||
static char *get_field_name (JCF *, int, JCF_u2);
|
||
static void print_field_name (FILE *, JCF *, int, JCF_u2);
|
||
static const unsigned char *super_class_name (JCF *, int *);
|
||
static void print_include (FILE *, const unsigned char *, int);
|
||
static int gcjh_streq (const void *p1, const void *p2);
|
||
static int throwable_p (const unsigned char *signature);
|
||
static const unsigned char *
|
||
decode_signature_piece (FILE *, const unsigned char *,
|
||
const unsigned char *, int *);
|
||
static void print_class_decls (FILE *, JCF *, int);
|
||
static void error (const char *gmsgid, ...) ATTRIBUTE_PRINTF_1;
|
||
static void usage (void) ATTRIBUTE_NORETURN;
|
||
static void help (void) ATTRIBUTE_NORETURN;
|
||
static void version (void) ATTRIBUTE_NORETURN;
|
||
static int overloaded_jni_method_exists_p (const unsigned char *, int,
|
||
const char *, int);
|
||
static void jni_print_char (FILE *, int);
|
||
static void jni_print_float (FILE *, jfloat);
|
||
static void jni_print_double (FILE *, jdouble);
|
||
static void decompile_return_statement (FILE *, JCF *, int, int, int);
|
||
|
||
static void handle_inner_classes (int);
|
||
|
||
JCF_u2 current_field_name;
|
||
JCF_u2 current_field_value;
|
||
JCF_u2 current_field_signature;
|
||
JCF_u2 current_field_flags;
|
||
|
||
#define HANDLE_START_FIELD(ACCESS_FLAGS, NAME, SIGNATURE, ATTRIBUTE_COUNT) \
|
||
( current_field_name = (NAME), current_field_signature = (SIGNATURE), \
|
||
current_field_flags = (ACCESS_FLAGS), current_field_value = 0)
|
||
|
||
/* We pass over fields twice. The first time we just note the types
|
||
of the fields and then the start of the methods. Then we go back
|
||
and parse the fields for real. This is ugly. */
|
||
static int field_pass;
|
||
/* Likewise we pass over methods twice. The first time we generate
|
||
class decl information; the second time we generate actual method
|
||
decls. */
|
||
static int method_pass;
|
||
|
||
#define HANDLE_END_FIELD() \
|
||
if (field_pass) \
|
||
{ \
|
||
if (out && ! stubs) \
|
||
print_field_info (out, jcf, current_field_name, \
|
||
current_field_signature, \
|
||
current_field_flags); \
|
||
} \
|
||
else if (! stubs && ! flag_jni) \
|
||
add_class_decl (out, jcf, current_field_signature);
|
||
|
||
#define HANDLE_CONSTANTVALUE(VALUEINDEX) current_field_value = (VALUEINDEX)
|
||
|
||
static int method_declared = 0;
|
||
static int method_access = 0;
|
||
static int method_printed = 0;
|
||
static int method_synthetic = 0;
|
||
static int method_signature = 0;
|
||
|
||
/* Set to 1 while the very first data member of a class is being handled. */
|
||
static int is_first_data_member = 0;
|
||
|
||
#define HANDLE_METHOD(ACCESS_FLAGS, NAME, SIGNATURE, ATTRIBUTE_COUNT) \
|
||
{ \
|
||
method_synthetic = 0; \
|
||
method_printed = 0; \
|
||
decompiled = 0; \
|
||
method_signature = SIGNATURE; \
|
||
if (ATTRIBUTE_COUNT) \
|
||
method_synthetic = peek_attribute (jcf, ATTRIBUTE_COUNT, \
|
||
(const char *)"Synthetic", 9); \
|
||
/* If a synthetic methods have been declared, its attribute aren't \
|
||
worth reading (and triggering side-effects). We skip them an \
|
||
set ATTRIBUTE_COUNT to zero so that they'll be skipped in \
|
||
jcf_parse_one_method. */ \
|
||
if (method_synthetic) \
|
||
{ \
|
||
skip_attribute (jcf, ATTRIBUTE_COUNT); \
|
||
ATTRIBUTE_COUNT = 0; \
|
||
} \
|
||
if (method_pass && !method_synthetic) \
|
||
{ \
|
||
if (out) \
|
||
print_method_info (out, jcf, NAME, SIGNATURE, \
|
||
ACCESS_FLAGS); \
|
||
} \
|
||
else if (!method_synthetic) \
|
||
{ \
|
||
print_method_info (NULL, jcf, NAME, SIGNATURE, \
|
||
ACCESS_FLAGS); \
|
||
if (! stubs && ! flag_jni) \
|
||
add_class_decl (out, jcf, SIGNATURE); \
|
||
} \
|
||
}
|
||
|
||
/* Only include byte-code decompilation optimizations for ELF targets
|
||
since the generated headers are only known to work with ELF weak
|
||
symbol semnatics. Specifically, these optimizations are known to
|
||
not work on PE-COFF and possibly others. */
|
||
#ifdef OBJECT_FORMAT_ELF
|
||
#define HANDLE_CODE_ATTRIBUTE(MAX_STACK, MAX_LOCALS, CODE_LENGTH) \
|
||
if (out && method_declared) decompile_method (out, jcf, CODE_LENGTH);
|
||
#endif
|
||
|
||
static int decompiled = 0;
|
||
#define HANDLE_END_METHOD() \
|
||
if (out && method_printed && !method_synthetic) \
|
||
fputs (decompiled || stubs ? "\n" : ";\n", out);
|
||
|
||
#define HANDLE_INNERCLASSES_ATTRIBUTE(COUNT) handle_inner_classes (COUNT)
|
||
|
||
/* We're going to need {peek,skip}_attribute, enable their definition. */
|
||
#define NEED_PEEK_ATTRIBUTE
|
||
#define NEED_SKIP_ATTRIBUTE
|
||
|
||
#include "jcf-reader.c"
|
||
|
||
/* Print an error message and set found_error.
|
||
Not really gcc-internal-format message, but as error elsewhere
|
||
uses it, assume all users will use intersection between
|
||
c-format and gcc-internal-format. */
|
||
static void
|
||
error (const char *gmsgid, ...)
|
||
{
|
||
va_list ap;
|
||
|
||
va_start (ap, gmsgid);
|
||
|
||
fprintf (stderr, TOOLNAME ": ");
|
||
vfprintf (stderr, _(gmsgid), ap);
|
||
va_end (ap);
|
||
fprintf (stderr, "\n");
|
||
found_error = 1;
|
||
}
|
||
|
||
/* Print a single-precision float, suitable for parsing by g++. */
|
||
static void
|
||
jni_print_float (FILE *stream, jfloat f)
|
||
{
|
||
/* It'd be nice to use __builtin_nan/__builtin_inf here but they don't
|
||
work in data initializers. FIXME. */
|
||
if (JFLOAT_FINITE (f))
|
||
{
|
||
if (flag_jni)
|
||
{
|
||
fputs (" ", out);
|
||
if (f.negative)
|
||
putc ('-', stream);
|
||
if (f.exponent)
|
||
fprintf (stream, "0x1.%.6xp%+df",
|
||
((unsigned int)f.mantissa) << 1,
|
||
f.exponent - JFLOAT_EXP_BIAS);
|
||
else
|
||
/* Exponent of 0x01 is -125; exponent of 0x00 is *also* -125,
|
||
because the implicit leading 1 bit is no longer present. */
|
||
fprintf (stream, "0x0.%.6xp%+df",
|
||
((unsigned int)f.mantissa) << 1,
|
||
f.exponent + 1 - JFLOAT_EXP_BIAS);
|
||
}
|
||
}
|
||
if (! flag_jni)
|
||
fputs (";\n", stream);
|
||
}
|
||
|
||
/* Print a double-precision float, suitable for parsing by g++. */
|
||
static void
|
||
jni_print_double (FILE *stream, jdouble f)
|
||
{
|
||
/* It'd be nice to use __builtin_nan/__builtin_inf here but they don't
|
||
work in data initializers. FIXME. */
|
||
if (JDOUBLE_FINITE (f))
|
||
{
|
||
if (flag_jni)
|
||
{
|
||
fputs (" ", out);
|
||
if (f.negative)
|
||
putc ('-', stream);
|
||
if (f.exponent)
|
||
fprintf (stream, "0x1.%.5x%.8xp%+d",
|
||
f.mantissa0, f.mantissa1,
|
||
f.exponent - JDOUBLE_EXP_BIAS);
|
||
else
|
||
/* Exponent of 0x001 is -1022; exponent of 0x000 is *also* -1022,
|
||
because the implicit leading 1 bit is no longer present. */
|
||
fprintf (stream, "0x0.%.5x%.8xp%+d",
|
||
f.mantissa0, f.mantissa1,
|
||
f.exponent + 1 - JDOUBLE_EXP_BIAS);
|
||
}
|
||
}
|
||
fputs (flag_jni ? "\n" : ";\n", stream);
|
||
}
|
||
|
||
/* Print a character, appropriately mangled for JNI. */
|
||
|
||
static void
|
||
jni_print_char (FILE *stream, int ch)
|
||
{
|
||
if (! flag_jni)
|
||
jcf_print_char (stream, ch);
|
||
else if (ch == '(' || ch == ')')
|
||
{
|
||
/* Ignore. */
|
||
}
|
||
else if (ch == '_')
|
||
fputs ("_1", stream);
|
||
else if (ch == ';')
|
||
fputs ("_2", stream);
|
||
else if (ch == '[')
|
||
fputs ("_3", stream);
|
||
else if (ch == '/')
|
||
fputs ("_", stream);
|
||
else if (ISALNUM (ch))
|
||
fputc (ch, stream);
|
||
else
|
||
{
|
||
/* "Unicode" character. */
|
||
fprintf (stream, "_0%04x", ch);
|
||
}
|
||
}
|
||
|
||
/* Print a name from the class data. If the index does not point to a
|
||
string, an error results. */
|
||
|
||
static void
|
||
print_name (FILE* stream, JCF* jcf, int name_index)
|
||
{
|
||
if (JPOOL_TAG (jcf, name_index) != CONSTANT_Utf8)
|
||
{
|
||
fprintf (stream, "<not a UTF8 constant>");
|
||
found_error = 1;
|
||
}
|
||
else if (! flag_jni)
|
||
jcf_print_utf8 (stream, JPOOL_UTF_DATA (jcf, name_index),
|
||
JPOOL_UTF_LENGTH (jcf, name_index));
|
||
else
|
||
{
|
||
/* For JNI we must correctly quote each character. */
|
||
const unsigned char *str = JPOOL_UTF_DATA (jcf, name_index);
|
||
int length = JPOOL_UTF_LENGTH (jcf, name_index);
|
||
const unsigned char *limit = str + length;
|
||
while (str < limit)
|
||
{
|
||
int ch = UTF8_GET (str, limit);
|
||
if (ch < 0)
|
||
{
|
||
fprintf (stream, "\\<invalid>");
|
||
return;
|
||
}
|
||
jni_print_char (stream, ch);
|
||
}
|
||
}
|
||
}
|
||
|
||
/* Print base name of class. The base name is everything after the
|
||
final separator. */
|
||
|
||
static void
|
||
print_base_classname (FILE *stream, JCF *jcf, int index)
|
||
{
|
||
int name_index = JPOOL_USHORT1 (jcf, index);
|
||
int len;
|
||
const unsigned char *s, *p, *limit;
|
||
|
||
s = JPOOL_UTF_DATA (jcf, name_index);
|
||
len = JPOOL_UTF_LENGTH (jcf, name_index);
|
||
limit = s + len;
|
||
p = s;
|
||
while (s < limit)
|
||
{
|
||
int c = UTF8_GET (s, limit);
|
||
if (c == '/')
|
||
p = s;
|
||
}
|
||
|
||
while (p < limit)
|
||
{
|
||
int ch = UTF8_GET (p, limit);
|
||
if (ch == '/')
|
||
fputs ("::", stream);
|
||
else
|
||
jcf_print_char (stream, ch);
|
||
}
|
||
}
|
||
|
||
/* Return 0 if NAME is equal to STR, -1 if STR is "less" than NAME,
|
||
and 1 if STR is "greater" than NAME. */
|
||
|
||
static int
|
||
utf8_cmp (const unsigned char *str, int length, const char *name)
|
||
{
|
||
const unsigned char *limit = str + length;
|
||
int i;
|
||
|
||
for (i = 0; name[i]; ++i)
|
||
{
|
||
int ch = UTF8_GET (str, limit);
|
||
if (ch != name[i])
|
||
return ch - name[i];
|
||
}
|
||
|
||
return str == limit ? 0 : 1;
|
||
}
|
||
|
||
/* This is a sorted list of all C++ keywords. */
|
||
|
||
static const char *const cxx_keywords[] =
|
||
{
|
||
"_Complex",
|
||
"__alignof",
|
||
"__alignof__",
|
||
"__asm",
|
||
"__asm__",
|
||
"__attribute",
|
||
"__attribute__",
|
||
"__builtin_va_arg",
|
||
"__complex",
|
||
"__complex__",
|
||
"__const",
|
||
"__const__",
|
||
"__extension__",
|
||
"__imag",
|
||
"__imag__",
|
||
"__inline",
|
||
"__inline__",
|
||
"__label__",
|
||
"__null",
|
||
"__real",
|
||
"__real__",
|
||
"__restrict",
|
||
"__restrict__",
|
||
"__signed",
|
||
"__signed__",
|
||
"__typeof",
|
||
"__typeof__",
|
||
"__volatile",
|
||
"__volatile__",
|
||
"and",
|
||
"and_eq",
|
||
"asm",
|
||
"auto",
|
||
"bitand",
|
||
"bitor",
|
||
"bool",
|
||
"break",
|
||
"case",
|
||
"catch",
|
||
"char",
|
||
"class",
|
||
"compl",
|
||
"const",
|
||
"const_cast",
|
||
"continue",
|
||
"default",
|
||
"delete",
|
||
"do",
|
||
"double",
|
||
"dynamic_cast",
|
||
"else",
|
||
"enum",
|
||
"explicit",
|
||
"export",
|
||
"extern",
|
||
"false",
|
||
"float",
|
||
"for",
|
||
"friend",
|
||
"goto",
|
||
"if",
|
||
"inline",
|
||
"int",
|
||
"long",
|
||
"mutable",
|
||
"namespace",
|
||
"new",
|
||
"not",
|
||
"not_eq",
|
||
"operator",
|
||
"or",
|
||
"or_eq",
|
||
"private",
|
||
"protected",
|
||
"public",
|
||
"register",
|
||
"reinterpret_cast",
|
||
"return",
|
||
"short",
|
||
"signed",
|
||
"sizeof",
|
||
"static",
|
||
"static_cast",
|
||
"struct",
|
||
"switch",
|
||
"template",
|
||
"this",
|
||
"throw",
|
||
"true",
|
||
"try",
|
||
"typedef",
|
||
"typeid",
|
||
"typename",
|
||
"typeof",
|
||
"union",
|
||
"unsigned",
|
||
"using",
|
||
"virtual",
|
||
"void",
|
||
"volatile",
|
||
"wchar_t",
|
||
"while",
|
||
"xor",
|
||
"xor_eq"
|
||
};
|
||
|
||
|
||
/* If NAME is the name of a C++ keyword, then return an override name.
|
||
This is a name that can be used in place of the keyword.
|
||
Otherwise, return NULL. The return value is malloc()d. */
|
||
|
||
static char *
|
||
cxx_keyword_subst (const unsigned char *str, int length)
|
||
{
|
||
int last = ARRAY_SIZE (cxx_keywords);
|
||
int first = 0;
|
||
int mid = (last + first) / 2;
|
||
int old = -1;
|
||
|
||
for (mid = (last + first) / 2;
|
||
mid != old;
|
||
old = mid, mid = (last + first) / 2)
|
||
{
|
||
int kwl = strlen (cxx_keywords[mid]);
|
||
int min_length = kwl > length ? length : kwl;
|
||
int r = utf8_cmp (str, min_length, cxx_keywords[mid]);
|
||
|
||
if (r == 0)
|
||
{
|
||
int i;
|
||
|
||
/* Skip all trailing `$'. */
|
||
for (i = min_length; i < length && str[i] == '$'; ++i)
|
||
;
|
||
/* We've only found a match if all the remaining characters
|
||
are `$'. */
|
||
if (i == length)
|
||
{
|
||
char *dup = xmalloc (2 + length - min_length + kwl);
|
||
strcpy (dup, cxx_keywords[mid]);
|
||
for (i = kwl; i < length + 1; ++i)
|
||
dup[i] = '$';
|
||
dup[i] = '\0';
|
||
return dup;
|
||
}
|
||
r = 1;
|
||
}
|
||
|
||
if (r < 0)
|
||
last = mid;
|
||
else
|
||
first = mid;
|
||
}
|
||
return NULL;
|
||
}
|
||
|
||
/* Generate an access control keyword based on FLAGS. */
|
||
|
||
static void
|
||
generate_access (FILE *stream, JCF_u2 flags)
|
||
{
|
||
if ((flags & ACC_VISIBILITY) == last_access)
|
||
return;
|
||
last_access = (flags & ACC_VISIBILITY);
|
||
|
||
switch (last_access)
|
||
{
|
||
case 0:
|
||
fputs ("public: // actually package-private\n", stream);
|
||
break;
|
||
case ACC_PUBLIC:
|
||
fputs ("public:\n", stream);
|
||
break;
|
||
case ACC_PRIVATE:
|
||
fputs ("private:\n", stream);
|
||
break;
|
||
case ACC_PROTECTED:
|
||
fputs ("public: // actually protected\n", stream);
|
||
break;
|
||
default:
|
||
found_error = 1;
|
||
fprintf (stream, "#error unrecognized visibility %d\n",
|
||
(flags & ACC_VISIBILITY));
|
||
break;
|
||
}
|
||
}
|
||
|
||
/* See if NAME is already the name of a method. */
|
||
static int
|
||
name_is_method_p (const unsigned char *name, int length)
|
||
{
|
||
struct method_name *p;
|
||
|
||
for (p = method_name_list; p != NULL; p = p->next)
|
||
{
|
||
if (p->length == length && ! memcmp (p->name, name, length))
|
||
return 1;
|
||
}
|
||
return 0;
|
||
}
|
||
|
||
/* Free the method name list. */
|
||
static void
|
||
free_method_name_list (void)
|
||
{
|
||
struct method_name *p = method_name_list;
|
||
while (p != NULL)
|
||
{
|
||
struct method_name *next = p->next;
|
||
free (p->name);
|
||
free (p->signature);
|
||
free (p);
|
||
p = next;
|
||
}
|
||
method_name_list = NULL;
|
||
}
|
||
|
||
/* If there is already a native method named NAME, whose signature is not
|
||
SIGNATURE, then return true. Otherwise return false. */
|
||
static int
|
||
overloaded_jni_method_exists_p (const unsigned char *name, int length,
|
||
const char *signature, int sig_length)
|
||
{
|
||
struct method_name *p;
|
||
|
||
for (p = method_name_list; p != NULL; p = p->next)
|
||
{
|
||
if (p->is_native
|
||
&& p->length == length
|
||
&& ! memcmp (p->name, name, length)
|
||
&& (p->sig_length != sig_length
|
||
|| memcmp (p->signature, signature, sig_length)))
|
||
return 1;
|
||
}
|
||
return 0;
|
||
}
|
||
|
||
/* Get name of a field. This handles renamings due to C++ clash. */
|
||
static char *
|
||
get_field_name (JCF *jcf, int name_index, JCF_u2 flags)
|
||
{
|
||
unsigned char *name = JPOOL_UTF_DATA (jcf, name_index);
|
||
int length = JPOOL_UTF_LENGTH (jcf, name_index);
|
||
char *override;
|
||
|
||
if (name_is_method_p (name, length))
|
||
{
|
||
/* This field name matches a method. So override the name with
|
||
a dummy name. This is yucky, but it isn't clear what else to
|
||
do. FIXME: if the field is static, then we'll be in real
|
||
trouble. */
|
||
if ((flags & ACC_STATIC))
|
||
{
|
||
error ("static field has same name as method");
|
||
return NULL;
|
||
}
|
||
|
||
override = xmalloc (length + 3);
|
||
memcpy (override, name, length);
|
||
strcpy (override + length, "__");
|
||
}
|
||
else if (flag_jni)
|
||
override = NULL;
|
||
else
|
||
override = cxx_keyword_subst (name, length);
|
||
|
||
return override;
|
||
}
|
||
|
||
/* Print a field name. Convenience function for use with
|
||
get_field_name. */
|
||
static void
|
||
print_field_name (FILE *stream, JCF *jcf, int name_index, JCF_u2 flags)
|
||
{
|
||
char *override = get_field_name (jcf, name_index, flags);
|
||
|
||
if (override)
|
||
{
|
||
fputs (override, stream);
|
||
free (override);
|
||
}
|
||
else
|
||
jcf_print_utf8 (stream, JPOOL_UTF_DATA (jcf, name_index),
|
||
JPOOL_UTF_LENGTH (jcf, name_index));
|
||
}
|
||
|
||
static void
|
||
print_field_info (FILE *stream, JCF* jcf, int name_index, int sig_index,
|
||
JCF_u2 flags)
|
||
{
|
||
char *override = NULL;
|
||
|
||
if (! flag_jni)
|
||
generate_access (stream, flags);
|
||
if (JPOOL_TAG (jcf, name_index) != CONSTANT_Utf8)
|
||
{
|
||
fprintf (stream, "<not a UTF8 constant>");
|
||
found_error = 1;
|
||
return;
|
||
}
|
||
|
||
if (flag_jni)
|
||
{
|
||
/* For JNI we only want to print real constants. */
|
||
int val;
|
||
if (! (flags & ACC_STATIC)
|
||
|| ! (flags & ACC_FINAL)
|
||
|| current_field_value <= 0)
|
||
return;
|
||
val = JPOOL_TAG (jcf, current_field_value);
|
||
if (val != CONSTANT_Integer && val != CONSTANT_Long
|
||
&& val != CONSTANT_Float && val != CONSTANT_Double)
|
||
return;
|
||
}
|
||
else
|
||
{
|
||
/* Initial indentation. */
|
||
fputs (" ", stream);
|
||
}
|
||
|
||
if ((flags & ACC_STATIC))
|
||
{
|
||
if (flag_jni)
|
||
{
|
||
print_cxx_classname (stream, "#undef ", jcf, jcf->this_class, 1);
|
||
fputs ("_", stream);
|
||
print_field_name (stream, jcf, name_index, 0);
|
||
fputs ("\n", stream);
|
||
print_cxx_classname (stream, "#define ", jcf, jcf->this_class, 1);
|
||
fputs ("_", stream);
|
||
}
|
||
else
|
||
fputs ("static ", stream);
|
||
|
||
if ((flags & ACC_FINAL) && current_field_value > 0)
|
||
{
|
||
char buffer[25];
|
||
int done = 1;
|
||
|
||
switch (JPOOL_TAG (jcf, current_field_value))
|
||
{
|
||
case CONSTANT_Integer:
|
||
{
|
||
jint num;
|
||
int most_negative = 0;
|
||
if (! flag_jni)
|
||
fputs ("const jint ", stream);
|
||
print_field_name (stream, jcf, name_index, 0);
|
||
fputs (flag_jni ? " " : " = ", stream);
|
||
num = JPOOL_INT (jcf, current_field_value);
|
||
/* We single out the most negative number to print
|
||
specially. This avoids later warnings from g++. */
|
||
if (num == (jint) 0x80000000)
|
||
{
|
||
most_negative = 1;
|
||
++num;
|
||
}
|
||
format_int (buffer, (jlong) num, 10);
|
||
fprintf (stream, "%sL%s%s\n", buffer,
|
||
most_negative ? " - 1" : "",
|
||
flag_jni ? "" : ";");
|
||
}
|
||
break;
|
||
case CONSTANT_Long:
|
||
{
|
||
jlong num;
|
||
int most_negative = 0;
|
||
if (! flag_jni)
|
||
fputs ("const jlong ", stream);
|
||
print_field_name (stream, jcf, name_index, 0);
|
||
fputs (flag_jni ? " " : " = ", stream);
|
||
num = JPOOL_LONG (jcf, current_field_value);
|
||
/* We single out the most negative number to print
|
||
specially.. This avoids later warnings from g++. */
|
||
if (num == (jlong) 0x8000000000000000LL)
|
||
{
|
||
most_negative = 1;
|
||
++num;
|
||
}
|
||
format_int (buffer, num, 10);
|
||
fprintf (stream, "%sLL%s%s\n", buffer,
|
||
most_negative ? " - 1" :"",
|
||
flag_jni ? "" : ";");
|
||
}
|
||
break;
|
||
case CONSTANT_Float:
|
||
{
|
||
jfloat fnum = JPOOL_FLOAT (jcf, current_field_value);
|
||
if (! flag_jni)
|
||
fputs ("const jfloat ", stream);
|
||
print_field_name (stream, jcf, name_index, 0);
|
||
jni_print_float (stream, fnum);
|
||
}
|
||
break;
|
||
case CONSTANT_Double:
|
||
{
|
||
jdouble dnum = JPOOL_DOUBLE (jcf, current_field_value);
|
||
if (! flag_jni)
|
||
fputs ("const jdouble ", stream);
|
||
print_field_name (stream, jcf, name_index, 0);
|
||
jni_print_double (stream, dnum);
|
||
}
|
||
break;
|
||
default:
|
||
/* We can't print this as a constant, but we can still
|
||
print something sensible. */
|
||
done = 0;
|
||
break;
|
||
}
|
||
|
||
if (done)
|
||
return;
|
||
}
|
||
}
|
||
|
||
/* assert (! flag_jni); */
|
||
override = get_field_name (jcf, name_index, flags);
|
||
print_c_decl (stream, jcf, name_index, sig_index, 0, override, flags);
|
||
fputs (";\n", stream);
|
||
|
||
if (override)
|
||
free (override);
|
||
}
|
||
|
||
|
||
static void
|
||
print_method_info (FILE *stream, JCF* jcf, int name_index, int sig_index,
|
||
JCF_u2 flags)
|
||
{
|
||
const unsigned char *str;
|
||
int length, is_init = 0;
|
||
char *override = NULL;
|
||
|
||
method_declared = 0;
|
||
method_access = flags;
|
||
if (stream && JPOOL_TAG (jcf, name_index) != CONSTANT_Utf8)
|
||
fprintf (stream, "<not a UTF8 constant>");
|
||
str = JPOOL_UTF_DATA (jcf, name_index);
|
||
length = JPOOL_UTF_LENGTH (jcf, name_index);
|
||
|
||
if (str[0] == '<')
|
||
{
|
||
/* Ignore the internally generated method <clinit>. However,
|
||
treat <init> as a constructor. */
|
||
if (! utf8_cmp (str, length, "<init>"))
|
||
is_init = 1;
|
||
else if (! METHOD_IS_FINAL (jcf->access_flags, flags)
|
||
&& ! (flags & ACC_STATIC))
|
||
{
|
||
/* FIXME: i18n bug here. Order of prints should not be
|
||
fixed. */
|
||
fprintf (stderr, _("ignored method '"));
|
||
jcf_print_utf8 (stderr, str, length);
|
||
fprintf (stderr, _("' marked virtual\n"));
|
||
found_error = 1;
|
||
return;
|
||
}
|
||
else
|
||
return;
|
||
}
|
||
|
||
/* During the first method pass, build a list of method names. This will
|
||
be used to determine if field names conflict with method names. */
|
||
if (! stream)
|
||
{
|
||
struct method_name *nn;
|
||
|
||
nn = xmalloc (sizeof (struct method_name));
|
||
nn->name = xmalloc (length);
|
||
memcpy (nn->name, str, length);
|
||
nn->length = length;
|
||
nn->next = method_name_list;
|
||
nn->sig_length = JPOOL_UTF_LENGTH (jcf, sig_index);
|
||
nn->signature = xmalloc (nn->sig_length);
|
||
nn->is_native = METHOD_IS_NATIVE (flags);
|
||
memcpy (nn->signature, JPOOL_UTF_DATA (jcf, sig_index),
|
||
nn->sig_length);
|
||
method_name_list = nn;
|
||
|
||
/* The rest of this function doesn't matter. */
|
||
return;
|
||
}
|
||
|
||
/* We don't worry about overrides in JNI mode. */
|
||
if (! flag_jni)
|
||
{
|
||
/* We can't generate a method whose name is a C++ reserved word.
|
||
We can't just ignore the function, because that will cause
|
||
incorrect code to be generated if the function is virtual
|
||
(not only for calls to this function for for other functions
|
||
after it in the vtbl). So we give it a dummy name instead. */
|
||
override = cxx_keyword_subst (str, length);
|
||
}
|
||
|
||
if (! stubs && ! flag_jni)
|
||
{
|
||
method_printed = 1;
|
||
|
||
generate_access (stream, flags);
|
||
|
||
fputs (" ", out);
|
||
if ((flags & ACC_STATIC))
|
||
fputs ("static ", out);
|
||
else if (! METHOD_IS_PRIVATE (jcf->access_flags, flags))
|
||
{
|
||
/* Don't print `virtual' if we have a constructor. */
|
||
if (! is_init)
|
||
fputs ("virtual ", out);
|
||
}
|
||
print_c_decl (out, jcf, name_index, sig_index, is_init, override, flags);
|
||
|
||
if ((flags & ACC_ABSTRACT))
|
||
fputs (" = 0", out);
|
||
else
|
||
method_declared = 1;
|
||
}
|
||
else
|
||
{
|
||
if (METHOD_IS_NATIVE (flags))
|
||
{
|
||
method_printed = 1;
|
||
print_stub_or_jni (out, jcf, name_index, sig_index,
|
||
is_init, override, flags);
|
||
}
|
||
}
|
||
|
||
if (override)
|
||
free (override);
|
||
}
|
||
|
||
/* A helper for the decompiler which prints a `return' statement where
|
||
the type is a reference type. If METHODTYPE and OBJECTTYPE are not
|
||
identical, we emit a cast. We do this because the C++ compiler
|
||
doesn't know that a reference can be cast to the type of an
|
||
interface it implements. METHODTYPE is the index of the method's
|
||
signature. NAMEINDEX is the index of the field name; -1 for
|
||
`this'. OBJECTTYPE is the index of the object's type. */
|
||
static void
|
||
decompile_return_statement (FILE *out, JCF *jcf, int methodtype,
|
||
int nameindex, int objecttype)
|
||
{
|
||
int cast = 0;
|
||
int obj_name_len, method_name_len;
|
||
const unsigned char *obj_data, *method_data;
|
||
|
||
obj_name_len = JPOOL_UTF_LENGTH (jcf, objecttype);
|
||
obj_data = JPOOL_UTF_DATA (jcf, objecttype);
|
||
|
||
method_name_len = JPOOL_UTF_LENGTH (jcf, methodtype);
|
||
method_data = JPOOL_UTF_DATA (jcf, methodtype);
|
||
|
||
/* Skip forward to return type part of method. */
|
||
while (*method_data != ')')
|
||
{
|
||
++method_data;
|
||
--method_name_len;
|
||
}
|
||
/* Skip past `)'. */
|
||
++method_data;
|
||
--method_name_len;
|
||
|
||
/* If we see an `L', skip it and the trailing `;'. */
|
||
if (method_data[0] == 'L' && method_data[method_name_len - 1] == ';')
|
||
{
|
||
++method_data;
|
||
method_name_len -= 2;
|
||
}
|
||
if (obj_data[0] == 'L' && obj_data[obj_name_len - 1] == ';')
|
||
{
|
||
++obj_data;
|
||
obj_name_len -= 2;
|
||
}
|
||
|
||
/* FIXME: if METHODTYPE is a superclass of OBJECTTYPE then we don't
|
||
need a cast. Right now there is no way to determine if this is
|
||
the case. */
|
||
if (method_name_len != obj_name_len)
|
||
cast = 1;
|
||
else
|
||
{
|
||
int i;
|
||
for (i = 0; i < method_name_len; ++i)
|
||
{
|
||
if (method_data[i] != obj_data[i])
|
||
{
|
||
cast = 1;
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
|
||
fputs (" { return ", out);
|
||
|
||
if (cast)
|
||
{
|
||
int array_depth = 0;
|
||
const unsigned char *limit;
|
||
|
||
fputs ("reinterpret_cast<", out);
|
||
|
||
while (*method_data == '[')
|
||
{
|
||
++method_data;
|
||
++array_depth;
|
||
--method_name_len;
|
||
fputs ("JArray<", out);
|
||
}
|
||
|
||
/* Leading space to avoid C++ digraphs. */
|
||
fputs (" ::", out);
|
||
|
||
/* If we see an `L', skip it and the trailing `;'. Only do this
|
||
if we've seen an array specification. If we don't have an
|
||
array then the `L' was stripped earlier. */
|
||
if (array_depth && method_data[0] == 'L'
|
||
&& method_data[method_name_len - 1] == ';')
|
||
{
|
||
++method_data;
|
||
method_name_len -= 2;
|
||
}
|
||
|
||
limit = method_data + method_name_len;
|
||
while (method_data < limit)
|
||
{
|
||
int ch = UTF8_GET (method_data, limit);
|
||
if (ch == '/')
|
||
fputs ("::", out);
|
||
else
|
||
jcf_print_char (out, ch);
|
||
}
|
||
fputs (" *", out);
|
||
|
||
/* Close each array. */
|
||
while (array_depth > 0)
|
||
{
|
||
fputs ("> *", out);
|
||
--array_depth;
|
||
}
|
||
|
||
/* Close the cast. */
|
||
fputs ("> (", out);
|
||
}
|
||
|
||
if (nameindex == -1)
|
||
fputs ("this", out);
|
||
else
|
||
print_field_name (out, jcf, nameindex, 0);
|
||
|
||
if (cast)
|
||
fputs (")", out);
|
||
|
||
fputs ("; }", out);
|
||
}
|
||
|
||
|
||
/* Try to decompile a method body. Right now we just try to handle a
|
||
simple case that we can do. Expand as desired. */
|
||
static void
|
||
decompile_method (FILE *out, JCF *jcf, int code_len)
|
||
{
|
||
const unsigned char *codes = jcf->read_ptr;
|
||
int index;
|
||
uint16 name_and_type, name;
|
||
|
||
/* If the method is synchronized, don't touch it. */
|
||
if ((method_access & ACC_SYNCHRONIZED))
|
||
return;
|
||
|
||
if (code_len == 5
|
||
&& codes[0] == OPCODE_aload_0
|
||
&& codes[1] == OPCODE_getfield
|
||
&& (codes[4] == OPCODE_areturn
|
||
|| codes[4] == OPCODE_dreturn
|
||
|| codes[4] == OPCODE_freturn
|
||
|| codes[4] == OPCODE_ireturn
|
||
|| codes[4] == OPCODE_lreturn))
|
||
{
|
||
/* Found code like `return FIELD'. */
|
||
index = (codes[2] << 8) | codes[3];
|
||
/* FIXME: ensure that tag is CONSTANT_Fieldref. */
|
||
name_and_type = JPOOL_USHORT2 (jcf, index);
|
||
/* FIXME: ensure that tag is CONSTANT_NameAndType. */
|
||
name = JPOOL_USHORT1 (jcf, name_and_type);
|
||
if (codes[4] == OPCODE_areturn)
|
||
decompile_return_statement (out, jcf, method_signature,
|
||
name, JPOOL_USHORT2 (jcf, name_and_type));
|
||
else
|
||
{
|
||
fputs (" { return ", out);
|
||
/* FIXME: flags. */
|
||
print_field_name (out, jcf, name, 0);
|
||
fputs ("; }", out);
|
||
}
|
||
decompiled = 1;
|
||
}
|
||
else if (code_len == 2
|
||
&& codes[0] == OPCODE_aload_0
|
||
&& codes[1] == OPCODE_areturn
|
||
/* We're going to generate `return this'. This only makes
|
||
sense for non-static methods. */
|
||
&& ! (method_access & ACC_STATIC))
|
||
{
|
||
decompile_return_statement (out, jcf, method_signature, -1,
|
||
JPOOL_USHORT1 (jcf, jcf->this_class));
|
||
decompiled = 1;
|
||
}
|
||
else if (code_len == 1 && codes[0] == OPCODE_return)
|
||
{
|
||
/* Found plain `return'. */
|
||
fputs (" { }", out);
|
||
decompiled = 1;
|
||
}
|
||
else if (code_len == 2
|
||
&& codes[0] == OPCODE_aconst_null
|
||
&& codes[1] == OPCODE_areturn)
|
||
{
|
||
/* Found `return null'. We don't want to depend on NULL being
|
||
defined. */
|
||
fputs (" { return 0; }", out);
|
||
decompiled = 1;
|
||
}
|
||
}
|
||
|
||
/* Like strcmp, but invert the return result for the hash table. This
|
||
should probably be in hashtab.c to complement the existing string
|
||
hash function. */
|
||
static int
|
||
gcjh_streq (const void *p1, const void *p2)
|
||
{
|
||
return ! strcmp ((char *) p1, (char *) p2);
|
||
}
|
||
|
||
/* Return 1 if the initial part of CLNAME names a subclass of throwable,
|
||
or 0 if not. CLNAME may be extracted from a signature, and can be
|
||
terminated with either `;' or NULL. */
|
||
static int
|
||
throwable_p (const unsigned char *clname)
|
||
{
|
||
int length;
|
||
unsigned char *current;
|
||
int i;
|
||
int result = 0;
|
||
|
||
/* We keep two hash tables of class names. In one we list all the
|
||
classes which are subclasses of Throwable. In the other we will
|
||
all other classes. We keep two tables to make the code a bit
|
||
simpler; we don't have to have a structure mapping class name to
|
||
a `throwable?' bit. */
|
||
static htab_t throw_hash;
|
||
static htab_t non_throw_hash;
|
||
static int init_done = 0;
|
||
|
||
if (! init_done)
|
||
{
|
||
void **slot;
|
||
unsigned char *str;
|
||
|
||
/* Self-initializing. The cost of this really doesn't matter.
|
||
We also don't care about freeing these, either. */
|
||
throw_hash = htab_create (10, htab_hash_string, gcjh_streq,
|
||
(htab_del) free);
|
||
non_throw_hash = htab_create (10, htab_hash_string, gcjh_streq,
|
||
(htab_del) free);
|
||
|
||
/* Make sure the root classes show up in the tables. */
|
||
str = (unsigned char *) xstrdup ("java.lang.Throwable");
|
||
slot = htab_find_slot (throw_hash, str, INSERT);
|
||
*slot = str;
|
||
|
||
str = (unsigned char *) xstrdup ("java.lang.Object");
|
||
slot = htab_find_slot (non_throw_hash, str, INSERT);
|
||
*slot = str;
|
||
|
||
init_done = 1;
|
||
}
|
||
|
||
for (length = 0; clname[length] != ';' && clname[length] != '\0'; ++length)
|
||
;
|
||
current = ALLOC (length + 1);
|
||
for (i = 0; i < length; ++i)
|
||
current[i] = clname[i] == '/' ? '.' : clname[i];
|
||
current[length] = '\0';
|
||
|
||
/* We don't compute the hash slot here because the table might be
|
||
modified by the recursion. In that case the slot could be
|
||
invalidated. */
|
||
if (htab_find (throw_hash, current))
|
||
result = 1;
|
||
else if (htab_find (non_throw_hash, current))
|
||
result = 0;
|
||
else
|
||
{
|
||
JCF jcf;
|
||
void **slot;
|
||
unsigned char *super, *tmp;
|
||
int super_length = -1;
|
||
const char *classfile_name = find_class ((char *) current, strlen ((const char *) current),
|
||
&jcf, 0);
|
||
|
||
if (! classfile_name)
|
||
{
|
||
error ("couldn't find class %s", current);
|
||
return 0;
|
||
}
|
||
if (jcf_parse_preamble (&jcf) != 0
|
||
|| jcf_parse_constant_pool (&jcf) != 0
|
||
|| verify_constant_pool (&jcf) > 0)
|
||
{
|
||
error ("parse error while reading %s", classfile_name);
|
||
return 0;
|
||
}
|
||
jcf_parse_class (&jcf);
|
||
|
||
tmp = (unsigned char *) super_class_name (&jcf, &super_length);
|
||
super = ALLOC (super_length + 1);
|
||
memcpy (super, tmp, super_length);
|
||
super[super_length] = '\0';
|
||
|
||
result = throwable_p (super);
|
||
slot = htab_find_slot (result ? throw_hash : non_throw_hash,
|
||
current, INSERT);
|
||
*slot = current;
|
||
current = NULL;
|
||
|
||
JCF_FINISH (&jcf);
|
||
}
|
||
|
||
return result;
|
||
}
|
||
|
||
/* Print one piece of a signature. Returns pointer to next parseable
|
||
character on success, NULL on error. */
|
||
static const unsigned char *
|
||
decode_signature_piece (FILE *stream, const unsigned char *signature,
|
||
const unsigned char *limit, int *need_space)
|
||
{
|
||
const char *ctype;
|
||
int array_depth = 0;
|
||
|
||
switch (signature[0])
|
||
{
|
||
case '[':
|
||
/* More spaghetti. */
|
||
|
||
array_loop:
|
||
for (signature++; (signature < limit
|
||
&& ISDIGIT (*signature)); signature++)
|
||
;
|
||
switch (*signature)
|
||
{
|
||
case 'B':
|
||
ctype = "jbyteArray";
|
||
break;
|
||
case 'C':
|
||
ctype = "jcharArray";
|
||
break;
|
||
case 'D':
|
||
ctype = "jdoubleArray";
|
||
break;
|
||
case 'F':
|
||
ctype = "jfloatArray";
|
||
break;
|
||
case 'I':
|
||
ctype = "jintArray";
|
||
break;
|
||
case 'S':
|
||
ctype = "jshortArray";
|
||
break;
|
||
case 'J':
|
||
ctype = "jlongArray";
|
||
break;
|
||
case 'Z':
|
||
ctype = "jbooleanArray";
|
||
break;
|
||
case '[':
|
||
/* We have a nested array. */
|
||
++array_depth;
|
||
if (! flag_jni)
|
||
fputs ("JArray<", stream);
|
||
goto array_loop;
|
||
|
||
case 'L':
|
||
/* We have to generate a reference to JArray here, so that
|
||
our output matches what the compiler does. */
|
||
++signature;
|
||
/* Space between `<' and `:' to avoid C++ digraphs. */
|
||
if (! flag_jni)
|
||
fputs ("JArray< ::", stream);
|
||
while (signature < limit && *signature != ';')
|
||
{
|
||
int ch = UTF8_GET (signature, limit);
|
||
if (! flag_jni)
|
||
{
|
||
if (ch == '/')
|
||
fputs ("::", stream);
|
||
else
|
||
jcf_print_char (stream, ch);
|
||
}
|
||
}
|
||
if (! flag_jni)
|
||
fputs (" *> *", stream);
|
||
*need_space = 0;
|
||
ctype = NULL;
|
||
break;
|
||
default:
|
||
/* Unparseable signature. */
|
||
return NULL;
|
||
}
|
||
|
||
/* If the previous iterations left us with something to print,
|
||
print it. For JNI, we always print `jobjectArray' in the
|
||
nested cases. */
|
||
if (flag_jni && (ctype == NULL || array_depth > 0))
|
||
{
|
||
ctype = "jobjectArray";
|
||
*need_space = 1;
|
||
}
|
||
/* The `printit' case will advance SIGNATURE for us. If we
|
||
don't go there, we must advance past the `;' ourselves. */
|
||
if (ctype != NULL)
|
||
goto printit;
|
||
++signature;
|
||
break;
|
||
|
||
case '(':
|
||
case ')':
|
||
/* This shouldn't happen. */
|
||
return NULL;
|
||
|
||
case 'B': ctype = "jbyte"; goto printit;
|
||
case 'C': ctype = "jchar"; goto printit;
|
||
case 'D': ctype = "jdouble"; goto printit;
|
||
case 'F': ctype = "jfloat"; goto printit;
|
||
case 'I': ctype = "jint"; goto printit;
|
||
case 'J': ctype = "jlong"; goto printit;
|
||
case 'S': ctype = "jshort"; goto printit;
|
||
case 'Z': ctype = "jboolean"; goto printit;
|
||
case 'V': ctype = "void"; goto printit;
|
||
case 'L':
|
||
if (flag_jni)
|
||
{
|
||
/* We know about certain types and special-case their names. */
|
||
if (! strncmp ((const char *) signature, "Ljava/lang/String;",
|
||
sizeof ("Ljava/lang/String;") -1))
|
||
ctype = "jstring";
|
||
else if (! strncmp ((const char *) signature, "Ljava/lang/Class;",
|
||
sizeof ("Ljava/lang/Class;") - 1))
|
||
ctype = "jclass";
|
||
/* Skip leading 'L' for throwable_p call. */
|
||
else if (throwable_p (signature + 1))
|
||
ctype = "jthrowable";
|
||
else
|
||
ctype = "jobject";
|
||
|
||
while (*signature && *signature != ';')
|
||
++signature;
|
||
|
||
goto printit;
|
||
}
|
||
/* Print a leading "::" so we look in the right namespace. */
|
||
fputs ("::", stream);
|
||
++signature;
|
||
while (*signature && *signature != ';')
|
||
{
|
||
int ch = UTF8_GET (signature, limit);
|
||
if (ch == '/')
|
||
fputs ("::", stream);
|
||
else
|
||
jcf_print_char (stream, ch);
|
||
}
|
||
fputs (" *", stream);
|
||
if (*signature == ';')
|
||
signature++;
|
||
*need_space = 0;
|
||
break;
|
||
default:
|
||
*need_space = 1;
|
||
jni_print_char (stream, *signature++);
|
||
break;
|
||
printit:
|
||
signature++;
|
||
*need_space = 1;
|
||
fputs (ctype, stream);
|
||
break;
|
||
}
|
||
|
||
if (! flag_jni)
|
||
{
|
||
while (array_depth-- > 0)
|
||
fputs ("> *", stream);
|
||
}
|
||
|
||
return signature;
|
||
}
|
||
|
||
static void
|
||
print_c_decl (FILE* stream, JCF* jcf, int name_index, int signature_index,
|
||
int is_init, const char *name_override, int flags)
|
||
{
|
||
if (JPOOL_TAG (jcf, signature_index) != CONSTANT_Utf8)
|
||
{
|
||
fprintf (stream, "<not a UTF8 constant>");
|
||
found_error = 1;
|
||
}
|
||
else
|
||
{
|
||
int length = JPOOL_UTF_LENGTH (jcf, signature_index);
|
||
const unsigned char *str0 = JPOOL_UTF_DATA (jcf, signature_index);
|
||
const unsigned char *str = str0;
|
||
const unsigned char *limit = str + length;
|
||
int need_space = 0;
|
||
int is_method = str[0] == '(';
|
||
const unsigned char *next;
|
||
|
||
/* If printing a method, skip to the return signature and print
|
||
that first. However, there is no return value if this is a
|
||
constructor. */
|
||
if (is_method && ! is_init)
|
||
{
|
||
while (str < limit)
|
||
{
|
||
int ch = *str++;
|
||
if (ch == ')')
|
||
break;
|
||
}
|
||
}
|
||
|
||
/* If printing a field or an ordinary method, then print the
|
||
"return value" now. */
|
||
if (! is_method || ! is_init)
|
||
{
|
||
next = decode_signature_piece (stream, str, limit, &need_space);
|
||
if (! next)
|
||
{
|
||
error ("unparseable signature: '%s'", str0);
|
||
return;
|
||
}
|
||
}
|
||
|
||
/* Force the alignment of the first data member. This is
|
||
because the "new" C++ ABI changed the alignment of non-POD
|
||
classes. gcj, however, still uses the "old" alignment. */
|
||
if (is_first_data_member && ! (flags & ACC_STATIC) && ! is_method)
|
||
{
|
||
is_first_data_member = 0;
|
||
print_cxx_classname (out, " __attribute__((aligned(__alignof__( ",
|
||
jcf, jcf->super_class, 1);
|
||
fputs (" )))) ", stream);
|
||
}
|
||
|
||
/* Now print the name of the thing. */
|
||
if (need_space)
|
||
fputs (" ", stream);
|
||
print_full_cxx_name (stream, jcf, name_index,
|
||
signature_index, is_init, name_override,
|
||
flags);
|
||
}
|
||
}
|
||
|
||
/* Print the unqualified method name followed by the signature. */
|
||
static void
|
||
print_full_cxx_name (FILE* stream, JCF* jcf, int name_index,
|
||
int signature_index, int is_init,
|
||
const char *name_override, int flags)
|
||
{
|
||
int length = JPOOL_UTF_LENGTH (jcf, signature_index);
|
||
const unsigned char *str0 = JPOOL_UTF_DATA (jcf, signature_index);
|
||
const unsigned char *str = str0;
|
||
const unsigned char *limit = str + length;
|
||
int need_space = 0;
|
||
int is_method = str[0] == '(';
|
||
const unsigned char *next;
|
||
|
||
if (name_override)
|
||
fputs (name_override, stream);
|
||
else if (name_index)
|
||
{
|
||
/* Declare constructors specially. */
|
||
if (is_init)
|
||
print_base_classname (stream, jcf, jcf->this_class);
|
||
else
|
||
print_name (stream, jcf, name_index);
|
||
}
|
||
|
||
if (flag_jni)
|
||
{
|
||
unsigned char *signature = JPOOL_UTF_DATA (jcf, signature_index);
|
||
int sig_len = JPOOL_UTF_LENGTH (jcf, signature_index);
|
||
if (overloaded_jni_method_exists_p (JPOOL_UTF_DATA (jcf, name_index),
|
||
JPOOL_UTF_LENGTH (jcf, name_index),
|
||
(const char *) signature, sig_len))
|
||
{
|
||
/* If this method is overloaded by another native method,
|
||
then include the argument information in the mangled
|
||
name. */
|
||
unsigned char *limit = signature + sig_len;
|
||
fputs ("__", stream);
|
||
while (signature < limit)
|
||
{
|
||
int ch = UTF8_GET (signature, limit);
|
||
jni_print_char (stream, ch);
|
||
if (ch == ')')
|
||
{
|
||
/* Done. */
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
if (is_method)
|
||
{
|
||
/* Have a method or a constructor. Print signature pieces
|
||
until done. */
|
||
fputs (" (", stream);
|
||
|
||
str = str0 + 1;
|
||
|
||
/* In JNI mode, add extra arguments. */
|
||
if (flag_jni)
|
||
{
|
||
/* FIXME: it would be nice to know if we are printing a decl
|
||
or a definition, and only print `env' for the latter. */
|
||
fputs ("JNIEnv *env", stream);
|
||
|
||
fputs ((flags & ACC_STATIC) ? ", jclass" : ", jobject", stream);
|
||
|
||
if (*str != ')')
|
||
fputs (", ", stream);
|
||
}
|
||
|
||
while (str < limit && *str != ')')
|
||
{
|
||
next = decode_signature_piece (stream, str, limit, &need_space);
|
||
if (! next)
|
||
{
|
||
error ("unparseable signature: '%s'", str0);
|
||
return;
|
||
}
|
||
|
||
if (next < limit && *next != ')')
|
||
fputs (", ", stream);
|
||
str = next;
|
||
}
|
||
|
||
fputs (")", stream);
|
||
}
|
||
}
|
||
|
||
/* This is a helper for print_stub_or_jni. */
|
||
static void
|
||
print_name_for_stub_or_jni (FILE *stream, JCF *jcf, int name_index,
|
||
int signature_index, int is_init,
|
||
const char *name_override, int flags)
|
||
{
|
||
const char *const prefix = flag_jni ? "Java_" : "";
|
||
print_cxx_classname (stream, prefix, jcf, jcf->this_class, 1);
|
||
fputs (flag_jni ? "_" : "::", stream);
|
||
print_full_cxx_name (stream, jcf, name_index,
|
||
signature_index, is_init, name_override,
|
||
flags);
|
||
}
|
||
|
||
static void
|
||
print_stub_or_jni (FILE* stream, JCF* jcf, int name_index,
|
||
int signature_index, int is_init,
|
||
const char *name_override, int flags)
|
||
{
|
||
if (JPOOL_TAG (jcf, signature_index) != CONSTANT_Utf8)
|
||
{
|
||
fprintf (stream, "<not a UTF8 constant>");
|
||
found_error = 1;
|
||
}
|
||
else
|
||
{
|
||
int length = JPOOL_UTF_LENGTH (jcf, signature_index);
|
||
const unsigned char *str0 = JPOOL_UTF_DATA (jcf, signature_index);
|
||
const unsigned char *str = str0;
|
||
const unsigned char *limit = str + length;
|
||
int need_space = 0;
|
||
int is_method = str[0] == '(';
|
||
const unsigned char *next;
|
||
|
||
/* Don't print fields in the JNI case. */
|
||
if (! is_method && flag_jni)
|
||
return;
|
||
|
||
if (flag_jni && ! stubs)
|
||
fputs ("JNIEXPORT ", stream);
|
||
|
||
/* If printing a method, skip to the return signature and print
|
||
that first. However, there is no return value if this is a
|
||
constructor. */
|
||
if (is_method && ! is_init)
|
||
{
|
||
while (str < limit)
|
||
{
|
||
int ch = *str++;
|
||
if (ch == ')')
|
||
break;
|
||
}
|
||
}
|
||
|
||
/* If printing a field or an ordinary method, then print the
|
||
"return value" now. Note that a constructor can't be native,
|
||
so we don't bother checking this in the JNI case. */
|
||
if (! is_method || ! is_init)
|
||
{
|
||
next = decode_signature_piece (stream, str, limit, &need_space);
|
||
if (! next)
|
||
{
|
||
error ("unparseable signature: '%s'", str0);
|
||
return;
|
||
}
|
||
}
|
||
|
||
/* When printing a JNI header we need to respect the space. In
|
||
other cases we're just going to insert a newline anyway. */
|
||
fputs (need_space && ! stubs ? " " : "\n", stream);
|
||
|
||
if (flag_jni && ! stubs)
|
||
fputs ("JNICALL ", stream);
|
||
|
||
/* Now print the name of the thing. */
|
||
print_name_for_stub_or_jni (stream, jcf, name_index,
|
||
signature_index, is_init, name_override,
|
||
flags);
|
||
|
||
/* Print the body. */
|
||
if (stubs)
|
||
{
|
||
if (flag_jni)
|
||
fputs ("\n{\n (*env)->FatalError (env, \"", stream);
|
||
else
|
||
fputs ("\n{\n throw new ::java::lang::UnsupportedOperationException (JvNewStringLatin1 (\"", stream);
|
||
print_name_for_stub_or_jni (stream, jcf, name_index,
|
||
signature_index, is_init,
|
||
name_override,
|
||
flags);
|
||
fprintf (stream, " not implemented\")%s;\n}\n\n",
|
||
flag_jni ? "" : ")");
|
||
}
|
||
}
|
||
}
|
||
|
||
static void
|
||
print_mangled_classname (FILE *stream, JCF *jcf, const char *prefix, int index)
|
||
{
|
||
int name_index = JPOOL_USHORT1 (jcf, index);
|
||
fputs (prefix, stream);
|
||
jcf_print_utf8_replace (out,
|
||
JPOOL_UTF_DATA (jcf, name_index),
|
||
JPOOL_UTF_LENGTH (jcf, name_index),
|
||
'/', '_');
|
||
}
|
||
|
||
/* Print PREFIX, then a class name in C++ format. If the name refers
|
||
to an array, ignore it and don't print PREFIX. Returns 1 if
|
||
something was printed, 0 otherwise. */
|
||
static int
|
||
print_cxx_classname (FILE *stream, const char *prefix,
|
||
JCF *jcf, int index, int add_scope)
|
||
{
|
||
int name_index = JPOOL_USHORT1 (jcf, index);
|
||
int len, c;
|
||
const unsigned char *s, *p, *limit;
|
||
|
||
s = JPOOL_UTF_DATA (jcf, name_index);
|
||
len = JPOOL_UTF_LENGTH (jcf, name_index);
|
||
limit = s + len;
|
||
|
||
/* Explicitly omit arrays here. */
|
||
p = s;
|
||
c = UTF8_GET (p, limit);
|
||
if (c == '[')
|
||
return 0;
|
||
|
||
fputs (prefix, stream);
|
||
|
||
/* Print a leading "::" so we look in the right namespace. */
|
||
if (! flag_jni && ! stubs && add_scope)
|
||
fputs ("::", stream);
|
||
|
||
while (s < limit)
|
||
{
|
||
c = UTF8_GET (s, limit);
|
||
if (c == '/')
|
||
fputs (flag_jni ? "_" : "::", stream);
|
||
else
|
||
jni_print_char (stream, c);
|
||
}
|
||
|
||
return 1;
|
||
}
|
||
|
||
int written_class_count = 0;
|
||
|
||
/* Return name of superclass. If LEN is not NULL, fill it with length
|
||
of name. */
|
||
static const unsigned char *
|
||
super_class_name (JCF *derived_jcf, int *len)
|
||
{
|
||
int supername_index = JPOOL_USHORT1 (derived_jcf, derived_jcf->super_class);
|
||
int supername_length = JPOOL_UTF_LENGTH (derived_jcf, supername_index);
|
||
const unsigned char *supername =
|
||
JPOOL_UTF_DATA (derived_jcf, supername_index);
|
||
|
||
if (len)
|
||
*len = supername_length;
|
||
|
||
return supername;
|
||
}
|
||
|
||
static void
|
||
handle_inner_classes (int count)
|
||
{
|
||
int i;
|
||
|
||
if (out && ! flag_jni && ! stubs && count > 0)
|
||
fprintf (out, "\n");
|
||
|
||
for (i = 0; i < count; ++i)
|
||
{
|
||
JCF_u2 inner_info_index = JCF_readu2 (current_jcf);
|
||
|
||
/* There are a few more values here, but we don't care about
|
||
them. The (void) cast is apparently the only way to avoid a
|
||
warning here. */
|
||
(void) JCF_readu2 (current_jcf);
|
||
(void) JCF_readu2 (current_jcf);
|
||
(void) JCF_readu2 (current_jcf);
|
||
|
||
if (out && ! flag_jni && ! stubs)
|
||
{
|
||
print_mangled_classname (out, current_jcf, " friend class ",
|
||
inner_info_index);
|
||
fprintf (out, ";\n");
|
||
}
|
||
}
|
||
}
|
||
|
||
|
||
|
||
/* We keep track of all the `#include's we generate, so we can avoid
|
||
duplicates. */
|
||
struct include
|
||
{
|
||
char *name;
|
||
struct include *next;
|
||
};
|
||
|
||
/* List of all includes. */
|
||
static struct include *all_includes = NULL;
|
||
|
||
/* Generate a #include. */
|
||
static void
|
||
print_include (FILE *out, const unsigned char *utf8, int len)
|
||
{
|
||
struct include *incl;
|
||
|
||
if (! out)
|
||
return;
|
||
|
||
if (len == -1)
|
||
len = strlen ((const char *) utf8);
|
||
|
||
for (incl = all_includes; incl; incl = incl->next)
|
||
{
|
||
/* We check the length because we might have a proper prefix. */
|
||
if (len == (int) strlen (incl->name)
|
||
&& ! strncmp (incl->name, (const char *) utf8, len))
|
||
return;
|
||
}
|
||
|
||
incl = xmalloc (sizeof (struct include));
|
||
incl->name = xmalloc (len + 1);
|
||
strncpy (incl->name, (const char *) utf8, len);
|
||
incl->name[len] = '\0';
|
||
incl->next = all_includes;
|
||
all_includes = incl;
|
||
|
||
fputs ("#include <", out);
|
||
jcf_print_utf8_replace (out, utf8, len,
|
||
'/',
|
||
flag_jni ? '_' : '/');
|
||
fputs (".h>\n", out);
|
||
}
|
||
|
||
|
||
|
||
/* This is used to represent part of a package or class name. */
|
||
struct namelet
|
||
{
|
||
/* The text of this part of the name. */
|
||
char *name;
|
||
/* True if this represents a class. */
|
||
int is_class;
|
||
/* Linked list of all classes and packages inside this one. */
|
||
struct namelet *subnamelets;
|
||
/* Pointer to next sibling. */
|
||
struct namelet *next;
|
||
};
|
||
|
||
static void add_namelet (const unsigned char *, const unsigned char *,
|
||
struct namelet *);
|
||
static void print_namelet (FILE *, struct namelet *, int);
|
||
|
||
/* The special root namelet. */
|
||
static struct namelet root =
|
||
{
|
||
NULL,
|
||
0,
|
||
NULL,
|
||
NULL
|
||
};
|
||
|
||
/* This extracts the next name segment from the full UTF-8 encoded
|
||
package or class name and links it into the tree. It does this
|
||
recursively. */
|
||
static void
|
||
add_namelet (const unsigned char *name, const unsigned char *name_limit,
|
||
struct namelet *parent)
|
||
{
|
||
const unsigned char *p;
|
||
struct namelet *n = NULL, *np;
|
||
|
||
/* We want to skip the standard namespaces that we assume the
|
||
runtime already knows about. We only do this at the top level,
|
||
though, hence the check for `root'. */
|
||
if (parent == &root)
|
||
{
|
||
#define JAVALANG "java/lang/"
|
||
#define JAVAIO "java/io/"
|
||
#define JAVAUTIL "java/util/"
|
||
if ((name_limit - name >= (int) sizeof (JAVALANG) - 1
|
||
&& ! strncmp ((const char *) name, JAVALANG, sizeof (JAVALANG) - 1))
|
||
|| (name_limit - name >= (int) sizeof (JAVAUTIL) - 1
|
||
&& ! strncmp ((const char *) name, JAVAUTIL, sizeof (JAVAUTIL) - 1))
|
||
|| (name_limit - name >= (int) sizeof (JAVAIO) - 1
|
||
&& ! strncmp ((const char *) name, JAVAIO, sizeof (JAVAIO) - 1)))
|
||
return;
|
||
}
|
||
|
||
for (p = name; p < name_limit && *p != '/'; ++p)
|
||
;
|
||
|
||
/* Search for this name beneath the PARENT node. */
|
||
for (np = parent->subnamelets; np != NULL; np = np->next)
|
||
{
|
||
/* We check the length because we might have a proper prefix. */
|
||
if ((int) strlen (np->name) == p - name &&
|
||
! strncmp ((const char *) name, np->name, p - name))
|
||
{
|
||
n = np;
|
||
break;
|
||
}
|
||
}
|
||
|
||
if (n == NULL)
|
||
{
|
||
n = xmalloc (sizeof (struct namelet));
|
||
n->name = xmalloc (p - name + 1);
|
||
strncpy (n->name, (const char *) name, p - name);
|
||
n->name[p - name] = '\0';
|
||
n->is_class = (p == name_limit);
|
||
n->subnamelets = NULL;
|
||
n->next = parent->subnamelets;
|
||
parent->subnamelets = n;
|
||
}
|
||
|
||
/* We recurse if there is more text, and if the trailing piece does
|
||
not represent an inner class. */
|
||
if (p < name_limit)
|
||
add_namelet (p + 1, name_limit, n);
|
||
}
|
||
|
||
/* Print a single namelet. Destroys namelets while printing. */
|
||
static void
|
||
print_namelet (FILE *out, struct namelet *name, int depth)
|
||
{
|
||
int i, term = 0;
|
||
struct namelet *c;
|
||
|
||
if (name->name)
|
||
{
|
||
for (i = 0; i < depth; ++i)
|
||
fputc (' ', out);
|
||
fprintf (out, "%s %s", name->is_class ? "class" : "namespace",
|
||
name->name);
|
||
if (name->is_class && name->subnamelets == NULL)
|
||
fputs (";\n", out);
|
||
else
|
||
{
|
||
term = 1;
|
||
fputs ("\n", out);
|
||
for (i = 0; i < depth; ++i)
|
||
fputc (' ', out);
|
||
fputs ("{\n", out);
|
||
}
|
||
}
|
||
|
||
c = name->subnamelets;
|
||
while (c != NULL)
|
||
{
|
||
struct namelet *next = c->next;
|
||
print_namelet (out, c, depth + 2);
|
||
c = next;
|
||
}
|
||
name->subnamelets = NULL;
|
||
|
||
if (name->name)
|
||
{
|
||
if (term)
|
||
{
|
||
for (i = 0; i < depth; ++i)
|
||
fputc (' ', out);
|
||
fputs ("}\n", out);
|
||
/* Only print a `;' when printing a class. C++ is evil. */
|
||
if (name->is_class)
|
||
fputs (";", out);
|
||
}
|
||
|
||
free (name->name);
|
||
free (name);
|
||
}
|
||
}
|
||
|
||
/* This is called to add some classes to the list of classes for which
|
||
we need decls. The signature argument can be a function
|
||
signature. */
|
||
static void
|
||
add_class_decl (FILE *out, JCF *jcf, JCF_u2 signature)
|
||
{
|
||
const unsigned char *s = JPOOL_UTF_DATA (jcf, signature);
|
||
int len = JPOOL_UTF_LENGTH (jcf, signature);
|
||
int i;
|
||
|
||
for (i = 0; i < len; ++i)
|
||
{
|
||
int start;
|
||
|
||
/* If we see an array, then we include the array header. */
|
||
if (s[i] == '[')
|
||
{
|
||
print_include (out, (const unsigned char *) "gcj/array", -1);
|
||
continue;
|
||
}
|
||
|
||
/* We're looking for `L<stuff>;' -- everything else is
|
||
ignorable. */
|
||
if (s[i] != 'L')
|
||
continue;
|
||
|
||
for (start = ++i; i < len && s[i] != ';'; ++i)
|
||
;
|
||
|
||
add_namelet (&s[start], &s[i], &root);
|
||
}
|
||
}
|
||
|
||
/* Print declarations for all classes required by this class. Any
|
||
class or package in the `java' package is assumed to be handled
|
||
statically in libjava; we don't generate declarations for these.
|
||
This makes the generated headers a bit easier to read. */
|
||
static void
|
||
print_class_decls (FILE *out, JCF *jcf, int self)
|
||
{
|
||
/* Make sure to always add the current class to the list of things
|
||
that should be declared. */
|
||
int name_index = JPOOL_USHORT1 (jcf, self);
|
||
int len;
|
||
const unsigned char *s;
|
||
|
||
s = JPOOL_UTF_DATA (jcf, name_index);
|
||
len = JPOOL_UTF_LENGTH (jcf, name_index);
|
||
add_namelet (s, s + len, &root);
|
||
|
||
if (root.subnamelets)
|
||
{
|
||
fputs ("extern \"Java\"\n{\n", out);
|
||
/* We use an initial offset of 0 because the root namelet
|
||
doesn't cause anything to print. */
|
||
print_namelet (out, &root, 0);
|
||
fputs ("}\n\n", out);
|
||
}
|
||
}
|
||
|
||
|
||
|
||
static void
|
||
process_file (JCF *jcf, FILE *out)
|
||
{
|
||
int code, i;
|
||
uint32 field_start, method_end, method_start;
|
||
|
||
current_jcf = jcf;
|
||
|
||
last_access = -1;
|
||
|
||
if (jcf_parse_preamble (jcf) != 0)
|
||
{
|
||
error ("Not a valid Java .class file.");
|
||
return;
|
||
}
|
||
|
||
/* Parse and possibly print constant pool */
|
||
code = jcf_parse_constant_pool (jcf);
|
||
if (code != 0)
|
||
{
|
||
error ("error while parsing constant pool");
|
||
return;
|
||
}
|
||
code = verify_constant_pool (jcf);
|
||
if (code > 0)
|
||
{
|
||
error ("error in constant pool entry #%d", code);
|
||
return;
|
||
}
|
||
|
||
jcf_parse_class (jcf);
|
||
|
||
if (written_class_count++ == 0 && out)
|
||
{
|
||
const char *cstart, *cstart2, *mode, *cend, *what, *jflag;
|
||
if (flag_jni)
|
||
{
|
||
cstart = "/*";
|
||
cstart2 = " ";
|
||
cend = " */";
|
||
mode = "";
|
||
what = "JNI";
|
||
jflag = " -jni";
|
||
}
|
||
else
|
||
{
|
||
cstart = "//";
|
||
cstart2 = "//";
|
||
cend = "";
|
||
mode = " -*- c++ -*-";
|
||
what = "CNI";
|
||
jflag = "";
|
||
}
|
||
|
||
if (! stubs)
|
||
fprintf (out, "%s DO NOT EDIT THIS FILE - it is machine generated%s%s\n\n",
|
||
cstart, mode, cend);
|
||
else
|
||
{
|
||
fprintf (out, "%s This file was created by `" TOOLNAME " -stubs%s'.%s\n\
|
||
%s\n\
|
||
%s This file is intended to give you a head start on implementing native\n\
|
||
%s methods using %s.\n\
|
||
%s Be aware: running `" TOOLNAME " -stubs %s' once more for this class may\n\
|
||
%s overwrite any edits you have made to this file.%s\n\n",
|
||
cstart, jflag, mode,
|
||
cstart2,
|
||
cstart2,
|
||
cstart2,
|
||
what,
|
||
cstart2,
|
||
jflag,
|
||
cstart2,
|
||
cend);
|
||
}
|
||
}
|
||
|
||
if (out)
|
||
{
|
||
if (! stubs)
|
||
{
|
||
print_mangled_classname (out, jcf, "#ifndef __", jcf->this_class);
|
||
fprintf (out, "__\n");
|
||
|
||
print_mangled_classname (out, jcf, "#define __", jcf->this_class);
|
||
fprintf (out, "__\n\n");
|
||
|
||
if (flag_jni)
|
||
{
|
||
fprintf (out, "#include <jni.h>\n\n");
|
||
fprintf (out, "#ifdef __cplusplus\n");
|
||
fprintf (out, "extern \"C\"\n");
|
||
fprintf (out, "{\n");
|
||
fprintf (out, "#endif\n");
|
||
}
|
||
else
|
||
{
|
||
/* We do this to ensure that inline methods won't be
|
||
`outlined' by g++. This works as long as method and
|
||
fields are not added by the user. */
|
||
fprintf (out, "#pragma interface\n");
|
||
|
||
if (jcf->super_class)
|
||
{
|
||
int super_length;
|
||
const unsigned char *supername =
|
||
super_class_name (jcf, &super_length);
|
||
|
||
fputs ("\n", out);
|
||
print_include (out, supername, super_length);
|
||
}
|
||
}
|
||
}
|
||
else
|
||
{
|
||
/* Strip off the ".class" portion of the name when printing
|
||
the include file name. */
|
||
char *name;
|
||
int i, len = strlen (jcf->classname);
|
||
if (len > 6 && ! strcmp (&jcf->classname[len - 6], ".class"))
|
||
len -= 6;
|
||
/* Turn the class name into a file name. */
|
||
name = xmalloc (len + 1);
|
||
for (i = 0; i < len; ++i)
|
||
name[i] = jcf->classname[i] == '.' ? '/' : jcf->classname[i];
|
||
name[i] = '\0';
|
||
print_include (out, (const unsigned char *) name, len);
|
||
free (name);
|
||
|
||
if (! flag_jni)
|
||
{
|
||
print_include (out, (const unsigned char *) "gcj/cni", -1);
|
||
print_include (out, (const unsigned char *) "java/lang/UnsupportedOperationException",
|
||
-1);
|
||
}
|
||
}
|
||
}
|
||
|
||
/* We want to parse the methods first. But we need to find where
|
||
they start. So first we skip the fields, then parse the methods.
|
||
Then we parse the fields and skip the methods. This is ugly, but
|
||
not too bad since we need two full passes to get class decl
|
||
information anyway. */
|
||
field_pass = 0;
|
||
field_start = JCF_TELL (jcf);
|
||
jcf_parse_fields (jcf);
|
||
|
||
method_start = JCF_TELL (jcf);
|
||
method_pass = 0;
|
||
jcf_parse_methods (jcf);
|
||
|
||
if (out)
|
||
fputs ("\n", out);
|
||
|
||
if (out && ! flag_jni)
|
||
{
|
||
if (! stubs)
|
||
print_class_decls (out, jcf, jcf->this_class);
|
||
|
||
for (i = 0; i < prepend_count; ++i)
|
||
fprintf (out, "%s\n", prepend_specs[i]);
|
||
if (prepend_count > 0)
|
||
fputc ('\n', out);
|
||
|
||
if (! stubs)
|
||
{
|
||
if (! print_cxx_classname (out, "class ", jcf,
|
||
jcf->this_class, 0))
|
||
{
|
||
error ("class is of array type\n");
|
||
return;
|
||
}
|
||
if (jcf->super_class)
|
||
{
|
||
if (! print_cxx_classname (out, " : public ",
|
||
jcf, jcf->super_class, 1))
|
||
{
|
||
error ("base class is of array type");
|
||
return;
|
||
}
|
||
}
|
||
|
||
fputs ("\n{\n", out);
|
||
}
|
||
}
|
||
|
||
/* Now go back for second pass over methods and fields. */
|
||
is_first_data_member = 1;
|
||
|
||
JCF_SEEK (jcf, method_start);
|
||
method_pass = 1;
|
||
jcf_parse_methods (jcf);
|
||
method_end = JCF_TELL (jcf);
|
||
|
||
field_pass = 1;
|
||
JCF_SEEK (jcf, field_start);
|
||
jcf_parse_fields (jcf);
|
||
JCF_SEEK (jcf, method_end);
|
||
|
||
jcf_parse_final_attributes (jcf);
|
||
|
||
if (out && ! stubs)
|
||
{
|
||
if (flag_jni)
|
||
{
|
||
fprintf (out, "\n#ifdef __cplusplus\n");
|
||
fprintf (out, "}\n");
|
||
fprintf (out, "#endif\n");
|
||
}
|
||
else
|
||
{
|
||
/* Generate friend decl if we still must. */
|
||
for (i = 0; i < friend_count; ++i)
|
||
fprintf (out, " friend %s\n", friend_specs[i]);
|
||
|
||
/* Generate extra declarations. */
|
||
if (add_count > 0)
|
||
fputc ('\n', out);
|
||
for (i = 0; i < add_count; ++i)
|
||
fprintf (out, " %s\n", add_specs[i]);
|
||
|
||
/* Generate an entry for the class object. */
|
||
generate_access (out, ACC_PUBLIC);
|
||
fprintf (out, "\n static ::java::lang::Class class$;\n");
|
||
|
||
fputs ("}", out);
|
||
|
||
if (jcf->access_flags & ACC_INTERFACE)
|
||
fputs (" __attribute__ ((java_interface))", out);
|
||
|
||
fputs (";\n", out);
|
||
|
||
if (append_count > 0)
|
||
fputc ('\n', out);
|
||
for (i = 0; i < append_count; ++i)
|
||
fprintf (out, "%s\n", append_specs[i]);
|
||
}
|
||
|
||
print_mangled_classname (out, jcf,
|
||
"\n#endif /* __", jcf->this_class);
|
||
fprintf (out, "__ */\n");
|
||
}
|
||
}
|
||
|
||
|
||
|
||
/* This is used to mark options with no short value. */
|
||
#define LONG_OPT(Num) ((Num) + 128)
|
||
|
||
#define OPT_classpath LONG_OPT (0)
|
||
#define OPT_CLASSPATH OPT_classpath
|
||
#define OPT_bootclasspath LONG_OPT (1)
|
||
#define OPT_extdirs LONG_OPT (2)
|
||
#define OPT_HELP LONG_OPT (3)
|
||
#define OPT_TEMP LONG_OPT (4)
|
||
#define OPT_VERSION LONG_OPT (5)
|
||
#define OPT_PREPEND LONG_OPT (6)
|
||
#define OPT_FRIEND LONG_OPT (7)
|
||
#define OPT_ADD LONG_OPT (8)
|
||
#define OPT_APPEND LONG_OPT (9)
|
||
#define OPT_M LONG_OPT (10)
|
||
#define OPT_MM LONG_OPT (11)
|
||
#define OPT_MG LONG_OPT (12)
|
||
#define OPT_MD LONG_OPT (13)
|
||
#define OPT_MMD LONG_OPT (14)
|
||
#define OPT_FORCE LONG_OPT (15)
|
||
#define OPT_OLD LONG_OPT (16)
|
||
#define OPT_TRACE LONG_OPT (17)
|
||
|
||
static const struct option options[] =
|
||
{
|
||
{ "classpath", required_argument, NULL, OPT_classpath },
|
||
{ "bootclasspath", required_argument, NULL, OPT_bootclasspath },
|
||
{ "extdirs", required_argument, NULL, OPT_extdirs },
|
||
{ "CLASSPATH", required_argument, NULL, OPT_CLASSPATH },
|
||
{ "help", no_argument, NULL, OPT_HELP },
|
||
{ "stubs", no_argument, &stubs, 1 },
|
||
{ "td", required_argument, NULL, OPT_TEMP },
|
||
{ "verbose", no_argument, NULL, 'v' },
|
||
{ "version", no_argument, NULL, OPT_VERSION },
|
||
{ "prepend", required_argument, NULL, OPT_PREPEND },
|
||
{ "friend", required_argument, NULL, OPT_FRIEND },
|
||
{ "add", required_argument, NULL, OPT_ADD },
|
||
{ "append", required_argument, NULL, OPT_APPEND },
|
||
{ "M", no_argument, NULL, OPT_M },
|
||
{ "MM", no_argument, NULL, OPT_MM },
|
||
{ "MG", no_argument, NULL, OPT_MG },
|
||
{ "MD", no_argument, NULL, OPT_MD },
|
||
{ "MMD", no_argument, NULL, OPT_MMD },
|
||
{ "jni", no_argument, &flag_jni, 1 },
|
||
{ "force", no_argument, NULL, OPT_FORCE },
|
||
/* If the output file should be named "ld" then a space is needed
|
||
between -o and its argument, ld. */
|
||
{ "old", no_argument, NULL, OPT_OLD },
|
||
{ "trace", no_argument, NULL, OPT_TRACE },
|
||
{ NULL, required_argument, NULL, 'J' },
|
||
{ NULL, no_argument, NULL, 0 }
|
||
};
|
||
|
||
static void
|
||
usage (void)
|
||
{
|
||
fprintf (stderr, _("Try '" TOOLNAME " --help' for more information.\n"));
|
||
exit (1);
|
||
}
|
||
|
||
static void
|
||
help (void)
|
||
{
|
||
printf (_("Usage: " TOOLNAME " [OPTION]... CLASS...\n\n"));
|
||
printf (_("Generate C or C++ header files from .class files\n\n"));
|
||
printf (_(" -stubs Generate an implementation stub file\n"));
|
||
printf (_(" -jni Generate a JNI header or stub\n"));
|
||
printf (_(" -force Always overwrite output files\n"));
|
||
printf (_(" -old Unused compatibility option\n"));
|
||
printf (_(" -trace Unused compatibility option\n"));
|
||
printf (_(" -J OPTION Unused compatibility option\n"));
|
||
printf ("\n");
|
||
printf (_(" -add TEXT Insert TEXT into class body\n"));
|
||
printf (_(" -append TEXT Insert TEXT after class declaration\n"));
|
||
printf (_(" -friend TEXT Insert TEXT as 'friend' declaration\n"));
|
||
printf (_(" -prepend TEXT Insert TEXT before start of class\n"));
|
||
printf ("\n");
|
||
printf (_(" --classpath PATH Set path to find .class files\n"));
|
||
printf (_(" -IDIR Append directory to class path\n"));
|
||
printf (_(" --bootclasspath PATH Override built-in class path\n"));
|
||
printf (_(" --extdirs PATH Set extensions directory path\n"));
|
||
printf (_(" -d DIRECTORY Set output directory name\n"));
|
||
printf (_(" -o FILE Set output file name\n"));
|
||
printf (_(" -td DIRECTORY Set temporary directory name\n"));
|
||
printf ("\n");
|
||
printf (_(" --help Print this help, then exit\n"));
|
||
printf (_(" --version Print version number, then exit\n"));
|
||
printf (_(" -v, --verbose Print extra information while running\n"));
|
||
printf ("\n");
|
||
printf (_(" -M Print all dependencies to stdout;\n"
|
||
" suppress ordinary output\n"));
|
||
printf (_(" -MM Print non-system dependencies to stdout;\n"
|
||
" suppress ordinary output\n"));
|
||
printf (_(" -MD Print all dependencies to stdout\n"));
|
||
printf (_(" -MMD Print non-system dependencies to stdout\n"));
|
||
/* We omit -MG until it is implemented. */
|
||
printf ("\n");
|
||
printf (_("For bug reporting instructions, please see:\n"
|
||
"%s.\n"), bug_report_url);
|
||
exit (0);
|
||
}
|
||
|
||
static void
|
||
version (void)
|
||
{
|
||
printf (TOOLNAME " (GCC) %s\n\n", version_string);
|
||
printf ("Copyright %s 2004 Free Software Foundation, Inc.\n", _("(C)"));
|
||
printf (_("This is free software; see the source for copying conditions. There is NO\n"
|
||
"warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\n"));
|
||
exit (0);
|
||
}
|
||
|
||
int
|
||
main (int argc, char** argv)
|
||
{
|
||
JCF jcf;
|
||
int argi;
|
||
char *output_file = NULL;
|
||
int emit_dependencies = 0, suppress_output = 0;
|
||
int opt;
|
||
int local_found_error;
|
||
|
||
/* Unlock the stdio streams. */
|
||
unlock_std_streams ();
|
||
|
||
gcc_init_libintl ();
|
||
|
||
if (argc <= 1)
|
||
{
|
||
error ("no classes specified");
|
||
usage ();
|
||
}
|
||
|
||
jcf_path_init ();
|
||
|
||
/* We use getopt_long_only to allow single `-' long options. For
|
||
some of our options this is more natural. */
|
||
while ((opt = getopt_long_only (argc, argv, "J:I:d:o:v", options, NULL)) != -1)
|
||
{
|
||
switch (opt)
|
||
{
|
||
case 0:
|
||
/* Already handled. */
|
||
break;
|
||
|
||
case 'o':
|
||
output_file = optarg;
|
||
break;
|
||
|
||
case 'd':
|
||
output_directory = optarg;
|
||
break;
|
||
|
||
case 'I':
|
||
jcf_path_include_arg (optarg);
|
||
break;
|
||
|
||
case 'v':
|
||
verbose++;
|
||
break;
|
||
|
||
case OPT_classpath:
|
||
jcf_path_classpath_arg (optarg);
|
||
break;
|
||
|
||
case OPT_bootclasspath:
|
||
jcf_path_bootclasspath_arg (optarg);
|
||
break;
|
||
|
||
case OPT_extdirs:
|
||
jcf_path_extdirs_arg (optarg);
|
||
break;
|
||
|
||
case OPT_HELP:
|
||
help ();
|
||
break;
|
||
|
||
case OPT_TEMP:
|
||
temp_directory = optarg;
|
||
break;
|
||
|
||
case OPT_VERSION:
|
||
version ();
|
||
break;
|
||
|
||
case OPT_PREPEND:
|
||
if (prepend_count == 0)
|
||
prepend_specs = ALLOC (argc * sizeof (char*));
|
||
prepend_specs[prepend_count++] = optarg;
|
||
break;
|
||
|
||
case OPT_FRIEND:
|
||
if (friend_count == 0)
|
||
friend_specs = ALLOC (argc * sizeof (char*));
|
||
friend_specs[friend_count++] = optarg;
|
||
break;
|
||
|
||
case OPT_ADD:
|
||
if (add_count == 0)
|
||
add_specs = ALLOC (argc * sizeof (char*));
|
||
add_specs[add_count++] = optarg;
|
||
break;
|
||
|
||
case OPT_APPEND:
|
||
if (append_count == 0)
|
||
append_specs = ALLOC (argc * sizeof (char*));
|
||
append_specs[append_count++] = optarg;
|
||
break;
|
||
|
||
case OPT_M:
|
||
emit_dependencies = 1;
|
||
suppress_output = 1;
|
||
jcf_dependency_init (1);
|
||
break;
|
||
|
||
case OPT_MM:
|
||
emit_dependencies = 1;
|
||
suppress_output = 1;
|
||
jcf_dependency_init (0);
|
||
break;
|
||
|
||
case OPT_MG:
|
||
error ("'-MG' option is unimplemented");
|
||
exit (1);
|
||
|
||
case OPT_MD:
|
||
emit_dependencies = 1;
|
||
jcf_dependency_init (1);
|
||
break;
|
||
|
||
case OPT_MMD:
|
||
emit_dependencies = 1;
|
||
jcf_dependency_init (0);
|
||
break;
|
||
|
||
case OPT_FORCE:
|
||
break;
|
||
|
||
case OPT_OLD:
|
||
break;
|
||
|
||
case OPT_TRACE:
|
||
break;
|
||
|
||
case 'J':
|
||
/* Ignore -J options. */
|
||
break;
|
||
|
||
default:
|
||
usage ();
|
||
break;
|
||
}
|
||
}
|
||
|
||
if (optind == argc)
|
||
{
|
||
error ("no classes specified");
|
||
usage ();
|
||
}
|
||
|
||
jcf_path_seal (verbose);
|
||
|
||
if (output_file && emit_dependencies)
|
||
{
|
||
error ("can't specify both -o and -MD");
|
||
exit (1);
|
||
}
|
||
|
||
local_found_error = 0;
|
||
for (argi = optind; argi < argc; argi++)
|
||
{
|
||
char *classname = argv[argi];
|
||
char *current_output_file = NULL;
|
||
const char *classfile_name;
|
||
|
||
/* We reset the error state here so that we can detect errors
|
||
that occur when processing this file, so the output can be
|
||
unlinked if need be. */
|
||
found_error = 0;
|
||
|
||
if (verbose)
|
||
printf (_("Processing %s\n"), classname);
|
||
if (! output_file)
|
||
jcf_dependency_reset ();
|
||
classfile_name = find_class (classname, strlen (classname), &jcf, 0);
|
||
if (classfile_name == NULL)
|
||
{
|
||
error ("%s: no such class", classname);
|
||
exit (1);
|
||
}
|
||
if (verbose)
|
||
printf (_("Found in %s\n"), classfile_name);
|
||
if (output_file)
|
||
{
|
||
if (strcmp (output_file, "-") == 0)
|
||
out = stdout;
|
||
else if (out == NULL)
|
||
{
|
||
out = fopen (output_file, "w");
|
||
}
|
||
if (out == NULL)
|
||
{
|
||
perror (output_file);
|
||
exit (1);
|
||
}
|
||
current_output_file = output_file;
|
||
}
|
||
else
|
||
{
|
||
int dir_len = strlen (output_directory);
|
||
int i, classname_length = strlen (classname);
|
||
current_output_file = ALLOC (dir_len + classname_length + 5);
|
||
strcpy (current_output_file, output_directory);
|
||
if (dir_len > 0 && output_directory[dir_len-1] != '/')
|
||
current_output_file[dir_len++] = '/';
|
||
for (i = 0; classname[i] != '\0'; i++)
|
||
{
|
||
char ch = classname[i];
|
||
if (ch == '.')
|
||
ch = '/';
|
||
if (flag_jni && ch == '/')
|
||
ch = '_';
|
||
current_output_file[dir_len++] = ch;
|
||
}
|
||
if (emit_dependencies)
|
||
{
|
||
if (suppress_output)
|
||
{
|
||
jcf_dependency_set_dep_file ("-");
|
||
out = NULL;
|
||
}
|
||
else
|
||
{
|
||
/* We use `.hd' and not `.d' to avoid clashes with
|
||
dependency tracking from straight compilation. */
|
||
strcpy (current_output_file + dir_len, ".hd");
|
||
jcf_dependency_set_dep_file (current_output_file);
|
||
}
|
||
}
|
||
strcpy (current_output_file + dir_len,
|
||
stubs ? (flag_jni ? ".c" : ".cc") : ".h");
|
||
jcf_dependency_set_target (current_output_file);
|
||
if (! suppress_output)
|
||
{
|
||
out = fopen (current_output_file, "w");
|
||
if (out == NULL)
|
||
{
|
||
perror (current_output_file);
|
||
exit (1);
|
||
}
|
||
}
|
||
}
|
||
free_method_name_list ();
|
||
process_file (&jcf, out);
|
||
JCF_FINISH (&jcf);
|
||
|
||
/* If we found an error and we're writing to a real file,
|
||
delete it. */
|
||
if (found_error && ! suppress_output && current_output_file != NULL
|
||
&& strcmp (current_output_file, "-"))
|
||
unlink (current_output_file);
|
||
|
||
if (current_output_file != output_file)
|
||
free (current_output_file);
|
||
jcf_dependency_write ();
|
||
|
||
local_found_error |= found_error;
|
||
}
|
||
|
||
if (out != NULL && out != stdout)
|
||
fclose (out);
|
||
|
||
return local_found_error;
|
||
}
|