You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@freemarker.apache.org by dd...@apache.org on 2019/08/07 23:09:33 UTC

[freemarker] 01/02: Fixed bug when lazily generated result was passed to ?join/?seq_contains/?seq_index_of, and the resulting method wasn't immediately called. Now left hand operand will be eagerly evaluated in such case.

This is an automated email from the ASF dual-hosted git repository.

ddekany pushed a commit to branch 2.3-gae
in repository https://gitbox.apache.org/repos/asf/freemarker.git

commit fa54bbfbcf1d2a5bc98e69a0f4a9950d9aff1b3a
Author: ddekany <dd...@apache.org>
AuthorDate: Wed Aug 7 23:06:32 2019 +0200

    Fixed bug when lazily generated result was passed to ?join/?seq_contains/?seq_index_of, and the resulting method wasn't immediately called. Now left hand operand will be eagerly evaluated in such case.
---
 .../core/BuiltInWithDirectCallOptimization.java    | 31 ++++++++++++++++++++++
 .../java/freemarker/core/BuiltInsForSequences.java | 20 +++++++-------
 src/main/javacc/FTL.jj                             | 18 +++++++++++++
 .../core/LazilyGeneratedCollectionTest.java        | 22 +++++++++++++++
 src/test/java/freemarker/core/MapBiTest.java       |  5 ++++
 5 files changed, 86 insertions(+), 10 deletions(-)

diff --git a/src/main/java/freemarker/core/BuiltInWithDirectCallOptimization.java b/src/main/java/freemarker/core/BuiltInWithDirectCallOptimization.java
new file mode 100644
index 0000000..a92ae43
--- /dev/null
+++ b/src/main/java/freemarker/core/BuiltInWithDirectCallOptimization.java
@@ -0,0 +1,31 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package freemarker.core;
+
+abstract class BuiltInWithDirectCallOptimization extends SpecialBuiltIn {
+
+    /**
+     * Called if the built-in is directly followed by a "(" (ignoring comments and white-space). This can be utilized
+     * for optimizations that only work correctly if the method returned by the built-in is a called immediately (as
+     * opposed to being stored in a variable and then called at an arbitrary later point in time).
+     */
+    protected abstract void setDirectlyCalled();
+
+}
diff --git a/src/main/java/freemarker/core/BuiltInsForSequences.java b/src/main/java/freemarker/core/BuiltInsForSequences.java
index 2ba6b11..55d634e 100644
--- a/src/main/java/freemarker/core/BuiltInsForSequences.java
+++ b/src/main/java/freemarker/core/BuiltInsForSequences.java
@@ -181,11 +181,11 @@ class BuiltInsForSequences {
         
     }
 
-    static class joinBI extends BuiltIn {
+    static class joinBI extends BuiltInWithDirectCallOptimization {
 
         @Override
-        protected boolean isLazilyGeneratedTargetResultSupported() {
-            return true;
+        protected void setDirectlyCalled() {
+            target.enableLazilyGeneratedResult();
         }
 
         private class BIMethodForCollection implements TemplateMethodModelEx {
@@ -295,11 +295,11 @@ class BuiltInsForSequences {
         }
     }
 
-    static class seq_containsBI extends BuiltIn {
+    static class seq_containsBI extends BuiltInWithDirectCallOptimization {
 
         @Override
-        protected boolean isLazilyGeneratedTargetResultSupported() {
-            return true;
+        protected void setDirectlyCalled() {
+            target.enableLazilyGeneratedResult();
         }
 
         private class BIMethodForCollection implements TemplateMethodModelEx {
@@ -367,15 +367,15 @@ class BuiltInsForSequences {
     
     }
     
-    static class seq_index_ofBI extends BuiltIn {
+    static class seq_index_ofBI extends BuiltInWithDirectCallOptimization {
 
         @Override
-        protected boolean isLazilyGeneratedTargetResultSupported() {
-            return true;
+        protected void setDirectlyCalled() {
+            target.enableLazilyGeneratedResult();
         }
 
         private class BIMethod implements TemplateMethodModelEx {
-            
+
             protected final TemplateSequenceModel m_seq;
             protected final TemplateCollectionModel m_col;
             protected final Environment m_env;
diff --git a/src/main/javacc/FTL.jj b/src/main/javacc/FTL.jj
index a157871..363e8bc 100644
--- a/src/main/javacc/FTL.jj
+++ b/src/main/javacc/FTL.jj
@@ -2182,6 +2182,7 @@ Expression BuiltIn(Expression lhoExp) :
     ArrayList<Expression> args = null;
     Token openParen;
     Token closeParen;
+    MethodCall methodCall;
 }
 {
     <BUILT_IN>
@@ -2236,6 +2237,7 @@ Expression BuiltIn(Expression lhoExp) :
             return result;
         }
     }
+
     [
         LOOKAHEAD({
                 result instanceof BuiltInWithParseTimeParameters
@@ -2265,10 +2267,26 @@ Expression BuiltIn(Expression lhoExp) :
             return result;
         }
     ]
+
+    [
+        LOOKAHEAD(<OPEN_PAREN>, { result instanceof BuiltInWithDirectCallOptimization })
+        methodCall = MethodArgs(result)
+        {
+            ((BuiltInWithDirectCallOptimization) result).setDirectlyCalled();
+            return methodCall;
+        }
+    ]
+
     {
+        if (result instanceof BuiltInWithDirectCallOptimization) {
+            // We had no (...)
+            return result;
+        }
+
         // Should have already return-ed
         throw new AssertionError("Unhandled " + SpecialBuiltIn.class.getName() + " subclass: " + result.getClass());
     }
+
 }
 
 // Only supported as the argument of certain built-ins, so it's not called inside Expression.
diff --git a/src/test/java/freemarker/core/LazilyGeneratedCollectionTest.java b/src/test/java/freemarker/core/LazilyGeneratedCollectionTest.java
index fbbf226..0dee5f4 100644
--- a/src/test/java/freemarker/core/LazilyGeneratedCollectionTest.java
+++ b/src/test/java/freemarker/core/LazilyGeneratedCollectionTest.java
@@ -265,6 +265,28 @@ public class LazilyGeneratedCollectionTest extends TemplateTest {
         assertOutput("${seqLong?filter(x->true)[2..*3]?size}", "[size][get 0][get 1][get 2][get 3][get 4]3");
     }
 
+    @Test
+    public void testNonDirectCalledBuiltInsAreNotLazy() throws Exception {
+        assertOutput("" +
+                "<#assign changing = 1>" +
+                "<#assign method = [1, 2]?filter(it -> it != changing)?join>" +
+                "<#assign changing = 2>" +
+                "${method(', ')}",
+                "2");
+        assertOutput("" +
+                "<#assign changing = 1>" +
+                "<#assign method = [1, 2]?filter(it -> it != changing)?seq_contains>" +
+                "<#assign changing = 2>" +
+                "${method(2)?c}",
+                "true");
+        assertOutput("" +
+                "<#assign changing = 1>" +
+                "<#assign method = [1, 2]?filter(it -> it != changing)?seq_index_of>" +
+                "<#assign changing = 2>" +
+                "${method(2)}",
+                "0");
+    }
+
     public static abstract class ListContainingTemplateModel {
         protected final List<Number> elements;
 
diff --git a/src/test/java/freemarker/core/MapBiTest.java b/src/test/java/freemarker/core/MapBiTest.java
index 3e70c0f..927ea52 100644
--- a/src/test/java/freemarker/core/MapBiTest.java
+++ b/src/test/java/freemarker/core/MapBiTest.java
@@ -165,6 +165,11 @@ public class MapBiTest extends TemplateTest {
                 "<#function tenTimes(x)><#assign s += '${x};'><#return x * 10></#function>" +
                 "${(1..3)?map(tenTimes)?seqIndexOf(20)} ${s}", "1 1;2;");
 
+        assertOutput("" +
+                "<#assign s = ''>" +
+                "<#function tenTimes(x)><#assign s += '${x};'><#return x * 10></#function>" +
+                "${[1, 2, 3, 2, 5]?map(tenTimes)?seqLastIndexOf(20)} ${s}", "3 1;2;3;2;5;");
+
         // For these this test can't check that there was no sequence built, but at least we know they are working:
         assertOutput("${(1..3)?map(it -> it * 10)?min}", "10");
         assertOutput("${(1..3)?map(it -> it * 10)?max}", "30");