feat: rework memory allocation to throw when pointers are invalid, fix pointers yielded to java, add pattern for yielding stable pointers to data not owned by Runtime
This commit is contained in:
89
src/main/java/dev.toad/RefHawk.java
Normal file
89
src/main/java/dev.toad/RefHawk.java
Normal file
@@ -0,0 +1,89 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,17 +1,19 @@
|
||||
package dev.toad.msg;
|
||||
|
||||
import dev.toad.RefHawk;
|
||||
import dev.toad.RefHawk.Ptr;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
public class MessageOptionRef implements MessageOption {
|
||||
public class MessageOptionRef implements MessageOption, AutoCloseable {
|
||||
|
||||
private final long addr;
|
||||
private Ptr ptr;
|
||||
private final long number;
|
||||
|
||||
private native MessageOptionValueRef[] values(long addr);
|
||||
private native MessageOptionValueRef[] values(long ptr);
|
||||
|
||||
public MessageOptionRef(long addr, long number) {
|
||||
this.addr = addr;
|
||||
this.ptr = RefHawk.register(this.getClass(), addr);
|
||||
this.number = number;
|
||||
}
|
||||
|
||||
@@ -20,14 +22,19 @@ public class MessageOptionRef implements MessageOption {
|
||||
}
|
||||
|
||||
public MessageOptionValueRef[] valueRefs() {
|
||||
return this.values(this.addr);
|
||||
return this.values(this.ptr.addr());
|
||||
}
|
||||
|
||||
public List<MessageOptionValue> values() {
|
||||
return Arrays.asList(this.values(this.addr));
|
||||
return Arrays.asList(this.values(this.ptr.addr()));
|
||||
}
|
||||
|
||||
public MessageOption clone() {
|
||||
return new MessageOptionOwned(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
RefHawk.release(this.ptr);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,24 +1,33 @@
|
||||
package dev.toad.msg;
|
||||
|
||||
public class MessageOptionValueRef implements MessageOptionValue {
|
||||
import dev.toad.RefHawk;
|
||||
import dev.toad.RefHawk.Ptr;
|
||||
|
||||
private final long addr;
|
||||
public class MessageOptionValueRef
|
||||
implements MessageOptionValue, AutoCloseable {
|
||||
|
||||
private final Ptr ptr;
|
||||
|
||||
private native byte[] bytes(long addr);
|
||||
|
||||
public MessageOptionValueRef(long addr) {
|
||||
this.addr = addr;
|
||||
this.ptr = RefHawk.register(this.getClass(), addr);
|
||||
}
|
||||
|
||||
public byte[] asBytes() {
|
||||
return this.bytes(this.addr);
|
||||
return this.bytes(this.ptr.addr());
|
||||
}
|
||||
|
||||
public String asString() {
|
||||
return new String(this.bytes(this.addr));
|
||||
return new String(this.bytes(this.ptr.addr()));
|
||||
}
|
||||
|
||||
public MessageOptionValue clone() {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
RefHawk.release(this.ptr);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package dev.toad.msg;
|
||||
|
||||
import dev.toad.RefHawk;
|
||||
import dev.toad.RefHawk.Ptr;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
@@ -10,9 +12,9 @@ import java.util.List;
|
||||
* control is yielded back to the rust runtime, meaning instances of
|
||||
* MessageRef should never be stored in state; invoke `.clone()` first.
|
||||
*/
|
||||
public class MessageRef implements Message {
|
||||
public class MessageRef implements Message, AutoCloseable {
|
||||
|
||||
private final long addr;
|
||||
private Ptr ptr;
|
||||
|
||||
private static native int id(long addr);
|
||||
|
||||
@@ -27,7 +29,7 @@ public class MessageRef implements Message {
|
||||
private static native MessageOptionRef[] opts(long addr);
|
||||
|
||||
public MessageRef(long addr) {
|
||||
this.addr = addr;
|
||||
this.ptr = RefHawk.register(this.getClass(), addr);
|
||||
}
|
||||
|
||||
public Message clone() {
|
||||
@@ -35,34 +37,39 @@ public class MessageRef implements Message {
|
||||
}
|
||||
|
||||
public int id() {
|
||||
return this.id(this.addr);
|
||||
return this.id(this.ptr.addr());
|
||||
}
|
||||
|
||||
public byte[] token() {
|
||||
return this.token(this.addr);
|
||||
return this.token(this.ptr.addr());
|
||||
}
|
||||
|
||||
public MessageCode code() {
|
||||
return this.code(this.addr);
|
||||
return this.code(this.ptr.addr());
|
||||
}
|
||||
|
||||
public MessageType type() {
|
||||
return this.type(this.addr);
|
||||
return this.type(this.ptr.addr());
|
||||
}
|
||||
|
||||
public MessageOptionRef[] optionRefs() {
|
||||
return this.opts(this.addr);
|
||||
return this.opts(this.ptr.addr());
|
||||
}
|
||||
|
||||
public List<MessageOption> options() {
|
||||
return Arrays.asList(this.opts(this.addr));
|
||||
return Arrays.asList(this.opts(this.ptr.addr()));
|
||||
}
|
||||
|
||||
public byte[] payloadBytes() {
|
||||
return this.payload(this.addr);
|
||||
return this.payload(this.ptr.addr());
|
||||
}
|
||||
|
||||
public String payloadString() {
|
||||
return new String(this.payload(this.addr));
|
||||
return new String(this.payload(this.ptr.addr()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
RefHawk.release(this.ptr);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user