[MAGNOLIA-8738] Weird exception in freemarker when rendering empty string from expression (${""}) Created: 02/Feb/23 Updated: 03/Feb/23 |
|
| Status: | Open |
| Project: | Magnolia |
| Component/s: | None |
| Affects Version/s: | 6.2.26 |
| Fix Version/s: | None |
| Type: | Bug | Priority: | Neutral |
| Reporter: | Samuli Saarinen | Assignee: | Unassigned |
| Resolution: | Unresolved | Votes: | 0 |
| Labels: | None | ||
| Remaining Estimate: | Not Specified | ||
| Time Spent: | Not Specified | ||
| Original Estimate: | Not Specified | ||
| Template: |
|
| Acceptance criteria: |
Empty
|
| Task DoD: |
[ ]*
Doc/release notes changes? Comment present?
[ ]*
Downstream builds green?
[ ]*
Solution information and context easily available?
[ ]*
Tests
[ ]*
FixVersion filled and not yet released
[ ] 
Architecture Decision Record (ADR)
|
| Bug DoR: |
[ ]*
Steps to reproduce, expected, and actual results filled
[ ]*
Affected version filled
|
| Description |
Steps to reproduce
Caused by: java.lang.StringIndexOutOfBoundsException: endIndex must be valid at org.apache.commons.text.TextStringBuilder.append(TextStringBuilder.java:548) ~[commons-text-1.10.0.jar:1.10.0] at org.apache.commons.text.TextStringBuilder.append(TextStringBuilder.java:77) ~[commons-text-1.10.0.jar:1.10.0] at info.magnolia.rendering.util.AppendableWriter.write(AppendableWriter.java:66) ~[classes/:?] at java.io.Writer.write(Writer.java:290) ~[?:?] at java.io.Writer.write(Writer.java:249) ~[?:?] at java.io.Writer.append(Writer.java:322) ~[?:?] at java.io.Writer.append(Writer.java:366) ~[?:?] at java.io.Writer.append(Writer.java:51) ~[?:?] at info.magnolia.rendering.util.AppendableWriter.write(AppendableWriter.java:66) ~[classes/:?] at java.io.Writer.write(Writer.java:290) ~[?:?] at java.io.Writer.write(Writer.java:249) ~[?:?] at freemarker.core.DollarVariable.accept(DollarVariable.java:70) ~[freemarker-2.3.31.jar:2.3.31] at freemarker.core.Environment.visit(Environment.java:347) ~[freemarker-2.3.31.jar:2.3.31] at freemarker.core.Environment.visit(Environment.java:389) ~[freemarker-2.3.31.jar:2.3.31] at freemarker.core.Environment.invokeMacroOrFunctionCommonPart(Environment.java:889) ~[freemarker-2.3.31.jar:2.3.31] at freemarker.core.Environment.invokeMacro(Environment.java:825) ~[freemarker-2.3.31.jar:2.3.31] at freemarker.core.UnifiedCall.accept(UnifiedCall.java:84) ~[freemarker-2.3.31.jar:2.3.31] at freemarker.core.Environment.visit(Environment.java:383) ~[freemarker-2.3.31.jar:2.3.31] at freemarker.core.Environment.invokeNestedContent(Environment.java:633) ~[freemarker-2.3.31.jar:2.3.31] at freemarker.core.BodyInstruction.accept(BodyInstruction.java:60) ~[freemarker-2.3.31.jar:2.3.31] at freemarker.core.Environment.visit(Environment.java:383) ~[freemarker-2.3.31.jar:2.3.31] at freemarker.core.Environment.invokeMacroOrFunctionCommonPart(Environment.java:889) ~[freemarker-2.3.31.jar:2.3.31] at freemarker.core.Environment.invokeMacro(Environment.java:825) ~[freemarker-2.3.31.jar:2.3.31] at freemarker.core.UnifiedCall.accept(UnifiedCall.java:84) ~[freemarker-2.3.31.jar:2.3.31] at freemarker.core.Environment.visit(Environment.java:347) ~[freemarker-2.3.31.jar:2.3.31] at freemarker.core.Environment.visit(Environment.java:389) ~[freemarker-2.3.31.jar:2.3.31] at freemarker.core.Environment.invokeMacroOrFunctionCommonPart(Environment.java:889) ~[freemarker-2.3.31.jar:2.3.31] at freemarker.core.Environment.invokeMacro(Environment.java:825) ~[freemarker-2.3.31.jar:2.3.31] at freemarker.core.UnifiedCall.accept(UnifiedCall.java:84) ~[freemarker-2.3.31.jar:2.3.31] at freemarker.core.Environment.visit(Environment.java:347) ~[freemarker-2.3.31.jar:2.3.31] at freemarker.core.Environment.visit(Environment.java:353) ~[freemarker-2.3.31.jar:2.3.31] at freemarker.core.Environment.visit(Environment.java:353) ~[freemarker-2.3.31.jar:2.3.31] at freemarker.core.Environment.process(Environment.java:326) ~[freemarker-2.3.31.jar:2.3.31] at freemarker.template.Template.process(Template.java:383) ~[freemarker-2.3.31.jar:2.3.31] at info.magnolia.freemarker.FreemarkerHelper.render(FreemarkerHelper.java:174) ~[magnolia-freemarker-support-6.2.26.jar:?] at info.magnolia.rendering.renderer.FreemarkerRenderer.onRender(FreemarkerRenderer.java:100) ~[magnolia-rendering-6.2.26.jar:?] at info.magnolia.rendering.renderer.AbstractRenderer.render(AbstractRenderer.java:166) ~[magnolia-rendering-6.2.26.jar:?] at jdk.internal.reflect.GeneratedMethodAccessor405.invoke(Unknown Source) ~[?:?] at jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[?:?] at java.lang.reflect.Method.invoke(Method.java:566) ~[?:?] at info.magnolia.config.source.DefinitionProviderWrapperWithProxyFallback$DirectDelegator.interceptWithReturnValue(DefinitionProviderWrapperWithProxyFallback.java:151) ~[magnolia-configuration-6.2.26.jar:?] at info.magnolia.rendering.renderer.FreemarkerRenderer$ByteBuddy$wmpCBbn3.render(Unknown Source) ~[?:?] at info.magnolia.rendering.engine.DefaultRenderingEngine.render(DefaultRenderingEngine.java:120) ~[magnolia-rendering-6.2.26.jar:?] at info.magnolia.rendering.engine.DefaultRenderingEngine.render(DefaultRenderingEngine.java:101) ~[magnolia-rendering-6.2.26.jar:?] at info.magnolia.rendering.engine.DefaultRenderingEngine.render(DefaultRenderingEngine.java:96) ~[magnolia-rendering-6.2.26.jar:?] at info.magnolia.rendering.engine.DefaultRenderingEngine$$EnhancerByCGLIB$$704b1c97.render(<generated>) ~[magnolia-rendering-6.2.26.jar:?] at info.magnolia.templating.elements.ComponentElement.begin(ComponentElement.java:122) ~[magnolia-templating-6.2.26.jar:?] at info.magnolia.templating.renderers.NoScriptRenderer.onRender(NoScriptRenderer.java:103) ~[magnolia-templating-6.2.26.jar:?]
This is happening in some special situation when freemarker output is written to TextStringBuilder which might not always be the case as this was happening on some of our components and I could not reproduce it every where (like page template or some other component for example) I also don't know what has changed that started causing this as the components that started showing the error have been in use for several years.
As a workraround it seems that it is enough to render e.g. space (${" "}) or if I wrap the component in 'attempt' block that probably changes the "sink" where output is written. Also overriding the info.magnolia.rendering.util.AppendableWriter#write with something like this seems to fix the issue @Override public void write(char[] chars, int start, int end) throws IOException { if(end > 0) { appendable.append(new String(chars), start, end); } }
Expected resultsNo exception |
| Comments |
| Comment by Samuli Saarinen [ 03/Feb/23 ] |
|
I dig this a bit deeper and found out that the special case was in our code base that was rendering parts of web page content to a string using TextStringBuilder from commons-io. I could resolve the situation using java.lang.StringBuilder as the appendable. But I still think that your AppendableWriter could be fixed as well and for a reference you can look for the implementation in e.g. Guava that behaves correctly: https://github.com/google/guava/blob/master/guava/src/com/google/common/io/AppendableWriter.java#L58 Here is my test set that I used import info.magnolia.rendering.util.AppendableWriter; import org.junit.Test; import java.io.IOException; public class MagnoliaAppendableWriterTest { @Test public void writeEmptyWithCommonsTextStringBuilder() throws Exception { AppendableWriter w1 = new AppendableWriter(new org.apache.commons.text.TextStringBuilder()); w1.write(""); // fail } @Test public void writeEmptyWithStringBuilder() throws Exception { AppendableWriter w1 = new AppendableWriter(new StringBuilder()); w1.write(""); // pass } @Test public void writeEmptyWithCommonsTextStringBuilderWithGuavaAppendableWriter() throws Exception { GuavaAppendableWriter w3 = new GuavaAppendableWriter(new org.apache.commons.text.TextStringBuilder()); w3.write(""); //pass } public static class GuavaAppendableWriter extends AppendableWriter { private final Appendable appendable; public GuavaAppendableWriter(Appendable appendable) { super(appendable); this.appendable = appendable; } @Override public void write(char[] chars, int start, int end) throws IOException { // from https://github.com/google/guava/blob/master/guava/src/com/google/common/io/AppendableWriter.java#L58 appendable.append(new String(chars, start, end)); } } } |