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
3 changes: 2 additions & 1 deletion src/main/java/dev/openfeature/sdk/HookSupport.java
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,8 @@ public void executeBeforeHooks(HookSupportData data) {
.orElse(Optional.empty());
if (returnedEvalContext.isPresent()) {
var returnedContext = returnedEvalContext.get();
if (!returnedContext.isEmpty()) {
// yes, we want to check for reference equality here, this prevents recursive layered contexts
if (returnedContext != hookContext.getCtx() && !returnedContext.isEmpty()) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if (returnedContext != hookContext.getCtx() && !returnedContext.isEmpty()) {
if (!returnedContext.isEmpty() && returnedContext != hookContext.getCtx()) {

[Q] would java optimize this anyway or could this be a slight improvement checking for empty first?

data.evaluationContext.putHookContext(returnedContext);
}
}
Expand Down
28 changes: 28 additions & 0 deletions src/test/java/dev/openfeature/sdk/HookSupportTest.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package dev.openfeature.sdk;

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatNoException;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
Expand Down Expand Up @@ -113,6 +114,33 @@ void shouldIsolateDataBetweenHooks(FlagValueType flagValueType) {
assertHookData(testHook2, 2, "before", "after", "finallyAfter", "error");
}

@Test
void hookThatReturnsTheGivenContext_doesNotResultInAStackOverflow() {
var hookSupportData = new HookSupportData();
var recursiveHook = new Hook() {
@Override
public Optional<EvaluationContext> before(HookContext ctx, Map hints) {
return Optional.of(ctx.getCtx());
}
};
var emptyHook = new Hook() {
@Override
public Optional<EvaluationContext> before(HookContext ctx, Map hints) {
return Optional.of(ImmutableContext.EMPTY);
}
};
var layeredEvaluationContext =
new LayeredEvaluationContext(evaluationContextWithValue("key", "value"), null, null, null);
hookSupportData.evaluationContext = layeredEvaluationContext;
hookSupport.setHooks(hookSupportData, List.of(recursiveHook, emptyHook), FlagValueType.STRING);
hookSupport.setHookContexts(
hookSupportData, getBaseHookContextForType(FlagValueType.STRING), layeredEvaluationContext);

callAllHooks(hookSupportData);

assertThatNoException().isThrownBy(layeredEvaluationContext::asObjectMap);
}

private static void callAllHooks(HookSupportData hookSupportData) {
hookSupport.executeBeforeHooks(hookSupportData);
hookSupport.executeAfterHooks(
Expand Down
Loading