1 /*
2  Copyright (c) 2010, Oracle and/or its affiliates. All rights reserved.
3 
4  This program is free software; you can redistribute it and/or modify
5  it under the terms of the GNU General Public License, version 2.0,
6  as published by the Free Software Foundation.
7 
8  This program is also distributed with certain software (including
9  but not limited to OpenSSL) that is licensed under separate terms,
10  as designated in a particular file or component or in included license
11  documentation.  The authors of MySQL hereby grant you an additional
12  permission to link the program and your derivative works with the
13  separately licensed software that they have included with MySQL.
14 
15  This program is distributed in the hope that it will be useful,
16  but WITHOUT ANY WARRANTY; without even the implied warranty of
17  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18  GNU General Public License, version 2.0, for more details.
19 
20  You should have received a copy of the GNU General Public License
21  along with this program; if not, write to the Free Software
22  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA
23 */
24 /*
25  * jtie_tconv_array.hpp
26  */
27 
28 #ifndef jtie_tconv_array_impl_hpp
29 #define jtie_tconv_array_impl_hpp
30 
31 #include <assert.h> // not using namespaces yet
32 #include <jni.h>
33 
34 #include "jtie_tconv_value.hpp"
35 #include "jtie_tconv_value_impl.hpp"
36 #include "jtie_tconv_object_impl.hpp"
37 
38 // ---------------------------------------------------------------------------
39 // Utilities for Java array <-> C array type conversions
40 // ---------------------------------------------------------------------------
41 
42 /**
43  * A class with helper methods to convert native arrays into Java arrays
44  * and vice versa.
45  * ArrayConv's function signatures support both, const and non-const base type
46  * specializations.
47  *
48  * This class only documents prototypes, no functions are declared or
49  * defined here, in order to have undefined type mappings result in a
50  * compilation error.
51  */
52 template< typename JA,
53           typename CA >
54 struct ArrayConv {
55     /**
56      * Returns the body of the primitive array, or NULL if the operation
57      * fails.
58      * The result is valid until releaseArrayElements() is called.
59      * Returns a non-const C array to allow for use in non-const context.
60      *
61      * Pre condition:
62      * - no JNI exception is pending: assert(!env->ExceptionCheck())
63      * - argument: assert(j != NULL)
64      * - argument: assert(env != NULL)
65      *
66      * Post condition:
67      * - return value:
68      *   non-NULL:
69      *     - this thread has no pending JNI exception (!env->ExceptionCheck())
70      *     - the return value is valid and initialized array
71      *     - corresponding releaseArrayElements() must be called
72      *   NULL:
73      *     - this thread has a pending JNI exception (env->ExceptionCheck())
74      *     - corresponding releaseArrayElements() must not be called
75      */
76 #if 0 // disabled on purpose, only document function
77     static CA
78     getArrayElements(JNIEnv * env, JA j, jboolean * isCopy) {
79         TRACE("CA ArrayConv.getArrayElements(JNIEnv *, JA, jboolean *)");
80         (void)env; (void)j; (void)isCopy;
81         static_assert(false, "missing specialization of array conversion");
82         return 0;
83     }
84 #endif // disabled on purpose, only document function
85 
86     /**
87      * Informs the VM that the native code no longer needs access to elems.
88      * Accepts a const C array to allow for use in const context.
89      *
90      * Pre condition:
91      * - getArrayElements() has been called with 'j' and returned 'c'
92      * - argument: assert(j != NULL)
93      * - argument: assert(c != NULL)
94      * - argument: assert(env != NULL)
95      *
96      * Post condition:
97      * - In case of a pending exception (env->ExceptionCheck()), only these
98      *   safe JNI functions have been called:
99      *     - ExceptionOccurred
100      *     - ExceptionDescribe
101      *     - ExceptionClear
102      *     - ExceptionCheck
103      *     - ReleaseStringChars
104      *     - ReleaseStringUTFchars
105      *     - ReleaseStringCritical
106      *     - Release<Type>ArrayElements
107      *     - ReleasePrimitiveArrayCritical
108      *     - DeleteLocalRef
109      *     - DeleteGlobalRef
110      *     - DeleteWeakGlobalRef
111      *     - MonitorExit
112      */
113 #if 0 // disabled on purpose, only document function
114     static void
115     releaseArrayElements(JNIEnv * env, JA j, const CA c, jint mode) {
116         TRACE("void ArrayConv.releaseArrayElements(JNIEnv *, JA, const CA, jint)");
117         (void)env; (void)j; (void)c; (void)mode;
118         static_assert(false, "missing specialization of array conversion");
119         return 0;
120     }
121 #endif // disabled on purpose, only document function
122 
123     /**
124      * Constructs a new primitive array object with elements from a buffer.
125      * Accepts a const C array to allow for use in const context.
126      *
127      * Pre condition:
128      * - no JNI exception is pending: assert(!env->ExceptionCheck())
129      * - argument: assert(c != NULL)
130      * - argument: assert(env != NULL)
131      *
132      * Post condition:
133      * - return value:
134      *   non-NULL:
135      *     - this thread has no pending JNI exception (!env->ExceptionCheck())
136      *     - the return value is a valid and initialized Java array
137      *   NULL:
138      *     - a JNI exception is pending (env->ExceptionCheck())
139      *
140      *   In other words, any errors during the result conversion must be
141      *   signaled by registering a Java exception with the VM.
142      */
143 #if 0 // disabled on purpose, only document function
144     static JA
145     newArray(JNIEnv * env, jsize len, const CA c) {
146         TRACE("JA ArrayConv.newArray(JNIEnv *, jsize, const CA)");
147         (void)env; (void)len; (void)c;
148         static_assert(false, "missing specialization of array conversion");
149         return 0;
150     }
151 #endif // disabled on purpose, only document function
152 };
153 
154 /**
155  * Implements ArrayConv on primitive array types.
156  *
157  * The JNI headers, but not the spec, of JDKs >= 1.4 declare the last
158  * parameter of Set<Jtype>ArrayRegion(..., const J *) correctly as const
159  */
160 template< typename JA,
161           typename J,
162           typename C,
163           J * (JNIEnv::*GET)(JA, jboolean *),
164           void (JNIEnv::*REL)(JA, J *, jint),
165           JA (JNIEnv::*NEW)(jsize),
166           void (JNIEnv::*SET)(JA, jsize, jsize, const J *) >
167 struct PrimArrayConvImpl {
168     static C *
getArrayElementsPrimArrayConvImpl169     getArrayElements(JNIEnv * env, JA j, jboolean * isCopy) {
170         TRACE("C * PrimArrayConvImpl.getArrayElements(JNIEnv *, JA, jboolean *)");
171         // XXX currently, only exact-width base type conversions supported
172         assert(sizeof(J) == sizeof(C));
173         assert(j != NULL);
174 
175         // init return value to error
176         C * c = NULL;
177 
178         J * ja = (env->*GET)(j, isCopy);
179         if (env->ExceptionCheck() != JNI_OK) {
180             // exception pending
181         } else {
182             // the JNI Spec (1.4..6) on Get<PrimitiveType>ArrayElements is
183             // not explicit on whether an exception has been registered
184             // when the operation returns NULL; so, better code defensively:
185             if (ja == NULL) {
186                 const char * cl = "java/lang/AssertionError";
187                 const char * m = ("JTie: a JNI Get<PrimitiveType>ArrayElements"
188                                   " function call returned NULL but has not"
189                                   " registered an exception with the VM"
190                                   " (file: " __FILE__ ")");
191                 registerException(env, cl, m);
192             } else {
193                 // ok, convert pointer types
194                 c = reinterpret_cast< C * >(ja);
195             }
196         }
197         return c;
198     }
199 
200     static void
releaseArrayElementsPrimArrayConvImpl201     releaseArrayElements(JNIEnv * env, JA j, const C * c, jint mode) {
202         TRACE("void PrimArrayConvImpl.releaseArrayElements(JNIEnv *, JA, const C *, jint)");
203         // XXX currently, only exact-width base type conversions supported
204         assert(sizeof(J) == sizeof(C));
205         assert(j != NULL);
206         assert(c != NULL);
207 
208         if (c != NULL) {
209             // ok to strip const, pinned arrays are not changed by release
210             // and copies cannot be used after release
211             C * ca = const_cast< C * >(c);
212             // convert pointer types
213             (env->*REL)(j, reinterpret_cast< J * >(ca), mode);
214         }
215     }
216 
217     static JA
newArrayPrimArrayConvImpl218     newArray(JNIEnv * env, jsize len, const C * c) {
219         TRACE("JA PrimArrayConvImpl.newArray(JNIEnv *, jsize, const C *)");
220         // XXX currently, only exact-width base type conversions supported
221         assert(sizeof(J) == sizeof(C));
222         assert(c != NULL);
223 
224         // init return value to error
225         JA j = NULL;
226 
227         JA ja = (env->*NEW)(len);
228         if (env->ExceptionCheck() != JNI_OK) {
229             // exception pending
230         } else {
231             // the JNI Spec (1.4..6) on New<PrimitiveType>Array is not
232             // explicit on whether an exception has been registered when the
233             // operation returns NULL; so, better code defensively:
234             if (ja == NULL) {
235                 const char * cl = "java/lang/AssertionError";
236                 const char * m = ("JTie: a JNI New<PrimitiveType>Array"
237                                   " function call returned NULL but has not"
238                                   " registered an exception with the VM"
239                                   " (file: " __FILE__ ")");
240                 registerException(env, cl, m);
241             } else {
242                 // convert pointer types
243                 const J * cjc = reinterpret_cast< const J * >(c);
244 
245                 // copy values to Java array
246                 (env->*SET)(ja, 0, len, cjc);
247                 if (env->ExceptionCheck() != JNI_OK) {
248                     // exception pending
249                     assert(false); // coding error: invalid index
250                 } else {
251                     // ok
252                     j = ja;
253                 }
254             }
255         }
256         return j;
257     }
258 
259 private:
PrimArrayConvImplPrimArrayConvImpl260     PrimArrayConvImpl() {
261         // prohibit unsupported array type casts
262         is_valid_primitive_type_mapping< J, C >();
263     }
264 };
265 
266 /**
267  * Implements ArrayConv for Java Object array types.
268  *
269  * Please, note that on balance this type of object array mapping, while
270  * having a few merits, has turned out inferior to other array mappings:  It
271  * - comes with an unavoidable performance overhead (value-copy semantics)
272  * - displays different argument conversion semantics (pass-by-value for
273  *   Java-to-C while pass-by-reference for C-to-Java) and
274  * - hence, complicates the usage and caller's object management (making it
275  *   more error-prone to memory leaks or integrity errors), as well as
276  * - hampers the maintenance of this class due to fine points in the code.
277  *
278  * The reason for the difficulties of this mapping stems from an asymmetry:
279  * The natural 1-1 mapping of Java 'MyClass[]' is C 'MyClass**' while C
280  * 'MyClass*', strictly speaking, has no Java Object array pendant, for
281  * lack of notion of (contigous arrays of) embedded Java Objects.
282  *
283  * While assumed to work, this mapping has not been used and tested yet;
284  * we're keeping it, for it still meets best Java programmers' expectations
285  * for C object arrays to be mapped to Java Object[].
286  */
287 template< typename J, typename C >
288 struct ObjectArrayConvImpl {
289     static C *
getArrayElementsObjectArrayConvImpl290     getArrayElements(JNIEnv * env, jobjectArray j, jboolean * isCopy) {
291         TRACE("C * ObjectArrayConvImpl.getArrayElements(JNIEnv *, jobjectArray, jboolean *)");
292         assert(j != NULL);
293 
294         // init return value to error
295         C * c = NULL;
296 
297         const jsize n = env->GetArrayLength(j);
298         if (env->ExceptionCheck() != JNI_OK) {
299             // exception pending
300             assert(false); // coding error: invalid argument
301         } else {
302             // ISO C++: 'new' throws std::bad_alloc if unsuccessful
303             C * ca = new C[n];
304 
305             cstatus s = copyToCObjectArray(ca, j, n, env);
306             if (s != 0) {
307                 // exception pending
308                 assert(env->ExceptionCheck() != JNI_OK);
309                 delete[] ca;
310             } else {
311                 // assign isCopy out parameter
312                 if (isCopy != NULL) {
313                     *isCopy
314                         = ResultBasicT< jboolean, bool >::convert(true, env);
315                 }
316 
317                 // ok
318                 c = ca;
319             }
320         }
321         return c;
322     }
323 
324     static void
releaseArrayElementsObjectArrayConvImpl325     releaseArrayElements(JNIEnv * env, jobjectArray j, const C * c, jint mode) {
326         TRACE("void ObjectArrayConvImpl.releaseArrayElements(JNIEnv *, jobjectArray, const C *, jint)");
327         assert(j != NULL);
328         assert(c != NULL);
329         delete[] c;
330     }
331 
332     static jobjectArray
newArrayObjectArrayConvImpl333     newArray(JNIEnv * env, jsize len, const C * c) {
334         TRACE("jobjectArray ObjectArrayConvImpl.newArray(JNIEnv *, jsize, const C *)");
335         assert(c != NULL);
336 
337         // init return value to error
338         jobjectArray j = NULL;
339         jobjectArray ja = NULL;
340 
341         // get a (local or global) class object reference
342         jclass cls = ObjectResult< J *, C * >::J_ctor::getClass(env);
343         if (cls == NULL) {
344             // exception pending
345         } else {
346             J * ja = newJavaObjectArray(cls, len, env);
347             if (ja == NULL) {
348                 // exception pending
349             } else {
350                 cstatus s = copyToJavaObjectArray(ja, c, len, env);
351                 if (s != 0) {
352                     // exception pending
353                     assert(env->ExceptionCheck() != JNI_OK);
354                 } else {
355                     // ok
356                     j = ja;
357                 }
358             }
359 
360             // release reference (if needed)
361             ObjectResult< J *, C * >::J_ctor::releaseRef(env, cls);
362         }
363         return j;
364     }
365 
366 private:
367     // Returns a new Java Object array with all elements initialized to null;
368     // in case of a NULL return, an exception is pending.
369     J *
370     newJavaObjectArray(jclass cls, jsize n, JNIEnv * env);
371 
372     // Copies objects referred by a Java Object array over a C object array;
373     // a non-zero return value indicates failure with an exception pending.
374     cstatus
375     copyToCObjectArray(C * c, jobjectArray j, jsize n, JNIEnv * env);
376 
377     // Initializes a Java Object array with references from a C object array;
378     // a non-zero return value indicates failure with an exception pending.
379     cstatus
380     copyToJavaObjectArray(jobjectArray j, C * c, jsize n, JNIEnv * env);
381 };
382 
383 template< typename J, typename C >
384 inline J *
385 ObjectArrayConvImpl< J, C >::
newJavaObjectArray(jclass cls,jsize n,JNIEnv * env)386 newJavaObjectArray(jclass cls, jsize n, JNIEnv * env) {
387     assert(cls);
388 
389     // init return value to error
390     J * j = NULL;
391 
392     jobjectArray ja = env->NewObjectArray(n, cls, NULL);
393     if (env->ExceptionCheck() != JNI_OK) {
394         // exception pending
395     } else {
396         // the JNI Spec (1.4..6) on NewObjectArray is not explicit on
397         // whether an exception has been registered when the operation
398         // returns NULL; so, better code defensively:
399         if (ja == NULL) {
400             const char * cl = "java/lang/AssertionError";
401             const char * m = ("JTie: a JNI NewObjectArray function call"
402                               " returned NULL but has not registered an"
403                               " exception with the VM"
404                               " (file: " __FILE__ ")");
405             registerException(env, cl, m);
406         } else {
407             // ok
408             j = ja;
409         }
410     }
411     return j;
412 }
413 
414 template< typename J, typename C >
415 inline cstatus
416 ObjectArrayConvImpl< J, C >::
copyToCObjectArray(C * c,jobjectArray j,jsize n,JNIEnv * env)417 copyToCObjectArray(C * c, jobjectArray j, jsize n, JNIEnv * env) {
418     assert(j != NULL);
419     assert(c != NULL);
420 
421     // init return value to error
422     cstatus s = -1;
423 
424     // copy objects referenced from Java array to new array
425     jsize i;
426     for (i = 0; i < n; i++) {
427         // get the Java array element
428         _jobject * jfo = env->GetObjectArrayElement(j, i);
429         if (env->ExceptionCheck() != JNI_OK) {
430             // exception pending
431             assert(false); // coding error: invalid index
432             break;
433         }
434         assert(env->ExceptionCheck() == JNI_OK);
435 
436         // get the instance referenced by Java array element
437         _jtie_Object * jao = cast< _jtie_Object *, _jobject * >(jfo);
438         if (jao == NULL) {
439             const char * cl = "java/lang/IllegalArgumentException";
440             const char * m = ("JTie: the Java Object array must not have"
441                               " null as elements when mapped to a"
442                               " C object array (file: " __FILE__ ")");
443             registerException(env, cl, m);
444             break;
445         }
446         assert(jao != NULL);
447 
448         // get the C/C++ object referenced by Java array element
449         C * co = ObjectParam< _jtie_Object *, C * >::convert(s, jao, env);
450         assert(s != 0 || co != NULL);
451         if (s != 0) {
452             // exception pending
453             break;
454         }
455         assert(co != NULL);
456 
457         // copy referenced object to array element
458         // - copy-by-value semantics for Java only knows object references;
459         // - therefore, this mapping requires an accessible copy c'tor;
460         // - asymmetry to copyToCObjectArray()'s reference semantics
461         c[i] = *co;
462     }
463     if (i < n) {
464         // exception pending
465     } else {
466         // ok
467         s = 0;
468     }
469 
470     return c;
471 }
472 
473 template< typename J, typename C >
474 inline cstatus
475 ObjectArrayConvImpl< J, C >::
copyToJavaObjectArray(jobjectArray j,C * c,jsize n,JNIEnv * env)476 copyToJavaObjectArray(jobjectArray j, C * c, jsize n, JNIEnv * env) {
477     assert(c != NULL);
478     assert(j != NULL);
479 
480     // init return value to error
481     cstatus s = -1;
482 
483     // copy C object references to Java array
484     jsize i;
485     for (i = 0; i < n; i++) {
486         // obtain reference to array element
487         // - no need for value-copy, would burden app's object management
488         // - asymmetry to copyToJavaObjectArray()'s value-copy semantics
489         C * co = c + i;
490 
491         // get a Java object reference
492         J * jao = ObjectResult< J *, C * >::convert(co, env);
493         if (jao == NULL) {
494             // exception pending
495             assert(env->ExceptionCheck() != JNI_OK);
496             break;
497         }
498         assert(jao != NULL);
499 
500         // set the Java array element
501         _jobject * jfo = cast< _jobject *, J * >(jao);
502         env->SetObjectArrayElement(j, i, jfo);
503         if (env->ExceptionCheck() != JNI_OK) {
504             // exception pending
505             assert(false); // coding error: invalid index or jao not subclass
506             break;
507         }
508         assert(env->ExceptionCheck() == JNI_OK);
509     }
510     if (i < n) {
511         // exception pending
512     } else {
513         // ok
514         s = 0;
515     }
516 
517     return c;
518 }
519 
520 // ---------------------------------------------------------------------------
521 // Specializations for array conversions
522 // ---------------------------------------------------------------------------
523 
524 // Avoid mapping types by broad, generic rules, which easily results in
525 // template instantiation ambiguities for non-primitive types.  Therefore,
526 // we enumerate all specicializations for primitive types.
527 
528 // extend specializations to array of const
529 // Not sure why this generalized const specialization
530 //   template< typename JA,
531 //             typename CA >
532 //   struct ArrayConv< JA, const CA > : ArrayConv< JA, CA > {};
533 // doesn't resolve (no effect); specializing individually for each type then.
534 
535 // specialize arrays conversion helper (for non-const and const)
536 #define JTIE_SPECIALIZE_ARRAY_TYPE_HELPER( JA, J, JN, C )               \
537     template<>                                                          \
538     struct ArrayConv< JA, C * >                                         \
539         : PrimArrayConvImpl< JA, J, C,                                  \
540                          &JNIEnv::Get##JN##ArrayElements,               \
541                          &JNIEnv::Release##JN##ArrayElements,           \
542                          &JNIEnv::New##JN##Array,                       \
543                          &JNIEnv::Set##JN##ArrayRegion > {};            \
544     template<>                                                          \
545     struct ArrayConv< JA, const C * >                                   \
546         : ArrayConv< JA, C * > {};
547 
548 // ---------------------------------------------------------------------------
549 // Specializations for exact-width type array conversions
550 // ---------------------------------------------------------------------------
551 
552 JTIE_SPECIALIZE_ARRAY_TYPE_HELPER(jbooleanArray, jboolean, Boolean, bool)
553 
554 JTIE_SPECIALIZE_ARRAY_TYPE_HELPER(jbyteArray, jbyte, Byte, char)
555 JTIE_SPECIALIZE_ARRAY_TYPE_HELPER(jbyteArray, jbyte, Byte, signed char)
556 JTIE_SPECIALIZE_ARRAY_TYPE_HELPER(jbyteArray, jbyte, Byte, unsigned char)
557 
558 JTIE_SPECIALIZE_ARRAY_TYPE_HELPER(jfloatArray, jfloat, Float, float)
559 JTIE_SPECIALIZE_ARRAY_TYPE_HELPER(jdoubleArray, jdouble, Double, double)
560 
561 
562 // ---------------------------------------------------------------------------
563 // Specializations for variable-width type array conversions
564 // ---------------------------------------------------------------------------
565 
566 // jshort in LP32, ILP32, LP64, ILP64, LLP64
567 JTIE_SPECIALIZE_ARRAY_TYPE_HELPER(jshortArray, jshort, Short, signed short)
568 JTIE_SPECIALIZE_ARRAY_TYPE_HELPER(jshortArray, jshort, Short, unsigned short)
569 
570 // jshort in LP32
571 JTIE_SPECIALIZE_ARRAY_TYPE_HELPER(jshortArray, jshort, Short, signed int)
572 JTIE_SPECIALIZE_ARRAY_TYPE_HELPER(jshortArray, jshort, Short, unsigned int)
573 
574 // jint in ILP32, LP64, LLP64
575 JTIE_SPECIALIZE_ARRAY_TYPE_HELPER(jintArray, jint, Int, signed int)
576 JTIE_SPECIALIZE_ARRAY_TYPE_HELPER(jintArray, jint, Int, unsigned int)
577 
578 // jint in LP32, ILP32, LLP64
579 JTIE_SPECIALIZE_ARRAY_TYPE_HELPER(jintArray, jint, Int, signed long)
580 JTIE_SPECIALIZE_ARRAY_TYPE_HELPER(jintArray, jint, Int, unsigned long)
581 
582 // jlong in ILP64
583 JTIE_SPECIALIZE_ARRAY_TYPE_HELPER(jlongArray, jlong, Long, signed int)
584 JTIE_SPECIALIZE_ARRAY_TYPE_HELPER(jlongArray, jlong, Long, unsigned int)
585 
586 // jlong in LP64, ILP64
587 JTIE_SPECIALIZE_ARRAY_TYPE_HELPER(jlongArray, jlong, Long, signed long)
588 JTIE_SPECIALIZE_ARRAY_TYPE_HELPER(jlongArray, jlong, Long, unsigned long)
589 
590 // jlong in LLP64
591 JTIE_SPECIALIZE_ARRAY_TYPE_HELPER(jlongArray, jlong, Long, signed long long)
592 JTIE_SPECIALIZE_ARRAY_TYPE_HELPER(jlongArray, jlong, Long, unsigned long long)
593 
594 // jdouble
595 JTIE_SPECIALIZE_ARRAY_TYPE_HELPER(jdoubleArray, jdouble, Double, long double)
596 
597 // ---------------------------------------------------------------------------
598 
599 #endif // jtie_tconv_array_impl_hpp
600