[foreign] RFR 8217420: Have UniversalNativeInvoker and UniversalUpcallHandler create (and close) the scopes used by boxing code
Jorn Vernee
jbvernee at xs4all.nl
Mon Jan 21 18:13:52 UTC 2019
Hi, another patch :)
From the bug description:
Currently, when (un-)boxing code needs to do an allocation a Scope is
created for that, and this Scope is then leaked.
For an up-/down-call there are 2 kinds of scopes; 1) for values being
passed as arguments by value, which are valid for the duration of the
call. 2) for values being returned, which are up to the caller to
manage.
We can do better than the current scheme of always leaking, by having
UniversalUpcallHandler and UniversalNativeInvoker create both of these
types of scopes, pass them to boxing code, and then close scopes of type
1. when the call is finished.
This will reduce the amount of memory being leaked, and could also help
identify bugs where references to temporary objects are leaked.
---
After implementing this, it turns out that UniversalUpcallHandler only
needs a Scope for the arguments being passed to the java method being
called. For returns there should be no allocations; either the value is
being passed in registers, or written to a pointer passed in by the
caller.
I did discover a problem with this patch in the TestJextractFFI patch
code:
public Stream<Cursor> children() {
final ArrayList<Cursor> ar = new ArrayList<>();
// FIXME: need a way to pass ar down as user data d
try (Scope sc = Scope.newNativeScope()) {
LibClang.lib.clang_visitChildren(cursor,
sc.allocateCallback((c, p, d) -> {
ar.add(new Cursor(c));
return LibClang.lib.CXChildVisit_Continue();
}), Pointer.nullPointer());
return ar.stream();
}
}
According to C semantics, the argument to the lambda `c`, which is a
CXCursor struct being passed in by-value, is only valid for the duration
of the call. The Windows ABI depends on this fact, because it allocates
the struct on the stack of the caller, and then passes a pointer to the
callee (the lambda in this case). I think on SysV a by-value struct is
always passed in register + stack?
But, the `c` argument is being leaked from the lambda, by wrapping it in
a Cursor and storing it in a list in the enclosing scope. When the call
to the lambda ends, the struct is deallocated and the pointer that is
stored on the Java side is now dangling. I've initially solved the crash
this was causing by defensively copying the struct in the Windows boxing
code (instead of just wrapping the passed pointer), but I realized this
also means that a user would have to manually release a struct passed
into an upcall to avoid a memory leak.
We have 2 options here:
1.) Go with C style. Struct arguments are valid only during an upcall,
and we have to manually copy them if we want to 'leak' them from the
scope of the upcall.
2.) Go with status quo. Struct doesn't need to be copied manually when
saving it outside the scope of the upcall. The copy is defensively done
in Windows boxing code instead, and sometimes redundant. Only on Windows
(for now), struct arguments to an upcall have to be manually released to
avoid a memory leak.
I think 1 is the better option here since this adheres to the
expectations native code has when calling into Java. I've reflected that
in the patch as well, but of course I'm interested in hearing other
opinions as well :) Note that the crash I was seeing before no longer
happens since the Scope used in the boxing code is now closed after the
call, and we get an exception instead.
Bug: https://bugs.openjdk.java.net/browse/JDK-8217420
Webrev:
http://cr.openjdk.java.net/~jvernee/panama/webrevs/8217420/webrev.00/
Cheers,
Jorn
More information about the panama-dev
mailing list