diff --git a/glue/src/e2e.rs b/glue/src/e2e.rs index 0cc7e8f..4a6b935 100644 --- a/glue/src/e2e.rs +++ b/glue/src/e2e.rs @@ -69,8 +69,7 @@ fn message_ref_should_throw_when_used_after_close(State {runtime, env, client, s let err = env.exception_occurred().unwrap(); env.exception_clear().unwrap(); - assert!(env.is_instance_of(err, - concat!(package!(dev.toad.RefHawk), "$IllegalStorageOfRefError")) + assert!(env.is_instance_of(err, concat!(package!(dev.toad.ffi.Ptr), "$ExpiredError")) .unwrap()); } diff --git a/src/main/java/dev.toad/RefHawk.java b/src/main/java/dev.toad/RefHawk.java deleted file mode 100644 index fea98e5..0000000 --- a/src/main/java/dev.toad/RefHawk.java +++ /dev/null @@ -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 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); - } - } -} diff --git a/src/main/java/dev.toad/ffi/Ptr.java b/src/main/java/dev.toad/ffi/Ptr.java new file mode 100644 index 0000000..99a3421 --- /dev/null +++ b/src/main/java/dev.toad/ffi/Ptr.java @@ -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 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)); + } + } +} diff --git a/src/main/java/dev.toad/msg/MessageOptionRef.java b/src/main/java/dev.toad/msg/MessageOptionRef.java index 80e7c16..8dd5c9b 100644 --- a/src/main/java/dev.toad/msg/MessageOptionRef.java +++ b/src/main/java/dev.toad/msg/MessageOptionRef.java @@ -1,7 +1,6 @@ package dev.toad.msg; -import dev.toad.RefHawk; -import dev.toad.RefHawk.Ptr; +import dev.toad.ffi.Ptr; import java.util.Arrays; import java.util.List; @@ -13,7 +12,7 @@ public class MessageOptionRef implements MessageOption, AutoCloseable { private native MessageOptionValueRef[] values(long ptr); public MessageOptionRef(long addr, long number) { - this.ptr = RefHawk.register(this.getClass(), addr); + this.ptr = Ptr.register(this.getClass(), addr); this.number = number; } @@ -35,6 +34,6 @@ public class MessageOptionRef implements MessageOption, AutoCloseable { @Override public void close() { - RefHawk.release(this.ptr); + this.ptr.release(); } } diff --git a/src/main/java/dev.toad/msg/MessageOptionValueRef.java b/src/main/java/dev.toad/msg/MessageOptionValueRef.java index f34533e..5f5bb90 100644 --- a/src/main/java/dev.toad/msg/MessageOptionValueRef.java +++ b/src/main/java/dev.toad/msg/MessageOptionValueRef.java @@ -1,7 +1,6 @@ package dev.toad.msg; -import dev.toad.RefHawk; -import dev.toad.RefHawk.Ptr; +import dev.toad.ffi.Ptr; public class MessageOptionValueRef implements MessageOptionValue, AutoCloseable { @@ -11,7 +10,7 @@ public class MessageOptionValueRef private native byte[] bytes(long addr); public MessageOptionValueRef(long addr) { - this.ptr = RefHawk.register(this.getClass(), addr); + this.ptr = Ptr.register(this.getClass(), addr); } public byte[] asBytes() { @@ -28,6 +27,6 @@ public class MessageOptionValueRef @Override public void close() { - RefHawk.release(this.ptr); + this.ptr.release(); } } diff --git a/src/main/java/dev.toad/msg/MessageRef.java b/src/main/java/dev.toad/msg/MessageRef.java index 6740c74..e4fef5b 100644 --- a/src/main/java/dev.toad/msg/MessageRef.java +++ b/src/main/java/dev.toad/msg/MessageRef.java @@ -1,7 +1,6 @@ package dev.toad.msg; -import dev.toad.RefHawk; -import dev.toad.RefHawk.Ptr; +import dev.toad.ffi.Ptr; import java.util.Arrays; import java.util.List; @@ -29,7 +28,7 @@ public class MessageRef implements Message, AutoCloseable { private static native MessageOptionRef[] opts(long addr); public MessageRef(long addr) { - this.ptr = RefHawk.register(this.getClass(), addr); + this.ptr = Ptr.register(this.getClass(), addr); } public Message clone() { @@ -70,6 +69,6 @@ public class MessageRef implements Message, AutoCloseable { @Override public void close() { - RefHawk.release(this.ptr); + this.ptr.release(); } }