diff --git a/src/main/java/com/hubspot/jinjava/util/EagerContextWatcher.java b/src/main/java/com/hubspot/jinjava/util/EagerContextWatcher.java index 83ed8f674..334886152 100644 --- a/src/main/java/com/hubspot/jinjava/util/EagerContextWatcher.java +++ b/src/main/java/com/hubspot/jinjava/util/EagerContextWatcher.java @@ -123,8 +123,7 @@ private static Map getInitiallyResolvedAsStrings( ? entrySet : interpreter.getContext().getCombinedScope().entrySet()).stream() .filter(entry -> initiallyResolvedHashes.containsKey(entry.getKey())) - .filter(entry -> - EagerExpressionResolver.isResolvableObject(entry.getValue(), 4, 400) // TODO make this configurable + .filter(entry -> isResolvableForContextReverting(entry.getValue()) // TODO make this configurable ); entryStream.forEach(entry -> cacheRevertibleObject( @@ -392,15 +391,19 @@ private static Object getObjectOrHashCode(Object o) { o = ((LazyExpression) o).get(); } - if (o instanceof PyList && !((PyList) o).toList().contains(o)) { + if (o instanceof PyList && isResolvableForContextReverting(o)) { return o.hashCode(); } - if (o instanceof PyMap && !((PyMap) o).toMap().containsValue(o)) { + if (o instanceof PyMap && isResolvableForContextReverting(o)) { return o.hashCode() + ((PyMap) o).keySet().hashCode(); } return o; } + private static boolean isResolvableForContextReverting(Object o) { + return EagerExpressionResolver.isResolvableObject(o, 4, 400); + } + public static class EagerChildContextConfig { private final boolean takeNewValue; diff --git a/src/test/java/com/hubspot/jinjava/EagerTest.java b/src/test/java/com/hubspot/jinjava/EagerTest.java index f396de7f6..51bc5ae6f 100644 --- a/src/test/java/com/hubspot/jinjava/EagerTest.java +++ b/src/test/java/com/hubspot/jinjava/EagerTest.java @@ -1741,6 +1741,13 @@ public void itHandlesModifiedIncludePathSecondPass() { ); } + @Test + public void itDoesNotStackOverflowTryingToBuildHashcode() { + expectedTemplateInterpreter.assertExpectedOutput( + "does-not-stack-overflow-trying-to-build-hashcode/test" + ); + } + @Test public void itHandlesDeferredValueInRenderFilter() { expectedTemplateInterpreter.assertExpectedOutput( diff --git a/src/test/resources/eager/does-not-stack-overflow-trying-to-build-hashcode/test.expected.jinja b/src/test/resources/eager/does-not-stack-overflow-trying-to-build-hashcode/test.expected.jinja new file mode 100644 index 000000000..889c365e4 --- /dev/null +++ b/src/test/resources/eager/does-not-stack-overflow-trying-to-build-hashcode/test.expected.jinja @@ -0,0 +1,3 @@ +{% for i in deferred %} +hey +{% endfor %} \ No newline at end of file diff --git a/src/test/resources/eager/does-not-stack-overflow-trying-to-build-hashcode/test.jinja b/src/test/resources/eager/does-not-stack-overflow-trying-to-build-hashcode/test.jinja new file mode 100644 index 000000000..1e55549b4 --- /dev/null +++ b/src/test/resources/eager/does-not-stack-overflow-trying-to-build-hashcode/test.jinja @@ -0,0 +1,12 @@ +{% set l1000_1 = [] %} +{% set l1000_2 = [] %} +{% set l1000_3 = [] %} + +{% do l1000_1.append(l1000_2) %} +{% do l1000_2.append(l1000_3) %} +{% do l1000_3.append(l1000_1) %} + + +{% for i in deferred %} +{{ 'hey' }} +{% endfor %} \ No newline at end of file