~ubuntu-branches/debian/sid/subversion/sid

« back to all changes in this revision

Viewing changes to subversion/bindings/javahl/native/JNIUtil.cpp

  • Committer: Package Import Robot
  • Author(s): James McCoy
  • Date: 2015-08-07 21:32:47 UTC
  • mfrom: (0.2.15) (4.1.7 experimental)
  • Revision ID: package-import@ubuntu.com-20150807213247-ozyewtmgsr6tkewl
Tags: 1.9.0-1
* Upload to unstable
* New upstream release.
  + Security fixes
    - CVE-2015-3184: Mixed anonymous/authenticated path-based authz with
      httpd 2.4
    - CVE-2015-3187: svn_repos_trace_node_locations() reveals paths hidden
      by authz
* Add >= 2.7 requirement for python-all-dev Build-Depends, needed to run
  tests.
* Remove Build-Conflicts against ruby-test-unit.  (Closes: #791844)
* Remove patches/apache_module_dependency in favor of expressing the
  dependencies in authz_svn.load/dav_svn.load.
* Build-Depend on apache2-dev (>= 2.4.16) to ensure ap_some_authn_required()
  is available when building mod_authz_svn and Depend on apache2-bin (>=
  2.4.16) for runtime support.

Show diffs side-by-side

added added

removed removed

Lines of Context:
24
24
 * @brief Implementation of the class JNIUtil
25
25
 */
26
26
 
 
27
/* Include apr.h first, or INT64_C won't be defined properly on some C99
 
28
   compilers, when other headers include <stdint.h> before defining some
 
29
   macros.
 
30
 
 
31
   See apr.h for the ugly details */
 
32
#include <apr.h>
 
33
 
27
34
#include "JNIUtil.h"
28
35
#include "Array.h"
29
36
 
35
42
#include <apr_tables.h>
36
43
#include <apr_general.h>
37
44
#include <apr_lib.h>
 
45
#include <apr_file_info.h>
 
46
#include <apr_time.h>
38
47
 
39
48
#include "svn_pools.h"
 
49
#include "svn_error.h"
40
50
#include "svn_fs.h"
41
51
#include "svn_ra.h"
42
52
#include "svn_utf.h"
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"
49
 
#ifdef WIN32
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. */
54
 
extern "C" {
55
 
#include <arch/win32/apr_arch_utf8.h>
56
 
};
57
 
#endif
58
60
 
59
61
#include "SVNBase.h"
60
62
#include "JNIMutex.h"
61
63
#include "JNICriticalSection.h"
62
 
#include "JNIThreadData.h"
63
64
#include "JNIStringHolder.h"
64
65
#include "Pool.h"
65
66
 
 
67
 
 
68
#include "jniwrapper/jni_env.hpp"
 
69
 
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;
77
79
 
 
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;
 
84
 
 
85
/**
 
86
 * Return the JNI environment to use
 
87
 * @return the JNI environment
 
88
 */
 
89
JNIEnv *JNIUtil::getEnv()
 
90
{
 
91
  return Java::Env().get();
 
92
}
 
93
 
78
94
/**
79
95
 * Initialize the environment for all requests.
80
96
 * @param env   the JNI environment for this request
84
100
  // Clear all standing exceptions.
85
101
  env->ExceptionClear();
86
102
 
87
 
  // Remember the env parameter for the remainder of the request.
88
 
  setEnv(env);
89
 
 
90
103
  // Lock the list of finalized objects.
91
104
  JNICriticalSection cs(*g_finalizedObjectsMutex) ;
92
105
  if (isExceptionThrown())
104
117
  return true;
105
118
}
106
119
 
 
120
/* Forwarder for calling JNIGlobalInit from JNI_OnLoad(). */
 
121
bool initialize_jni_util(JNIEnv *env)
 
122
{
 
123
  return JNIUtil::JNIGlobalInit(env);
 
124
}
 
125
 
 
126
namespace {
 
127
 
 
128
volatile svn_atomic_t *gentle_crash_write_loc = NULL;
 
129
 
 
130
svn_error_t *
 
131
gently_crash_the_jvm(svn_boolean_t can_return,
 
132
                     const char *file, int line, const char *expr)
 
133
{
 
134
  if (!can_return)
 
135
    {
 
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.
 
141
 
 
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);
 
145
    }
 
146
 
 
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);
 
151
}
 
152
} // Anonymous namespace
 
153
 
107
154
/**
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
110
158
 */
111
159
bool JNIUtil::JNIGlobalInit(JNIEnv *env)
112
160
{
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
117
 
    return true;
118
 
 
119
 
  run = true;
120
 
 
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
125
 
  if (g_inInit)
126
 
    return false;
127
 
 
128
 
  g_inInit = true;
129
 
  g_initEnv = env;
130
 
 
131
 
  apr_status_t status;
132
 
 
133
 
 
134
 
 
135
 
  /* Initialize the APR subsystem, and register an atexit() function
136
 
   * to Uninitialize that subsystem at program exit. */
137
 
  status = apr_initialize();
138
 
  if (status)
139
 
    {
140
 
      if (stderr)
141
 
        {
142
 
          char buf[1024];
143
 
          apr_strerror(status, buf, sizeof(buf) - 1);
144
 
          fprintf(stderr,
145
 
                  "%s: error: cannot initialize APR: %s\n",
146
 
                  "svnjavahl", buf);
147
 
        }
148
 
      return FALSE;
149
 
    }
150
162
 
151
163
  /* This has to happen before any pools are created. */
152
164
  if ((err = svn_dso_initialize2()))
158
170
      return FALSE;
159
171
    }
160
172
 
161
 
  if (0 > atexit(apr_terminate))
162
 
    {
163
 
      if (stderr)
164
 
        fprintf(stderr,
165
 
                "%s: error: atexit registration failed\n",
166
 
                "svnjavahl");
167
 
      return FALSE;
168
 
    }
169
 
 
170
 
  /* Create our top-level pool. */
 
173
  /* Create our top-level pool.
 
174
     N.B.: APR was initialized by JNI_OnLoad. */
171
175
  g_pool = svn_pool_create(NULL);
172
176
 
173
177
  apr_allocator_t* allocator = apr_pool_allocator_get(g_pool);
180
184
    }
181
185
 
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);
185
 
 
186
 
  /* We shouldn't fill the JVMs memory with FS cache data unless explictly
187
 
     requested. */
 
187
 
 
188
  // Initialize the libraries we use
 
189
  err = svn_fs_initialize(g_pool);
 
190
  if (!err)
 
191
    err = svn_ra_initialize(g_pool);
 
192
  if (err)
 
193
    {
 
194
      if (stderr && err->message)
 
195
        fprintf(stderr, "%s", err->message);
 
196
 
 
197
      svn_error_clear(err);
 
198
      return FALSE;
 
199
    }
 
200
 
 
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. */
188
204
  {
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);
194
208
  }
197
211
#ifdef WIN32
198
212
  {
199
213
    WCHAR ucs2_path[MAX_PATH];
200
 
    char *utf8_path;
 
214
    const char *utf8_path;
201
215
    const char *internal_path;
202
 
    apr_pool_t *pool;
203
 
    apr_status_t apr_err;
204
 
    apr_size_t inwords, outbytes;
205
 
    unsigned int outlength;
 
216
    svn_error_t *err;
 
217
    apr_pool_t *pool = svn_pool_create(g_pool);
206
218
 
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;
219
 
    if (apr_err)
 
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);
 
224
    if (err)
220
225
      {
221
226
        if (stderr)
222
 
          fprintf(stderr, "Can't convert module path to UTF-8");
223
 
        return FALSE;
 
227
          svn_handle_error2(err, stderr, false, "svn: ");
 
228
        svn_error_clear(err);
 
229
        return false;
224
230
      }
225
 
    utf8_path[outlength - outbytes] = '\0';
 
231
 
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);
268
274
  if (isExceptionThrown())
269
275
    return false;
270
276
 
271
 
  // initialized the thread local storage
272
 
  if (!JNIThreadData::initThreadData())
273
 
    return false;
274
 
 
275
 
  setEnv(env);
 
277
  g_configMutex = new JNIMutex(g_pool);
276
278
  if (isExceptionThrown())
277
279
    return false;
278
280
 
279
 
  g_initEnv = NULL;
280
 
  g_inInit = false;
 
281
  // Set a malfunction handler that tries not to call abort, because
 
282
  // that would prevent the JVM from creating a crash and stack log file.
 
283
  svn_error_set_malfunction_handler(gently_crash_the_jvm);
 
284
 
281
285
  return true;
282
286
}
283
287
 
304
308
    return;
305
309
 
306
310
  env->ThrowNew(clazz, message);
307
 
  setExceptionThrown();
308
 
  env->DeleteLocalRef(clazz);
309
 
}
310
 
 
311
 
jstring JNIUtil::makeSVNErrorMessage(svn_error_t *err)
312
 
{
313
 
  if (err == NULL)
314
 
    return NULL;
315
 
  std::string buffer;
316
 
  assembleErrorMessage(err, 0, APR_SUCCESS, buffer);
317
 
  jstring jmessage = makeJString(buffer.c_str());
318
 
  return jmessage;
319
311
}
320
312
 
321
313
void
418
410
  env->DeleteLocalRef(jfileName);
419
411
}
420
412
 
421
 
void JNIUtil::wrappedHandleSVNError(svn_error_t *err)
422
 
{
423
 
  std::string msg;
424
 
  assembleErrorMessage(svn_error_purge_tracing(err), 0, APR_SUCCESS, msg);
 
413
namespace {
 
414
struct MessageStackItem
 
415
{
 
416
  apr_status_t m_code;
 
417
  std::string m_message;
 
418
  bool m_generic;
 
419
 
 
420
  MessageStackItem(apr_status_t code, const char* message,
 
421
                   bool generic = false)
 
422
    : m_code(code),
 
423
      m_message(message),
 
424
      m_generic(generic)
 
425
    {}
 
426
};
 
427
typedef std::vector<MessageStackItem> ErrorMessageStack;
 
428
 
 
429
/*
 
430
 * Build the error message from the svn error into buffer.  This
 
431
 * method iterates through all the chained errors
 
432
 *
 
433
 * @param err               the subversion error
 
434
 * @param buffer            the buffer where the formated error message will
 
435
 *                          be stored
 
436
 * @return An array of error codes and messages
 
437
 */
 
438
ErrorMessageStack assemble_error_message(
 
439
    svn_error_t *err, std::string &result)
 
440
{
 
441
  // buffer for a single error message
 
442
  char errbuf[1024];
 
443
  apr_status_t parent_apr_err = 0;
 
444
  ErrorMessageStack message_stack;
 
445
 
 
446
  /* Pretty-print the error */
 
447
  /* Note: we can also log errors here someday. */
 
448
 
 
449
  for (int depth = 0; err;
 
450
       ++depth, parent_apr_err = err->apr_err, err = err->child)
 
451
    {
 
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)
 
456
        {
 
457
          const char *message;
 
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. */
 
463
          else
 
464
            {
 
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);
 
470
              if (utf8_err)
 
471
                {
 
472
                  /* Use fuzzy transliteration instead. */
 
473
                  svn_error_clear(utf8_err);
 
474
                  message = svn_utf_cstring_from_utf8_fuzzy(errbuf, err->pool);
 
475
                }
 
476
            }
 
477
 
 
478
          message_stack.push_back(
 
479
              MessageStackItem(err->apr_err, message, true));
 
480
        }
 
481
      if (err->message)
 
482
        {
 
483
          message_stack.push_back(
 
484
              MessageStackItem(err->apr_err, err->message));
 
485
        }
 
486
    }
 
487
 
 
488
  for (ErrorMessageStack::const_iterator it = message_stack.begin();
 
489
       it != message_stack.end(); ++it)
 
490
    {
 
491
      if (!it->m_generic)
 
492
        result += "svn: ";
 
493
      result += it->m_message;
 
494
      result += '\n';
 
495
    }
 
496
  return message_stack;
 
497
}
 
498
 
 
499
jobject construct_Jmessage_stack(const ErrorMessageStack& message_stack)
 
500
{
 
501
  JNIEnv *env = JNIUtil::getEnv();
 
502
  env->PushLocalFrame(LOCAL_FRAME_SIZE);
 
503
  if (JNIUtil::isJavaExceptionThrown())
 
504
    return NULL;
 
505
 
 
506
  jclass list_clazz = env->FindClass("java/util/ArrayList");
 
507
  if (JNIUtil::isJavaExceptionThrown())
 
508
    POP_AND_RETURN_NULL;
 
509
  jmethodID mid = env->GetMethodID(list_clazz, "<init>", "(I)V");
 
510
  if (JNIUtil::isJavaExceptionThrown())
 
511
    POP_AND_RETURN_NULL;
 
512
  jmethodID add_mid = env->GetMethodID(list_clazz, "add",
 
513
                                       "(Ljava/lang/Object;)Z");
 
514
  if (JNIUtil::isJavaExceptionThrown())
 
515
    POP_AND_RETURN_NULL;
 
516
  jobject jlist = env->NewObject(list_clazz, mid, jint(message_stack.size()));
 
517
  if (JNIUtil::isJavaExceptionThrown())
 
518
    POP_AND_RETURN_NULL;
 
519
 
 
520
  jclass clazz = env->FindClass(JAVAHL_CLASS("/ClientException$ErrorMessage"));
 
521
  if (JNIUtil::isJavaExceptionThrown())
 
522
    POP_AND_RETURN_NULL;
 
523
  mid = env->GetMethodID(clazz, "<init>",
 
524
                         "(ILjava/lang/String;Z)V");
 
525
  if (JNIUtil::isJavaExceptionThrown())
 
526
    POP_AND_RETURN_NULL;
 
527
 
 
528
  for (ErrorMessageStack::const_iterator it = message_stack.begin();
 
529
       it != message_stack.end(); ++it)
 
530
    {
 
531
      jobject jmessage = JNIUtil::makeJString(it->m_message.c_str());
 
532
      if (JNIUtil::isJavaExceptionThrown())
 
533
        POP_AND_RETURN_NULL;
 
534
      jobject jitem = env->NewObject(clazz, mid,
 
535
                                     jint(it->m_code), jmessage,
 
536
                                     jboolean(it->m_generic));
 
537
      if (JNIUtil::isJavaExceptionThrown())
 
538
        POP_AND_RETURN_NULL;
 
539
      env->CallBooleanMethod(jlist, add_mid, jitem);
 
540
      if (JNIUtil::isJavaExceptionThrown())
 
541
        POP_AND_RETURN_NULL;
 
542
 
 
543
      env->DeleteLocalRef(jmessage);
 
544
      env->DeleteLocalRef(jitem);
 
545
    }
 
546
  return env->PopLocalFrame(jlist);
 
547
}
 
548
} // anonymous namespace
 
549
 
 
550
std::string JNIUtil::makeSVNErrorMessage(svn_error_t *err,
 
551
                                         jstring *jerror_message,
 
552
                                         jobject *jmessage_stack)
 
553
{
 
554
  if (jerror_message)
 
555
    *jerror_message = NULL;
 
556
  if (jmessage_stack)
 
557
    *jmessage_stack = NULL;
 
558
 
 
559
  std::string buffer;
 
560
  err = svn_error_purge_tracing(err);
 
561
  if (err == NULL || err->apr_err == 0
 
562
      || !(jerror_message || jmessage_stack))
 
563
  return buffer;
 
564
 
 
565
  ErrorMessageStack message_stack = assemble_error_message(err, buffer);
 
566
  if (jerror_message)
 
567
    *jerror_message = makeJString(buffer.c_str());
 
568
  if (jmessage_stack)
 
569
    *jmessage_stack = construct_Jmessage_stack(message_stack);
 
570
  return buffer;
 
571
}
 
572
 
 
573
jthrowable JNIUtil::wrappedCreateClientException(svn_error_t *err, jthrowable jcause)
 
574
{
 
575
  jstring jmessage;
 
576
  jobject jstack;
 
577
  std::string msg = makeSVNErrorMessage(err, &jmessage, &jstack);
 
578
  if (JNIUtil::isJavaExceptionThrown())
 
579
    return NULL;
 
580
 
425
581
  const char *source = NULL;
426
582
#ifdef SVN_DEBUG
427
583
#ifndef SVN_ERR__TRACING
436
592
#endif
437
593
#endif
438
594
 
 
595
  if (!jcause)
 
596
    jcause = JNIUtil::unwrapJavaException(err);
 
597
 
439
598
  // Much of the following is stolen from throwNativeException().  As much as
440
599
  // we'd like to call that function, we need to do some manual stack
441
600
  // unrolling, so it isn't feasible.
445
604
  // Create a local frame for our references
446
605
  env->PushLocalFrame(LOCAL_FRAME_SIZE);
447
606
  if (JNIUtil::isJavaExceptionThrown())
448
 
    return;
 
607
    return NULL;
449
608
 
450
 
  jclass clazz = env->FindClass(JAVA_PACKAGE "/ClientException");
 
609
  jclass clazz = env->FindClass(JAVAHL_CLASS("/ClientException"));
451
610
  if (isJavaExceptionThrown())
452
 
    POP_AND_RETURN_NOTHING();
 
611
    POP_AND_RETURN_NULL;
453
612
 
454
613
  if (getLogLevel() >= exceptionLog)
455
614
    {
463
622
      g_logStream << std::endl;
464
623
    }
465
624
  if (isJavaExceptionThrown())
466
 
    POP_AND_RETURN_NOTHING();
 
625
    POP_AND_RETURN_NULL;
467
626
 
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();
 
629
    POP_AND_RETURN_NULL;
474
630
 
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())
 
637
    POP_AND_RETURN_NULL;
 
638
  jobject nativeException = env->NewObject(clazz, mid, jmessage, jcause,
 
639
                                           jsource, jint(err->apr_err),
 
640
                                           jstack);
 
641
  if (isJavaExceptionThrown())
 
642
    POP_AND_RETURN_NULL;
483
643
 
484
644
#ifdef SVN_ERR__TRACING
485
645
  // Add all the C error stack trace information to the Java Exception
491
651
      mid_gst = env->GetMethodID(clazz, "getStackTrace",
492
652
                                 "()[Ljava/lang/StackTraceElement;");
493
653
      if (isJavaExceptionThrown())
494
 
        POP_AND_RETURN_NOTHING();
 
654
        POP_AND_RETURN_NULL;
495
655
    }
496
656
  Array stackTraceArray((jobjectArray) env->CallObjectMethod(nativeException,
497
657
                                                             mid_gst));
510
670
 
511
671
  jclass stClazz = env->FindClass("java/lang/StackTraceElement");
512
672
  if (isJavaExceptionThrown())
513
 
    POP_AND_RETURN_NOTHING();
 
673
    POP_AND_RETURN_NULL;
514
674
 
515
675
  const jsize stSize = static_cast<jsize>(newStackTrace.size());
516
 
  if (stSize != newStackTrace.size())
 
676
  if (stSize < 0 || stSize != newStackTrace.size())
517
677
    {
518
678
      env->ThrowNew(env->FindClass("java.lang.ArithmeticException"),
519
679
                    "Overflow converting C size_t to JNI jsize");
520
 
      POP_AND_RETURN_NOTHING();
 
680
      POP_AND_RETURN_NULL;
521
681
    }
522
682
  jobjectArray jStackTrace = env->NewObjectArray(stSize, stClazz, NULL);
523
683
  if (isJavaExceptionThrown())
524
 
    POP_AND_RETURN_NOTHING();
 
684
    POP_AND_RETURN_NULL;
525
685
 
526
686
  int i = 0;
527
687
  for (std::vector<jobject>::const_iterator it = newStackTrace.begin();
538
698
      mid_sst = env->GetMethodID(clazz, "setStackTrace",
539
699
                                 "([Ljava/lang/StackTraceElement;)V");
540
700
      if (isJavaExceptionThrown())
541
 
        POP_AND_RETURN_NOTHING();
 
701
        POP_AND_RETURN_NULL;
542
702
    }
543
703
  env->CallVoidMethod(nativeException, mid_sst, jStackTrace);
544
704
  if (isJavaExceptionThrown())
545
 
    POP_AND_RETURN_NOTHING();
 
705
    POP_AND_RETURN_NULL;
546
706
#endif
547
707
 
548
 
  env->Throw(static_cast<jthrowable>(env->PopLocalFrame(nativeException)));
 
708
  return static_cast<jthrowable>(env->PopLocalFrame(nativeException));
549
709
}
550
710
 
551
 
void JNIUtil::handleSVNError(svn_error_t *err)
 
711
jthrowable JNIUtil::createClientException(svn_error_t *err, jthrowable jcause)
552
712
{
 
713
  jthrowable jexc = NULL;
553
714
  try {
554
 
    wrappedHandleSVNError(err);
 
715
    jexc = wrappedCreateClientException(err, jcause);
555
716
  } catch (...) {
556
717
    svn_error_clear(err);
557
718
    throw;
558
719
  }
559
720
  svn_error_clear(err);
 
721
  return jexc;
 
722
}
 
723
 
 
724
void JNIUtil::handleSVNError(svn_error_t *err, jthrowable jcause)
 
725
{
 
726
  jthrowable jexc = createClientException(err, jcause);
 
727
  if (jexc)
 
728
    getEnv()->Throw(jexc);
560
729
}
561
730
 
562
731
void JNIUtil::putFinalizedClient(SVNBase *object)
578
747
 */
579
748
void JNIUtil::handleAPRError(int error, const char *op)
580
749
{
581
 
  char *buffer = getFormatBuffer();
582
 
  if (buffer == NULL)
583
 
    return;
 
750
  char buffer[2048];
584
751
 
585
 
  apr_snprintf(buffer, formatBufferSize,
 
752
  apr_snprintf(buffer, sizeof(buffer),
586
753
               _("an error occurred in function %s with return value %d"),
587
754
               op, error);
588
755
 
589
756
  throwError(buffer);
590
757
}
591
758
 
592
 
/**
593
 
 * Return if an exception has been detected.
594
 
 * @return a exception has been detected
595
 
 */
596
 
bool JNIUtil::isExceptionThrown()
597
 
{
598
 
  // During init -> look in the global member.
599
 
  if (g_inInit)
600
 
    return g_initException;
601
 
 
602
 
  // Look in the thread local storage.
603
 
  JNIThreadData *data = JNIThreadData::getThreadData();
604
 
  return data == NULL || data->m_exceptionThrown;
605
 
}
606
 
 
607
 
/**
608
 
 * Store the JNI environment for this request in the thread local
609
 
 * storage.
610
 
 * @param env   the JNI environment
611
 
 */
612
 
void JNIUtil::setEnv(JNIEnv *env)
613
 
{
614
 
  JNIThreadData::pushNewThreadData();
615
 
  JNIThreadData *data = JNIThreadData::getThreadData();
616
 
  data->m_env = env;
617
 
  data->m_exceptionThrown = false;
618
 
}
619
 
 
620
 
/**
621
 
 * Return the JNI environment to use
622
 
 * @return the JNI environment
623
 
 */
624
 
JNIEnv *JNIUtil::getEnv()
625
 
{
626
 
  // During init -> look into the global variable.
627
 
  if (g_inInit)
628
 
    return g_initEnv;
629
 
 
630
 
  // Look in the thread local storage.
631
 
  JNIThreadData *data = JNIThreadData::getThreadData();
632
 
  return data->m_env;
633
 
}
634
 
 
635
 
/**
636
 
 * Check if a Java exception has been thrown.
637
 
 * @return is a Java exception has been thrown
638
 
 */
639
 
bool JNIUtil::isJavaExceptionThrown()
640
 
{
641
 
  JNIEnv *env = getEnv();
642
 
  if (env->ExceptionCheck())
643
 
    {
644
 
      // Retrieving the exception removes it so we rethrow it here.
645
 
      jthrowable exp = env->ExceptionOccurred();
646
 
      env->ExceptionDescribe();
647
 
      env->Throw(exp);
648
 
      env->DeleteLocalRef(exp);
649
 
      setExceptionThrown();
650
 
      return true;
651
 
    }
652
 
  return false;
653
 
}
 
759
namespace {
 
760
const char* known_exception_to_cstring(apr_pool_t* pool)
 
761
{
 
762
  JNIEnv *env = JNIUtil::getEnv();
 
763
  jthrowable t = env->ExceptionOccurred();
 
764
  jclass cls = env->GetObjectClass(t);
 
765
 
 
766
  jstring jclass_name;
 
767
  {
 
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);
 
773
  }
 
774
 
 
775
  jstring jmessage;
 
776
  {
 
777
    jmethodID mid = env->GetMethodID(cls, "getMessage",
 
778
                                     "()Ljava/lang/String;");
 
779
    jmessage = (jstring) env->CallObjectMethod(t, mid);
 
780
  }
 
781
 
 
782
  JNIStringHolder class_name(jclass_name);
 
783
  if (jmessage)
 
784
    {
 
785
      JNIStringHolder message(jmessage);
 
786
      return apr_pstrcat(pool, class_name.c_str(), ": ", message.c_str(), NULL);
 
787
    }
 
788
  else
 
789
    return class_name.pstrdup(pool);
 
790
  // ### Conditionally add t.printStackTrace() to msg?
 
791
}
 
792
 
 
793
const char* exception_to_cstring(apr_pool_t* pool)
 
794
{
 
795
  const char *msg;
 
796
  if (JNIUtil::getEnv()->ExceptionCheck())
 
797
    {
 
798
      msg = known_exception_to_cstring(pool);
 
799
    }
 
800
  else
 
801
    {
 
802
      msg = NULL;
 
803
    }
 
804
  return msg;
 
805
}
 
806
} // anonymous namespace
654
807
 
655
808
const char *
656
809
JNIUtil::thrownExceptionToCString(SVN::Pool &in_pool)
657
810
{
658
 
  const char *msg = NULL;
659
 
  JNIEnv *env = getEnv();
660
 
  apr_pool_t *pool = in_pool.getPool();
661
 
  if (env->ExceptionCheck())
662
 
    {
663
 
      jthrowable t = env->ExceptionOccurred();
664
 
      jclass cls = env->GetObjectClass(t);
665
 
 
666
 
      jstring jclass_name;
667
 
      {
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);
673
 
      }
674
 
 
675
 
      jstring jmessage;
676
 
      {
677
 
        jmethodID mid = env->GetMethodID(cls, "getMessage",
678
 
                                         "()Ljava/lang/String;");
679
 
        jmessage = (jstring) env->CallObjectMethod(t, mid);
680
 
      }
681
 
 
682
 
      JNIStringHolder class_name(jclass_name);
683
 
      if (jmessage)
684
 
        {
685
 
          JNIStringHolder message(jmessage);
686
 
          msg = apr_pstrcat(pool,
687
 
                            static_cast<const char*>(class_name), ": ",
688
 
                            static_cast<const char*>(message), NULL);
689
 
        }
690
 
      else
691
 
        msg = class_name.pstrdup(pool);
692
 
      // ### Conditionally add t.printStackTrace() to msg?
693
 
    }
694
 
  return msg;
 
811
  return exception_to_cstring(in_pool.getPool());
 
812
}
 
813
 
 
814
svn_error_t*
 
815
JNIUtil::checkJavaException(apr_status_t errorcode)
 
816
{
 
817
  if (!getEnv()->ExceptionCheck())
 
818
    return SVN_NO_ERROR;
 
819
  svn_error_t* err = svn_error_create(errorcode, NULL, NULL);
 
820
  const char* const msg = known_exception_to_cstring(err->pool);
 
821
  if (msg)
 
822
    err->message = apr_psprintf(err->pool, _("Java exception: %s"), msg);
 
823
  else
 
824
    err->message = _("Java exception");
 
825
  return err;
695
826
}
696
827
 
697
828
/**
710
841
  return env->NewStringUTF(txt);
711
842
}
712
843
 
713
 
void
714
 
JNIUtil::setExceptionThrown(bool flag)
715
 
{
716
 
  if (g_inInit)
717
 
    {
718
 
      // During global initialization, store any errors that occur
719
 
      // in a global variable (since thread-local storage may not
720
 
      // yet be available).
721
 
      g_initException = flag;
722
 
    }
723
 
  else
724
 
    {
725
 
      // When global initialization is complete, thread-local
726
 
      // storage should be available, so store the error there.
727
 
      JNIThreadData *data = JNIThreadData::getThreadData();
728
 
      data->m_exceptionThrown = flag;
729
 
    }
730
 
}
731
 
 
732
844
/**
733
845
 * Initialite the log file.
734
846
 * @param level the log level
752
864
}
753
865
 
754
866
/**
755
 
 * Returns a buffer to format error messages.
756
 
 * @return a buffer for formating error messages
757
 
 */
758
 
char *JNIUtil::getFormatBuffer()
759
 
{
760
 
  if (g_inInit) // during init -> use the global buffer
761
 
    return g_initFormatBuffer;
762
 
 
763
 
  // use the buffer in the thread local storage
764
 
  JNIThreadData *data = JNIThreadData::getThreadData();
765
 
  if (data == NULL) // if that does not exists -> use the global buffer
766
 
    return g_initFormatBuffer;
767
 
 
768
 
  return data->m_formatBuffer;
769
 
}
770
 
 
771
 
/**
772
867
 * Returns the current log level.
773
868
 * @return the log level
774
869
 */
819
914
  return ret;
820
915
}
821
916
 
 
917
apr_time_t
 
918
JNIUtil::getDate(jobject jdate)
 
919
{
 
920
  JNIEnv *env = getEnv();
 
921
  jclass clazz = env->FindClass("java/util/Date");
 
922
  if (isJavaExceptionThrown())
 
923
    return 0;
 
924
 
 
925
  static jmethodID mid = 0;
 
926
  if (mid == 0)
 
927
    {
 
928
      mid = env->GetMethodID(clazz, "getTime", "()J");
 
929
      if (isJavaExceptionThrown())
 
930
        return 0;
 
931
    }
 
932
 
 
933
  jlong jmillis = env->CallLongMethod(jdate, mid);
 
934
  if (isJavaExceptionThrown())
 
935
    return 0;
 
936
 
 
937
  env->DeleteLocalRef(clazz);
 
938
 
 
939
  return jmillis * 1000;
 
940
}
 
941
 
822
942
/**
823
943
 * Create a Java byte array from an array of characters.
824
944
 * @param data      the character array
826
946
 */
827
947
jbyteArray JNIUtil::makeJByteArray(const void *data, int length)
828
948
{
829
 
  if (data == NULL)
830
 
    {
831
 
      // a NULL will create no Java array
832
 
      return NULL;
833
 
    }
 
949
  // a NULL will create no Java array
 
950
  if (!data)
 
951
    return NULL;
834
952
 
835
953
  JNIEnv *env = getEnv();
836
954
 
861
979
 */
862
980
jbyteArray JNIUtil::makeJByteArray(const svn_string_t *str)
863
981
{
 
982
  // a NULL will create no Java array
 
983
  if (!str)
 
984
    return NULL;
 
985
 
864
986
  return JNIUtil::makeJByteArray(str->data, static_cast<int>(str->len));
865
987
}
866
988
 
867
989
/**
868
 
 * Build the error message from the svn error into buffer.  This
869
 
 * method calls itselft recursively for all the chained errors
870
 
 *
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
875
 
 *                          be stored
876
 
 */
877
 
void JNIUtil::assembleErrorMessage(svn_error_t *err, int depth,
878
 
                                   apr_status_t parent_apr_err,
879
 
                                   std::string &buffer)
880
 
{
881
 
  // buffer for a single error message
882
 
  char errbuf[256];
883
 
 
884
 
  /* Pretty-print the error */
885
 
  /* Note: we can also log errors here someday. */
886
 
 
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)
890
 
    {
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. */
896
 
      else
897
 
        {
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);
904
 
          if (utf8_err)
905
 
            {
906
 
              /* Use fuzzy transliteration instead. */
907
 
              svn_error_clear(utf8_err);
908
 
              utf8_message = svn_utf_cstring_from_utf8_fuzzy(errbuf, err->pool);
909
 
            }
910
 
          buffer.append(utf8_message);
911
 
        }
912
 
      buffer.append("\n");
913
 
    }
914
 
  if (err->message)
915
 
    buffer.append(_("svn: ")).append(err->message).append("\n");
916
 
 
917
 
  if (err->child)
918
 
    assembleErrorMessage(err->child, depth + 1, err->apr_err, buffer);
919
 
 
920
 
}
921
 
 
922
 
/**
923
990
 * Throw a Java NullPointerException.  Used when input parameters
924
991
 * which should not be null are that.
925
992
 *
936
1003
    return;
937
1004
 
938
1005
  env->ThrowNew(clazz, message);
939
 
  setExceptionThrown();
940
 
  env->DeleteLocalRef(clazz);
941
1006
}
942
1007
 
943
1008
svn_error_t *JNIUtil::preprocessPath(const char *&path, apr_pool_t *pool)
996
1061
 
997
1062
  return NULL;
998
1063
}
 
1064
 
 
1065
/* Tag to use on the apr_pool_t to store a WrappedException reference */
 
1066
static const char *WrapExceptionTag = "org.apache.subversion.JavaHL.svnerror";
 
1067
 
 
1068
class WrappedException
 
1069
{
 
1070
  JNIEnv *m_env;
 
1071
  jthrowable m_exception;
 
1072
#ifdef SVN_DEBUG
 
1073
  bool m_fetched;
 
1074
#endif
 
1075
public:
 
1076
  WrappedException(JNIEnv *env)
 
1077
  {
 
1078
    m_env = env;
 
1079
 
 
1080
    // Fetch exception inside local frame
 
1081
    jthrowable exceptionObj = env->ExceptionOccurred();
 
1082
 
 
1083
    // Now clear exception status
 
1084
    env->ExceptionClear();
 
1085
 
 
1086
    // As adding a reference in exception state fails
 
1087
    m_exception = static_cast<jthrowable>(env->NewGlobalRef(exceptionObj));
 
1088
 
 
1089
#ifdef SVN_DEBUG
 
1090
    m_fetched = false;
 
1091
#endif
 
1092
  }
 
1093
 
 
1094
  static jthrowable get_exception(apr_pool_t *pool)
 
1095
  {
 
1096
      void *data;
 
1097
      if (! apr_pool_userdata_get(&data, WrapExceptionTag, pool))
 
1098
      {
 
1099
          WrappedException *we = reinterpret_cast<WrappedException *>(data);
 
1100
 
 
1101
          if (we)
 
1102
          {
 
1103
#ifdef SVN_DEBUG
 
1104
              we->m_fetched = TRUE;
 
1105
#endif
 
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));
 
1109
          }
 
1110
      }
 
1111
      return NULL;
 
1112
  }
 
1113
 
 
1114
private:
 
1115
  ~WrappedException()
 
1116
  {
 
1117
#ifdef SVN_DEBUG
 
1118
      if (!m_fetched)
 
1119
          SVN_DBG(("Cleared svn_error_t * before Java exception was fetched"));
 
1120
#endif
 
1121
      m_env->DeleteGlobalRef(m_exception);
 
1122
  }
 
1123
public:
 
1124
  static apr_status_t cleanup(void *data)
 
1125
  {
 
1126
    WrappedException *we = reinterpret_cast<WrappedException *>(data);
 
1127
 
 
1128
    delete we;
 
1129
    return APR_SUCCESS;
 
1130
  }
 
1131
};
 
1132
 
 
1133
svn_error_t* JNIUtil::wrapJavaException()
 
1134
{
 
1135
  if (!isExceptionThrown())
 
1136
    return SVN_NO_ERROR;
 
1137
 
 
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);
 
1142
  return err;
 
1143
}
 
1144
 
 
1145
jthrowable JNIUtil::unwrapJavaException(const svn_error_t *err)
 
1146
{
 
1147
    if (!err)
 
1148
        return NULL;
 
1149
    return
 
1150
        WrappedException::get_exception(err->pool);
 
1151
}