[jdk11u-dev] RFR: 8315135: Memory leak in the native implementation of Pack200.Unpacker.unpack() [v2]
Thomas Stuefe
stuefe at openjdk.org
Tue Aug 29 19:47:25 UTC 2023
On Tue, 29 Aug 2023 18:40:23 GMT, Volker Simonis <simonis at openjdk.org> wrote:
>> src/java.base/share/classes/com/sun/java/util/jar/pack/UnpackerImpl.java line 123:
>>
>>> 121: // Free up native memory and JNI handles to prevent leaks
>>> 122: ((NativeUnpack) _nunp).finish();
>>> 123: }
>>
>> What happens if `NativeUnpack` already called `finish()`? In case an error happened in the window after it calling `finish()` and it returning? Would that be harmless?
>
> The normal control flow if no error happens is that `NativeUnpack::run()` calls `NativeUnpack::finish()` before returning to its caller `UnpackerImpl::unpack()`. You are right that with these changes `NativeUnpack::finish()` will now be called two times in the case of successful unpacking. That's not a problem, because `NativeUnpack::finish()` is implemented as follows:
>
> JNIEXPORT jlong JNICALL
> Java_com_sun_java_util_jar_pack_NativeUnpack_finish(JNIEnv *env, jobject pObj) {
> unpacker* uPtr = get_unpacker(env, pObj, false);
> CHECK_EXCEPTION_RETURN_VALUE(uPtr, 0);
> size_t consumed = uPtr->input_consumed();
> free_unpacker(env, pObj, uPtr);
> return consumed;
> }
>
> and `get_unpacker(env, pObj, false)` will always return a valid pointer to a `unpacker` object (or throw an exception). That's controlled by the third argument, which basically instructs `get_unpacker()` to create a new `unpacker` object if the current one is NULL. `free_unpacker(env, pObj, uPtr)` in a subsequent line will than release all the `unpacker`'s native memory and set the unpacker field (i.e. `NativeUnpack::unpackerPtr`) to NULL.
>
> So while calling `NativeUnpack::finish()` repeatedly is correct, it is unnecessarily expensive because we may create a new native `unpacker` object in `Java_com_sun_java_util_jar_pack_NativeUnpack_finish()` just to delete it right afterwards. I've therefore changed `Java_com_sun_java_util_jar_pack_NativeUnpack_finish()` as follows:
>
> JNIEXPORT jlong JNICALL
> Java_com_sun_java_util_jar_pack_NativeUnpack_finish(JNIEnv *env, jobject pObj) {
> // There's no need to create a new unpacker here if we don't already have one
> // just to immediatly free it afterwards.
> unpacker* uPtr = get_unpacker(env, pObj, /* noCreate= */ true);
> CHECK_EXCEPTION_RETURN_VALUE(uPtr, 0);
> size_t consumed = uPtr->input_consumed();
> // free_unpacker() will set the unpacker field on 'pObj' to null
> free_unpacker(env, pObj, uPtr);
> return consumed;
> }
>
> Now, if we call `get_unpacker()` with the third argument set to `true`, `get_unpacker()` will not create a new `unpacker` object any more if the `NativeUnpack::unpackerPtr` is already NULL. The subsequent `CHECK_EXCEPTION_RETURN_VALUE` will check for a NULL return value and simple return from `Java_com_sun_java_util_jar_pack_NativeUnpack_finish()`.
>
> PS: the third, boolean argument `noCreate` of `get_unpacker()` is a default argument with the default value being `false`. All other call sites of `get_unpacker()` use this default, only the cal...
Nice, thanks. I double-checked `CHECK_EXCEPTION_RETURN_VALUE` to make sure it really checks also for null and not only for `env->ExceptionOccurred`.
-------------
PR Review Comment: https://git.openjdk.org/jdk11u-dev/pull/2100#discussion_r1309280278
More information about the jdk-updates-dev
mailing list