Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 8c1d7aa0 authored by Nader Jawad's avatar Nader Jawad
Browse files

Fix "using JNI after critical" exceptions

Refactored ScopedJavaNioBuffer implementation to copy
directly to std::vector<uint8_t> instances and release
the jni critical array queries immediately after copying
into a vector. This is to avoid potential interleavings
of JNI method calls before destructor of ScopedJavaNioBuffer
is invoked. As per jni requirements no other JNI calls are
allowed after GetPrimitiveArrayCritical until a matching call
to ReleasePrimitiveArrayCritical is made.

Fixes: 271468824
Test: Added tests to MeshTest
Change-Id: Ia4afd8b8584ae43f021c79b6a341b05e80e0e205
parent 02f47849
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -338,6 +338,7 @@ cc_defaults {
        "jni/android_util_PathParser.cpp",

        "jni/Bitmap.cpp",
        "jni/BufferUtils.cpp",
        "jni/HardwareBufferHelpers.cpp",
        "jni/BitmapFactory.cpp",
        "jni/ByteBufferStreamAdaptor.cpp",
+10 −19
Original line number Diff line number Diff line
@@ -104,33 +104,31 @@ private:

class Mesh {
public:
    Mesh(const sk_sp<SkMeshSpecification>& meshSpec, int mode, const void* vertexBuffer,
         size_t vertexBufferSize, jint vertexCount, jint vertexOffset,
    Mesh(const sk_sp<SkMeshSpecification>& meshSpec, int mode,
         std::vector<uint8_t>&& vertexBufferData, jint vertexCount, jint vertexOffset,
         std::unique_ptr<MeshUniformBuilder> builder, const SkRect& bounds)
            : mMeshSpec(meshSpec)
            , mMode(mode)
            , mVertexBufferData(std::move(vertexBufferData))
            , mVertexCount(vertexCount)
            , mVertexOffset(vertexOffset)
            , mBuilder(std::move(builder))
            , mBounds(bounds) {
        copyToVector(mVertexBufferData, vertexBuffer, vertexBufferSize);
    }
            , mBounds(bounds) {}

    Mesh(const sk_sp<SkMeshSpecification>& meshSpec, int mode, const void* vertexBuffer,
         size_t vertexBufferSize, jint vertexCount, jint vertexOffset, const void* indexBuffer,
         size_t indexBufferSize, jint indexCount, jint indexOffset,
    Mesh(const sk_sp<SkMeshSpecification>& meshSpec, int mode,
         std::vector<uint8_t>&& vertexBufferData, jint vertexCount, jint vertexOffset,
         std::vector<uint8_t>&& indexBuffer, jint indexCount, jint indexOffset,
         std::unique_ptr<MeshUniformBuilder> builder, const SkRect& bounds)
            : mMeshSpec(meshSpec)
            , mMode(mode)
            , mVertexBufferData(std::move(vertexBufferData))
            , mVertexCount(vertexCount)
            , mVertexOffset(vertexOffset)
            , mIndexBufferData(std::move(indexBuffer))
            , mIndexCount(indexCount)
            , mIndexOffset(indexOffset)
            , mBuilder(std::move(builder))
            , mBounds(bounds) {
        copyToVector(mVertexBufferData, vertexBuffer, vertexBufferSize);
        copyToVector(mIndexBufferData, indexBuffer, indexBufferSize);
    }
            , mBounds(bounds) {}

    Mesh(Mesh&&) = default;

@@ -180,13 +178,6 @@ public:
    MeshUniformBuilder* uniformBuilder() { return mBuilder.get(); }

private:
    void copyToVector(std::vector<uint8_t>& dst, const void* src, size_t srcSize) {
        if (src) {
            dst.resize(srcSize);
            memcpy(dst.data(), src, srcSize);
        }
    }

    sk_sp<SkMeshSpecification> mMeshSpec;
    int mMode = 0;

+130 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2023 The Android Open Source Project
 *
 * Licensed 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
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
#include "BufferUtils.h"

#include "graphics_jni_helpers.h"

static void copyToVector(std::vector<uint8_t>& dst, const void* src, size_t srcSize) {
    if (src) {
        dst.resize(srcSize);
        memcpy(dst.data(), src, srcSize);
    }
}

/**
 * This code is taken and modified from com_google_android_gles_jni_GLImpl.cpp to extract data
 * from a java.nio.Buffer.
 */
static void* getDirectBufferPointer(JNIEnv* env, jobject buffer) {
    if (buffer == nullptr) {
        return nullptr;
    }

    jint position;
    jint limit;
    jint elementSizeShift;
    jlong pointer;
    pointer = jniGetNioBufferFields(env, buffer, &position, &limit, &elementSizeShift);
    if (pointer == 0) {
        jniThrowException(env, "java/lang/IllegalArgumentException",
                          "Must use a native order direct Buffer");
        return nullptr;
    }
    pointer += position << elementSizeShift;
    return reinterpret_cast<void*>(pointer);
}

static void releasePointer(JNIEnv* env, jarray array, void* data, jboolean commit) {
    env->ReleasePrimitiveArrayCritical(array, data, commit ? 0 : JNI_ABORT);
}

static void* getPointer(JNIEnv* env, jobject buffer, jarray* array, jint* remaining, jint* offset) {
    jint position;
    jint limit;
    jint elementSizeShift;

    jlong pointer;
    pointer = jniGetNioBufferFields(env, buffer, &position, &limit, &elementSizeShift);
    *remaining = (limit - position) << elementSizeShift;
    if (pointer != 0L) {
        *array = nullptr;
        pointer += position << elementSizeShift;
        return reinterpret_cast<void*>(pointer);
    }

    *array = jniGetNioBufferBaseArray(env, buffer);
    *offset = jniGetNioBufferBaseArrayOffset(env, buffer);
    return nullptr;
}

/**
 * This is a copy of
 * static void android_glBufferData__IILjava_nio_Buffer_2I
 * from com_google_android_gles_jni_GLImpl.cpp
 */
static void setIndirectData(JNIEnv* env, size_t size, jobject data_buf,
                            std::vector<uint8_t>& result) {
    jint exception = 0;
    const char* exceptionType = nullptr;
    const char* exceptionMessage = nullptr;
    jarray array = nullptr;
    jint bufferOffset = 0;
    jint remaining;
    void* data = 0;
    char* dataBase = nullptr;

    if (data_buf) {
        data = getPointer(env, data_buf, (jarray*)&array, &remaining, &bufferOffset);
        if (remaining < size) {
            exception = 1;
            exceptionType = "java/lang/IllegalArgumentException";
            exceptionMessage = "remaining() < size < needed";
            goto exit;
        }
    }
    if (data_buf && data == nullptr) {
        dataBase = (char*)env->GetPrimitiveArrayCritical(array, (jboolean*)0);
        data = (void*)(dataBase + bufferOffset);
    }

    copyToVector(result, data, size);

exit:
    if (array) {
        releasePointer(env, array, (void*)dataBase, JNI_FALSE);
    }
    if (exception) {
        jniThrowException(env, exceptionType, exceptionMessage);
    }
}

std::vector<uint8_t> copyJavaNioBufferToVector(JNIEnv* env, jobject buffer, size_t size,
                                               jboolean isDirect) {
    std::vector<uint8_t> data;
    if (buffer == nullptr) {
        jniThrowNullPointerException(env);
    } else {
        if (isDirect) {
            void* directBufferPtr = getDirectBufferPointer(env, buffer);
            if (directBufferPtr) {
                copyToVector(data, directBufferPtr, size);
            }
        } else {
            setIndirectData(env, size, buffer, data);
        }
    }
    return data;
}
+32 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2023 The Android Open Source Project
 *
 * Licensed 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
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
#ifndef BUFFERUTILS_H_
#define BUFFERUTILS_H_

#include <jni.h>

#include <vector>

/**
 * Helper method to load a java.nio.Buffer instance into a vector. This handles
 * both direct and indirect buffers and promptly releases any critical arrays that
 * have been retrieved in order to avoid potential jni exceptions due to interleaved
 * jni calls between get/release primitive method invocations.
 */
std::vector<uint8_t> copyJavaNioBufferToVector(JNIEnv* env, jobject buffer, size_t size,
                                               jboolean isDirect);

#endif  // BUFFERUTILS_H_
+16 −161
Original line number Diff line number Diff line
@@ -14,172 +14,18 @@
 * limitations under the License.
 */

#include <GrDirectContext.h>
#include <Mesh.h>
#include <SkMesh.h>
#include <jni.h>
#include <log/log.h>

#include <utility>

#include "BufferUtils.h"
#include "GraphicsJNI.h"
#include "graphics_jni_helpers.h"

#define gIndexByteSize 2

// A smart pointer that provides read only access to Java.nio.Buffer. This handles both
// direct and indrect buffers, allowing access to the underlying data in both
// situations. If passed a null buffer, we will throw NullPointerException,
// and c_data will return nullptr.
//
// This class draws from com_google_android_gles_jni_GLImpl.cpp for Buffer to void *
// conversion.
class ScopedJavaNioBuffer {
public:
    ScopedJavaNioBuffer(JNIEnv* env, jobject buffer, size_t size, jboolean isDirect)
            : mEnv(env), mBuffer(buffer) {
        if (buffer == nullptr) {
            mDataBase = nullptr;
            mData = nullptr;
            jniThrowNullPointerException(env);
        } else {
            mArray = (jarray) nullptr;
            if (isDirect) {
                mData = getDirectBufferPointer(mEnv, mBuffer);
            } else {
                mData = setIndirectData(size);
            }
        }
    }

    ScopedJavaNioBuffer(ScopedJavaNioBuffer&& rhs) noexcept { *this = std::move(rhs); }

    ~ScopedJavaNioBuffer() { reset(); }

    void reset() {
        if (mDataBase) {
            releasePointer(mEnv, mArray, mDataBase, JNI_FALSE);
            mDataBase = nullptr;
        }
    }

    ScopedJavaNioBuffer& operator=(ScopedJavaNioBuffer&& rhs) noexcept {
        if (this != &rhs) {
            reset();

            mEnv = rhs.mEnv;
            mBuffer = rhs.mBuffer;
            mDataBase = rhs.mDataBase;
            mData = rhs.mData;
            mArray = rhs.mArray;
            rhs.mEnv = nullptr;
            rhs.mData = nullptr;
            rhs.mBuffer = nullptr;
            rhs.mArray = nullptr;
            rhs.mDataBase = nullptr;
        }
        return *this;
    }

    const void* data() const { return mData; }

private:
    /**
     * This code is taken and modified from com_google_android_gles_jni_GLImpl.cpp to extract data
     * from a java.nio.Buffer.
     */
    void* getDirectBufferPointer(JNIEnv* env, jobject buffer) {
        if (buffer == nullptr) {
            return nullptr;
        }

        jint position;
        jint limit;
        jint elementSizeShift;
        jlong pointer;
        pointer = jniGetNioBufferFields(env, buffer, &position, &limit, &elementSizeShift);
        if (pointer == 0) {
            jniThrowException(mEnv, "java/lang/IllegalArgumentException",
                              "Must use a native order direct Buffer");
            return nullptr;
        }
        pointer += position << elementSizeShift;
        return reinterpret_cast<void*>(pointer);
    }

    static void releasePointer(JNIEnv* env, jarray array, void* data, jboolean commit) {
        env->ReleasePrimitiveArrayCritical(array, data, commit ? 0 : JNI_ABORT);
    }

    static void* getPointer(JNIEnv* env, jobject buffer, jarray* array, jint* remaining,
                            jint* offset) {
        jint position;
        jint limit;
        jint elementSizeShift;

        jlong pointer;
        pointer = jniGetNioBufferFields(env, buffer, &position, &limit, &elementSizeShift);
        *remaining = (limit - position) << elementSizeShift;
        if (pointer != 0L) {
            *array = nullptr;
            pointer += position << elementSizeShift;
            return reinterpret_cast<void*>(pointer);
        }

        *array = jniGetNioBufferBaseArray(env, buffer);
        *offset = jniGetNioBufferBaseArrayOffset(env, buffer);
        return nullptr;
    }

    /**
     * This is a copy of
     * static void android_glBufferData__IILjava_nio_Buffer_2I
     * from com_google_android_gles_jni_GLImpl.cpp
     */
    void* setIndirectData(size_t size) {
        jint exception;
        const char* exceptionType;
        const char* exceptionMessage;
        jint bufferOffset = (jint)0;
        jint remaining;
        void* tempData;

        if (mBuffer) {
            tempData =
                    (void*)getPointer(mEnv, mBuffer, (jarray*)&mArray, &remaining, &bufferOffset);
            if (remaining < size) {
                exception = 1;
                exceptionType = "java/lang/IllegalArgumentException";
                exceptionMessage = "remaining() < size < needed";
                goto exit;
            }
        }
        if (mBuffer && tempData == nullptr) {
            mDataBase = (char*)mEnv->GetPrimitiveArrayCritical(mArray, (jboolean*)0);
            tempData = (void*)(mDataBase + bufferOffset);
        }
        return tempData;
    exit:
        if (mArray) {
            releasePointer(mEnv, mArray, (void*)(mDataBase), JNI_FALSE);
        }
        if (exception) {
            jniThrowException(mEnv, exceptionType, exceptionMessage);
        }
        return nullptr;
    }

    JNIEnv* mEnv;

    // Java Buffer data
    void* mData;
    jobject mBuffer;

    // Indirect Buffer Data
    jarray mArray;
    char* mDataBase;
};

namespace android {

static jlong make(JNIEnv* env, jobject, jlong meshSpec, jint mode, jobject vertexBuffer,
@@ -187,9 +33,12 @@ static jlong make(JNIEnv* env, jobject, jlong meshSpec, jint mode, jobject verte
                  jfloat right, jfloat bottom) {
    auto skMeshSpec = sk_ref_sp(reinterpret_cast<SkMeshSpecification*>(meshSpec));
    size_t bufferSize = vertexCount * skMeshSpec->stride();
    auto buff = ScopedJavaNioBuffer(env, vertexBuffer, bufferSize, isDirect);
    auto buffer = copyJavaNioBufferToVector(env, vertexBuffer, bufferSize, isDirect);
    if (env->ExceptionCheck()) {
        return 0;
    }
    auto skRect = SkRect::MakeLTRB(left, top, right, bottom);
    auto meshPtr = new Mesh(skMeshSpec, mode, buff.data(), bufferSize, vertexCount, vertexOffset,
    auto meshPtr = new Mesh(skMeshSpec, mode, std::move(buffer), vertexCount, vertexOffset,
                            std::make_unique<MeshUniformBuilder>(skMeshSpec), skRect);
    auto [valid, msg] = meshPtr->validate();
    if (!valid) {
@@ -205,11 +54,17 @@ static jlong makeIndexed(JNIEnv* env, jobject, jlong meshSpec, jint mode, jobjec
    auto skMeshSpec = sk_ref_sp(reinterpret_cast<SkMeshSpecification*>(meshSpec));
    auto vertexBufferSize = vertexCount * skMeshSpec->stride();
    auto indexBufferSize = indexCount * gIndexByteSize;
    auto vBuf = ScopedJavaNioBuffer(env, vertexBuffer, vertexBufferSize, isVertexDirect);
    auto iBuf = ScopedJavaNioBuffer(env, indexBuffer, indexBufferSize, isIndexDirect);
    auto vBuf = copyJavaNioBufferToVector(env, vertexBuffer, vertexBufferSize, isVertexDirect);
    if (env->ExceptionCheck()) {
        return 0;
    }
    auto iBuf = copyJavaNioBufferToVector(env, indexBuffer, indexBufferSize, isIndexDirect);
    if (env->ExceptionCheck()) {
        return 0;
    }
    auto skRect = SkRect::MakeLTRB(left, top, right, bottom);
    auto meshPtr = new Mesh(skMeshSpec, mode, vBuf.data(), vertexBufferSize, vertexCount,
                            vertexOffset, iBuf.data(), indexBufferSize, indexCount, indexOffset,
    auto meshPtr = new Mesh(skMeshSpec, mode, std::move(vBuf), vertexCount, vertexOffset,
                            std::move(iBuf), indexCount, indexOffset,
                            std::make_unique<MeshUniformBuilder>(skMeshSpec), skRect);
    auto [valid, msg] = meshPtr->validate();
    if (!valid) {