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

Commit b508865f authored by Nader Jawad's avatar Nader Jawad Committed by Android (Google) Code Review
Browse files

Merge "Fix "using JNI after critical" exceptions" into udc-dev

parents fe222501 8c1d7aa0
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) {