Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -316,6 +316,14 @@ public static StackTraceElement[] mergeStackTraces(
StackTraceElement[] hotSpotStackTrace,
StackTraceElement[] nativeStackTrace,
boolean originatedInHotSpot) {
return mergeStackTraces(hotSpotStackTrace, nativeStackTrace, ExceptionKind.THROWN, originatedInHotSpot);
}

public static StackTraceElement[] mergeStackTraces(
StackTraceElement[] hotSpotStackTrace,
StackTraceElement[] nativeStackTrace,
ExceptionKind exceptionKind,
boolean originatedInHotSpot) {
if (originatedInHotSpot) {
if (containsJNIHostCall(hotSpotStackTrace)) {
// Already merged
Expand All @@ -327,8 +335,12 @@ public static StackTraceElement[] mergeStackTraces(
return nativeStackTrace;
}
}
return mergeStackTraces(hotSpotStackTrace, nativeStackTrace, originatedInHotSpot ? 0 : getIndexOfTransitionToNativeFrame(hotSpotStackTrace),
getIndexOfPropagateJNIExceptionFrame(nativeStackTrace), originatedInHotSpot);
int hotSpotStackStartIndex = originatedInHotSpot ? 0 : getIndexOfTransitionToNativeFrame(hotSpotStackTrace);
int nativeStackStartIndex = switch (exceptionKind) {
case THROWN -> getIndexOfPropagateJNIExceptionFrame(nativeStackTrace);
case RETURNED -> 0;
};
return mergeStackTraces(hotSpotStackTrace, nativeStackTrace, hotSpotStackStartIndex, nativeStackStartIndex, originatedInHotSpot);
}

/**
Expand Down Expand Up @@ -489,6 +501,20 @@ private static int getIndexOfPropagateJNIExceptionFrame(StackTraceElement[] stac
return 0;
}

/**
* Gets the index of the first frame of JNI call to host method.
*
* @return {@code 0} if no caller found
*/
private static int getIndexOfJNIHostCall(StackTraceElement[] stackTrace) {
for (int i = 0; i < stackTrace.length - 1; i++) {
if (isJNIAPICall(stackTrace[i]) && isJNIHostCall(stackTrace[i + 1])) {
return i;
}
}
return 0;
}

/**
* Gets the index of the first frame denoting the native method call.
*
Expand Down Expand Up @@ -661,4 +687,21 @@ private static boolean isJNIHostCall(StackTraceElement frame) {
JNI_TRANSITION_METHODS = Set.copyOf(entryPoints.keySet());
JNI_FUNCTION_POINTER_CLASS_PREFIX = JNI.class.getName() + '$';
}

/**
* Describes how an exception was delivered to the caller.
* <p>
* An exception may either be:
* <ul>
* <li>{@link #THROWN} - the exception is propagated using the Java {@code throw} statement and
* normal exception-handling semantics.</li>
* <li>{@link #RETURNED} - the exception is not thrown but instead returned as a regular
* value.</li>
* </ul>
* <p>
*/
public enum ExceptionKind {
RETURNED,
THROWN
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -160,26 +160,22 @@ public static StackTraceElement[] mergeStackTrace(Isolate<?> isolate, StackTrace
}
ForeignException localException = pendingException.get();
if (localException != null) {
StackMerger merger;
if (isolate instanceof ProcessIsolate) {
merger = ProcessIsolateStack::mergeStackTraces;
} else if (isolate instanceof NativeIsolate) {
merger = JNIExceptionWrapper::mergeStackTraces;
} else if (isolate instanceof HSIsolate) {
merger = JNIExceptionWrapper::mergeStackTraces;
} else {
throw new IllegalArgumentException("Unsupported isolate type: " + isolate);
}
StackMerger merger = StackMerger.forIsolate(isolate);
return switch (localException.kind) {
case HOST_TO_GUEST -> merger.merge(localException.getStackTrace(), foreignExceptionStack, false);
case GUEST_TO_HOST -> merger.merge(foreignExceptionStack, localException.getStackTrace(), true);
case HOST_TO_GUEST -> merger.merge(localException.getStackTrace(), foreignExceptionStack, ExceptionKind.THROWN, false);
case GUEST_TO_HOST -> merger.merge(foreignExceptionStack, localException.getStackTrace(), ExceptionKind.THROWN, true);
default -> throw new IllegalStateException("Unsupported kind " + localException.kind);
};
} else {
return foreignExceptionStack;
}
}

public static StackTraceElement[] mergeStackTrace(Isolate<?> isolate, StackTraceElement[] hostStack, StackTraceElement[] isolateStack, ExceptionKind exceptionKind, boolean originatedInHost) {
StackMerger merger = StackMerger.forIsolate(isolate);
return merger.merge(hostStack, isolateStack, exceptionKind, originatedInHost);
}

/**
* Creates a {@link ForeignException} by marshaling the {@code exception} using
* {@code marshaller}. This method is intended to be called by the code generated by the bridge
Expand Down Expand Up @@ -374,6 +370,43 @@ public void handleException(ExceptionHandlerContext context) {

@FunctionalInterface
private interface StackMerger {
StackTraceElement[] merge(StackTraceElement[] hostStack, StackTraceElement[] isolateStack, boolean originatedInHost);

StackTraceElement[] merge(StackTraceElement[] hostStack, StackTraceElement[] isolateStack, ExceptionKind exceptionKind, boolean originatedInHost);

static StackMerger forIsolate(Isolate<?> isolate) {
if (isolate instanceof ProcessIsolate) {
return ProcessIsolateStack::mergeStackTraces;
} else if (isolate instanceof NativeIsolate) {
return (hostStack, isolateStack, kind, fromHost) -> JNIExceptionWrapper.mergeStackTraces(hostStack, isolateStack, kind.translateToJNIExceptionKind(), fromHost);
} else if (isolate instanceof HSIsolate) {
return (hostStack, isolateStack, kind, fromHost) -> JNIExceptionWrapper.mergeStackTraces(hostStack, isolateStack, kind.translateToJNIExceptionKind(), fromHost);
} else {
throw new IllegalArgumentException("Unsupported isolate type: " + isolate);
}
}
}

/**
* Describes how an exception was delivered to the caller.
* <p>
* An exception may either be:
* <ul>
* <li>{@link #THROWN} - the exception is propagated using the Java {@code throw} statement and
* normal exception-handling semantics.</li>
* <li>{@link #RETURNED} - the exception is not thrown but instead returned as a regular
* value.</li>
* </ul>
* <p>
*/
public enum ExceptionKind {
RETURNED,
THROWN;

private JNIExceptionWrapper.ExceptionKind translateToJNIExceptionKind() {
return switch (this) {
case RETURNED -> JNIExceptionWrapper.ExceptionKind.RETURNED;
case THROWN -> JNIExceptionWrapper.ExceptionKind.THROWN;
};
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ private ProcessIsolateStack() {
* Merges {@code host} and {@code isolate} stack traces respecting process isolate transition
* boundaries.
*/
static StackTraceElement[] mergeStackTraces(StackTraceElement[] host, StackTraceElement[] isolate, boolean originatedInHost) {
static StackTraceElement[] mergeStackTraces(StackTraceElement[] host, StackTraceElement[] isolate, ForeignException.ExceptionKind exceptionKind, boolean originatedInHost) {
int hostStackEndIndex;
int isolateStackEndIndex;
if (originatedInHost) {
Expand All @@ -80,7 +80,9 @@ static StackTraceElement[] mergeStackTraces(StackTraceElement[] host, StackTrace
}
hostStackEndIndex = getMirrorThreadEntryMethodInfo(host).runnableIndex;
}
StackTraceElement[] merged = mergeStackTraces(host, isolate, getTransitionIndex(host), hostStackEndIndex, getTransitionIndex(isolate), isolateStackEndIndex, originatedInHost);
int hostStackStartIndex = exceptionKind == ForeignException.ExceptionKind.THROWN || originatedInHost ? getTransitionIndex(host) : getBoundaryIndex(host);
int isolateStackStartIndex = exceptionKind == ForeignException.ExceptionKind.THROWN || !originatedInHost ? getTransitionIndex(isolate) : 0;
StackTraceElement[] merged = mergeStackTraces(host, isolate, hostStackStartIndex, hostStackEndIndex, isolateStackStartIndex, isolateStackEndIndex, originatedInHost);
return merged;
}

Expand Down Expand Up @@ -126,6 +128,15 @@ private static int getTransitionIndex(StackTraceElement[] stack) {
return FOREIGN_EXCEPTION_CLASS.equals(stack[0].getClassName()) && CREATE_METHOD.equals(stack[0].getMethodName()) ? 1 : 0;
}

private static int getBoundaryIndex(StackTraceElement[] stack) {
for (int i = 0; i < stack.length; i++) {
if (isBoundary(stack[i])) {
return i;
}
}
return 0;
}

private static MirrorThreadInfo getMirrorThreadEntryMethodInfo(StackTraceElement[] stack) {
int dispatchIndex = -1;
int index = 0;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1262,6 +1262,11 @@ public boolean allowsPublicAccess(Object access) {
return ((HostAccess) access).allowPublic;
}

@Override
public boolean allowsHostStackTrace(Object hostAccess) {
return ((HostAccess) hostAccess).hostStackFrames == HostAccess.HostStackFrames.ALL;
}

@Override
public boolean allowsAccessInheritance(Object access) {
return ((HostAccess) access).allowAccessInheritance;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -120,15 +120,16 @@ public final class HostAccess {
private final EconomicSet<Class<? extends Annotation>> disableMethodScopingAnnotations;
private final EconomicSet<Executable> disableMethodScoping;
final Lookup methodLookup;
final HostStackFrames hostStackFrames;
volatile Object impl;

private static final HostAccess EMPTY = new HostAccess(null, null, null, null, null, null, null, false, false, false, false, false, false, false, false, false, false, false,
null, false, null, null, null);
null, false, null, null, null, HostStackFrames.NONE);

/**
* Predefined host access policy that allows access to public host methods or fields that were
* annotated with {@linkplain Export @Export} and were declared in public class. This is the
* default configuration if {@link Context.Builder#allowAllAccess(boolean)} is
* default configuration if {@link HostAccess.Builder#allowPublicAccess(boolean)} is
* <code>false</code>.
* <p>
* Equivalent of using the following builder configuration:
Expand Down Expand Up @@ -213,6 +214,7 @@ public final class HostAccess {
allowArrayAccess(true).allowListAccess(true).allowBufferAccess(true).//
allowIterableAccess(true).allowIteratorAccess(true).allowMapAccess(true).//
allowAccessInheritance(true).//
allowHostStackFrames(HostStackFrames.ALL).//
name("HostAccess.ALL").build();

/**
Expand Down Expand Up @@ -333,14 +335,40 @@ public enum MutableTargetMapping {
EXECUTABLE_TO_JAVA_INTERFACE,
}

/**
* Defines the visibility policy for host stack frames as observed by guest applications.
* <p>
* This setting controls whether stack frames originating from the host are exposed to the guest
* application when inspecting exception stack traces.
*
* @since 26.0
* @see Builder#allowHostStackFrames(HostStackFrames)
*/
public enum HostStackFrames {

/**
* Host stack frames are visible to the guest application.
*
* @since 26.0
*/
ALL,

/**
* Host stack frames are hidden from the guest application.
*
* @since 26.0
*/
NONE
}

HostAccess(EconomicSet<Class<? extends Annotation>> annotations, EconomicMap<Class<?>, Boolean> excludeTypes, EconomicSet<AnnotatedElement> members,
EconomicSet<Class<? extends Annotation>> implementableAnnotations,
EconomicSet<Class<?>> implementableTypes, List<Object> targetMappings,
String name,
boolean allowPublic, boolean allowAllImplementations, boolean allowAllClassImplementations, boolean allowArrayAccess, boolean allowListAccess, boolean allowBufferAccess,
boolean allowIterableAccess, boolean allowIteratorAccess, boolean allowMapAccess, boolean allowBigIntegerNumberAccess, boolean allowAccessInheritance,
MutableTargetMapping[] allowMutableTargetMappings, boolean methodScopingDefault, EconomicSet<Class<? extends Annotation>> disableMethodScopingAnnotations,
EconomicSet<Executable> disableMethodScoping, Lookup methodLookup) {
EconomicSet<Executable> disableMethodScoping, Lookup methodLookup, HostStackFrames hostStackFrames) {
// create defensive copies
this.accessAnnotations = copySet(annotations, Equivalence.IDENTITY);
this.excludeTypes = copyMap(excludeTypes, Equivalence.IDENTITY);
Expand All @@ -365,6 +393,7 @@ public enum MutableTargetMapping {
this.disableMethodScopingAnnotations = disableMethodScopingAnnotations;
this.disableMethodScoping = disableMethodScoping;
this.methodLookup = methodLookup;
this.hostStackFrames = hostStackFrames;
}

/**
Expand Down Expand Up @@ -393,7 +422,8 @@ && equalsSet(implementableAnnotations, other.implementableAnnotations)//
&& equalsSet(implementableTypes, other.implementableTypes)//
&& Objects.equals(targetMappings, other.targetMappings)//
&& equalsSet(accessAnnotations, other.accessAnnotations)//
&& Arrays.equals(allowMutableTargetMappings, other.allowMutableTargetMappings);
&& Arrays.equals(allowMutableTargetMappings, other.allowMutableTargetMappings)//
&& hostStackFrames == other.hostStackFrames;
}

/**
Expand All @@ -418,7 +448,8 @@ public int hashCode() {
hashSet(implementableTypes),
hashSet(members),
targetMappings,
hashSet(accessAnnotations));
hashSet(accessAnnotations),
hostStackFrames);
}

private static <T, V> int hashMap(EconomicMap<T, V> map) {
Expand Down Expand Up @@ -796,6 +827,7 @@ public final class Builder {
private EconomicSet<Executable> disableMethodScoping;
private String name;
private Lookup methodLookup;
private HostStackFrames hostStackFrames;

Builder() {
}
Expand Down Expand Up @@ -823,6 +855,7 @@ public final class Builder {
this.methodScopingDefault = access.methodScopingDefault;
this.disableMethodScopingAnnotations = copySet(access.disableMethodScopingAnnotations, Equivalence.IDENTITY);
this.disableMethodScoping = copySet(access.disableMethodScoping, Equivalence.IDENTITY);
this.hostStackFrames = access.hostStackFrames;
}

/**
Expand Down Expand Up @@ -850,6 +883,10 @@ public Builder allowAccessAnnotatedBy(Class<? extends Annotation> annotation) {
*/
public Builder allowPublicAccess(boolean allow) {
allowPublic = allow;
// Compatibility behavior
if (hostStackFrames == null) {
hostStackFrames = HostStackFrames.ALL;
}
return this;
}

Expand Down Expand Up @@ -1368,16 +1405,39 @@ public Builder useModuleLookup(Lookup lookup) {
return this;
}

/**
* Configures the visibility of host stack frames to the guest application.
* <p>
* By default, guest applications do not have access to host stack frames unless either
* {@link HostAccess#ALL} is specified, or host stack frame visibility is explicitly enabled
* through {@code builder.allowHostStackFrames(HostStackFrames.ALL)}. For backward
* compatibility, host stack frames are also implicitly enabled when
* {@link #allowPublicAccess(boolean)} is invoked.
*
* @param value the policy determining how host stack frames are exposed to the guest
* @throws NullPointerException if {@code value} is {@code null}
* @since 26.0
*/
public Builder allowHostStackFrames(HostStackFrames value) {
Objects.requireNonNull(value);
this.hostStackFrames = value;
return this;
}

/**
* Creates an instance of the custom host access configuration.
*
* @since 19.0
*/
public HostAccess build() {
HostStackFrames useHostStackFrames = hostStackFrames;
if (useHostStackFrames == null) {
useHostStackFrames = HostStackFrames.NONE;
}
return new HostAccess(accessAnnotations, excludeTypes, members, implementationAnnotations, implementableTypes, targetMappings, name, allowPublic,
allowAllImplementations, allowAllClassImplementations, allowArrayAccess, allowListAccess, allowBufferAccess, allowIterableAccess,
allowIteratorAccess, allowMapAccess, allowBigIntegerNumberAccess, allowAccessInheritance, allowMutableTargetMappings, methodScopingDefault, disableMethodScopingAnnotations,
disableMethodScoping, methodLookup);
disableMethodScoping, methodLookup, useHostStackFrames);
}
}

Expand Down
Loading
Loading