ad686b26ce
Change-Id: I6eb9f0431a9402479a2d90d5b6f68b611d52a9f9 Reviewed-on: https://gerrit.libreoffice.org/c/core/+/175957 Tested-by: Jenkins Reviewed-by: Mike Kaganski <mike.kaganski@collabora.com>
570 lines
18 KiB
C++
570 lines
18 KiB
C++
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
|
|
/*
|
|
* This file is part of the LibreOffice project.
|
|
*
|
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
*
|
|
* This file incorporates work covered by the following license notice:
|
|
*
|
|
* Licensed to the Apache Software Foundation (ASF) under one or more
|
|
* contributor license agreements. See the NOTICE file distributed
|
|
* with this work for additional information regarding copyright
|
|
* ownership. The ASF licenses this file to you under the Apache
|
|
* License, Version 2.0 (the "License"); you may not use this file
|
|
* except in compliance with the License. You may obtain a copy of
|
|
* the License at http://www.apache.org/licenses/LICENSE-2.0 .
|
|
*/
|
|
|
|
#include <sal/config.h>
|
|
#include <sal/log.hxx>
|
|
|
|
#include <cassert>
|
|
#include <memory>
|
|
|
|
#include "jni_bridge.h"
|
|
#include "jni_helper.h"
|
|
#include "jniunoenvironmentdata.hxx"
|
|
|
|
#include <jvmaccess/unovirtualmachine.hxx>
|
|
#include <rtl/ref.hxx>
|
|
#include <uno/lbnames.h>
|
|
|
|
using namespace ::jni_uno;
|
|
|
|
namespace
|
|
{
|
|
extern "C"
|
|
{
|
|
|
|
|
|
void Mapping_acquire( uno_Mapping * mapping ) noexcept
|
|
{
|
|
Mapping const * that = static_cast< Mapping const * >( mapping );
|
|
that->m_bridge->acquire();
|
|
}
|
|
|
|
|
|
void Mapping_release( uno_Mapping * mapping ) noexcept
|
|
{
|
|
Mapping const * that = static_cast< Mapping const * >( mapping );
|
|
that->m_bridge->release();
|
|
}
|
|
|
|
|
|
void Mapping_map_to_uno(
|
|
uno_Mapping * mapping, void ** ppOut,
|
|
void * pIn, typelib_InterfaceTypeDescription * td ) noexcept
|
|
{
|
|
uno_Interface ** ppUnoI = reinterpret_cast<uno_Interface **>(ppOut);
|
|
jobject javaI = static_cast<jobject>(pIn);
|
|
|
|
static_assert(sizeof (void *) == sizeof (jobject), "must be the same size");
|
|
assert(ppUnoI != nullptr);
|
|
assert(td != nullptr);
|
|
|
|
if (javaI == nullptr)
|
|
{
|
|
if (*ppUnoI != nullptr)
|
|
{
|
|
uno_Interface * p = *ppUnoI;
|
|
(*p->release)( p );
|
|
*ppUnoI = nullptr;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
try
|
|
{
|
|
Bridge const * bridge =
|
|
static_cast< Mapping const * >( mapping )->m_bridge;
|
|
JNI_guarded_context jni(
|
|
bridge->getJniInfo(),
|
|
(static_cast<jni_uno::JniUnoEnvironmentData *>(
|
|
bridge->m_java_env->pContext)
|
|
->machine));
|
|
|
|
JNI_interface_type_info const * info =
|
|
static_cast< JNI_interface_type_info const * >(
|
|
bridge->getJniInfo()->get_type_info(
|
|
jni, &td->aBase ) );
|
|
uno_Interface * pUnoI = bridge->map_to_uno( jni, javaI, info );
|
|
if (*ppUnoI != nullptr)
|
|
{
|
|
uno_Interface * p = *ppUnoI;
|
|
(*p->release)( p );
|
|
}
|
|
*ppUnoI = pUnoI;
|
|
}
|
|
catch (const BridgeRuntimeError & err)
|
|
{
|
|
SAL_WARN(
|
|
"bridges",
|
|
"ignoring BridgeRuntimeError \"" << err.m_message << "\"");
|
|
}
|
|
catch (const ::jvmaccess::VirtualMachine::AttachGuard::CreationException &)
|
|
{
|
|
SAL_WARN("bridges", "attaching current thread to java failed");
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void Mapping_map_to_java(
|
|
uno_Mapping * mapping, void ** ppOut,
|
|
void * pIn, typelib_InterfaceTypeDescription * td ) noexcept
|
|
{
|
|
jobject * ppJavaI = reinterpret_cast<jobject *>(ppOut);
|
|
uno_Interface * pUnoI = static_cast<uno_Interface *>(pIn);
|
|
|
|
static_assert(sizeof (void *) == sizeof (jobject), "must be the same size");
|
|
assert(ppJavaI != nullptr);
|
|
assert(td != nullptr);
|
|
|
|
try
|
|
{
|
|
if (pUnoI == nullptr)
|
|
{
|
|
if (*ppJavaI != nullptr)
|
|
{
|
|
Bridge const * bridge =
|
|
static_cast< Mapping const * >( mapping )->m_bridge;
|
|
JNI_guarded_context jni(
|
|
bridge->getJniInfo(),
|
|
(static_cast<jni_uno::JniUnoEnvironmentData *>(
|
|
bridge->m_java_env->pContext)
|
|
->machine));
|
|
jni->DeleteGlobalRef( *ppJavaI );
|
|
*ppJavaI = nullptr;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Bridge const * bridge =
|
|
static_cast< Mapping const * >( mapping )->m_bridge;
|
|
JNI_guarded_context jni(
|
|
bridge->getJniInfo(),
|
|
(static_cast<jni_uno::JniUnoEnvironmentData *>(
|
|
bridge->m_java_env->pContext)
|
|
->machine));
|
|
|
|
JNI_interface_type_info const * info =
|
|
static_cast< JNI_interface_type_info const * >(
|
|
bridge->getJniInfo()->get_type_info(
|
|
jni, &td->aBase ) );
|
|
jobject jlocal = bridge->map_to_java( jni, pUnoI, info );
|
|
if (*ppJavaI != nullptr)
|
|
jni->DeleteGlobalRef( *ppJavaI );
|
|
*ppJavaI = jni->NewGlobalRef( jlocal );
|
|
jni->DeleteLocalRef( jlocal );
|
|
}
|
|
}
|
|
catch (const BridgeRuntimeError & err)
|
|
{
|
|
SAL_WARN(
|
|
"bridges",
|
|
"ignoring BridgeRuntimeError \"" << err.m_message << "\"");
|
|
}
|
|
catch (const ::jvmaccess::VirtualMachine::AttachGuard::CreationException &)
|
|
{
|
|
SAL_WARN("bridges", "attaching current thread to java failed");
|
|
}
|
|
}
|
|
|
|
|
|
void Bridge_free( uno_Mapping * mapping ) noexcept
|
|
{
|
|
Mapping * that = static_cast< Mapping * >( mapping );
|
|
delete that->m_bridge;
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
namespace jni_uno
|
|
{
|
|
|
|
|
|
void Bridge::acquire() const
|
|
{
|
|
if (++m_ref != 1)
|
|
return;
|
|
|
|
if (m_registered_java2uno)
|
|
{
|
|
uno_Mapping * mapping = const_cast< Mapping * >( &m_java2uno );
|
|
uno_registerMapping(
|
|
&mapping, Bridge_free,
|
|
m_java_env, &m_uno_env->aBase, nullptr );
|
|
}
|
|
else
|
|
{
|
|
uno_Mapping * mapping = const_cast< Mapping * >( &m_uno2java );
|
|
uno_registerMapping(
|
|
&mapping, Bridge_free,
|
|
&m_uno_env->aBase, m_java_env, nullptr );
|
|
}
|
|
}
|
|
|
|
|
|
void Bridge::release() const
|
|
{
|
|
if (! --m_ref )
|
|
{
|
|
uno_revokeMapping(
|
|
m_registered_java2uno
|
|
? const_cast< Mapping * >( &m_java2uno )
|
|
: const_cast< Mapping * >( &m_uno2java ) );
|
|
}
|
|
}
|
|
|
|
|
|
Bridge::Bridge(
|
|
uno_Environment * java_env, uno_ExtEnvironment * uno_env,
|
|
bool registered_java2uno )
|
|
: m_ref( 1 ),
|
|
m_uno_env( uno_env ),
|
|
m_java_env( java_env ),
|
|
m_registered_java2uno( registered_java2uno )
|
|
{
|
|
assert(m_java_env != nullptr);
|
|
assert(m_uno_env != nullptr);
|
|
|
|
// uno_initEnvironment (below) cannot report errors directly, so it clears
|
|
// its pContext upon error to indirectly report errors from here:
|
|
if (static_cast<jni_uno::JniUnoEnvironmentData *>(m_java_env->pContext)
|
|
== nullptr)
|
|
{
|
|
throw BridgeRuntimeError(u"error during JNI-UNO's uno_initEnvironment"_ustr);
|
|
}
|
|
|
|
(*m_uno_env->aBase.acquire)( &m_uno_env->aBase );
|
|
(*m_java_env->acquire)( m_java_env );
|
|
|
|
// java2uno
|
|
m_java2uno.acquire = Mapping_acquire;
|
|
m_java2uno.release = Mapping_release;
|
|
m_java2uno.mapInterface = Mapping_map_to_uno;
|
|
m_java2uno.m_bridge = this;
|
|
// uno2java
|
|
m_uno2java.acquire = Mapping_acquire;
|
|
m_uno2java.release = Mapping_release;
|
|
m_uno2java.mapInterface = Mapping_map_to_java;
|
|
m_uno2java.m_bridge = this;
|
|
}
|
|
|
|
|
|
Bridge::~Bridge()
|
|
{
|
|
(*m_java_env->release)( m_java_env );
|
|
(*m_uno_env->aBase.release)( &m_uno_env->aBase );
|
|
}
|
|
|
|
JNI_info const * Bridge::getJniInfo() const {
|
|
return static_cast<jni_uno::JniUnoEnvironmentData *>(m_java_env->pContext)
|
|
->info;
|
|
}
|
|
|
|
void JNI_context::java_exc_occurred() const
|
|
{
|
|
// !don't rely on JNI_info!
|
|
|
|
JLocalAutoRef jo_exc( *this, m_env->ExceptionOccurred() );
|
|
m_env->ExceptionClear();
|
|
assert(jo_exc.is());
|
|
if (! jo_exc.is())
|
|
{
|
|
throw BridgeRuntimeError(
|
|
"java exception occurred, but not available!?" +
|
|
get_stack_trace() );
|
|
}
|
|
|
|
// call toString(); don't rely on m_jni_info
|
|
jclass jo_class = m_env->FindClass( "java/lang/Object" );
|
|
if (m_env->ExceptionCheck())
|
|
{
|
|
m_env->ExceptionClear();
|
|
throw BridgeRuntimeError(
|
|
"cannot get class java.lang.Object!" + get_stack_trace() );
|
|
}
|
|
JLocalAutoRef jo_Object( *this, jo_class );
|
|
// method Object.toString()
|
|
jmethodID method_Object_toString = m_env->GetMethodID(
|
|
static_cast<jclass>(jo_Object.get()), "toString", "()Ljava/lang/String;" );
|
|
if (m_env->ExceptionCheck())
|
|
{
|
|
m_env->ExceptionClear();
|
|
throw BridgeRuntimeError(
|
|
"cannot get method id of java.lang.Object.toString()!" +
|
|
get_stack_trace() );
|
|
}
|
|
assert(method_Object_toString != nullptr);
|
|
|
|
JLocalAutoRef jo_descr(
|
|
*this, m_env->CallObjectMethodA(
|
|
jo_exc.get(), method_Object_toString, nullptr ) );
|
|
if (m_env->ExceptionCheck()) // no chance at all
|
|
{
|
|
m_env->ExceptionClear();
|
|
throw BridgeRuntimeError(
|
|
"error examining java exception object!" +
|
|
get_stack_trace() );
|
|
}
|
|
|
|
jsize len = m_env->GetStringLength( static_cast<jstring>(jo_descr.get()) );
|
|
std::unique_ptr< rtl_mem > ustr_mem(
|
|
rtl_mem::allocate(
|
|
sizeof (rtl_uString) + (len * sizeof (sal_Unicode)) ) );
|
|
rtl_uString * ustr = reinterpret_cast<rtl_uString *>(ustr_mem.get());
|
|
m_env->GetStringRegion( static_cast<jstring>(jo_descr.get()), 0, len, reinterpret_cast<jchar *>(ustr->buffer) );
|
|
if (m_env->ExceptionCheck())
|
|
{
|
|
m_env->ExceptionClear();
|
|
throw BridgeRuntimeError(
|
|
"invalid java string object!" + get_stack_trace() );
|
|
}
|
|
ustr->refCount = 1;
|
|
ustr->length = len;
|
|
ustr->buffer[ len ] = '\0';
|
|
OUString message( reinterpret_cast<rtl_uString *>(ustr_mem.release()), SAL_NO_ACQUIRE );
|
|
|
|
throw BridgeRuntimeError( message + get_stack_trace( jo_exc.get() ) );
|
|
}
|
|
|
|
|
|
void JNI_context::getClassForName(
|
|
jclass * classClass, jmethodID * methodForName) const
|
|
{
|
|
jclass c = m_env->FindClass("java/lang/Class");
|
|
if (c != nullptr) {
|
|
*methodForName = m_env->GetStaticMethodID(
|
|
c, "forName",
|
|
"(Ljava/lang/String;ZLjava/lang/ClassLoader;)Ljava/lang/Class;");
|
|
}
|
|
*classClass = c;
|
|
}
|
|
|
|
|
|
jclass JNI_context::findClass(
|
|
char const * name, jclass classClass, jmethodID methodForName,
|
|
bool inException) const
|
|
{
|
|
jclass c = nullptr;
|
|
JLocalAutoRef s(*this, m_env->NewStringUTF(name));
|
|
if (s.is()) {
|
|
jvalue a[3];
|
|
a[0].l = s.get();
|
|
a[1].z = JNI_FALSE;
|
|
a[2].l = m_class_loader;
|
|
c = static_cast< jclass >(
|
|
m_env->CallStaticObjectMethodA(classClass, methodForName, a));
|
|
}
|
|
if (!inException) {
|
|
ensure_no_exception();
|
|
}
|
|
return c;
|
|
}
|
|
|
|
|
|
OUString JNI_context::get_stack_trace( jobject jo_exc ) const
|
|
{
|
|
JLocalAutoRef jo_JNI_proxy(
|
|
*this,
|
|
find_class( *this, "com.sun.star.bridges.jni_uno.JNI_proxy", true ) );
|
|
if (assert_no_exception())
|
|
{
|
|
// static method JNI_proxy.get_stack_trace()
|
|
jmethodID method = m_env->GetStaticMethodID(
|
|
static_cast<jclass>(jo_JNI_proxy.get()), "get_stack_trace",
|
|
"(Ljava/lang/Throwable;)Ljava/lang/String;" );
|
|
if (assert_no_exception() && (method != nullptr))
|
|
{
|
|
jvalue arg;
|
|
arg.l = jo_exc;
|
|
JLocalAutoRef jo_stack_trace(
|
|
*this, m_env->CallStaticObjectMethodA(
|
|
static_cast<jclass>(jo_JNI_proxy.get()), method, &arg ) );
|
|
if (assert_no_exception())
|
|
{
|
|
jsize len =
|
|
m_env->GetStringLength( static_cast<jstring>(jo_stack_trace.get()) );
|
|
std::unique_ptr< rtl_mem > ustr_mem(
|
|
rtl_mem::allocate(
|
|
sizeof (rtl_uString) + (len * sizeof (sal_Unicode)) ) );
|
|
rtl_uString * ustr = reinterpret_cast<rtl_uString *>(ustr_mem.get());
|
|
m_env->GetStringRegion(
|
|
static_cast<jstring>(jo_stack_trace.get()), 0, len, reinterpret_cast<jchar *>(ustr->buffer) );
|
|
if (assert_no_exception())
|
|
{
|
|
ustr->refCount = 1;
|
|
ustr->length = len;
|
|
ustr->buffer[ len ] = '\0';
|
|
return OUString(
|
|
reinterpret_cast<rtl_uString *>(ustr_mem.release()), SAL_NO_ACQUIRE );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return OUString();
|
|
}
|
|
|
|
}
|
|
|
|
using namespace ::jni_uno;
|
|
|
|
extern "C" {
|
|
|
|
static void java_env_dispose(uno_Environment * env) {
|
|
auto * envData
|
|
= static_cast<jni_uno::JniUnoEnvironmentData *>(env->pContext);
|
|
if (envData == nullptr) return;
|
|
|
|
jobject async;
|
|
{
|
|
std::unique_lock g(envData->mutex);
|
|
async = envData->asynchronousFinalizer;
|
|
envData->asynchronousFinalizer = nullptr;
|
|
}
|
|
if (async == nullptr) return;
|
|
|
|
try {
|
|
JNI_guarded_context jni(envData->info, envData->machine);
|
|
jni->CallObjectMethodA(
|
|
async, envData->info->m_method_AsynchronousFinalizer_drain,
|
|
nullptr);
|
|
jni.ensure_no_exception();
|
|
jni->DeleteGlobalRef(async);
|
|
} catch (const BridgeRuntimeError & e) {
|
|
SAL_WARN(
|
|
"bridges",
|
|
"ignoring BridgeRuntimeError \"" << e.m_message << "\"");
|
|
} catch (
|
|
jvmaccess::VirtualMachine::AttachGuard::CreationException &)
|
|
{
|
|
SAL_WARN(
|
|
"bridges",
|
|
("ignoring jvmaccess::VirtualMachine::AttachGuard"
|
|
"::CreationException"));
|
|
}
|
|
}
|
|
|
|
static void java_env_disposing(uno_Environment * env) {
|
|
java_env_dispose(env);
|
|
delete static_cast<jni_uno::JniUnoEnvironmentData *>(env->pContext);
|
|
}
|
|
|
|
#ifdef DISABLE_DYNLOADING
|
|
#define uno_initEnvironment java_uno_initEnvironment
|
|
#endif
|
|
|
|
|
|
SAL_DLLPUBLIC_EXPORT void uno_initEnvironment( uno_Environment * java_env ) noexcept
|
|
{
|
|
try {
|
|
// JavaComponentLoader::getJavaLoader (in
|
|
// stoc/source/javaloader/javaloader.cxx) stores a
|
|
// jvmaccess::UnoVirtualMachine pointer into java_env->pContext; replace
|
|
// it here with either a pointer to a full JniUnoEnvironmentData upon
|
|
// success, or with a null pointer upon failure (as this function cannot
|
|
// directly report back failure, so it uses that way to indirectly
|
|
// report failure later from within the Bridge ctor):
|
|
rtl::Reference<jvmaccess::UnoVirtualMachine> vm(
|
|
static_cast<jvmaccess::UnoVirtualMachine *>(java_env->pContext));
|
|
java_env->pContext = nullptr;
|
|
java_env->dispose = java_env_dispose;
|
|
java_env->environmentDisposing = java_env_disposing;
|
|
java_env->pExtEnv = nullptr; // no extended support
|
|
std::unique_ptr<jni_uno::JniUnoEnvironmentData> envData(
|
|
new jni_uno::JniUnoEnvironmentData(vm));
|
|
{
|
|
JNI_guarded_context jni(envData->info, envData->machine);
|
|
JLocalAutoRef ref(
|
|
jni,
|
|
jni->NewObject(
|
|
envData->info->m_class_AsynchronousFinalizer,
|
|
envData->info->m_ctor_AsynchronousFinalizer));
|
|
jni.ensure_no_exception();
|
|
envData->asynchronousFinalizer = jni->NewGlobalRef(ref.get());
|
|
jni.ensure_no_exception();
|
|
}
|
|
java_env->pContext = envData.release();
|
|
} catch (const BridgeRuntimeError & e) {
|
|
SAL_WARN("bridges", "BridgeRuntimeError \"" << e.m_message << "\"");
|
|
} catch (jvmaccess::VirtualMachine::AttachGuard::CreationException &) {
|
|
SAL_WARN(
|
|
"bridges",
|
|
"jvmaccess::VirtualMachine::AttachGuard::CreationException");
|
|
}
|
|
}
|
|
|
|
#ifdef DISABLE_DYNLOADING
|
|
#define uno_ext_getMapping java_uno_ext_getMapping
|
|
#endif
|
|
|
|
|
|
SAL_DLLPUBLIC_EXPORT void uno_ext_getMapping(
|
|
uno_Mapping ** ppMapping, uno_Environment * pFrom, uno_Environment * pTo ) noexcept
|
|
{
|
|
assert(ppMapping != nullptr);
|
|
assert(pFrom != nullptr);
|
|
assert(pTo != nullptr);
|
|
if (*ppMapping != nullptr)
|
|
{
|
|
(*(*ppMapping)->release)( *ppMapping );
|
|
*ppMapping = nullptr;
|
|
}
|
|
|
|
static_assert(int(JNI_FALSE) == int(false), "must be equal");
|
|
static_assert(int(JNI_TRUE) == int(true), "must be equal");
|
|
static_assert(sizeof (jboolean) == sizeof (sal_Bool), "must be the same size");
|
|
static_assert(sizeof (jchar) == sizeof (sal_Unicode), "must be the same size");
|
|
static_assert(sizeof (jdouble) == sizeof (double), "must be the same size");
|
|
static_assert(sizeof (jfloat) == sizeof (float), "must be the same size");
|
|
static_assert(sizeof (jbyte) == sizeof (sal_Int8), "must be the same size");
|
|
static_assert(sizeof (jshort) == sizeof (sal_Int16), "must be the same size");
|
|
static_assert(sizeof (jint) == sizeof (sal_Int32), "must be the same size");
|
|
static_assert(sizeof (jlong) == sizeof (sal_Int64), "must be the same size");
|
|
|
|
OUString const & from_env_typename =
|
|
OUString::unacquired( &pFrom->pTypeName );
|
|
OUString const & to_env_typename =
|
|
OUString::unacquired( &pTo->pTypeName );
|
|
|
|
uno_Mapping * mapping = nullptr;
|
|
|
|
try
|
|
{
|
|
if ( from_env_typename == UNO_LB_JAVA && to_env_typename == UNO_LB_UNO )
|
|
{
|
|
Bridge * bridge =
|
|
new Bridge( pFrom, pTo->pExtEnv, true ); // ref count = 1
|
|
mapping = &bridge->m_java2uno;
|
|
uno_registerMapping(
|
|
&mapping, Bridge_free,
|
|
pFrom, &pTo->pExtEnv->aBase, nullptr );
|
|
// coverity[leaked_storage] - on purpose
|
|
}
|
|
else if ( from_env_typename == UNO_LB_UNO && to_env_typename == UNO_LB_JAVA )
|
|
{
|
|
Bridge * bridge =
|
|
new Bridge( pTo, pFrom->pExtEnv, false ); // ref count = 1
|
|
mapping = &bridge->m_uno2java;
|
|
uno_registerMapping(
|
|
&mapping, Bridge_free,
|
|
&pFrom->pExtEnv->aBase, pTo, nullptr );
|
|
// coverity[leaked_storage] - on purpose
|
|
}
|
|
}
|
|
catch (const BridgeRuntimeError & err)
|
|
{
|
|
SAL_WARN("bridges", "BridgeRuntimeError \"" << err.m_message << "\"");
|
|
}
|
|
|
|
*ppMapping = mapping;
|
|
}
|
|
|
|
}
|
|
|
|
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
|