44
54
#include "svn_dso.h"
45
55
#include "svn_path.h"
46
56
#include "svn_cache_config.h"
47
#include <apr_file_info.h>
57
#include "private/svn_atomic.h"
58
#include "private/svn_utf_private.h"
48
59
#include "svn_private_config.h"
50
/* FIXME: We're using an internal APR header here, which means we
51
have to build Subversion with APR sources. This being Win32-only,
52
that should be fine for now, but a better solution must be found in
53
combination with issue #850. */
55
#include <arch/win32/apr_arch_utf8.h>
59
61
#include "SVNBase.h"
60
62
#include "JNIMutex.h"
61
63
#include "JNICriticalSection.h"
62
#include "JNIThreadData.h"
63
64
#include "JNIStringHolder.h"
68
#include "jniwrapper/jni_env.hpp"
66
70
// Static members of JNIUtil are allocated here.
67
71
apr_pool_t *JNIUtil::g_pool = NULL;
68
72
std::list<SVNBase*> JNIUtil::g_finalizedObjects;
69
73
JNIMutex *JNIUtil::g_finalizedObjectsMutex = NULL;
70
74
JNIMutex *JNIUtil::g_logMutex = NULL;
75
JNIMutex *JNIUtil::g_configMutex = NULL;
71
76
bool JNIUtil::g_initException;
72
bool JNIUtil::g_inInit;
73
JNIEnv *JNIUtil::g_initEnv;
74
char JNIUtil::g_initFormatBuffer[formatBufferSize];
75
77
int JNIUtil::g_logLevel = JNIUtil::noLog;
76
78
std::ofstream JNIUtil::g_logStream;
80
/* The error code we will use to signal a Java exception */
81
static const apr_status_t
82
SVN_ERR_JAVAHL_WRAPPED = SVN_ERR_MALFUNC_CATEGORY_START
83
+ SVN_ERR_CATEGORY_SIZE - 10;
86
* Return the JNI environment to use
87
* @return the JNI environment
89
JNIEnv *JNIUtil::getEnv()
91
return Java::Env().get();
79
95
* Initialize the environment for all requests.
80
96
* @param env the JNI environment for this request
120
/* Forwarder for calling JNIGlobalInit from JNI_OnLoad(). */
121
bool initialize_jni_util(JNIEnv *env)
123
return JNIUtil::JNIGlobalInit(env);
128
volatile svn_atomic_t *gentle_crash_write_loc = NULL;
131
gently_crash_the_jvm(svn_boolean_t can_return,
132
const char *file, int line, const char *expr)
136
// Try not to abort; aborting prevents the JVM from creating
137
// a crash log, which is oh so useful for debugging.
138
// We can't just raise a SEGV signal, either, because it will
139
// be not be caught in the context that we're interested in
140
// getting the stack trace from.
142
// Try reading from and writing to the zero page
143
const svn_atomic_t zeropage = svn_atomic_read(gentle_crash_write_loc);
144
svn_atomic_set(gentle_crash_write_loc, zeropage);
147
// Forward to the standard malfunction handler, which does call
148
// abort when !can_return; this will only happen if the write to the
149
// zero page did not cause a SEGV.
150
return svn_error_raise_on_malfunction(can_return, file, line, expr);
152
} // Anonymous namespace
108
155
* Initialize the environment for all requests.
156
* This method must be called in a single-threaded context.
109
157
* @param env the JNI environment for this request
111
159
bool JNIUtil::JNIGlobalInit(JNIEnv *env)
113
// This method has to be run only once during the run a program.
114
static bool run = false;
115
161
svn_error_t *err;
116
if (run) // already run
121
// Do not run this part more than one time. This leaves a small
122
// time window when two threads create their first SVNClient and
123
// SVNAdmin at the same time, but I do not see a better option
124
// without APR already initialized
135
/* Initialize the APR subsystem, and register an atexit() function
136
* to Uninitialize that subsystem at program exit. */
137
status = apr_initialize();
143
apr_strerror(status, buf, sizeof(buf) - 1);
145
"%s: error: cannot initialize APR: %s\n",
151
163
/* This has to happen before any pools are created. */
152
164
if ((err = svn_dso_initialize2()))
182
186
svn_utf_initialize2(FALSE, g_pool); /* Optimize character conversions */
183
svn_fs_initialize(g_pool); /* Avoid some theoretical issues */
184
svn_ra_initialize(g_pool);
186
/* We shouldn't fill the JVMs memory with FS cache data unless explictly
188
// Initialize the libraries we use
189
err = svn_fs_initialize(g_pool);
191
err = svn_ra_initialize(g_pool);
194
if (stderr && err->message)
195
fprintf(stderr, "%s", err->message);
197
svn_error_clear(err);
201
/* We shouldn't fill the JVMs memory with FS cache data unless
202
explicitly requested. And we don't either, because the caches get
203
allocated outside the JVM heap. Duh. */
189
205
svn_cache_config_t settings = *svn_cache_config_get();
190
settings.cache_size = 0;
191
settings.file_handle_count = 0;
192
206
settings.single_threaded = FALSE;
193
207
svn_cache_config_set(&settings);
199
213
WCHAR ucs2_path[MAX_PATH];
214
const char *utf8_path;
201
215
const char *internal_path;
203
apr_status_t apr_err;
204
apr_size_t inwords, outbytes;
205
unsigned int outlength;
217
apr_pool_t *pool = svn_pool_create(g_pool);
207
pool = svn_pool_create(g_pool);
208
219
/* get dll name - our locale info will be in '../share/locale' */
209
inwords = sizeof(ucs2_path) / sizeof(ucs2_path[0]);
210
220
HINSTANCE moduleHandle = GetModuleHandle("libsvnjavahl-1");
211
GetModuleFileNameW(moduleHandle, ucs2_path, inwords);
212
inwords = lstrlenW(ucs2_path);
213
outbytes = outlength = 3 * (inwords + 1);
214
utf8_path = reinterpret_cast<char *>(apr_palloc(pool, outlength));
215
apr_err = apr_conv_ucs2_to_utf8((const apr_wchar_t *) ucs2_path,
216
&inwords, utf8_path, &outbytes);
217
if (!apr_err && (inwords > 0 || outbytes == 0))
218
apr_err = APR_INCOMPLETE;
221
GetModuleFileNameW(moduleHandle, ucs2_path,
222
sizeof(ucs2_path) / sizeof(ucs2_path[0]));
223
err = svn_utf__win32_utf16_to_utf8(&utf8_path, ucs2_path, NULL, pool);
222
fprintf(stderr, "Can't convert module path to UTF-8");
227
svn_handle_error2(err, stderr, false, "svn: ");
228
svn_error_clear(err);
225
utf8_path[outlength - outbytes] = '\0';
226
232
internal_path = svn_dirent_internal_style(utf8_path, pool);
227
233
/* get base path name */
228
234
internal_path = svn_dirent_dirname(internal_path, pool);
418
410
env->DeleteLocalRef(jfileName);
421
void JNIUtil::wrappedHandleSVNError(svn_error_t *err)
424
assembleErrorMessage(svn_error_purge_tracing(err), 0, APR_SUCCESS, msg);
414
struct MessageStackItem
417
std::string m_message;
420
MessageStackItem(apr_status_t code, const char* message,
421
bool generic = false)
427
typedef std::vector<MessageStackItem> ErrorMessageStack;
430
* Build the error message from the svn error into buffer. This
431
* method iterates through all the chained errors
433
* @param err the subversion error
434
* @param buffer the buffer where the formated error message will
436
* @return An array of error codes and messages
438
ErrorMessageStack assemble_error_message(
439
svn_error_t *err, std::string &result)
441
// buffer for a single error message
443
apr_status_t parent_apr_err = 0;
444
ErrorMessageStack message_stack;
446
/* Pretty-print the error */
447
/* Note: we can also log errors here someday. */
449
for (int depth = 0; err;
450
++depth, parent_apr_err = err->apr_err, err = err->child)
452
/* When we're recursing, don't repeat the top-level message if its
453
* the same as before. */
454
if ((depth == 0 || err->apr_err != parent_apr_err)
455
&& err->apr_err != SVN_ERR_JAVAHL_WRAPPED)
458
/* Is this a Subversion-specific error code? */
459
if ((err->apr_err > APR_OS_START_USEERR)
460
&& (err->apr_err <= APR_OS_START_CANONERR))
461
message = svn_strerror(err->apr_err, errbuf, sizeof(errbuf));
462
/* Otherwise, this must be an APR error code. */
465
/* Messages coming from apr_strerror are in the native
466
encoding, it's a good idea to convert them to UTF-8. */
467
apr_strerror(err->apr_err, errbuf, sizeof(errbuf));
468
svn_error_t* utf8_err =
469
svn_utf_cstring_to_utf8(&message, errbuf, err->pool);
472
/* Use fuzzy transliteration instead. */
473
svn_error_clear(utf8_err);
474
message = svn_utf_cstring_from_utf8_fuzzy(errbuf, err->pool);
478
message_stack.push_back(
479
MessageStackItem(err->apr_err, message, true));
483
message_stack.push_back(
484
MessageStackItem(err->apr_err, err->message));
488
for (ErrorMessageStack::const_iterator it = message_stack.begin();
489
it != message_stack.end(); ++it)
493
result += it->m_message;
496
return message_stack;
499
jobject construct_Jmessage_stack(const ErrorMessageStack& message_stack)
501
JNIEnv *env = JNIUtil::getEnv();
502
env->PushLocalFrame(LOCAL_FRAME_SIZE);
503
if (JNIUtil::isJavaExceptionThrown())
506
jclass list_clazz = env->FindClass("java/util/ArrayList");
507
if (JNIUtil::isJavaExceptionThrown())
509
jmethodID mid = env->GetMethodID(list_clazz, "<init>", "(I)V");
510
if (JNIUtil::isJavaExceptionThrown())
512
jmethodID add_mid = env->GetMethodID(list_clazz, "add",
513
"(Ljava/lang/Object;)Z");
514
if (JNIUtil::isJavaExceptionThrown())
516
jobject jlist = env->NewObject(list_clazz, mid, jint(message_stack.size()));
517
if (JNIUtil::isJavaExceptionThrown())
520
jclass clazz = env->FindClass(JAVAHL_CLASS("/ClientException$ErrorMessage"));
521
if (JNIUtil::isJavaExceptionThrown())
523
mid = env->GetMethodID(clazz, "<init>",
524
"(ILjava/lang/String;Z)V");
525
if (JNIUtil::isJavaExceptionThrown())
528
for (ErrorMessageStack::const_iterator it = message_stack.begin();
529
it != message_stack.end(); ++it)
531
jobject jmessage = JNIUtil::makeJString(it->m_message.c_str());
532
if (JNIUtil::isJavaExceptionThrown())
534
jobject jitem = env->NewObject(clazz, mid,
535
jint(it->m_code), jmessage,
536
jboolean(it->m_generic));
537
if (JNIUtil::isJavaExceptionThrown())
539
env->CallBooleanMethod(jlist, add_mid, jitem);
540
if (JNIUtil::isJavaExceptionThrown())
543
env->DeleteLocalRef(jmessage);
544
env->DeleteLocalRef(jitem);
546
return env->PopLocalFrame(jlist);
548
} // anonymous namespace
550
std::string JNIUtil::makeSVNErrorMessage(svn_error_t *err,
551
jstring *jerror_message,
552
jobject *jmessage_stack)
555
*jerror_message = NULL;
557
*jmessage_stack = NULL;
560
err = svn_error_purge_tracing(err);
561
if (err == NULL || err->apr_err == 0
562
|| !(jerror_message || jmessage_stack))
565
ErrorMessageStack message_stack = assemble_error_message(err, buffer);
567
*jerror_message = makeJString(buffer.c_str());
569
*jmessage_stack = construct_Jmessage_stack(message_stack);
573
jthrowable JNIUtil::wrappedCreateClientException(svn_error_t *err, jthrowable jcause)
577
std::string msg = makeSVNErrorMessage(err, &jmessage, &jstack);
578
if (JNIUtil::isJavaExceptionThrown())
425
581
const char *source = NULL;
427
583
#ifndef SVN_ERR__TRACING
463
622
g_logStream << std::endl;
465
624
if (isJavaExceptionThrown())
466
POP_AND_RETURN_NOTHING();
468
jstring jmessage = makeJString(msg.c_str());
469
if (isJavaExceptionThrown())
470
POP_AND_RETURN_NOTHING();
471
627
jstring jsource = makeJString(source);
472
628
if (isJavaExceptionThrown())
473
POP_AND_RETURN_NOTHING();
475
631
jmethodID mid = env->GetMethodID(clazz, "<init>",
476
"(Ljava/lang/String;Ljava/lang/String;I)V");
477
if (isJavaExceptionThrown())
478
POP_AND_RETURN_NOTHING();
479
jobject nativeException = env->NewObject(clazz, mid, jmessage, jsource,
480
static_cast<jint>(err->apr_err));
481
if (isJavaExceptionThrown())
482
POP_AND_RETURN_NOTHING();
632
"(Ljava/lang/String;"
633
"Ljava/lang/Throwable;"
634
"Ljava/lang/String;I"
635
"Ljava/util/List;)V");
636
if (isJavaExceptionThrown())
638
jobject nativeException = env->NewObject(clazz, mid, jmessage, jcause,
639
jsource, jint(err->apr_err),
641
if (isJavaExceptionThrown())
484
644
#ifdef SVN_ERR__TRACING
485
645
// Add all the C error stack trace information to the Java Exception
538
698
mid_sst = env->GetMethodID(clazz, "setStackTrace",
539
699
"([Ljava/lang/StackTraceElement;)V");
540
700
if (isJavaExceptionThrown())
541
POP_AND_RETURN_NOTHING();
543
703
env->CallVoidMethod(nativeException, mid_sst, jStackTrace);
544
704
if (isJavaExceptionThrown())
545
POP_AND_RETURN_NOTHING();
548
env->Throw(static_cast<jthrowable>(env->PopLocalFrame(nativeException)));
708
return static_cast<jthrowable>(env->PopLocalFrame(nativeException));
551
void JNIUtil::handleSVNError(svn_error_t *err)
711
jthrowable JNIUtil::createClientException(svn_error_t *err, jthrowable jcause)
713
jthrowable jexc = NULL;
554
wrappedHandleSVNError(err);
715
jexc = wrappedCreateClientException(err, jcause);
556
717
svn_error_clear(err);
559
720
svn_error_clear(err);
724
void JNIUtil::handleSVNError(svn_error_t *err, jthrowable jcause)
726
jthrowable jexc = createClientException(err, jcause);
728
getEnv()->Throw(jexc);
562
731
void JNIUtil::putFinalizedClient(SVNBase *object)
579
748
void JNIUtil::handleAPRError(int error, const char *op)
581
char *buffer = getFormatBuffer();
585
apr_snprintf(buffer, formatBufferSize,
752
apr_snprintf(buffer, sizeof(buffer),
586
753
_("an error occurred in function %s with return value %d"),
589
756
throwError(buffer);
593
* Return if an exception has been detected.
594
* @return a exception has been detected
596
bool JNIUtil::isExceptionThrown()
598
// During init -> look in the global member.
600
return g_initException;
602
// Look in the thread local storage.
603
JNIThreadData *data = JNIThreadData::getThreadData();
604
return data == NULL || data->m_exceptionThrown;
608
* Store the JNI environment for this request in the thread local
610
* @param env the JNI environment
612
void JNIUtil::setEnv(JNIEnv *env)
614
JNIThreadData::pushNewThreadData();
615
JNIThreadData *data = JNIThreadData::getThreadData();
617
data->m_exceptionThrown = false;
621
* Return the JNI environment to use
622
* @return the JNI environment
624
JNIEnv *JNIUtil::getEnv()
626
// During init -> look into the global variable.
630
// Look in the thread local storage.
631
JNIThreadData *data = JNIThreadData::getThreadData();
636
* Check if a Java exception has been thrown.
637
* @return is a Java exception has been thrown
639
bool JNIUtil::isJavaExceptionThrown()
641
JNIEnv *env = getEnv();
642
if (env->ExceptionCheck())
644
// Retrieving the exception removes it so we rethrow it here.
645
jthrowable exp = env->ExceptionOccurred();
646
env->ExceptionDescribe();
648
env->DeleteLocalRef(exp);
649
setExceptionThrown();
760
const char* known_exception_to_cstring(apr_pool_t* pool)
762
JNIEnv *env = JNIUtil::getEnv();
763
jthrowable t = env->ExceptionOccurred();
764
jclass cls = env->GetObjectClass(t);
768
jmethodID mid = env->GetMethodID(cls, "getClass", "()Ljava/lang/Class;");
769
jobject clsobj = env->CallObjectMethod(t, mid);
770
jclass basecls = env->GetObjectClass(clsobj);
771
mid = env->GetMethodID(basecls, "getName", "()Ljava/lang/String;");
772
jclass_name = (jstring) env->CallObjectMethod(clsobj, mid);
777
jmethodID mid = env->GetMethodID(cls, "getMessage",
778
"()Ljava/lang/String;");
779
jmessage = (jstring) env->CallObjectMethod(t, mid);
782
JNIStringHolder class_name(jclass_name);
785
JNIStringHolder message(jmessage);
786
return apr_pstrcat(pool, class_name.c_str(), ": ", message.c_str(), NULL);
789
return class_name.pstrdup(pool);
790
// ### Conditionally add t.printStackTrace() to msg?
793
const char* exception_to_cstring(apr_pool_t* pool)
796
if (JNIUtil::getEnv()->ExceptionCheck())
798
msg = known_exception_to_cstring(pool);
806
} // anonymous namespace
656
809
JNIUtil::thrownExceptionToCString(SVN::Pool &in_pool)
658
const char *msg = NULL;
659
JNIEnv *env = getEnv();
660
apr_pool_t *pool = in_pool.getPool();
661
if (env->ExceptionCheck())
663
jthrowable t = env->ExceptionOccurred();
664
jclass cls = env->GetObjectClass(t);
668
jmethodID mid = env->GetMethodID(cls, "getClass", "()Ljava/lang/Class;");
669
jobject clsobj = env->CallObjectMethod(t, mid);
670
jclass basecls = env->GetObjectClass(clsobj);
671
mid = env->GetMethodID(basecls, "getName", "()Ljava/lang/String;");
672
jclass_name = (jstring) env->CallObjectMethod(clsobj, mid);
677
jmethodID mid = env->GetMethodID(cls, "getMessage",
678
"()Ljava/lang/String;");
679
jmessage = (jstring) env->CallObjectMethod(t, mid);
682
JNIStringHolder class_name(jclass_name);
685
JNIStringHolder message(jmessage);
686
msg = apr_pstrcat(pool,
687
static_cast<const char*>(class_name), ": ",
688
static_cast<const char*>(message), NULL);
691
msg = class_name.pstrdup(pool);
692
// ### Conditionally add t.printStackTrace() to msg?
811
return exception_to_cstring(in_pool.getPool());
815
JNIUtil::checkJavaException(apr_status_t errorcode)
817
if (!getEnv()->ExceptionCheck())
819
svn_error_t* err = svn_error_create(errorcode, NULL, NULL);
820
const char* const msg = known_exception_to_cstring(err->pool);
822
err->message = apr_psprintf(err->pool, _("Java exception: %s"), msg);
824
err->message = _("Java exception");
862
980
jbyteArray JNIUtil::makeJByteArray(const svn_string_t *str)
982
// a NULL will create no Java array
864
986
return JNIUtil::makeJByteArray(str->data, static_cast<int>(str->len));
868
* Build the error message from the svn error into buffer. This
869
* method calls itselft recursively for all the chained errors
871
* @param err the subversion error
872
* @param depth the depth of the call, used for formating
873
* @param parent_apr_err the apr of the previous level, used for formating
874
* @param buffer the buffer where the formated error message will
877
void JNIUtil::assembleErrorMessage(svn_error_t *err, int depth,
878
apr_status_t parent_apr_err,
881
// buffer for a single error message
884
/* Pretty-print the error */
885
/* Note: we can also log errors here someday. */
887
/* When we're recursing, don't repeat the top-level message if its
888
* the same as before. */
889
if (depth == 0 || err->apr_err != parent_apr_err)
891
/* Is this a Subversion-specific error code? */
892
if ((err->apr_err > APR_OS_START_USEERR)
893
&& (err->apr_err <= APR_OS_START_CANONERR))
894
buffer.append(svn_strerror(err->apr_err, errbuf, sizeof(errbuf)));
895
/* Otherwise, this must be an APR error code. */
898
/* Messages coming from apr_strerror are in the native
899
encoding, it's a good idea to convert them to UTF-8. */
900
const char* utf8_message;
901
apr_strerror(err->apr_err, errbuf, sizeof(errbuf));
902
svn_error_t* utf8_err = svn_utf_cstring_to_utf8(
903
&utf8_message, errbuf, err->pool);
906
/* Use fuzzy transliteration instead. */
907
svn_error_clear(utf8_err);
908
utf8_message = svn_utf_cstring_from_utf8_fuzzy(errbuf, err->pool);
910
buffer.append(utf8_message);
915
buffer.append(_("svn: ")).append(err->message).append("\n");
918
assembleErrorMessage(err->child, depth + 1, err->apr_err, buffer);
923
990
* Throw a Java NullPointerException. Used when input parameters
924
991
* which should not be null are that.
1065
/* Tag to use on the apr_pool_t to store a WrappedException reference */
1066
static const char *WrapExceptionTag = "org.apache.subversion.JavaHL.svnerror";
1068
class WrappedException
1071
jthrowable m_exception;
1076
WrappedException(JNIEnv *env)
1080
// Fetch exception inside local frame
1081
jthrowable exceptionObj = env->ExceptionOccurred();
1083
// Now clear exception status
1084
env->ExceptionClear();
1086
// As adding a reference in exception state fails
1087
m_exception = static_cast<jthrowable>(env->NewGlobalRef(exceptionObj));
1094
static jthrowable get_exception(apr_pool_t *pool)
1097
if (! apr_pool_userdata_get(&data, WrapExceptionTag, pool))
1099
WrappedException *we = reinterpret_cast<WrappedException *>(data);
1104
we->m_fetched = TRUE;
1106
// Create reference in local frame, as the pool will be cleared
1107
return static_cast<jthrowable>(
1108
we->m_env->NewLocalRef(we->m_exception));
1119
SVN_DBG(("Cleared svn_error_t * before Java exception was fetched"));
1121
m_env->DeleteGlobalRef(m_exception);
1124
static apr_status_t cleanup(void *data)
1126
WrappedException *we = reinterpret_cast<WrappedException *>(data);
1133
svn_error_t* JNIUtil::wrapJavaException()
1135
if (!isExceptionThrown())
1136
return SVN_NO_ERROR;
1138
svn_error_t *err = svn_error_create(SVN_ERR_JAVAHL_WRAPPED, NULL,
1139
"Wrapped Java Exception");
1140
apr_pool_userdata_set(new WrappedException(getEnv()), WrapExceptionTag,
1141
WrappedException::cleanup, err->pool);
1145
jthrowable JNIUtil::unwrapJavaException(const svn_error_t *err)
1150
WrappedException::get_exception(err->pool);