gcc/libjava/prims.cc
Keith Seitz e6789bef7a jvm.h (_Jv_JVMTI_Init): Declare.
* include/jvm.h (_Jv_JVMTI_Init): Declare.
        * jvmti.cc (_Jv_JVMTI_Init): New function.
        * prims.cc (_Jv_CreateJavaVM): Initialize JVMTI.

        * jvmti.cc (ILLEGAL_ARGUMENT): New macro.
        (_Jv_JVMTI_Allocate): Use ILLEGAL_ARUMENT.

        * jvmti.cc (_jvmtiEnvironments): New linked list of
        JVMTI environments.
        (FOREACH_ENVIRONMENT): New macro.
        (_envListLock): New object to act as synchronization lock
        for _jvmtiEnvironments.
        (_Jv_JVMTI_DisposeEnvironment): Check for NULL environment.
        Remove the environment from the list of known environments.
        (_Jv_GetJVMTIEnv): Add the new environment to the list
        of known environments.

From-SVN: r116635
2006-09-01 17:42:23 +00:00

1808 lines
45 KiB
C++
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// prims.cc - Code for core of runtime environment.
/* Copyright (C) 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006 Free Software Foundation
This file is part of libgcj.
This software is copyrighted work licensed under the terms of the
Libgcj License. Please consult the file "LIBGCJ_LICENSE" for
details. */
#include <config.h>
#include <platform.h>
#include <stdlib.h>
#include <stdarg.h>
#include <stdio.h>
#include <string.h>
#include <signal.h>
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
#include <gcj/cni.h>
#include <jvm.h>
#include <java-signal.h>
#include <java-threads.h>
#include <java-interp.h>
#ifdef ENABLE_JVMPI
#include <jvmpi.h>
#include <java/lang/ThreadGroup.h>
#endif
#ifndef DISABLE_GETENV_PROPERTIES
#include <ctype.h>
#include <java-props.h>
#define PROCESS_GCJ_PROPERTIES process_gcj_properties()
#else
#define PROCESS_GCJ_PROPERTIES
#endif // DISABLE_GETENV_PROPERTIES
#include <java/lang/Class.h>
#include <java/lang/ClassLoader.h>
#include <java/lang/Runtime.h>
#include <java/lang/String.h>
#include <java/lang/Thread.h>
#include <java/lang/ThreadGroup.h>
#include <java/lang/ArrayIndexOutOfBoundsException.h>
#include <java/lang/ArithmeticException.h>
#include <java/lang/ClassFormatError.h>
#include <java/lang/ClassNotFoundException.h>
#include <java/lang/InternalError.h>
#include <java/lang/NegativeArraySizeException.h>
#include <java/lang/NoClassDefFoundError.h>
#include <java/lang/NullPointerException.h>
#include <java/lang/OutOfMemoryError.h>
#include <java/lang/System.h>
#include <java/lang/VMClassLoader.h>
#include <java/lang/reflect/Modifier.h>
#include <java/io/PrintStream.h>
#include <java/lang/UnsatisfiedLinkError.h>
#include <java/lang/VirtualMachineError.h>
#include <gnu/gcj/runtime/ExtensionClassLoader.h>
#include <gnu/gcj/runtime/FinalizerThread.h>
#include <execution.h>
#include <gnu/classpath/jdwp/Jdwp.h>
#include <gnu/classpath/jdwp/VMVirtualMachine.h>
#include <gnu/classpath/jdwp/event/VmDeathEvent.h>
#include <gnu/classpath/jdwp/event/VmInitEvent.h>
#include <gnu/java/lang/MainThread.h>
#ifdef USE_LTDL
#include <ltdl.h>
#endif
// Execution engine for compiled code.
_Jv_CompiledEngine _Jv_soleCompiledEngine;
// Execution engine for code compiled with -findirect-classes
_Jv_IndirectCompiledEngine _Jv_soleIndirectCompiledEngine;
// We allocate a single OutOfMemoryError exception which we keep
// around for use if we run out of memory.
static java::lang::OutOfMemoryError *no_memory;
// Number of bytes in largest array object we create. This could be
// increased to the largest size_t value, so long as the appropriate
// functions are changed to take a size_t argument instead of jint.
#define MAX_OBJECT_SIZE ((1<<31) - 1)
// Properties set at compile time.
const char **_Jv_Compiler_Properties = NULL;
int _Jv_Properties_Count = 0;
#ifndef DISABLE_GETENV_PROPERTIES
// Property key/value pairs.
property_pair *_Jv_Environment_Properties;
#endif
// Stash the argv pointer to benefit native libraries that need it.
const char **_Jv_argv;
int _Jv_argc;
// Debugging options
static bool remoteDebug = false;
static char *jdwpOptions = "";
// Argument support.
int
_Jv_GetNbArgs (void)
{
// _Jv_argc is 0 if not explicitly initialized.
return _Jv_argc;
}
const char *
_Jv_GetSafeArg (int index)
{
if (index >=0 && index < _Jv_GetNbArgs ())
return _Jv_argv[index];
else
return "";
}
void
_Jv_SetArgs (int argc, const char **argv)
{
_Jv_argc = argc;
_Jv_argv = argv;
}
#ifdef ENABLE_JVMPI
// Pointer to JVMPI notification functions.
void (*_Jv_JVMPI_Notify_OBJECT_ALLOC) (JVMPI_Event *event);
void (*_Jv_JVMPI_Notify_THREAD_START) (JVMPI_Event *event);
void (*_Jv_JVMPI_Notify_THREAD_END) (JVMPI_Event *event);
#endif
#if defined (HANDLE_SEGV) || defined(HANDLE_FPE)
/* Unblock a signal. Unless we do this, the signal may only be sent
once. */
static void
unblock_signal (int signum __attribute__ ((__unused__)))
{
#ifdef _POSIX_VERSION
sigset_t sigs;
sigemptyset (&sigs);
sigaddset (&sigs, signum);
sigprocmask (SIG_UNBLOCK, &sigs, NULL);
#endif
}
#endif
#ifdef HANDLE_SEGV
SIGNAL_HANDLER (catch_segv)
{
unblock_signal (SIGSEGV);
MAKE_THROW_FRAME (nullp);
java::lang::NullPointerException *nullp
= new java::lang::NullPointerException;
throw nullp;
}
#endif
#ifdef HANDLE_FPE
SIGNAL_HANDLER (catch_fpe)
{
unblock_signal (SIGFPE);
#ifdef HANDLE_DIVIDE_OVERFLOW
HANDLE_DIVIDE_OVERFLOW;
#else
MAKE_THROW_FRAME (arithexception);
#endif
java::lang::ArithmeticException *arithexception
= new java::lang::ArithmeticException (JvNewStringLatin1 ("/ by zero"));
throw arithexception;
}
#endif
jboolean
_Jv_equalUtf8Consts (const Utf8Const* a, const Utf8Const *b)
{
int len;
const _Jv_ushort *aptr, *bptr;
if (a == b)
return true;
if (a->hash != b->hash)
return false;
len = a->length;
if (b->length != len)
return false;
aptr = (const _Jv_ushort *)a->data;
bptr = (const _Jv_ushort *)b->data;
len = (len + 1) >> 1;
while (--len >= 0)
if (*aptr++ != *bptr++)
return false;
return true;
}
/* True iff A is equal to STR.
HASH is STR->hashCode().
*/
jboolean
_Jv_equal (Utf8Const* a, jstring str, jint hash)
{
if (a->hash != (_Jv_ushort) hash)
return false;
jint len = str->length();
jint i = 0;
jchar *sptr = _Jv_GetStringChars (str);
unsigned char* ptr = (unsigned char*) a->data;
unsigned char* limit = ptr + a->length;
for (;; i++, sptr++)
{
int ch = UTF8_GET (ptr, limit);
if (i == len)
return ch < 0;
if (ch != *sptr)
return false;
}
return true;
}
/* Like _Jv_equal, but stop after N characters. */
jboolean
_Jv_equaln (Utf8Const *a, jstring str, jint n)
{
jint len = str->length();
jint i = 0;
jchar *sptr = _Jv_GetStringChars (str);
unsigned char* ptr = (unsigned char*) a->data;
unsigned char* limit = ptr + a->length;
for (; n-- > 0; i++, sptr++)
{
int ch = UTF8_GET (ptr, limit);
if (i == len)
return ch < 0;
if (ch != *sptr)
return false;
}
return true;
}
// Determines whether the given Utf8Const object contains
// a type which is primitive or some derived form of it, eg.
// an array or multi-dimensional array variant.
jboolean
_Jv_isPrimitiveOrDerived(const Utf8Const *a)
{
unsigned char *aptr = (unsigned char *) a->data;
unsigned char *alimit = aptr + a->length;
int ac = UTF8_GET(aptr, alimit);
// Skips any leading array marks.
while (ac == '[')
ac = UTF8_GET(aptr, alimit);
// There should not be another character. This implies that
// the type name is only one character long.
if (UTF8_GET(aptr, alimit) == -1)
switch ( ac )
{
case 'Z':
case 'B':
case 'C':
case 'S':
case 'I':
case 'J':
case 'F':
case 'D':
return true;
default:
break;
}
return false;
}
// Find out whether two _Jv_Utf8Const candidates contain the same
// classname.
// The method is written to handle the different formats of classnames.
// Eg. "Ljava/lang/Class;", "Ljava.lang.Class;", "java/lang/Class" and
// "java.lang.Class" will be seen as equal.
// Warning: This function is not smart enough to declare "Z" and "boolean"
// and similar cases as equal (and is not meant to be used this way)!
jboolean
_Jv_equalUtf8Classnames (const Utf8Const *a, const Utf8Const *b)
{
// If the class name's length differs by two characters
// it is possible that we have candidates which are given
// in the two different formats ("Lp1/p2/cn;" vs. "p1/p2/cn")
switch (a->length - b->length)
{
case -2:
case 0:
case 2:
break;
default:
return false;
}
unsigned char *aptr = (unsigned char *) a->data;
unsigned char *alimit = aptr + a->length;
unsigned char *bptr = (unsigned char *) b->data;
unsigned char *blimit = bptr + b->length;
if (alimit[-1] == ';')
alimit--;
if (blimit[-1] == ';')
blimit--;
int ac = UTF8_GET(aptr, alimit);
int bc = UTF8_GET(bptr, blimit);
// Checks whether both strings have the same amount of leading [ characters.
while (ac == '[')
{
if (bc == '[')
{
ac = UTF8_GET(aptr, alimit);
bc = UTF8_GET(bptr, blimit);
continue;
}
return false;
}
// Skips leading L character.
if (ac == 'L')
ac = UTF8_GET(aptr, alimit);
if (bc == 'L')
bc = UTF8_GET(bptr, blimit);
// Compares the remaining characters.
while (ac != -1 && bc != -1)
{
// Replaces package separating dots with slashes.
if (ac == '.')
ac = '/';
if (bc == '.')
bc = '/';
// Now classnames differ if there is at least one non-matching
// character.
if (ac != bc)
return false;
ac = UTF8_GET(aptr, alimit);
bc = UTF8_GET(bptr, blimit);
}
return (ac == bc);
}
/* Count the number of Unicode chars encoded in a given Ut8 string. */
int
_Jv_strLengthUtf8(const char* str, int len)
{
unsigned char* ptr;
unsigned char* limit;
int str_length;
ptr = (unsigned char*) str;
limit = ptr + len;
str_length = 0;
for (; ptr < limit; str_length++)
{
if (UTF8_GET (ptr, limit) < 0)
return (-1);
}
return (str_length);
}
/* Calculate a hash value for a string encoded in Utf8 format.
* This returns the same hash value as specified or java.lang.String.hashCode.
*/
jint
_Jv_hashUtf8String (const char* str, int len)
{
unsigned char* ptr = (unsigned char*) str;
unsigned char* limit = ptr + len;
jint hash = 0;
for (; ptr < limit;)
{
int ch = UTF8_GET (ptr, limit);
/* Updated specification from
http://www.javasoft.com/docs/books/jls/clarify.html. */
hash = (31 * hash) + ch;
}
return hash;
}
void
_Jv_Utf8Const::init(const char *s, int len)
{
::memcpy (data, s, len);
data[len] = 0;
length = len;
hash = _Jv_hashUtf8String (s, len) & 0xFFFF;
}
_Jv_Utf8Const *
_Jv_makeUtf8Const (const char* s, int len)
{
if (len < 0)
len = strlen (s);
Utf8Const* m
= (Utf8Const*) _Jv_AllocBytes (_Jv_Utf8Const::space_needed(s, len));
m->init(s, len);
return m;
}
_Jv_Utf8Const *
_Jv_makeUtf8Const (jstring string)
{
jint hash = string->hashCode ();
jint len = _Jv_GetStringUTFLength (string);
Utf8Const* m = (Utf8Const*)
_Jv_AllocBytes (sizeof(Utf8Const) + len + 1);
m->hash = hash;
m->length = len;
_Jv_GetStringUTFRegion (string, 0, string->length (), m->data);
m->data[len] = 0;
return m;
}
#ifdef DEBUG
void
_Jv_Abort (const char *function, const char *file, int line,
const char *message)
#else
void
_Jv_Abort (const char *, const char *, int, const char *message)
#endif
{
#ifdef DEBUG
fprintf (stderr,
"libgcj failure: %s\n in function %s, file %s, line %d\n",
message, function, file, line);
#else
fprintf (stderr, "libgcj failure: %s\n", message);
#endif
abort ();
}
static void
fail_on_finalization (jobject)
{
JvFail ("object was finalized");
}
void
_Jv_GCWatch (jobject obj)
{
_Jv_RegisterFinalizer (obj, fail_on_finalization);
}
void
_Jv_ThrowBadArrayIndex(jint bad_index)
{
throw new java::lang::ArrayIndexOutOfBoundsException
(java::lang::String::valueOf (bad_index));
}
void
_Jv_ThrowNullPointerException ()
{
throw new java::lang::NullPointerException;
}
// Resolve an entry in the constant pool and return the target
// address.
void *
_Jv_ResolvePoolEntry (jclass this_class, jint index)
{
_Jv_Constants *pool = &this_class->constants;
if ((pool->tags[index] & JV_CONSTANT_ResolvedFlag) != 0)
return pool->data[index].field->u.addr;
JvSynchronize sync (this_class);
return (_Jv_Linker::resolve_pool_entry (this_class, index))
.field->u.addr;
}
// Explicitly throw a no memory exception.
// The collector calls this when it encounters an out-of-memory condition.
void _Jv_ThrowNoMemory()
{
throw no_memory;
}
#ifdef ENABLE_JVMPI
# define JVMPI_NOTIFY_ALLOC(klass,size,obj) \
if (__builtin_expect (_Jv_JVMPI_Notify_OBJECT_ALLOC != 0, false)) \
jvmpi_notify_alloc(klass,size,obj);
static void
jvmpi_notify_alloc(jclass klass, jint size, jobject obj)
{
// Service JVMPI allocation request.
JVMPI_Event event;
event.event_type = JVMPI_EVENT_OBJECT_ALLOC;
event.env_id = NULL;
event.u.obj_alloc.arena_id = 0;
event.u.obj_alloc.class_id = (jobjectID) klass;
event.u.obj_alloc.is_array = 0;
event.u.obj_alloc.size = size;
event.u.obj_alloc.obj_id = (jobjectID) obj;
// FIXME: This doesn't look right for the Boehm GC. A GC may
// already be in progress. _Jv_DisableGC () doesn't wait for it.
// More importantly, I don't see the need for disabling GC, since we
// blatantly have a pointer to obj on our stack, ensuring that the
// object can't be collected. Even for a nonconservative collector,
// it appears to me that this must be true, since we are about to
// return obj. Isn't this whole approach way too intrusive for
// a useful profiling interface? - HB
_Jv_DisableGC ();
(*_Jv_JVMPI_Notify_OBJECT_ALLOC) (&event);
_Jv_EnableGC ();
}
#else /* !ENABLE_JVMPI */
# define JVMPI_NOTIFY_ALLOC(klass,size,obj) /* do nothing */
#endif
// Allocate a new object of class KLASS.
// First a version that assumes that we have no finalizer, and that
// the class is already initialized.
// If we know that JVMPI is disabled, this can be replaced by a direct call
// to the allocator for the appropriate GC.
jobject
_Jv_AllocObjectNoInitNoFinalizer (jclass klass)
{
jint size = klass->size ();
jobject obj = (jobject) _Jv_AllocObj (size, klass);
JVMPI_NOTIFY_ALLOC (klass, size, obj);
return obj;
}
// And now a version that initializes if necessary.
jobject
_Jv_AllocObjectNoFinalizer (jclass klass)
{
if (_Jv_IsPhantomClass(klass) )
throw new java::lang::NoClassDefFoundError(klass->getName());
_Jv_InitClass (klass);
jint size = klass->size ();
jobject obj = (jobject) _Jv_AllocObj (size, klass);
JVMPI_NOTIFY_ALLOC (klass, size, obj);
return obj;
}
// And now the general version that registers a finalizer if necessary.
jobject
_Jv_AllocObject (jclass klass)
{
jobject obj = _Jv_AllocObjectNoFinalizer (klass);
// We assume that the compiler only generates calls to this routine
// if there really is an interesting finalizer.
// Unfortunately, we still have to the dynamic test, since there may
// be cni calls to this routine.
// Note that on IA64 get_finalizer() returns the starting address of the
// function, not a function pointer. Thus this still works.
if (klass->vtable->get_finalizer ()
!= java::lang::Object::class$.vtable->get_finalizer ())
_Jv_RegisterFinalizer (obj, _Jv_FinalizeObject);
return obj;
}
// Allocate a String, including variable length storage.
jstring
_Jv_AllocString(jsize len)
{
using namespace java::lang;
jsize sz = sizeof(java::lang::String) + len * sizeof(jchar);
// We assert that for strings allocated this way, the data field
// will always point to the object itself. Thus there is no reason
// for the garbage collector to scan any of it.
// Furthermore, we're about to overwrite the string data, so
// initialization of the object is not an issue.
// String needs no initialization, and there is no finalizer, so
// we can go directly to the collector's allocator interface.
jstring obj = (jstring) _Jv_AllocPtrFreeObj(sz, &String::class$);
obj->data = obj;
obj->boffset = sizeof(java::lang::String);
obj->count = len;
obj->cachedHashCode = 0;
JVMPI_NOTIFY_ALLOC (&String::class$, sz, obj);
return obj;
}
// A version of the above that assumes the object contains no pointers,
// and requires no finalization. This can't happen if we need pointers
// to locks.
#ifdef JV_HASH_SYNCHRONIZATION
jobject
_Jv_AllocPtrFreeObject (jclass klass)
{
_Jv_InitClass (klass);
jint size = klass->size ();
jobject obj = (jobject) _Jv_AllocPtrFreeObj (size, klass);
JVMPI_NOTIFY_ALLOC (klass, size, obj);
return obj;
}
#endif /* JV_HASH_SYNCHRONIZATION */
// Allocate a new array of Java objects. Each object is of type
// `elementClass'. `init' is used to initialize each slot in the
// array.
jobjectArray
_Jv_NewObjectArray (jsize count, jclass elementClass, jobject init)
{
// Creating an array of an unresolved type is impossible. So we throw
// the NoClassDefFoundError.
if ( _Jv_IsPhantomClass(elementClass) )
throw new java::lang::NoClassDefFoundError(elementClass->getName());
if (__builtin_expect (count < 0, false))
throw new java::lang::NegativeArraySizeException;
JvAssert (! elementClass->isPrimitive ());
// Ensure that elements pointer is properly aligned.
jobjectArray obj = NULL;
size_t size = (size_t) elements (obj);
// Check for overflow.
if (__builtin_expect ((size_t) count >
(MAX_OBJECT_SIZE - 1 - size) / sizeof (jobject), false))
throw no_memory;
size += count * sizeof (jobject);
jclass klass = _Jv_GetArrayClass (elementClass,
elementClass->getClassLoaderInternal());
obj = (jobjectArray) _Jv_AllocArray (size, klass);
// Cast away const.
jsize *lp = const_cast<jsize *> (&obj->length);
*lp = count;
// We know the allocator returns zeroed memory. So don't bother
// zeroing it again.
if (init)
{
jobject *ptr = elements(obj);
while (--count >= 0)
*ptr++ = init;
}
return obj;
}
// Allocate a new array of primitives. ELTYPE is the type of the
// element, COUNT is the size of the array.
jobject
_Jv_NewPrimArray (jclass eltype, jint count)
{
int elsize = eltype->size();
if (__builtin_expect (count < 0, false))
throw new java::lang::NegativeArraySizeException;
JvAssert (eltype->isPrimitive ());
jobject dummy = NULL;
size_t size = (size_t) _Jv_GetArrayElementFromElementType (dummy, eltype);
// Check for overflow.
if (__builtin_expect ((size_t) count >
(MAX_OBJECT_SIZE - size) / elsize, false))
throw no_memory;
jclass klass = _Jv_GetArrayClass (eltype, 0);
# ifdef JV_HASH_SYNCHRONIZATION
// Since the vtable is always statically allocated,
// these are completely pointerfree! Make sure the GC doesn't touch them.
__JArray *arr =
(__JArray*) _Jv_AllocPtrFreeObj (size + elsize * count, klass);
memset((char *)arr + size, 0, elsize * count);
# else
__JArray *arr = (__JArray*) _Jv_AllocObj (size + elsize * count, klass);
// Note that we assume we are given zeroed memory by the allocator.
# endif
// Cast away const.
jsize *lp = const_cast<jsize *> (&arr->length);
*lp = count;
return arr;
}
jobject
_Jv_NewArray (jint type, jint size)
{
switch (type)
{
case 4: return JvNewBooleanArray (size);
case 5: return JvNewCharArray (size);
case 6: return JvNewFloatArray (size);
case 7: return JvNewDoubleArray (size);
case 8: return JvNewByteArray (size);
case 9: return JvNewShortArray (size);
case 10: return JvNewIntArray (size);
case 11: return JvNewLongArray (size);
}
throw new java::lang::InternalError
(JvNewStringLatin1 ("invalid type code in _Jv_NewArray"));
}
// Allocate a possibly multi-dimensional array but don't check that
// any array length is <0.
static jobject
_Jv_NewMultiArrayUnchecked (jclass type, jint dimensions, jint *sizes)
{
JvAssert (type->isArray());
jclass element_type = type->getComponentType();
jobject result;
if (element_type->isPrimitive())
result = _Jv_NewPrimArray (element_type, sizes[0]);
else
result = _Jv_NewObjectArray (sizes[0], element_type, NULL);
if (dimensions > 1)
{
JvAssert (! element_type->isPrimitive());
JvAssert (element_type->isArray());
jobject *contents = elements ((jobjectArray) result);
for (int i = 0; i < sizes[0]; ++i)
contents[i] = _Jv_NewMultiArrayUnchecked (element_type, dimensions - 1,
sizes + 1);
}
return result;
}
jobject
_Jv_NewMultiArray (jclass type, jint dimensions, jint *sizes)
{
for (int i = 0; i < dimensions; ++i)
if (sizes[i] < 0)
throw new java::lang::NegativeArraySizeException;
return _Jv_NewMultiArrayUnchecked (type, dimensions, sizes);
}
jobject
_Jv_NewMultiArray (jclass array_type, jint dimensions, ...)
{
// Creating an array of an unresolved type is impossible. So we throw
// the NoClassDefFoundError.
if (_Jv_IsPhantomClass(array_type))
throw new java::lang::NoClassDefFoundError(array_type->getName());
va_list args;
jint sizes[dimensions];
va_start (args, dimensions);
for (int i = 0; i < dimensions; ++i)
{
jint size = va_arg (args, jint);
if (size < 0)
throw new java::lang::NegativeArraySizeException;
sizes[i] = size;
}
va_end (args);
return _Jv_NewMultiArrayUnchecked (array_type, dimensions, sizes);
}
// Ensure 8-byte alignment, for hash synchronization.
#define DECLARE_PRIM_TYPE(NAME) \
java::lang::Class _Jv_##NAME##Class __attribute__ ((aligned (8)));
DECLARE_PRIM_TYPE(byte)
DECLARE_PRIM_TYPE(short)
DECLARE_PRIM_TYPE(int)
DECLARE_PRIM_TYPE(long)
DECLARE_PRIM_TYPE(boolean)
DECLARE_PRIM_TYPE(char)
DECLARE_PRIM_TYPE(float)
DECLARE_PRIM_TYPE(double)
DECLARE_PRIM_TYPE(void)
void
_Jv_InitPrimClass (jclass cl, const char *cname, char sig, int len)
{
using namespace java::lang::reflect;
// We must set the vtable for the class; the Java constructor
// doesn't do this.
(*(_Jv_VTable **) cl) = java::lang::Class::class$.vtable;
// Initialize the fields we care about. We do this in the same
// order they are declared in Class.h.
cl->name = _Jv_makeUtf8Const ((char *) cname, -1);
cl->accflags = Modifier::PUBLIC | Modifier::FINAL | Modifier::ABSTRACT;
cl->method_count = sig;
cl->size_in_bytes = len;
cl->vtable = JV_PRIMITIVE_VTABLE;
cl->state = JV_STATE_DONE;
cl->depth = -1;
}
jclass
_Jv_FindClassFromSignature (char *sig, java::lang::ClassLoader *loader,
char **endp)
{
// First count arrays.
int array_count = 0;
while (*sig == '[')
{
++sig;
++array_count;
}
jclass result = NULL;
switch (*sig)
{
case 'B':
result = JvPrimClass (byte);
break;
case 'S':
result = JvPrimClass (short);
break;
case 'I':
result = JvPrimClass (int);
break;
case 'J':
result = JvPrimClass (long);
break;
case 'Z':
result = JvPrimClass (boolean);
break;
case 'C':
result = JvPrimClass (char);
break;
case 'F':
result = JvPrimClass (float);
break;
case 'D':
result = JvPrimClass (double);
break;
case 'V':
result = JvPrimClass (void);
break;
case 'L':
{
char *save = ++sig;
while (*sig && *sig != ';')
++sig;
// Do nothing if signature appears to be malformed.
if (*sig == ';')
{
_Jv_Utf8Const *name = _Jv_makeUtf8Const (save, sig - save);
result = _Jv_FindClass (name, loader);
}
break;
}
default:
// Do nothing -- bad signature.
break;
}
if (endp)
{
// Not really the "end", but the last valid character that we
// looked at.
*endp = sig;
}
if (! result)
return NULL;
// Find arrays.
while (array_count-- > 0)
result = _Jv_GetArrayClass (result, loader);
return result;
}
jclass
_Jv_FindClassFromSignatureNoException (char *sig, java::lang::ClassLoader *loader,
char **endp)
{
jclass klass;
try
{
klass = _Jv_FindClassFromSignature(sig, loader, endp);
}
catch (java::lang::NoClassDefFoundError *ncdfe)
{
return NULL;
}
catch (java::lang::ClassNotFoundException *cnfe)
{
return NULL;
}
return klass;
}
JArray<jstring> *
JvConvertArgv (int argc, const char **argv)
{
if (argc < 0)
argc = 0;
jobjectArray ar = JvNewObjectArray(argc, &java::lang::String::class$, NULL);
jobject *ptr = elements(ar);
jbyteArray bytes = NULL;
for (int i = 0; i < argc; i++)
{
const char *arg = argv[i];
int len = strlen (arg);
if (bytes == NULL || bytes->length < len)
bytes = JvNewByteArray (len);
jbyte *bytePtr = elements (bytes);
// We assume jbyte == char.
memcpy (bytePtr, arg, len);
// Now convert using the default encoding.
*ptr++ = new java::lang::String (bytes, 0, len);
}
return (JArray<jstring>*) ar;
}
// FIXME: These variables are static so that they will be
// automatically scanned by the Boehm collector. This is needed
// because with qthreads the collector won't scan the initial stack --
// it will only scan the qthreads stacks.
// Command line arguments.
static JArray<jstring> *arg_vec;
// The primary thread.
static java::lang::Thread *main_thread;
#ifndef DISABLE_GETENV_PROPERTIES
static char *
next_property_key (char *s, size_t *length)
{
size_t l = 0;
JvAssert (s);
// Skip over whitespace
while (isspace (*s))
s++;
// If we've reached the end, return NULL. Also return NULL if for
// some reason we've come across a malformed property string.
if (*s == 0
|| *s == ':'
|| *s == '=')
return NULL;
// Determine the length of the property key.
while (s[l] != 0
&& ! isspace (s[l])
&& s[l] != ':'
&& s[l] != '=')
{
if (s[l] == '\\'
&& s[l+1] != 0)
l++;
l++;
}
*length = l;
return s;
}
static char *
next_property_value (char *s, size_t *length)
{
size_t l = 0;
JvAssert (s);
while (isspace (*s))
s++;
if (*s == ':'
|| *s == '=')
s++;
while (isspace (*s))
s++;
// Determine the length of the property value.
while (s[l] != 0
&& ! isspace (s[l])
&& s[l] != ':'
&& s[l] != '=')
{
if (s[l] == '\\'
&& s[l+1] != 0)
l += 2;
else
l++;
}
*length = l;
return s;
}
static void
process_gcj_properties ()
{
char *props = getenv("GCJ_PROPERTIES");
if (NULL == props)
return;
// Later on we will write \0s into this string. It is simplest to
// just duplicate it here.
props = strdup (props);
char *p = props;
size_t length;
size_t property_count = 0;
// Whip through props quickly in order to count the number of
// property values.
while (p && (p = next_property_key (p, &length)))
{
// Skip to the end of the key
p += length;
p = next_property_value (p, &length);
if (p)
p += length;
property_count++;
}
// Allocate an array of property value/key pairs.
_Jv_Environment_Properties =
(property_pair *) malloc (sizeof(property_pair)
* (property_count + 1));
// Go through the properties again, initializing _Jv_Properties
// along the way.
p = props;
property_count = 0;
while (p && (p = next_property_key (p, &length)))
{
_Jv_Environment_Properties[property_count].key = p;
_Jv_Environment_Properties[property_count].key_length = length;
// Skip to the end of the key
p += length;
p = next_property_value (p, &length);
_Jv_Environment_Properties[property_count].value = p;
_Jv_Environment_Properties[property_count].value_length = length;
if (p)
p += length;
property_count++;
}
memset ((void *) &_Jv_Environment_Properties[property_count],
0, sizeof (property_pair));
// Null terminate the strings.
for (property_pair *prop = &_Jv_Environment_Properties[0];
prop->key != NULL;
prop++)
{
prop->key[prop->key_length] = 0;
prop->value[prop->value_length] = 0;
}
}
#endif // DISABLE_GETENV_PROPERTIES
namespace gcj
{
_Jv_Utf8Const *void_signature;
_Jv_Utf8Const *clinit_name;
_Jv_Utf8Const *init_name;
_Jv_Utf8Const *finit_name;
bool runtimeInitialized = false;
// When true, print debugging information about class loading.
bool verbose_class_flag;
// When true, enable the bytecode verifier and BC-ABI type verification.
bool verifyClasses = true;
// Thread stack size specified by the -Xss runtime argument.
size_t stack_size = 0;
}
// We accept all non-standard options accepted by Sun's java command,
// for compatibility with existing application launch scripts.
static jint
parse_x_arg (char* option_string)
{
if (strlen (option_string) <= 0)
return -1;
if (! strcmp (option_string, "int"))
{
// FIXME: this should cause the vm to never load shared objects
}
else if (! strcmp (option_string, "mixed"))
{
// FIXME: allow interpreted and native code
}
else if (! strcmp (option_string, "batch"))
{
// FIXME: disable background JIT'ing
}
else if (! strcmp (option_string, "debug"))
{
remoteDebug = true;
}
else if (! strncmp (option_string, "runjdwp:", 8))
{
if (strlen (option_string) > 8)
jdwpOptions = &option_string[8];
else
{
fprintf (stderr,
"libgcj: argument required for JDWP options");
return -1;
}
}
else if (! strncmp (option_string, "bootclasspath:", 14))
{
// FIXME: add a parse_bootclasspath_arg function
}
else if (! strncmp (option_string, "bootclasspath/a:", 16))
{
}
else if (! strncmp (option_string, "bootclasspath/p:", 16))
{
}
else if (! strcmp (option_string, "check:jni"))
{
// FIXME: enable strict JNI checking
}
else if (! strcmp (option_string, "future"))
{
// FIXME: enable strict class file format checks
}
else if (! strcmp (option_string, "noclassgc"))
{
// FIXME: disable garbage collection for classes
}
else if (! strcmp (option_string, "incgc"))
{
// FIXME: incremental garbage collection
}
else if (! strncmp (option_string, "loggc:", 6))
{
if (option_string[6] == '\0')
{
fprintf (stderr,
"libgcj: filename argument expected for loggc option\n");
return -1;
}
// FIXME: set gc logging filename
}
else if (! strncmp (option_string, "ms", 2))
{
// FIXME: ignore this option until PR 20699 is fixed.
// _Jv_SetInitialHeapSize (option_string + 2);
}
else if (! strncmp (option_string, "mx", 2))
_Jv_SetMaximumHeapSize (option_string + 2);
else if (! strcmp (option_string, "prof"))
{
// FIXME: enable profiling of program running in vm
}
else if (! strncmp (option_string, "runhprof:", 9))
{
// FIXME: enable specific type of vm profiling. add a
// parse_runhprof_arg function
}
else if (! strcmp (option_string, "rs"))
{
// FIXME: reduced system signal usage. disable thread dumps,
// only terminate in response to user-initiated calls,
// e.g. System.exit()
}
else if (! strncmp (option_string, "ss", 2))
{
_Jv_SetStackSize (option_string + 2);
}
else if (! strcmp (option_string, "X:+UseAltSigs"))
{
// FIXME: use signals other than SIGUSR1 and SIGUSR2
}
else if (! strcmp (option_string, "share:off"))
{
// FIXME: don't share class data
}
else if (! strcmp (option_string, "share:auto"))
{
// FIXME: share class data where possible
}
else if (! strcmp (option_string, "share:on"))
{
// FIXME: fail if impossible to share class data
}
return 0;
}
static jint
parse_verbose_args (char* option_string,
bool ignore_unrecognized)
{
size_t len = sizeof ("-verbose") - 1;
if (strlen (option_string) < len)
return -1;
if (option_string[len] == ':'
&& option_string[len + 1] != '\0')
{
char* verbose_args = option_string + len + 1;
do
{
if (! strncmp (verbose_args,
"gc", sizeof ("gc") - 1))
{
if (verbose_args[sizeof ("gc") - 1] == '\0'
|| verbose_args[sizeof ("gc") - 1] == ',')
{
// FIXME: we should add functions to boehm-gc that
// toggle GC_print_stats, GC_PRINT_ADDRESS_MAP and
// GC_print_back_height.
verbose_args += sizeof ("gc") - 1;
}
else
{
verbose_arg_err:
fprintf (stderr, "libgcj: unknown verbose option: %s\n",
option_string);
return -1;
}
}
else if (! strncmp (verbose_args,
"class",
sizeof ("class") - 1))
{
if (verbose_args[sizeof ("class") - 1] == '\0'
|| verbose_args[sizeof ("class") - 1] == ',')
{
gcj::verbose_class_flag = true;
verbose_args += sizeof ("class") - 1;
}
else
goto verbose_arg_err;
}
else if (! strncmp (verbose_args, "jni",
sizeof ("jni") - 1))
{
if (verbose_args[sizeof ("jni") - 1] == '\0'
|| verbose_args[sizeof ("jni") - 1] == ',')
{
// FIXME: enable JNI messages.
verbose_args += sizeof ("jni") - 1;
}
else
goto verbose_arg_err;
}
else if (ignore_unrecognized
&& verbose_args[0] == 'X')
{
// ignore unrecognized non-standard verbose option
while (verbose_args[0] != '\0'
&& verbose_args[0] != ',')
verbose_args++;
}
else if (verbose_args[0] == ',')
{
verbose_args++;
}
else
goto verbose_arg_err;
if (verbose_args[0] == ',')
verbose_args++;
}
while (verbose_args[0] != '\0');
}
else if (option_string[len] == 'g'
&& option_string[len + 1] == 'c'
&& option_string[len + 2] == '\0')
{
// FIXME: we should add functions to boehm-gc that
// toggle GC_print_stats, GC_PRINT_ADDRESS_MAP and
// GC_print_back_height.
return 0;
}
else if (option_string[len] == '\0')
{
gcj::verbose_class_flag = true;
return 0;
}
else
{
// unrecognized option beginning with -verbose
return -1;
}
return 0;
}
static jint
parse_init_args (JvVMInitArgs* vm_args)
{
// if _Jv_Compiler_Properties is non-NULL then it needs to be
// re-allocated dynamically.
if (_Jv_Compiler_Properties)
{
const char** props = _Jv_Compiler_Properties;
_Jv_Compiler_Properties = NULL;
for (int i = 0; props[i]; i++)
{
_Jv_Compiler_Properties = (const char**) _Jv_Realloc
(_Jv_Compiler_Properties,
(_Jv_Properties_Count + 1) * sizeof (const char*));
_Jv_Compiler_Properties[_Jv_Properties_Count++] = props[i];
}
}
if (vm_args == NULL)
return 0;
for (int i = 0; i < vm_args->nOptions; ++i)
{
char* option_string = vm_args->options[i].optionString;
if (! strcmp (option_string, "vfprintf")
|| ! strcmp (option_string, "exit")
|| ! strcmp (option_string, "abort"))
{
// FIXME: we are required to recognize these, but for
// now we don't handle them in any way.
continue;
}
else if (! strncmp (option_string,
"-verbose", sizeof ("-verbose") - 1))
{
jint result = parse_verbose_args (option_string,
vm_args->ignoreUnrecognized);
if (result < 0)
return result;
}
else if (! strncmp (option_string, "-D", 2))
{
_Jv_Compiler_Properties = (const char**) _Jv_Realloc
(_Jv_Compiler_Properties,
(_Jv_Properties_Count + 1) * sizeof (char*));
_Jv_Compiler_Properties[_Jv_Properties_Count++] =
strdup (option_string + 2);
continue;
}
else if (vm_args->ignoreUnrecognized)
{
if (option_string[0] == '_')
parse_x_arg (option_string + 1);
else if (! strncmp (option_string, "-X", 2))
parse_x_arg (option_string + 2);
else
{
unknown_option:
fprintf (stderr, "libgcj: unknown option: %s\n", option_string);
return -1;
}
}
else
goto unknown_option;
}
return 0;
}
jint
_Jv_CreateJavaVM (JvVMInitArgs* vm_args)
{
using namespace gcj;
if (runtimeInitialized)
return -1;
jint result = parse_init_args (vm_args);
if (result < 0)
return -1;
PROCESS_GCJ_PROPERTIES;
/* Threads must be initialized before the GC, so that it inherits the
signal mask. */
_Jv_InitThreads ();
_Jv_InitGC ();
_Jv_InitializeSyncMutex ();
#ifdef INTERPRETER
_Jv_InitInterpreter ();
#endif
#ifdef HANDLE_SEGV
INIT_SEGV;
#endif
#ifdef HANDLE_FPE
INIT_FPE;
#endif
/* Initialize Utf8 constants declared in jvm.h. */
void_signature = _Jv_makeUtf8Const ("()V", 3);
clinit_name = _Jv_makeUtf8Const ("<clinit>", 8);
init_name = _Jv_makeUtf8Const ("<init>", 6);
finit_name = _Jv_makeUtf8Const ("finit$", 6);
/* Initialize built-in classes to represent primitive TYPEs. */
_Jv_InitPrimClass (&_Jv_byteClass, "byte", 'B', 1);
_Jv_InitPrimClass (&_Jv_shortClass, "short", 'S', 2);
_Jv_InitPrimClass (&_Jv_intClass, "int", 'I', 4);
_Jv_InitPrimClass (&_Jv_longClass, "long", 'J', 8);
_Jv_InitPrimClass (&_Jv_booleanClass, "boolean", 'Z', 1);
_Jv_InitPrimClass (&_Jv_charClass, "char", 'C', 2);
_Jv_InitPrimClass (&_Jv_floatClass, "float", 'F', 4);
_Jv_InitPrimClass (&_Jv_doubleClass, "double", 'D', 8);
_Jv_InitPrimClass (&_Jv_voidClass, "void", 'V', 0);
// We have to initialize this fairly early, to avoid circular class
// initialization. In particular we want to start the
// initialization of ClassLoader before we start the initialization
// of VMClassLoader.
_Jv_InitClass (&java::lang::ClassLoader::class$);
// Set up the system class loader and the bootstrap class loader.
gnu::gcj::runtime::ExtensionClassLoader::initialize();
java::lang::VMClassLoader::initialize(JvNewStringLatin1(TOOLEXECLIBDIR));
_Jv_RegisterBootstrapPackages();
no_memory = new java::lang::OutOfMemoryError;
#ifdef USE_LTDL
LTDL_SET_PRELOADED_SYMBOLS ();
#endif
_Jv_platform_initialize ();
_Jv_JNI_Init ();
_Jv_JVMTI_Init ();
_Jv_GCInitializeFinalizers (&::gnu::gcj::runtime::FinalizerThread::finalizerReady);
// Start the GC finalizer thread. A VirtualMachineError can be
// thrown by the runtime if, say, threads aren't available.
try
{
using namespace gnu::gcj::runtime;
FinalizerThread *ft = new FinalizerThread ();
ft->start ();
}
catch (java::lang::VirtualMachineError *ignore)
{
}
runtimeInitialized = true;
return 0;
}
void
_Jv_RunMain (JvVMInitArgs *vm_args, jclass klass, const char *name, int argc,
const char **argv, bool is_jar)
{
#ifndef DISABLE_MAIN_ARGS
_Jv_SetArgs (argc, argv);
#endif
java::lang::Runtime *runtime = NULL;
try
{
if (_Jv_CreateJavaVM (vm_args) < 0)
{
fprintf (stderr, "libgcj: couldn't create virtual machine\n");
exit (1);
}
// Get the Runtime here. We want to initialize it before searching
// for `main'; that way it will be set up if `main' is a JNI method.
runtime = java::lang::Runtime::getRuntime ();
#ifdef DISABLE_MAIN_ARGS
arg_vec = JvConvertArgv (0, 0);
#else
arg_vec = JvConvertArgv (argc - 1, argv + 1);
#endif
using namespace gnu::java::lang;
if (klass)
main_thread = new MainThread (klass, arg_vec);
else
main_thread = new MainThread (JvNewStringUTF (name),
arg_vec, is_jar);
_Jv_AttachCurrentThread (main_thread);
// Start JDWP
if (remoteDebug)
{
using namespace gnu::classpath::jdwp;
VMVirtualMachine::initialize ();
Jdwp *jdwp = new Jdwp ();
jdwp->setDaemon (true);
jdwp->configure (JvNewStringLatin1 (jdwpOptions));
jdwp->start ();
// Wait for JDWP to initialize and start
jdwp->join ();
}
// Send VmInit
gnu::classpath::jdwp::event::VmInitEvent *event;
event = new gnu::classpath::jdwp::event::VmInitEvent (main_thread);
gnu::classpath::jdwp::Jdwp::notify (event);
}
catch (java::lang::Throwable *t)
{
java::lang::System::err->println (JvNewStringLatin1
("Exception during runtime initialization"));
t->printStackTrace();
if (runtime)
java::lang::Runtime::exitNoChecksAccessor (1);
// In case the runtime creation failed.
::exit (1);
}
_Jv_ThreadRun (main_thread);
// Notify debugger of VM's death
if (gnu::classpath::jdwp::Jdwp::isDebugging)
{
using namespace gnu::classpath::jdwp;
event::VmDeathEvent *event = new event::VmDeathEvent ();
Jdwp::notify (event);
}
// If we got here then something went wrong, as MainThread is not
// supposed to terminate.
::exit (1);
}
void
_Jv_RunMain (jclass klass, const char *name, int argc, const char **argv,
bool is_jar)
{
_Jv_RunMain (NULL, klass, name, argc, argv, is_jar);
}
void
JvRunMain (jclass klass, int argc, const char **argv)
{
_Jv_RunMain (klass, NULL, argc, argv, false);
}
// Parse a string and return a heap size.
static size_t
parse_memory_size (const char *spec)
{
char *end;
unsigned long val = strtoul (spec, &end, 10);
if (*end == 'k' || *end == 'K')
val *= 1024;
else if (*end == 'm' || *end == 'M')
val *= 1048576;
return (size_t) val;
}
// Set the initial heap size. This might be ignored by the GC layer.
// This must be called before _Jv_RunMain.
void
_Jv_SetInitialHeapSize (const char *arg)
{
size_t size = parse_memory_size (arg);
_Jv_GCSetInitialHeapSize (size);
}
// Set the maximum heap size. This might be ignored by the GC layer.
// This must be called before _Jv_RunMain.
void
_Jv_SetMaximumHeapSize (const char *arg)
{
size_t size = parse_memory_size (arg);
_Jv_GCSetMaximumHeapSize (size);
}
void
_Jv_SetStackSize (const char *arg)
{
size_t size = parse_memory_size (arg);
gcj::stack_size = size;
}
void *
_Jv_Malloc (jsize size)
{
if (__builtin_expect (size == 0, false))
size = 1;
void *ptr = malloc ((size_t) size);
if (__builtin_expect (ptr == NULL, false))
throw no_memory;
return ptr;
}
void *
_Jv_Realloc (void *ptr, jsize size)
{
if (__builtin_expect (size == 0, false))
size = 1;
ptr = realloc (ptr, (size_t) size);
if (__builtin_expect (ptr == NULL, false))
throw no_memory;
return ptr;
}
void *
_Jv_MallocUnchecked (jsize size)
{
if (__builtin_expect (size == 0, false))
size = 1;
return malloc ((size_t) size);
}
void
_Jv_Free (void* ptr)
{
return free (ptr);
}
// In theory, these routines can be #ifdef'd away on machines which
// support divide overflow signals. However, we never know if some
// code might have been compiled with "-fuse-divide-subroutine", so we
// always include them in libgcj.
jint
_Jv_divI (jint dividend, jint divisor)
{
if (__builtin_expect (divisor == 0, false))
{
java::lang::ArithmeticException *arithexception
= new java::lang::ArithmeticException (JvNewStringLatin1 ("/ by zero"));
throw arithexception;
}
if (dividend == (jint) 0x80000000L && divisor == -1)
return dividend;
return dividend / divisor;
}
jint
_Jv_remI (jint dividend, jint divisor)
{
if (__builtin_expect (divisor == 0, false))
{
java::lang::ArithmeticException *arithexception
= new java::lang::ArithmeticException (JvNewStringLatin1 ("/ by zero"));
throw arithexception;
}
if (dividend == (jint) 0x80000000L && divisor == -1)
return 0;
return dividend % divisor;
}
jlong
_Jv_divJ (jlong dividend, jlong divisor)
{
if (__builtin_expect (divisor == 0, false))
{
java::lang::ArithmeticException *arithexception
= new java::lang::ArithmeticException (JvNewStringLatin1 ("/ by zero"));
throw arithexception;
}
if (dividend == (jlong) 0x8000000000000000LL && divisor == -1)
return dividend;
return dividend / divisor;
}
jlong
_Jv_remJ (jlong dividend, jlong divisor)
{
if (__builtin_expect (divisor == 0, false))
{
java::lang::ArithmeticException *arithexception
= new java::lang::ArithmeticException (JvNewStringLatin1 ("/ by zero"));
throw arithexception;
}
if (dividend == (jlong) 0x8000000000000000LL && divisor == -1)
return 0;
return dividend % divisor;
}
// Return true if SELF_KLASS can access a field or method in
// OTHER_KLASS. The field or method's access flags are specified in
// FLAGS.
jboolean
_Jv_CheckAccess (jclass self_klass, jclass other_klass, jint flags)
{
using namespace java::lang::reflect;
return ((self_klass == other_klass)
|| ((flags & Modifier::PUBLIC) != 0)
|| (((flags & Modifier::PROTECTED) != 0)
&& _Jv_IsAssignableFromSlow (self_klass, other_klass))
|| (((flags & Modifier::PRIVATE) == 0)
&& _Jv_ClassNameSamePackage (self_klass->name,
other_klass->name)));
}
// Prepend GCJ_VERSIONED_LIBDIR to a module search path stored in a C
// char array, if the path is not already prefixed by
// GCJ_VERSIONED_LIBDIR. Return a newly JvMalloc'd char buffer. The
// result should be freed using JvFree.
char*
_Jv_PrependVersionedLibdir (char* libpath)
{
char* retval = 0;
if (libpath && libpath[0] != '\0')
{
if (! strncmp (libpath,
GCJ_VERSIONED_LIBDIR,
sizeof (GCJ_VERSIONED_LIBDIR) - 1))
{
// LD_LIBRARY_PATH is already prefixed with
// GCJ_VERSIONED_LIBDIR.
retval = (char*) _Jv_Malloc (strlen (libpath) + 1);
strcpy (retval, libpath);
}
else
{
// LD_LIBRARY_PATH is not prefixed with
// GCJ_VERSIONED_LIBDIR.
jsize total = (sizeof (GCJ_VERSIONED_LIBDIR) - 1)
+ (sizeof (PATH_SEPARATOR) - 1) + strlen (libpath) + 1;
retval = (char*) _Jv_Malloc (total);
strcpy (retval, GCJ_VERSIONED_LIBDIR);
strcat (retval, PATH_SEPARATOR);
strcat (retval, libpath);
}
}
else
{
// LD_LIBRARY_PATH was not specified or is empty.
retval = (char*) _Jv_Malloc (sizeof (GCJ_VERSIONED_LIBDIR));
strcpy (retval, GCJ_VERSIONED_LIBDIR);
}
return retval;
}