Skip to content

Commit

Permalink
Qute: fix possible stack overflow error in InsertSectionHelper
Browse files Browse the repository at this point in the history
  • Loading branch information
mkouba committed Jun 28, 2024
1 parent edb58b5 commit 8230825
Show file tree
Hide file tree
Showing 4 changed files with 149 additions and 5 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,38 @@ public InsertSectionHelper(String name, SectionBlock defaultBlock) {

@Override
public CompletionStage<ResultNode> resolve(SectionResolutionContext context) {
SectionBlock extending = context.resolutionContext().getExtendingBlock(name);
// Note that {#insert} is evaluated on the current resolution context
// Therefore, we need to try to find the "correct" parent context to avoid stack
// overflow errors when using the same block names
ResolutionContext rc = findParentResolutionContext(context.resolutionContext());
if (rc == null) {
// No parent context found - use the current
rc = context.resolutionContext();
}
SectionBlock extending = rc.getExtendingBlock(name);
if (extending != null) {
return context.execute(extending, context.resolutionContext());
return context.execute(extending, rc);
} else {
return context.execute(defaultBlock, context.resolutionContext());
return context.execute(defaultBlock, rc);
}
}

private ResolutionContext findParentResolutionContext(ResolutionContext context) {
if (context.getParent() == null) {
return null;
}
// Let's iterate over all extending blocks and try to find the "correct" parent context
// The "correct" parent context is the parent of a context that contains this helper
// instance in any of its extending block
SectionBlock block = context.getCurrentExtendingBlock(name);
if (block != null && block.findNode(this::containsThisHelperInstance) != null) {
return context.getParent();
}
return findParentResolutionContext(context.getParent());
}

private boolean containsThisHelperInstance(TemplateNode node) {
return node.isSection() && node.asSection().helper == this;
}

public static class Factory implements SectionHelperFactory<InsertSectionHelper> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
import java.util.concurrent.CompletionStage;

/**
*
* The resolution context holds the current context object.
*/
public interface ResolutionContext {

Expand Down Expand Up @@ -46,12 +46,21 @@ public interface ResolutionContext {
ResolutionContext getParent();

/**
* If no extending block exists for the given name then the parent context (if present) is queried.
*
* @param name
* @return the extending block for the specified name or null
* @return the extending block for the specified name or {@code null}
*/
SectionBlock getExtendingBlock(String name);

/**
* Unlike {@link #getExtendingBlock(String)} this method never queries the parent context.
*
* @param name
* @return the extending block for the specified name or {@code null}
*/
SectionBlock getCurrentExtendingBlock(String name);

/**
*
* @param key
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,11 @@ public SectionBlock getExtendingBlock(String name) {
return null;
}

@Override
public SectionBlock getCurrentExtendingBlock(String name) {
return getExtendingBlock(name);
}

@Override
public Object getAttribute(String key) {
return attributeFun.apply(key);
Expand Down Expand Up @@ -116,6 +121,14 @@ public SectionBlock getExtendingBlock(String name) {
return null;
}

@Override
public SectionBlock getCurrentExtendingBlock(String name) {
if (extendingBlocks != null) {
return extendingBlocks.get(name);
}
return null;
}

@Override
public Object getAttribute(String key) {
return parent.getAttribute(key);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -251,4 +251,100 @@ public void testIsolation() {
assertEquals("NOT_FOUND", engine.parse("{#include foo _isolated /}").data("name", "Dorka").render());
}

@Test
public void testNestedMainBlocks() {
Engine engine = Engine.builder()
.addDefaults()
.build();

engine.putTemplate("root", engine.parse("""
<html>
<body>{#insert /}</body>
</html>
"""));
engine.putTemplate("auth", engine.parse("""
{#include root}
<div>
{#insert /}
</div>
{/include}
"""));
assertEquals("<html><body><div><form>LoginForm</form></div></body>"
+ "</html>", engine.parse("""
{#include auth}
<form>Login Form</form>
{/include}
""").render().replaceAll("\\s+", ""));

engine.putTemplate("next", engine.parse("""
{#include auth}
<foo>
{#insert /}
</foo>
{/include}
"""));

// 1. top -> push child rc#1 with extending block $default$
// 2. next -> push child rc#2 with extending block $default$
// 3. auth -> push child rc#3 with extending block $default$
// 4. root -> eval {#insert}, looks up $default$ in rc#3
// 5. auth -> eval {#insert}, looks up $default$ in rc#2
// 6. next -> eval {#insert}, looks up $default$ in rc#1
assertEquals("<html><body><div><foo><form>LoginForm</form></foo></div></body>"
+ "</html>", engine.parse("""
{#include next}
<form>Login Form</form>
{/include}
""").render().replaceAll("\\s+", ""));
}

@Test
public void testNestedBlocksWithSameName() {
Engine engine = Engine.builder()
.addDefaults()
.build();

engine.putTemplate("root", engine.parse("""
<html>
<body>{#insert foo /}</body>
</html>
"""));
engine.putTemplate("auth", engine.parse("""
{#include root}
{#foo}
<div>
{#insert foo /}
</div>
{/foo}
{/include}
"""));
assertEquals("<html><body><div><form>LoginForm</form></div></body>"
+ "</html>", engine.parse("""
{#include auth}
{#foo}
<form>Login Form</form>
{/foo}
{/include}
""").render().replaceAll("\\s+", ""));

engine.putTemplate("next", engine.parse("""
{#include auth}
{#foo}
<foo>
{#insert foo /}
</foo>
{/foo}
{/include}
"""));

assertEquals("<html><body><div><foo><form>LoginForm</form></foo></div></body>"
+ "</html>", engine.parse("""
{#include next}
{#foo}
<form>Login Form</form>
{/foo}
{/include}
""").render().replaceAll("\\s+", ""));
}

}

0 comments on commit 8230825

Please sign in to comment.