refactor: ptr abstraction
This commit is contained in:
parent
44ffe4f073
commit
f561c4e9c5
@ -69,8 +69,7 @@ fn message_ref_should_throw_when_used_after_close(State {runtime, env, client, s
|
|||||||
|
|
||||||
let err = env.exception_occurred().unwrap();
|
let err = env.exception_occurred().unwrap();
|
||||||
env.exception_clear().unwrap();
|
env.exception_clear().unwrap();
|
||||||
assert!(env.is_instance_of(err,
|
assert!(env.is_instance_of(err, concat!(package!(dev.toad.ffi.Ptr), "$ExpiredError"))
|
||||||
concat!(package!(dev.toad.RefHawk), "$IllegalStorageOfRefError"))
|
|
||||||
.unwrap());
|
.unwrap());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,89 +0,0 @@
|
|||||||
package dev.toad;
|
|
||||||
|
|
||||||
import java.lang.ref.Cleaner;
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Optional;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Static class used to track pointers issued by rust code
|
|
||||||
*
|
|
||||||
* When an object instance containing a pointer tracked by RefHawk
|
|
||||||
* is not automatically freed before `RefHawk.ensureReleased` invoked,
|
|
||||||
* an error is thrown indicating incorrect usage of an object containing
|
|
||||||
* a native pointer.
|
|
||||||
*/
|
|
||||||
public class RefHawk {
|
|
||||||
|
|
||||||
private static final HashSet<Long> addrs = new HashSet<>();
|
|
||||||
|
|
||||||
private RefHawk() {}
|
|
||||||
|
|
||||||
public static class IllegalStorageOfRefError extends Error {
|
|
||||||
|
|
||||||
private static final String fmt =
|
|
||||||
"Instance of %s may not be stored by user code.\n" +
|
|
||||||
"Object was registered by:\n" +
|
|
||||||
">>>>>>\n" +
|
|
||||||
"%s\n" +
|
|
||||||
"<<<<<<\n";
|
|
||||||
|
|
||||||
IllegalStorageOfRefError(Ptr ptr) {
|
|
||||||
super(String.format(fmt, ptr.clazz, ptr.trace));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class Ptr {
|
|
||||||
|
|
||||||
protected final long addr;
|
|
||||||
private final String clazz;
|
|
||||||
private final String trace;
|
|
||||||
|
|
||||||
Ptr(long addr, String clazz, String trace) {
|
|
||||||
this.clazz = clazz;
|
|
||||||
this.addr = addr;
|
|
||||||
this.trace = trace;
|
|
||||||
}
|
|
||||||
|
|
||||||
public long addr() {
|
|
||||||
RefHawk.ensureValid(this);
|
|
||||||
return this.addr;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Associate an object with a raw `long` pointer and a short text
|
|
||||||
* describing the scope in which the object is intended to be valid for
|
|
||||||
* (e.g. "lambda")
|
|
||||||
*/
|
|
||||||
public static Ptr register(Class c, long addr) {
|
|
||||||
var trace = Thread.currentThread().getStackTrace();
|
|
||||||
var traceStr = Arrays
|
|
||||||
.asList(trace)
|
|
||||||
.stream()
|
|
||||||
.skip(2)
|
|
||||||
.map(StackTraceElement::toString)
|
|
||||||
.reduce("", (s, tr) -> s == "" ? tr : s + "\n\t" + tr);
|
|
||||||
|
|
||||||
RefHawk.addrs.add(addr);
|
|
||||||
return new Ptr(addr, c.toString(), traceStr);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Invokes the cleaning action on the object associated with an address
|
|
||||||
*/
|
|
||||||
public static void release(Ptr ptr) {
|
|
||||||
RefHawk.addrs.remove(ptr.addr);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Throw `IllegalStorageOfRefError` if object has been leaked
|
|
||||||
* outside of its appropriate context.
|
|
||||||
*/
|
|
||||||
public static void ensureValid(Ptr ptr) {
|
|
||||||
if (!RefHawk.addrs.contains(ptr.addr)) {
|
|
||||||
throw new IllegalStorageOfRefError(ptr);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
84
src/main/java/dev.toad/ffi/Ptr.java
Normal file
84
src/main/java/dev.toad/ffi/Ptr.java
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
package dev.toad.ffi;
|
||||||
|
|
||||||
|
import java.lang.ref.Cleaner;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A native pointer into the memory region shared between toadlib and the jvm
|
||||||
|
*
|
||||||
|
* Most notably, this ties into all `_Ref` classes' `close()` implementations;
|
||||||
|
* when the toad runtime closes these objects, their pointers are `release()`d.
|
||||||
|
*
|
||||||
|
* Attempts to store and then use one of these Ref objects will throw a
|
||||||
|
* `Ptr.ExpiredError`, as control has since been yielded to Rust code and the
|
||||||
|
* address we had may have been invalidated by it.
|
||||||
|
*/
|
||||||
|
public class Ptr {
|
||||||
|
|
||||||
|
private static final HashSet<Long> validAddresses = new HashSet<>();
|
||||||
|
|
||||||
|
protected final long addr;
|
||||||
|
private final String clazz;
|
||||||
|
private final String trace;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Associate a class instance with a native pointer
|
||||||
|
*/
|
||||||
|
public static Ptr register(Class c, long addr) {
|
||||||
|
var trace = Thread.currentThread().getStackTrace();
|
||||||
|
var traceStr = Arrays
|
||||||
|
.asList(trace)
|
||||||
|
.stream()
|
||||||
|
.skip(2)
|
||||||
|
.map(StackTraceElement::toString)
|
||||||
|
.reduce("", (s, tr) -> s == "" ? tr : s + "\n\t" + tr);
|
||||||
|
|
||||||
|
Ptr.validAddresses.add(addr);
|
||||||
|
return new Ptr(addr, c.toString(), traceStr);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Ptr(long addr, String clazz, String trace) {
|
||||||
|
this.clazz = clazz;
|
||||||
|
this.addr = addr;
|
||||||
|
this.trace = trace;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invokes the cleaning action on the object associated with an address
|
||||||
|
*/
|
||||||
|
public void release() {
|
||||||
|
Ptr.validAddresses.remove(this.addr);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Throw `ExpiredError` if object has been leaked
|
||||||
|
* outside of its appropriate context.
|
||||||
|
*/
|
||||||
|
public void ensureValid() {
|
||||||
|
if (!Ptr.validAddresses.contains(this.addr)) {
|
||||||
|
throw new ExpiredError(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public long addr() {
|
||||||
|
this.ensureValid();
|
||||||
|
return this.addr;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class ExpiredError extends Error {
|
||||||
|
|
||||||
|
private static final String fmt =
|
||||||
|
"Instance of %s may not be stored by user code.\n" +
|
||||||
|
"Object was registered by:\n" +
|
||||||
|
">>>>>>\n" +
|
||||||
|
"%s\n" +
|
||||||
|
"<<<<<<\n";
|
||||||
|
|
||||||
|
ExpiredError(Ptr ptr) {
|
||||||
|
super(String.format(fmt, ptr.clazz, ptr.trace));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,7 +1,6 @@
|
|||||||
package dev.toad.msg;
|
package dev.toad.msg;
|
||||||
|
|
||||||
import dev.toad.RefHawk;
|
import dev.toad.ffi.Ptr;
|
||||||
import dev.toad.RefHawk.Ptr;
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@ -13,7 +12,7 @@ public class MessageOptionRef implements MessageOption, AutoCloseable {
|
|||||||
private native MessageOptionValueRef[] values(long ptr);
|
private native MessageOptionValueRef[] values(long ptr);
|
||||||
|
|
||||||
public MessageOptionRef(long addr, long number) {
|
public MessageOptionRef(long addr, long number) {
|
||||||
this.ptr = RefHawk.register(this.getClass(), addr);
|
this.ptr = Ptr.register(this.getClass(), addr);
|
||||||
this.number = number;
|
this.number = number;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -35,6 +34,6 @@ public class MessageOptionRef implements MessageOption, AutoCloseable {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void close() {
|
public void close() {
|
||||||
RefHawk.release(this.ptr);
|
this.ptr.release();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
package dev.toad.msg;
|
package dev.toad.msg;
|
||||||
|
|
||||||
import dev.toad.RefHawk;
|
import dev.toad.ffi.Ptr;
|
||||||
import dev.toad.RefHawk.Ptr;
|
|
||||||
|
|
||||||
public class MessageOptionValueRef
|
public class MessageOptionValueRef
|
||||||
implements MessageOptionValue, AutoCloseable {
|
implements MessageOptionValue, AutoCloseable {
|
||||||
@ -11,7 +10,7 @@ public class MessageOptionValueRef
|
|||||||
private native byte[] bytes(long addr);
|
private native byte[] bytes(long addr);
|
||||||
|
|
||||||
public MessageOptionValueRef(long addr) {
|
public MessageOptionValueRef(long addr) {
|
||||||
this.ptr = RefHawk.register(this.getClass(), addr);
|
this.ptr = Ptr.register(this.getClass(), addr);
|
||||||
}
|
}
|
||||||
|
|
||||||
public byte[] asBytes() {
|
public byte[] asBytes() {
|
||||||
@ -28,6 +27,6 @@ public class MessageOptionValueRef
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void close() {
|
public void close() {
|
||||||
RefHawk.release(this.ptr);
|
this.ptr.release();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
package dev.toad.msg;
|
package dev.toad.msg;
|
||||||
|
|
||||||
import dev.toad.RefHawk;
|
import dev.toad.ffi.Ptr;
|
||||||
import dev.toad.RefHawk.Ptr;
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@ -29,7 +28,7 @@ public class MessageRef implements Message, AutoCloseable {
|
|||||||
private static native MessageOptionRef[] opts(long addr);
|
private static native MessageOptionRef[] opts(long addr);
|
||||||
|
|
||||||
public MessageRef(long addr) {
|
public MessageRef(long addr) {
|
||||||
this.ptr = RefHawk.register(this.getClass(), addr);
|
this.ptr = Ptr.register(this.getClass(), addr);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Message clone() {
|
public Message clone() {
|
||||||
@ -70,6 +69,6 @@ public class MessageRef implements Message, AutoCloseable {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void close() {
|
public void close() {
|
||||||
RefHawk.release(this.ptr);
|
this.ptr.release();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user