Skip to content
Open
Show file tree
Hide file tree
Changes from 2 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
22 changes: 22 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,27 @@ 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 layeredEvaluationContext =
new LayeredEvaluationContext(evaluationContextWithValue("key", "value"), null, null, null);
hookSupportData.evaluationContext = layeredEvaluationContext;
hookSupport.setHooks(hookSupportData, List.of(recursiveHook), 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