This is an automated email from the ASF dual-hosted git repository.
ddekany pushed a commit to branch 3
in repository https://gitbox.apache.org/repos/asf/freemarker.git
The following commit(s) were added to refs/heads/3 by this push:
new c178512 Forward ported from 2.3-gae: MemberAccessPolicy now also
covers the special case when toString() is called to convert and object to
string in a template. This was added as toString() might shows information that
you don't want to be exposed.
c178512 is described below
commit c1785120c32fb77dd16e94b9bb91ccc4fd6e9f8a
Author: ddekany <[email protected]>
AuthorDate: Tue Jan 14 23:13:54 2020 +0100
Forward ported from 2.3-gae: MemberAccessPolicy now also covers the special
case when toString() is called to convert and object to string in a template.
This was added as toString() might shows information that you don't want to be
exposed.
---
.../model/impl/DefaultMemberAccessPolicyTest.java | 6 ++
...DefaultObjectWrapperMemberAccessPolicyTest.java | 75 ++++++++++++++++++++++
.../MemberSelectorListMemberAccessPolicyTest.java | 45 ++++++++++++-
.../model/impl/AllowAllMemberAccessPolicy.java | 5 ++
.../core/model/impl/BeanAndStringModel.java | 9 ++-
.../model/impl/BlacklistMemberAccessPolicy.java | 18 ++++++
.../core/model/impl/ClassIntrospector.java | 36 +++++++++--
.../core/model/impl/DefaultMemberAccessPolicy.java | 10 +++
.../core/model/impl/MemberAccessPolicy.java | 10 +++
.../freemarker/core/model/impl/StaticModel.java | 2 +-
.../model/impl/WhitelistMemberAccessPolicy.java | 18 ++++++
11 files changed, 225 insertions(+), 9 deletions(-)
diff --git
a/freemarker-core-test/src/test/java/org/apache/freemarker/core/model/impl/DefaultMemberAccessPolicyTest.java
b/freemarker-core-test/src/test/java/org/apache/freemarker/core/model/impl/DefaultMemberAccessPolicyTest.java
index 42cd905..5993071 100644
---
a/freemarker-core-test/src/test/java/org/apache/freemarker/core/model/impl/DefaultMemberAccessPolicyTest.java
+++
b/freemarker-core-test/src/test/java/org/apache/freemarker/core/model/impl/DefaultMemberAccessPolicyTest.java
@@ -81,6 +81,12 @@ public class DefaultMemberAccessPolicyTest {
}
@Test
+ public void testToString() throws NoSuchMethodException {
+ assertTrue(POLICY.isToStringAlwaysExposed());
+
assertTrue(POLICY.forClass(UserClass.class).isMethodExposed(Object.class.getMethod("toString")));
+ }
+
+ @Test
public void testWellKnownUnsafeMethodsAreBanned() throws
NoSuchMethodException {
{
ClassMemberAccessPolicy classPolicy = POLICY.forClass(Class.class);
diff --git
a/freemarker-core-test/src/test/java/org/apache/freemarker/core/model/impl/DefaultObjectWrapperMemberAccessPolicyTest.java
b/freemarker-core-test/src/test/java/org/apache/freemarker/core/model/impl/DefaultObjectWrapperMemberAccessPolicyTest.java
index 39c3030..529fd84 100644
---
a/freemarker-core-test/src/test/java/org/apache/freemarker/core/model/impl/DefaultObjectWrapperMemberAccessPolicyTest.java
+++
b/freemarker-core-test/src/test/java/org/apache/freemarker/core/model/impl/DefaultObjectWrapperMemberAccessPolicyTest.java
@@ -27,6 +27,7 @@ import java.io.StringWriter;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
+import java.util.Collections;
import java.util.Map;
import org.apache.freemarker.core.Configuration;
@@ -37,6 +38,7 @@ import org.apache.freemarker.core.model.TemplateFunctionModel;
import org.apache.freemarker.core.model.TemplateHashModel;
import org.apache.freemarker.core.model.TemplateModel;
import org.apache.freemarker.core.model.TemplateNumberModel;
+import org.apache.freemarker.core.model.TemplateStringModel;
import org.junit.Test;
import com.google.common.collect.ImmutableMap;
@@ -243,6 +245,11 @@ public class DefaultObjectWrapperMemberAccessPolicyTest {
}
};
}
+
+ @Override
+ public boolean isToStringAlwaysExposed() {
+ return true;
+ }
});
DefaultObjectWrapper ow = owb.build();
@@ -286,6 +293,11 @@ public class DefaultObjectWrapperMemberAccessPolicyTest {
}
};
}
+
+ @Override
+ public boolean isToStringAlwaysExposed() {
+ return true;
+ }
});
DefaultObjectWrapper ow = owb.build();
@@ -319,6 +331,11 @@ public class DefaultObjectWrapperMemberAccessPolicyTest {
}
};
}
+
+ @Override
+ public boolean isToStringAlwaysExposed() {
+ return true;
+ }
});
DefaultObjectWrapper ow = owb.build();
@@ -350,6 +367,11 @@ public class DefaultObjectWrapperMemberAccessPolicyTest {
}
};
}
+
+ @Override
+ public boolean isToStringAlwaysExposed() {
+ return true;
+ }
});
DefaultObjectWrapper ow = owb.build();
@@ -398,6 +420,11 @@ public class DefaultObjectWrapperMemberAccessPolicyTest {
}
};
}
+
+ @Override
+ public boolean isToStringAlwaysExposed() {
+ return true;
+ }
});
DefaultObjectWrapper ow = owb.build();
@@ -449,6 +476,11 @@ public class DefaultObjectWrapperMemberAccessPolicyTest {
}
};
}
+
+ @Override
+ public boolean isToStringAlwaysExposed() {
+ return true;
+ }
})
.build();
@@ -503,6 +535,11 @@ public class DefaultObjectWrapperMemberAccessPolicyTest {
}
};
}
+
+ @Override
+ public boolean isToStringAlwaysExposed() {
+ return true;
+ }
})
.build();
TemplateHashModel statics = (TemplateHashModel)
ow.getStaticModels().get(Statics.class.getName());
@@ -548,6 +585,34 @@ public class DefaultObjectWrapperMemberAccessPolicyTest {
}
}
+ @Test
+ public void testToString1()
+ throws TemplateException, NoSuchMethodException,
NoSuchFieldException, ClassNotFoundException {
+ DefaultObjectWrapper ow = new
DefaultObjectWrapper.Builder(Configuration.VERSION_3_0_0)
+ .memberAccessPolicy(
+ new WhitelistMemberAccessPolicy(
+
MemberSelectorListMemberAccessPolicy.MemberSelector.parse(
+
Collections.singleton(CExtended.class.getName() + ".toString()"),
+ false,
+
DefaultObjectWrapperMemberAccessPolicyTest.class.getClassLoader()
+ )
+ )
+ )
+ .build();
+
+ assertEquals(BeanAndStringModel.TO_STRING_NOT_EXPOSED,
((TemplateStringModel) ow.wrap(new C())).getAsString());
+ assertEquals(CExtended.class.getSimpleName(), ((TemplateStringModel)
ow.wrap(new CExtended())).getAsString());
+ }
+
+ @Test
+ public void testToString2() throws TemplateException {
+ DefaultObjectWrapper ow = new
DefaultObjectWrapper.Builder(Configuration.VERSION_3_0_0)
+
.memberAccessPolicy(DefaultMemberAccessPolicy.getInstance(Configuration.VERSION_3_0_0))
+ .build();
+
+ assertEquals(C.class.getSimpleName(), ((TemplateStringModel)
ow.wrap(new C())).getAsString());
+ }
+
private static Object getHashValue(ObjectWrapperAndUnwrapper ow,
TemplateHashModel objM, String key)
throws TemplateException {
return ow.unwrap(objM.get(key));
@@ -618,6 +683,11 @@ public class DefaultObjectWrapperMemberAccessPolicyTest {
}
public static void M1() { }
+
+ @Override
+ public String toString() {
+ return this.getClass().getSimpleName();
+ }
}
public static class CExtended extends C {
@@ -699,6 +769,11 @@ public class DefaultObjectWrapperMemberAccessPolicyTest {
}
};
}
+
+ @Override
+ public boolean isToStringAlwaysExposed() {
+ return false;
+ }
}
}
diff --git
a/freemarker-core-test/src/test/java/org/apache/freemarker/core/model/impl/MemberSelectorListMemberAccessPolicyTest.java
b/freemarker-core-test/src/test/java/org/apache/freemarker/core/model/impl/MemberSelectorListMemberAccessPolicyTest.java
index b8aeedd..9c7d92f 100644
---
a/freemarker-core-test/src/test/java/org/apache/freemarker/core/model/impl/MemberSelectorListMemberAccessPolicyTest.java
+++
b/freemarker-core-test/src/test/java/org/apache/freemarker/core/model/impl/MemberSelectorListMemberAccessPolicyTest.java
@@ -314,7 +314,7 @@ public class MemberSelectorListMemberAccessPolicyTest {
}
@Test
- public void testBlacklistIgnoredAnnotation() throws NoSuchMethodException,
NoSuchFieldException {
+ public void testBlacklistIgnoresAnnotation() throws NoSuchMethodException,
NoSuchFieldException {
BlacklistMemberAccessPolicy policy = newBlacklistMemberAccessPolicy(
CAnnotationsTest1.class.getName() + ".m5()",
CAnnotationsTest1.class.getName() + ".f5",
@@ -328,6 +328,49 @@ public class MemberSelectorListMemberAccessPolicyTest {
}
@Test
+ public void testBlacklistAndToString() throws NoSuchMethodException {
+ {
+ BlacklistMemberAccessPolicy policy =
newBlacklistMemberAccessPolicy(
+ C1.class.getName() + ".m1()",
+ C1.class.getName() + ".m2()"
+ );
+ assertTrue(policy.isToStringAlwaysExposed());
+
assertTrue(policy.forClass(C1.class).isMethodExposed(Object.class.getMethod("toString")));
+ }
+ {
+ BlacklistMemberAccessPolicy policy =
newBlacklistMemberAccessPolicy(
+ C1.class.getName() + ".m1()",
+ C2.class.getName() + ".toString()",
+ C1.class.getName() + ".m2()"
+ );
+ assertFalse(policy.isToStringAlwaysExposed());
+
assertTrue(policy.forClass(C1.class).isMethodExposed(Object.class.getMethod("toString")));
+
assertFalse(policy.forClass(C2.class).isMethodExposed(Object.class.getMethod("toString")));
+
assertFalse(policy.forClass(C3.class).isMethodExposed(Object.class.getMethod("toString")));
+ }
+ }
+
+ @Test
+ public void testWhitelistAndToString() throws NoSuchMethodException {
+ {
+ WhitelistMemberAccessPolicy policy =
newWhitelistMemberAccessPolicy(
+ C2.class.getName() + ".toString()"
+ );
+ assertFalse(policy.isToStringAlwaysExposed());
+
assertFalse(policy.forClass(C1.class).isMethodExposed(Object.class.getMethod("toString")));
+
assertTrue(policy.forClass(C2.class).isMethodExposed(Object.class.getMethod("toString")));
+
assertTrue(policy.forClass(C3.class).isMethodExposed(Object.class.getMethod("toString")));
+ }
+ {
+ WhitelistMemberAccessPolicy policy =
newWhitelistMemberAccessPolicy(
+ Object.class.getName() + ".toString()"
+ );
+ assertTrue(policy.isToStringAlwaysExposed());
+
assertTrue(policy.forClass(C1.class).isMethodExposed(Object.class.getMethod("toString")));
+ }
+ }
+
+ @Test
public void memberSelectorParserIgnoresWhitespace() throws
NoSuchMethodException {
WhitelistMemberAccessPolicy policy = newWhitelistMemberAccessPolicy(
(CArrayArgs.class.getName() +
".m1(java.lang.String)").replace(".", "\n\t. "),
diff --git
a/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/AllowAllMemberAccessPolicy.java
b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/AllowAllMemberAccessPolicy.java
index f96838b..3e5d747 100644
---
a/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/AllowAllMemberAccessPolicy.java
+++
b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/AllowAllMemberAccessPolicy.java
@@ -50,4 +50,9 @@ final class AllowAllMemberAccessPolicy implements
MemberAccessPolicy {
public ClassMemberAccessPolicy forClass(Class<?> contextClass) {
return CLASS_POLICY_INSTANCE;
}
+
+ @Override
+ public boolean isToStringAlwaysExposed() {
+ return true;
+ }
}
diff --git
a/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/BeanAndStringModel.java
b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/BeanAndStringModel.java
index 4fb931a..52c140c 100644
---
a/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/BeanAndStringModel.java
+++
b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/BeanAndStringModel.java
@@ -41,12 +41,19 @@ public class BeanAndStringModel extends BeanModel
implements TemplateStringModel
super(object, wrapper);
}
+ // Package visible for testing
+ static final String TO_STRING_NOT_EXPOSED = "[toString not exposed]";
+
/**
* Returns the result of calling {@link Object#toString()} on the wrapped
* object.
*/
@Override
public String getAsString() {
- return object.toString();
+ boolean exposeToString =
wrapper.getMemberAccessPolicy().isToStringAlwaysExposed()
+ || !wrapper.getClassIntrospector().get(object.getClass())
+
.containsKey(ClassIntrospector.TO_STRING_HIDDEN_FLAG_KEY);
+ return exposeToString ? object.toString() : TO_STRING_NOT_EXPOSED;
}
+
}
diff --git
a/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/BlacklistMemberAccessPolicy.java
b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/BlacklistMemberAccessPolicy.java
index a7dc07f..8e465e1 100644
---
a/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/BlacklistMemberAccessPolicy.java
+++
b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/BlacklistMemberAccessPolicy.java
@@ -19,6 +19,7 @@
package org.apache.freemarker.core.model.impl;
+import java.lang.reflect.Method;
import java.util.Collection;
/**
@@ -36,6 +37,8 @@ import java.util.Collection;
*/
public class BlacklistMemberAccessPolicy extends
MemberSelectorListMemberAccessPolicy {
+ private final boolean toStringAlwaysExposed;
+
/**
* @param memberSelectors
* List of member selectors; see {@link
MemberSelectorListMemberAccessPolicy} class-level documentation for
@@ -43,6 +46,21 @@ public class BlacklistMemberAccessPolicy extends
MemberSelectorListMemberAccessP
*/
public BlacklistMemberAccessPolicy(Collection<? extends MemberSelector>
memberSelectors) {
super(memberSelectors, ListType.BLACKLIST, null);
+
+ boolean toStringBlacklistedAnywhere = false;
+ for (MemberSelector memberSelector : memberSelectors) {
+ Method method = memberSelector.getMethod();
+ if (method != null && method.getName().equals("toString") &&
method.getParameterTypes().length == 0) {
+ toStringBlacklistedAnywhere = true;
+ break;
+ }
+ }
+ toStringAlwaysExposed = !toStringBlacklistedAnywhere;
+ }
+
+ @Override
+ public boolean isToStringAlwaysExposed() {
+ return toStringAlwaysExposed;
}
}
diff --git
a/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/ClassIntrospector.java
b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/ClassIntrospector.java
index b1650ff..dcb48c8 100644
---
a/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/ClassIntrospector.java
+++
b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/ClassIntrospector.java
@@ -82,6 +82,8 @@ class ClassIntrospector {
new ExecutableMemberSignature("get", new Class[] { String.class });
private static final ExecutableMemberSignature GET_OBJECT_SIGNATURE =
new ExecutableMemberSignature("get", new Class[] { Object.class });
+ private static final ExecutableMemberSignature TO_STRING_SIGNATURE =
+ new ExecutableMemberSignature("toString", new Class[0]);
private static final ClassChangeNotifier CLASS_CHANGE_NOTIFIER;
static {
@@ -129,6 +131,8 @@ class ClassIntrospector {
static final Object CONSTRUCTORS_KEY = new Object();
/** Key in the class info Map to the get(String|Object) Method */
static final Object GENERIC_GET_KEY = new Object();
+ /** Key in the class info Map to the toString() Method */
+ static final Object TO_STRING_HIDDEN_FLAG_KEY = new Object();
//
-----------------------------------------------------------------------------------------------------------------
// Introspection configuration properties:
@@ -256,7 +260,8 @@ class ClassIntrospector {
*/
private Map<Object, Object> createClassIntrospectionData(Class<?> clazz) {
final Map<Object, Object> introspData = new HashMap<>();
- ClassMemberAccessPolicy effClassMemberAccessPolicy =
getEffectiveClassMemberAccessPolicy(clazz);
+ MemberAccessPolicy effMemberAccessPolicy =
getEffectiveMemberAccessPolicy();
+ ClassMemberAccessPolicy effClassMemberAccessPolicy =
effMemberAccessPolicy.forClass(clazz);
if (exposeFields) {
addFieldsToClassIntrospectionData(introspData, clazz,
effClassMemberAccessPolicy);
@@ -264,6 +269,10 @@ class ClassIntrospector {
final Map<ExecutableMemberSignature, List<Method>> accessibleMethods =
discoverAccessibleMethods(clazz);
+ if (!effMemberAccessPolicy.isToStringAlwaysExposed()) {
+ addToStringHiddenFlagToClassIntrospectionData(introspData,
accessibleMethods, effClassMemberAccessPolicy);
+ }
+
addGenericGetToClassIntrospectionData(introspData, accessibleMethods,
effClassMemberAccessPolicy);
if (exposureLevel != DefaultObjectWrapper.EXPOSE_NOTHING) {
@@ -668,6 +677,19 @@ class ClassIntrospector {
}
}
+ private void addToStringHiddenFlagToClassIntrospectionData(Map<Object,
Object> introspData,
+ Map<ExecutableMemberSignature, List<Method>> accessibleMethods,
+ ClassMemberAccessPolicy effClassMemberAccessPolicy) {
+ Method toStringMethod = getFirstAccessibleMethod(TO_STRING_SIGNATURE,
accessibleMethods);
+ if (toStringMethod == null) {
+ throw new BugException("toString() method not found");
+ }
+ // toString() is pretty much always exposed, so we make the negative
case to take extra memory:
+ if (!effClassMemberAccessPolicy.isMethodExposed(toStringMethod)) {
+ introspData.put(TO_STRING_HIDDEN_FLAG_KEY, true);
+ }
+ }
+
private void addConstructorsToClassIntrospectionData(final Map<Object,
Object> introspData,
Class<?> clazz, ClassMemberAccessPolicy
effClassMemberAccessPolicy) {
try {
@@ -788,12 +810,14 @@ class ClassIntrospector {
}
/**
- * Returns the {@link ClassMemberAccessPolicy} to actually use, which is
not just
- * {@link DefaultObjectWrapper#getMemberAccessPolicy()} if {@link
DefaultObjectWrapper#getExposureLevel()} is more allowing than
- * {@link DefaultObjectWrapper#EXPOSE_SAFE}. {@link
DefaultObjectWrapper#EXPOSE_NOTHING} though is not factored in here.
+ * Returns the {@link MemberAccessPolicy} to actually use, which is not
just
+ * {@link DefaultObjectWrapper#getMemberAccessPolicy()} if {@link
DefaultObjectWrapper#getExposureLevel()} is more
+ * allowing than {@link DefaultObjectWrapper#EXPOSE_SAFE}. {@link
DefaultObjectWrapper#EXPOSE_NOTHING} though is
+ * not factored in here.
*/
- ClassMemberAccessPolicy getEffectiveClassMemberAccessPolicy(Class<?>
containingClass) {
- return exposureLevel < DefaultObjectWrapper.EXPOSE_SAFE ?
AllowAllMemberAccessPolicy.CLASS_POLICY_INSTANCE :
memberAccessPolicy.forClass(containingClass);
+ MemberAccessPolicy getEffectiveMemberAccessPolicy() {
+ return exposureLevel < DefaultObjectWrapper.EXPOSE_SAFE ?
AllowAllMemberAccessPolicy.INSTANCE
+ : memberAccessPolicy;
}
private static Map<Method, Class<?>[]> getArgTypesByMethod(Map<Object,
Object> classInfo) {
diff --git
a/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/DefaultMemberAccessPolicy.java
b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/DefaultMemberAccessPolicy.java
index 57659eb..faef934 100644
---
a/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/DefaultMemberAccessPolicy.java
+++
b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/DefaultMemberAccessPolicy.java
@@ -48,6 +48,7 @@ public final class DefaultMemberAccessPolicy implements
MemberAccessPolicy {
private final Set<Class<?>> whitelistRuleNonFinalClasses;
private final WhitelistMemberAccessPolicy whitelistMemberAccessPolicy;
private final BlacklistMemberAccessPolicy blacklistMemberAccessPolicy;
+ private final boolean toStringAlwaysExposed;
/**
* Returns the singleton that's compatible with the given incompatible
improvements version.
@@ -145,6 +146,10 @@ public final class DefaultMemberAccessPolicy implements
MemberAccessPolicy {
}
}
blacklistMemberAccessPolicy = new
BlacklistMemberAccessPolicy(blacklistMemberSelectors);
+
+ toStringAlwaysExposed =
+ whitelistMemberAccessPolicy.isToStringAlwaysExposed()
+ && blacklistMemberAccessPolicy.isToStringAlwaysExposed();
} catch (Exception e) {
throw new IllegalStateException("Couldn't init " +
this.getClass().getName() + " instance", e);
}
@@ -174,6 +179,11 @@ public final class DefaultMemberAccessPolicy implements
MemberAccessPolicy {
}
}
+ @Override
+ public boolean isToStringAlwaysExposed() {
+ return toStringAlwaysExposed;
+ }
+
private boolean isTypeWithWhitelistRule(Class<?> contextClass) {
if (whitelistRuleFinalClasses.contains(contextClass)) {
return true;
diff --git
a/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/MemberAccessPolicy.java
b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/MemberAccessPolicy.java
index b603c82..e4d9547 100644
---
a/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/MemberAccessPolicy.java
+++
b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/MemberAccessPolicy.java
@@ -66,4 +66,14 @@ public interface MemberAccessPolicy {
* The exact class of object from which members will be get in the
templates.
*/
ClassMemberAccessPolicy forClass(Class<?> contextClass);
+
+ /**
+ * If this returns {@code true}, we won't invoke the probably more
expensive lookup to figure out if
+ * {@link Object#toString()} (including its overridden variants) is
exposed for a given object. If this returns
+ * {@code false}, then no such optimization is made. This method was
introduced as {@link Object#toString()} is
+ * called frequently, as it's used whenever an object is converted to
string, like printed to the output, and it's
+ * not even a reflection-based call (we just call {@link
Object#toString()} in Java). So we try to avoid the
+ * overhead of a more generic method call.
+ */
+ boolean isToStringAlwaysExposed();
}
diff --git
a/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/StaticModel.java
b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/StaticModel.java
index e5e0e9d..fad96e8 100644
---
a/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/StaticModel.java
+++
b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/StaticModel.java
@@ -119,7 +119,7 @@ final class StaticModel implements TemplateHashModelEx {
}
ClassMemberAccessPolicy effClassMemberAccessPolicy =
-
wrapper.getClassIntrospector().getEffectiveClassMemberAccessPolicy(clazz);
+
wrapper.getClassIntrospector().getEffectiveMemberAccessPolicy().forClass(clazz);
Field[] fields = clazz.getFields();
for (Field field : fields) {
diff --git
a/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/WhitelistMemberAccessPolicy.java
b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/WhitelistMemberAccessPolicy.java
index ecd6c77..ffacad0 100644
---
a/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/WhitelistMemberAccessPolicy.java
+++
b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/WhitelistMemberAccessPolicy.java
@@ -19,6 +19,7 @@
package org.apache.freemarker.core.model.impl;
+import java.lang.reflect.Method;
import java.util.Collection;
import org.apache.freemarker.core.model.ObjectWrapper;
@@ -40,6 +41,17 @@ import org.apache.freemarker.core.model.ObjectWrapper;
*/
public class WhitelistMemberAccessPolicy extends
MemberSelectorListMemberAccessPolicy {
+ private static final Method TO_STRING_METHOD;
+ static {
+ try {
+ TO_STRING_METHOD = Object.class.getMethod("toString");
+ } catch (NoSuchMethodException e) {
+ throw new IllegalStateException(e);
+ }
+ }
+
+ private final boolean toStringAlwaysExposed;
+
/**
* @param memberSelectors
* List of member selectors; see {@link
MemberSelectorListMemberAccessPolicy} class-level documentation for
@@ -47,6 +59,12 @@ public class WhitelistMemberAccessPolicy extends
MemberSelectorListMemberAccessP
*/
public WhitelistMemberAccessPolicy(Collection<? extends MemberSelector>
memberSelectors) {
super(memberSelectors, ListType.WHITELIST, TemplateAccessible.class);
+ toStringAlwaysExposed =
forClass(Object.class).isMethodExposed(TO_STRING_METHOD);
+ }
+
+ @Override
+ public boolean isToStringAlwaysExposed() {
+ return toStringAlwaysExposed;
}
}
\ No newline at end of file