1 /**
2  * @copyright
3  * ====================================================================
4  *    Licensed to the Apache Software Foundation (ASF) under one
5  *    or more contributor license agreements.  See the NOTICE file
6  *    distributed with this work for additional information
7  *    regarding copyright ownership.  The ASF licenses this file
8  *    to you under the Apache License, Version 2.0 (the
9  *    "License"); you may not use this file except in compliance
10  *    with the License.  You may obtain a copy of the License at
11  *
12  *      http://www.apache.org/licenses/LICENSE-2.0
13  *
14  *    Unless required by applicable law or agreed to in writing,
15  *    software distributed under the License is distributed on an
16  *    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17  *    KIND, either express or implied.  See the License for the
18  *    specific language governing permissions and limitations
19  *    under the License.
20  * ====================================================================
21  * @endcopyright
22  */
23 
24 #include <cstring>
25 #include <memory>
26 #include <apr.h>
27 
28 #include "jni_env.hpp"
29 #include "jni_globalref.hpp"
30 #include "jni_exception.hpp"
31 #include "jni_object.hpp"
32 #include "jni_string.hpp"
33 #include "jni_array.hpp"
34 #include "jni_stack.hpp"
35 
36 #include "../JNIUtil.h"
37 bool initialize_jni_util(JNIEnv *env);
38 
39 #include "svn_private_config.h"
40 
41 
42 // Global library initializaiton
43 
44 /**
45  * Initializer function, called just after the JVM loads the native
46  * library.  Stores the global JVM reference and creates the global
47  * class cache.
48  */
49 JNIEXPORT jint JNICALL
JNI_OnLoad(JavaVM * jvm,void *)50 JNI_OnLoad(JavaVM* jvm, void*)
51 {
52   ::Java::Env::static_init(jvm);
53   const ::Java::Env env;
54 
55   const apr_status_t status = apr_initialize();
56   if (!status)
57     ::Java::ClassCache::create();
58   else
59     {
60       char buf[2048];
61       std::strcpy(buf, "Could not initialize APR: ");
62       const std::size_t offset = std::strlen(buf);
63       apr_strerror(status, buf + offset, sizeof(buf) - offset - 1);
64       env.ThrowNew(env.FindClass("java/lang/Error"), buf);
65     }
66 
67   // Initialize the old-style JavaHL infrastructure.
68   if (!initialize_jni_util(env.get()) && !env.ExceptionCheck())
69     {
70       env.ThrowNew(env.FindClass("java/lang/LinkageError"),
71                    "Native library initialization failed");
72     }
73 
74   return JNI_VERSION_1_2;
75 }
76 
77 /**
78  * Cleanup function, called just before the JVM unloads the native
79  * library.  Destroys the global class cache.
80  */
81 JNIEXPORT void JNICALL
JNI_OnUnload(JavaVM *,void *)82 JNI_OnUnload(JavaVM*, void*)
83 {
84   ::Java::ClassCache::destroy();
85   apr_terminate();
86 }
87 
88 
89 namespace Java {
90 
91 // class Java::Env
92 
93 ::JavaVM* Env::m_jvm = NULL;
static_init(::JavaVM * jvm)94 void Env::static_init(::JavaVM* jvm)
95 {
96   m_jvm = jvm;
97 }
98 
error_create_global_reference()99 const char* Env::error_create_global_reference() throw()
100 {
101   return _("Could not create global reference");
102 }
103 
error_get_contents_string()104 const char* Env::error_get_contents_string() throw()
105 {
106   return _("Could not get contents of Java String");
107 }
108 
error_release_null_string()109 const char* Env::error_release_null_string() throw()
110 {
111   return _("Could not release contents of a null String");
112 }
113 
error_create_object_array()114 const char* Env::error_create_object_array() throw()
115 {
116   return _("Could not create Object array");
117 }
118 
119 namespace {
120 // The typed array error messages are always fatal, so allocating
121 // the buffer on the heap and never releasing it does not really
122 // constitute a memory leak.
make_typed_error(const char * fmt,const char * type)123 const char* make_typed_error(const char* fmt, const char* type) throw()
124 {
125   const apr_size_t bufsize = 512;
126   char *msg = new(std::nothrow) char[bufsize];
127   apr_snprintf(msg, bufsize, fmt, type);
128   return msg;
129 }
130 } // anonymous namespace
131 
error_create_array(const char * type)132 const char* Env::error_create_array(const char* type) throw()
133 {
134   return make_typed_error(_("Could not create %sArray"), type);
135 }
136 
error_get_contents_array(const char * type)137 const char* Env::error_get_contents_array(const char* type) throw()
138 {
139   return make_typed_error(_("Could not get %s array contents"), type);
140 }
141 
error_release_null_array(const char * type)142 const char* Env::error_release_null_array(const char* type) throw()
143 {
144   return make_typed_error(
145       _("Could not release contents of a null %sArray"), type);
146 }
147 
env_from_jvm()148 ::JNIEnv* Env::env_from_jvm()
149 {
150   if (m_jvm)
151     {
152       void *penv;
153       switch (m_jvm->GetEnv(&penv, JNI_VERSION_1_2))
154         {
155         case JNI_OK:
156           return static_cast<JNIEnv*>(penv);
157 
158         case JNI_EDETACHED:
159           throw std::runtime_error(
160               _("Native thread is not attached to a Java VM"));
161 
162         case JNI_EVERSION:
163           throw std::runtime_error(_("Unsupported JNI version"));
164 
165         default:
166           throw std::runtime_error(_("Invalid JNI environment"));
167         }
168     }
169   throw std::logic_error(_("JavaVM instance was not initialized"));
170 }
171 
throw_java_out_of_memory(const char * message) const172 void Env::throw_java_out_of_memory(const char* message) const
173 {
174   OutOfMemoryError(*this).raise(message);
175 }
176 
177 // class Java::LocalFrame
178 
179 const jint LocalFrame::DEFAULT_CAPACITY = 16;
180 
181 
182 // Class Java::Object
183 
184 const char* const Object::m_class_name = "java/lang/Object";
~ClassImpl()185 Object::ClassImpl::~ClassImpl() {}
186 
187 
188 // Class Java::Class
189 
190 const char* const Class::m_class_name = "java/lang/Class";
~ClassImpl()191 Class::ClassImpl::~ClassImpl() {}
192 
193 MethodID Class::m_mid_get_class;
194 MethodID Class::m_mid_get_name;
195 
static_init(Env env,jclass cls)196 void Class::static_init(Env env, jclass cls)
197 {
198   m_mid_get_class = env.GetMethodID(
199       ClassCache::get_object(env)->get_class(),
200       "getClass", "()Ljava/lang/Class;");
201   m_mid_get_name = env.GetMethodID(
202       cls, "getName",  "()Ljava/lang/String;");
203 }
204 
205 namespace{
get_class_of_object(Env env,jobject obj,jmethodID mid_get_class)206 jobject get_class_of_object(Env env, jobject obj, jmethodID mid_get_class)
207 {
208   if (!obj)
209     return NULL;
210   return env.CallObjectMethod(obj, mid_get_class);
211 }
212 } // anonymous namespace
213 
Class(Env env,jobject obj)214 Class::Class(Env env, jobject obj)
215   : m_env(env),
216     m_jthis(get_class_of_object(env, obj, m_mid_get_class))
217 {}
218 
Class(const Object & obj)219 Class::Class(const Object& obj)
220   : m_env(obj.get_env()),
221     m_jthis(get_class_of_object(obj.get_env(), obj.get(), m_mid_get_class))
222 {}
223 
get_name() const224 jstring Class::get_name() const
225 {
226   if (!m_jthis)
227     return NULL;
228   return jstring(m_env.CallObjectMethod(m_jthis, m_mid_get_name));
229 }
230 
231 
232 // Class Java::String
233 
234 const char* const String::m_class_name = "java/lang/String";
~ClassImpl()235 String::ClassImpl::~ClassImpl() {}
236 
strdup(apr_pool_t * pool) const237 const char* String::strdup(apr_pool_t* pool) const
238 {
239   return apr_pstrdup(pool, String::Contents(*this).c_str());
240 }
241 
set_value(const char * new_text)242 void String::MutableContents::set_value(const char* new_text)
243 {
244   if (!m_new_text)
245     throw std::invalid_argument(
246         _("Cannot set String contents to null"));
247   if (m_text)
248     {
249       m_new_text = new_text;
250       m_length = jsize(::std::strlen(new_text));
251     }
252   else
253     throw std::logic_error(
254         _("Cannot change the contents of a null String"));
255 }
256 
257 // class Java::Exception
258 
throw_java_exception() const259 void Exception::throw_java_exception() const
260 {
261   if (instantiated()
262       ? m_env.Throw(m_jthis)
263       : m_env.ThrowNew(m_class, NULL))
264     throw std::runtime_error(_("Could not throw Java exception"));
265 }
266 
throw_java_exception(const char * message) const267 void Exception::throw_java_exception(const char* message) const
268 {
269   if (m_env.ThrowNew(m_class, message))
270     throw std::runtime_error(_("Could not throw Java exception"));
271 }
272 
get_message() const273 jstring Exception::get_message() const
274 {
275   if (instantiated())
276     return jstring(m_env.CallObjectMethod(m_jthis, m_mid_get_message));
277   throw std::logic_error(_("Could not get exception message:"
278                            " Exception instance is not available"));
279 }
280 
281 const char* const Exception::m_class_name = "java/lang/Throwable";
~ClassImpl()282 Exception::ClassImpl::~ClassImpl() {}
283 
284 MethodID Exception::m_mid_get_message;
static_init(Env env,jclass cls)285 void Exception::static_init(Env env, jclass cls)
286 {
287   m_mid_get_message = env.GetMethodID(
288       cls, "getMessage", "()Ljava/lang/String;");
289 }
290 
291 // Other exception class initializers
292 
293 const char* const RuntimeException::m_class_name =
294   "java/lang/RuntimeException";
295 
296 const char* const NullPointerException::m_class_name =
297   "java/lang/NullPointerException";
298 
299 const char* const OutOfMemoryError::m_class_name =
300   "java/lang/OutOfMemoryError";
301 
302 const char* const IndexOutOfBoundsException::m_class_name =
303   "java/lang/IndexOutOfBoundsException";
~ClassImpl()304 IndexOutOfBoundsException::ClassImpl::~ClassImpl() {}
305 
306 const char* const IOException::m_class_name =
307   "java/io/IOException";
308 
309 const char* const IllegalArgumentException::m_class_name =
310   "java/lang/IllegalArgumentException";
311 
312 const char *const NoSuchElementException::m_class_name =
313   "java/util/NoSuchElementException";
~ClassImpl()314 NoSuchElementException::ClassImpl::~ClassImpl() {}
315 
316 // Implementation of jni_stack.hpp
317 
handle_svn_error(Env env,::svn_error_t * err)318 void handle_svn_error(Env env, ::svn_error_t* err)
319 {
320   jthrowable cause = NULL;
321 
322   // If the exception being currently thrown was generated by the
323   // JavaHL bindings, then assume the error was propagated through
324   // native code and do not re-throw it.
325   if (env.ExceptionCheck())
326     {
327       cause = env.ExceptionOccurred();
328       if (env.IsInstanceOf(
329               cause, ClassCache::get_subversion_exception(env)->get_class()))
330         {
331           // XXX FIXME: Should really have a special error code
332           // specifically for propagating Java exceptions from
333           // callbacks through native code.
334           svn_error_clear(err);
335           throw SignalExceptionThrown();
336         }
337     }
338 
339   // Make sure there's only a single exception in the environment.
340   if (cause)
341     env.ExceptionClear();
342 
343   ::JNIUtil::handleSVNError(err, cause);
344   throw SignalExceptionThrown();
345 }
346 
unknown_cxx_exception_message()347 const char* unknown_cxx_exception_message() throw()
348 {
349   return _("Caught unknown C++ exception");
350 }
351 
caught_java_exception_error(apr_status_t status)352 svn_error_t* caught_java_exception_error(apr_status_t status) throw()
353 {
354   return svn_error_create(status, JNIUtil::wrapJavaException(),
355                           _("Java exception"));
356 }
357 
358 } // namespace Java
359