Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@

#include <jni.h>
#include <string>
#include <vector>

namespace jni
{
Expand All @@ -21,13 +22,19 @@ namespace jni
public:
explicit JavaString(JNIEnv * env);

// UTF-16 to UTF-8; null becomes "".
static std::string toNative(JNIEnv * env, const JavaRef<jstring> & jstr);
// UTF-8 to UTF-16: "" stays "", U+0000 and characters beyond the
// BMP survive. Throws no C++ exception; null with a pending Java
// exception if the string cannot be made.
static JavaLocalRef<jstring> toJava(JNIEnv * env, const std::string & str);
// For values that WebRTC leaves empty when they are absent and that
// Java reports as null.
static JavaLocalRef<jstring> toJavaOrNull(JNIEnv * env, const std::string & str);
static JavaLocalRef<jobjectArray> createArray(JNIEnv * env, const std::vector<std::string> & vector);

private:
jclass cls;
jmethodID getBytes;
};
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,12 +43,32 @@ namespace jni
{
const auto classDef = JavaClasses::get<T>(env);

jobject throwable = env->NewObject(classDef->cls, classDef->ctor, env->NewStringUTF(message.c_str()));
jstring text = createMessage();

jobject throwable = text != nullptr
? env->NewObject(classDef->cls, classDef->ctor, text)
: nullptr;

if (text != nullptr) {
env->DeleteLocalRef(text);
}

if (throwable == nullptr) {
// Hand on what made it fail, an OutOfMemoryError mostly, so
// that Throw never gets null.
jthrowable pending = env->ExceptionOccurred();
env->ExceptionClear();

return pending;
}

return static_cast<jthrowable>(throwable);
}

private:
// The message as a local reference, or null with an exception pending.
jstring createMessage() const;

JNIEnv * env;
std::string message;

Expand Down
189 changes: 178 additions & 11 deletions webrtc-jni/src/main/cpp/dependencies/jni-voithos/src/JavaString.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,155 @@
#include "JavaString.h"
#include "JavaUtils.h"

#include <new>
#include <vector>

namespace jni
{
namespace
{
constexpr jchar kReplacementChar = 0xFFFD;

bool IsContinuation(unsigned char byte, unsigned char low = 0x80, unsigned char high = 0xBF)
{
return byte >= low && byte <= high;
}

void AppendCodePoint(std::vector<jchar> & out, char32_t cp)
{
if (cp < 0x10000) {
out.push_back(static_cast<jchar>(cp));
}
else {
cp -= 0x10000;
out.push_back(static_cast<jchar>(0xD800 + (cp >> 10)));
out.push_back(static_cast<jchar>(0xDC00 + (cp & 0x3FF)));
}
}

// Standard UTF-8 to UTF-16. JNI's NewStringUTF takes Modified UTF-8,
// which encodes U+0000 and supplementary characters differently and
// mangles what WebRTC hands out. Every ill-formed sequence becomes one
// U+FFFD per maximal subpart (Unicode 3.9, Table 3-7), as Java's own
// UTF-8 decoder does.
std::vector<jchar> DecodeUtf8(const std::string & str)
{
const auto * bytes = reinterpret_cast<const unsigned char *>(str.data());
const size_t length = str.size();

std::vector<jchar> out;
out.reserve(length);

size_t i = 0;

while (i < length) {
const unsigned char lead = bytes[i];

if (lead < 0x80) {
out.push_back(lead);
i++;
continue;
}

size_t count;
char32_t cp;
unsigned char low = 0x80;
unsigned char high = 0xBF;

if (lead >= 0xC2 && lead <= 0xDF) {
count = 1;
cp = lead & 0x1F;
}
else if (lead >= 0xE0 && lead <= 0xEF) {
count = 2;
cp = lead & 0x0F;
low = lead == 0xE0 ? 0xA0 : 0x80;
high = lead == 0xED ? 0x9F : 0xBF; // No surrogates.
}
else if (lead >= 0xF0 && lead <= 0xF4) {
count = 3;
cp = lead & 0x07;
low = lead == 0xF0 ? 0x90 : 0x80;
high = lead == 0xF4 ? 0x8F : 0xBF; // Nothing above U+10FFFF.
}
else {
out.push_back(kReplacementChar);
i++;
continue;
}

size_t consumed = 1;

while (consumed <= count && i + consumed < length) {
const unsigned char next = bytes[i + consumed];

// Only the first continuation byte has a narrowed range.
if (consumed == 1 ? !IsContinuation(next, low, high) : !IsContinuation(next)) {
break;
}

cp = (cp << 6) | (next & 0x3F);
consumed++;
}

if (consumed == count + 1) {
AppendCodePoint(out, cp);
}
else {
out.push_back(kReplacementChar);
}

i += consumed;
}

return out;
}

// Standard UTF-16 to UTF-8. An unpaired surrogate becomes '?', as with
// String.getBytes(UTF_8).
std::string EncodeUtf8(const std::vector<jchar> & chars)
{
std::string out;
out.reserve(chars.size());

for (size_t i = 0; i < chars.size(); i++) {
char32_t cp = chars[i];

if (cp >= 0xD800 && cp <= 0xDBFF && i + 1 < chars.size() && chars[i + 1] >= 0xDC00 && chars[i + 1] <= 0xDFFF) {
cp = 0x10000 + ((cp - 0xD800) << 10) + (chars[i + 1] - 0xDC00);
i++;
}
else if (cp >= 0xD800 && cp <= 0xDFFF) {
cp = '?';
}

if (cp < 0x80) {
out.push_back(static_cast<char>(cp));
}
else if (cp < 0x800) {
out.push_back(static_cast<char>(0xC0 | (cp >> 6)));
out.push_back(static_cast<char>(0x80 | (cp & 0x3F)));
}
else if (cp < 0x10000) {
out.push_back(static_cast<char>(0xE0 | (cp >> 12)));
out.push_back(static_cast<char>(0x80 | ((cp >> 6) & 0x3F)));
out.push_back(static_cast<char>(0x80 | (cp & 0x3F)));
}
else {
out.push_back(static_cast<char>(0xF0 | (cp >> 18)));
out.push_back(static_cast<char>(0x80 | ((cp >> 12) & 0x3F)));
out.push_back(static_cast<char>(0x80 | ((cp >> 6) & 0x3F)));
out.push_back(static_cast<char>(0x80 | (cp & 0x3F)));
}
}

return out;
}
}

JavaString::JavaString(JNIEnv * env)
{
cls = FindClass(env, "java/lang/String");

getBytes = GetMethod(env, cls, "getBytes", "(Ljava/lang/String;)[B");
}

std::string JavaString::toNative(JNIEnv * env, const JavaRef<jstring> & jstr)
Expand All @@ -25,25 +167,50 @@ namespace jni
return "";
}

const auto strClass = JavaClasses::get<JavaString>(env);

jbyteArray stringBytes = static_cast<jbyteArray>(env->CallObjectMethod(jstr, strClass->getBytes, env->NewStringUTF("UTF-8")));
jsize length = env->GetArrayLength(stringBytes);
const jsize length = env->GetStringLength(jstr);

std::string str(length, '\0');
std::vector<jchar> chars(static_cast<size_t>(length));

env->GetByteArrayRegion(stringBytes, 0, length, reinterpret_cast<jbyte *>(&str[0]));
if (length > 0) {
env->GetStringRegion(jstr, 0, length, chars.data());
}

return str;
return EncodeUtf8(chars);
}

JavaLocalRef<jstring> JavaString::toJava(JNIEnv * env, const std::string & str)
{
static const jchar empty = 0;

std::vector<jchar> chars;

try {
chars = DecodeUtf8(str);
}
catch (const std::bad_alloc &) {
// Fail the JNI way, so that callers in noexcept code need not catch.
JavaLocalRef<jclass> error(env, env->FindClass("java/lang/OutOfMemoryError"));

if (error.get() != nullptr) {
env->ThrowNew(error.get(), "Converting a native string failed");
}

return nullptr;
}

const jchar * data = chars.empty() ? &empty : chars.data();

// A null result means an OutOfMemoryError is pending.
return JavaLocalRef<jstring>(env, env->NewString(data, static_cast<jsize>(chars.size())));
}

JavaLocalRef<jstring> JavaString::toJavaOrNull(JNIEnv * env, const std::string & str)
{
if (str.empty()) {
return nullptr;
}

return JavaLocalRef<jstring>(env, env->NewStringUTF(str.c_str()));
return toJava(env, str);
}

JavaLocalRef<jobjectArray> JavaString::createArray(JNIEnv * env, const std::vector<std::string> & vector)
Expand All @@ -52,4 +219,4 @@ namespace jni

return JavaArray::createObjectArray(env, vector, javaClass->cls, &toJava);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
*/

#include "JavaThrowable.h"
#include "JavaString.h"
#include "JavaUtils.h"

#include <stdarg.h>
Expand All @@ -27,6 +28,13 @@ namespace jni
}
}

jstring JavaThrowable::createMessage() const
{
// The message is standard UTF-8 and may come from WebRTC or the
// application, which NewStringUTF and its Modified UTF-8 would mangle.
return JavaString::toJava(env, message).release();
}

JavaThrowable::operator jthrowable() const
{
return createThrowable<JavaThrowableClass>();
Expand Down
27 changes: 23 additions & 4 deletions webrtc-jni/src/main/cpp/dependencies/jni-voithos/src/JavaUtils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,27 @@ void ThrowJavaException(JNIEnv * env, const char * className, const char * messa

jni::JavaLocalRef<jclass> cls(env, env->FindClass(className));

if (cls.get() != nullptr) {
env->ThrowNew(cls.get(), message);
if (cls.get() == nullptr) {
return;
}

// ThrowNew takes Modified UTF-8; messages may carry what WebRTC reports.
jmethodID ctor = env->GetMethodID(cls.get(), "<init>", "(Ljava/lang/String;)V");

if (ctor == nullptr) {
return;
}

jni::JavaLocalRef<jstring> text = message != nullptr ? jni::JavaString::toJava(env, message) : nullptr;

if (message != nullptr && text.get() == nullptr) {
return;
}

jni::JavaLocalRef<jthrowable> throwable(env, static_cast<jthrowable>(env->NewObject(cls.get(), ctor, text.get())));

if (throwable.get() != nullptr) {
env->Throw(throwable.get());
}
}

Expand All @@ -65,10 +84,10 @@ void ThrowCxxJavaException(JNIEnv * env)
env->Throw(e.exception().get());
}
catch (const std::ios_base::failure & e) {
env->Throw(jni::JavaIOException(env, e.what()));
env->Throw(jni::JavaIOException(env, "%s", e.what()));
}
catch (const std::exception & e) {
env->Throw(jni::JavaError(env, e.what()));
env->Throw(jni::JavaError(env, "%s", e.what()));
}
catch (...) {
env->Throw(jni::JavaError(env, "Unhandled Exception"));
Expand Down
12 changes: 10 additions & 2 deletions webrtc-jni/src/main/cpp/include/JNI_AudioResampler.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 0 additions & 8 deletions webrtc-jni/src/main/cpp/include/JNI_VideoDesktopSource.h

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading