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


The following commit(s) were added to refs/heads/2.3-gae by this push:
     new 7a6e6c5  FREEMARKER-124: Changed DefaultMemberAccessPolicy to block 
more methods. Added LegacyDefaultMemberAccessPolicy in case an existing 
application has problems with the stricter defaults. This caused some 
reworkings in other classes too: - Constructor member selectors (in a whitelist 
or blacklist) don't match constructors in subclasses anymore. - Added 
BlacklistMemberAccessPolicy (by refactoring WhitelistMemberAccessPolicy to 
extend MemberSelectorListMemberAccessPolicy). - I [...]
7a6e6c5 is described below

commit 7a6e6c578979186decf9fc3507522a2196e5e236
Author: ddekany <[email protected]>
AuthorDate: Sat Jan 11 15:07:18 2020 +0100

    FREEMARKER-124: Changed DefaultMemberAccessPolicy to block more methods. 
Added LegacyDefaultMemberAccessPolicy in case an existing application has 
problems with the stricter defaults.
    This caused some reworkings in other classes too:
    - Constructor member selectors (in a whitelist or blacklist) don't match 
constructors in subclasses anymore.
    - Added BlacklistMemberAccessPolicy (by refactoring 
WhitelistMemberAccessPolicy to extend MemberSelectorListMemberAccessPolicy).
    - Improved JavaDoc comments in related classes
---
 src/main/java/freemarker/core/Configurable.java    |   5 +
 .../java/freemarker/ext/beans/BeansWrapper.java    |   4 +-
 .../ext/beans/BeansWrapperConfiguration.java       |   1 +
 .../ext/beans/BlacklistMemberAccessPolicy.java     |  50 ++
 .../freemarker/ext/beans/ConstructorMatcher.java   |   5 +
 .../ext/beans/DefaultMemberAccessPolicy.java       | 209 +++++---
 .../java/freemarker/ext/beans/FieldMatcher.java    |   5 +
 ...y.java => LegacyDefaultMemberAccessPolicy.java} |  52 +-
 .../freemarker/ext/beans/MemberAccessPolicy.java   |  23 +-
 .../java/freemarker/ext/beans/MemberMatcher.java   |  14 +-
 ...a => MemberSelectorListMemberAccessPolicy.java} | 123 +++--
 .../java/freemarker/ext/beans/MethodMatcher.java   |   5 +
 .../ext/beans/WhitelistMemberAccessPolicy.java     | 387 +-------------
 .../freemarker/template/DefaultObjectWrapper.java  |   9 +-
 .../ext/beans/DefaultMemberAccessPolicy-rules      | 582 +++++++++++++++++++++
 .../freemarker/ext/beans/unsafeMethods.properties  |   8 +
 src/manual/en_US/book.xml                          |  25 +-
 .../ext/beans/DefaultMemberAccessPolicyTest.java   | 143 +++++
 ...DefaultObjectWrapperMemberAccessPolicyTest.java |  72 +--
 .../beans/LegacyDefaultMemberAccessPolicyTest.java |  73 +++
 ...ava => MemberSelectorListAccessPolicyTest.java} | 114 +++-
 .../freemarker/template/ConfigurationTest.java     |  38 ++
 22 files changed, 1340 insertions(+), 607 deletions(-)

diff --git a/src/main/java/freemarker/core/Configurable.java 
b/src/main/java/freemarker/core/Configurable.java
index 89f3445..a0ad656 100644
--- a/src/main/java/freemarker/core/Configurable.java
+++ b/src/main/java/freemarker/core/Configurable.java
@@ -52,6 +52,7 @@ import freemarker.cache.PathRegexMatcher;
 import freemarker.cache.TemplateLoader;
 import freemarker.ext.beans.BeansWrapper;
 import freemarker.ext.beans.BeansWrapperBuilder;
+import freemarker.ext.beans.MemberAccessPolicy;
 import freemarker.template.AttemptExceptionReporter;
 import freemarker.template.Configuration;
 import freemarker.template.DefaultObjectWrapper;
@@ -1565,6 +1566,10 @@ public class Configurable {
      * FreeMarker 2.3.x, and {@link TemplateClassResolver#SAFER_RESOLVER}
      * starting from FreeMarker 2.4.0. If you allow users to upload templates,
      * it's important to use a custom restrictive {@link 
TemplateClassResolver}.
+     *
+     * <p>Note that the {@link MemberAccessPolicy} used by the {@link 
ObjectWrapper} also influences what constructors
+     * are available. Allowing the resolution of the class here is not enough 
in itself, as the
+     * {@link MemberAccessPolicy} has to allow exposing the particular 
constructor you try to call as well.
      * 
      * @since 2.3.17
      */
diff --git a/src/main/java/freemarker/ext/beans/BeansWrapper.java 
b/src/main/java/freemarker/ext/beans/BeansWrapper.java
index d67c3ac..2bda4b9 100644
--- a/src/main/java/freemarker/ext/beans/BeansWrapper.java
+++ b/src/main/java/freemarker/ext/beans/BeansWrapper.java
@@ -666,8 +666,8 @@ public class BeansWrapper implements RichObjectWrapper, 
WriteProtectable {
     }
 
     /**
-     * Used to customize what  members will be hidden;
-     * see {@link 
BeansWrapperBuilder#setMemberAccessPolicy(MemberAccessPolicy)} for more.
+     * Sets the {@link MemberAccessPolicy}; default is {@link 
DefaultMemberAccessPolicy#getInstance(Version)}, which
+     * is not appropriate if template editors aren't trusted.
      *
      * @since 2.3.30
      */
diff --git a/src/main/java/freemarker/ext/beans/BeansWrapperConfiguration.java 
b/src/main/java/freemarker/ext/beans/BeansWrapperConfiguration.java
index f791d12..323275e 100644
--- a/src/main/java/freemarker/ext/beans/BeansWrapperConfiguration.java
+++ b/src/main/java/freemarker/ext/beans/BeansWrapperConfiguration.java
@@ -229,6 +229,7 @@ public abstract class BeansWrapperConfiguration implements 
Cloneable {
         return classIntrospectorBuilder.getMemberAccessPolicy();
     }
 
+    /** See {@link BeansWrapper#setMemberAccessPolicy(MemberAccessPolicy)}. */
     public void setMemberAccessPolicy(MemberAccessPolicy memberAccessPolicy) {
         classIntrospectorBuilder.setMemberAccessPolicy(memberAccessPolicy);
     }
diff --git 
a/src/main/java/freemarker/ext/beans/BlacklistMemberAccessPolicy.java 
b/src/main/java/freemarker/ext/beans/BlacklistMemberAccessPolicy.java
new file mode 100644
index 0000000..f81890c
--- /dev/null
+++ b/src/main/java/freemarker/ext/beans/BlacklistMemberAccessPolicy.java
@@ -0,0 +1,50 @@
+/*
+ * 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.ext.beans;
+
+import java.util.Collection;
+
+/**
+ * Blacklist-based member access policy, that is, members that are matched by 
the listing will not be accessible, all
+ * others will be. Note that {@link BeansWrapper} and its subclasses doesn't 
discover all members on the first place,
+ * and the {@link MemberAccessPolicy} just removes from that set of members, 
never adds to it.
+ *
+ * <p>This class is rarely useful in itself, and mostly meant to be used when 
composing a {@link MemberAccessPolicy}
+ * from other {@link MemberAccessPolicy}-es. If you are serious about 
security, never use this alone; consider using
+ * {@link WhitelistMemberAccessPolicy} as part of your solution.
+ *
+ * <p>See more about the rules at {@link 
MemberSelectorListMemberAccessPolicy}. Unlike
+ * {@link WhitelistMemberAccessPolicy}, {@link BlacklistMemberAccessPolicy} 
doesn't have annotations that can be used
+ * to add members to the member selector list.
+ *
+ * @since 2.3.30
+ */
+public class BlacklistMemberAccessPolicy extends 
MemberSelectorListMemberAccessPolicy {
+
+    /**
+     * @param memberSelectors
+     *      List of member selectors; see {@link 
MemberSelectorListMemberAccessPolicy} class-level documentation for
+     *      more.
+     */
+    public BlacklistMemberAccessPolicy(Collection<MemberSelector> 
memberSelectors) {
+        super(memberSelectors, ListType.BLACKLIST, null);
+    }
+
+}
diff --git a/src/main/java/freemarker/ext/beans/ConstructorMatcher.java 
b/src/main/java/freemarker/ext/beans/ConstructorMatcher.java
index 2c94576..0f5548c 100644
--- a/src/main/java/freemarker/ext/beans/ConstructorMatcher.java
+++ b/src/main/java/freemarker/ext/beans/ConstructorMatcher.java
@@ -31,4 +31,9 @@ final class ConstructorMatcher extends 
MemberMatcher<Constructor<?>, ExecutableM
     protected ExecutableMemberSignature toMemberSignature(Constructor<?> 
member) {
         return new ExecutableMemberSignature(member);
     }
+
+    @Override
+    protected boolean matchInUpperBoundTypeSubtypes() {
+        return false;
+    }
 }
diff --git a/src/main/java/freemarker/ext/beans/DefaultMemberAccessPolicy.java 
b/src/main/java/freemarker/ext/beans/DefaultMemberAccessPolicy.java
index 8c1186d..c27f662 100644
--- a/src/main/java/freemarker/ext/beans/DefaultMemberAccessPolicy.java
+++ b/src/main/java/freemarker/ext/beans/DefaultMemberAccessPolicy.java
@@ -19,93 +19,37 @@
 
 package freemarker.ext.beans;
 
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
 import java.lang.reflect.Constructor;
 import java.lang.reflect.Field;
 import java.lang.reflect.Method;
-import java.util.HashMap;
+import java.lang.reflect.Modifier;
+import java.util.ArrayList;
 import java.util.HashSet;
-import java.util.Map;
-import java.util.Properties;
+import java.util.List;
 import java.util.Set;
-import java.util.StringTokenizer;
 
+import 
freemarker.ext.beans.MemberSelectorListMemberAccessPolicy.MemberSelector;
 import freemarker.template.Version;
 import freemarker.template._TemplateAPI;
-import freemarker.template.utility.ClassUtil;
 
 /**
- * Legacy black list based member access policy, used only to keep old 
behavior, as it can't provide meaningful safety.
- * Do not use it if you allow untrusted users to edit templates!
+ * Member access policy, used  to implement default behavior that's mostly 
compatible with pre-2.3.30 versions, but is
+ * somewhat safer; it still can't provide safety in practice, if you allow 
untrusted users to edit templates! Use
+ * {@link WhitelistMemberAccessPolicy} if you need stricter control.
  *
  * @since 2.3.30
  */
 public final class DefaultMemberAccessPolicy implements MemberAccessPolicy {
 
-    private static final String UNSAFE_METHODS_PROPERTIES = 
"unsafeMethods.properties";
-    private static final Set<Method> UNSAFE_METHODS = createUnsafeMethodsSet();
-
-    private static Set<Method> createUnsafeMethodsSet() {
-        try {
-            Properties props = ClassUtil.loadProperties(BeansWrapper.class, 
UNSAFE_METHODS_PROPERTIES);
-            Set<Method> set = new HashSet<Method>(props.size() * 4 / 3, 1f);
-            Map<String, Class<?>> primClasses = createPrimitiveClassesMap();
-            for (Object key : props.keySet()) {
-                try {
-                    set.add(parseMethodSpec((String) key, primClasses));
-                } catch (ClassNotFoundException e) {
-                    if (ClassIntrospector.DEVELOPMENT_MODE) {
-                        throw e;
-                    }
-                } catch (NoSuchMethodException e) {
-                    if (ClassIntrospector.DEVELOPMENT_MODE) {
-                        throw e;
-                    }
-                }
-            }
-            return set;
-        } catch (Exception e) {
-            throw new RuntimeException("Could not load unsafe method set", e);
-        }
-    }
-
-    private static Method parseMethodSpec(String methodSpec, Map<String, 
Class<?>> primClasses)
-    throws ClassNotFoundException,
-        NoSuchMethodException {
-        int brace = methodSpec.indexOf('(');
-        int dot = methodSpec.lastIndexOf('.', brace);
-        Class<?> clazz = ClassUtil.forName(methodSpec.substring(0, dot));
-        String methodName = methodSpec.substring(dot + 1, brace);
-        String argSpec = methodSpec.substring(brace + 1, methodSpec.length() - 
1);
-        StringTokenizer tok = new StringTokenizer(argSpec, ",");
-        int argcount = tok.countTokens();
-        Class<?>[] argTypes = new Class[argcount];
-        for (int i = 0; i < argcount; i++) {
-            String argClassName = tok.nextToken();
-            argTypes[i] = primClasses.get(argClassName);
-            if (argTypes[i] == null) {
-                argTypes[i] = ClassUtil.forName(argClassName);
-            }
-        }
-        return clazz.getMethod(methodName, argTypes);
-    }
-
-    private static Map<String, Class<?>> createPrimitiveClassesMap() {
-        Map<String, Class<?>> map = new HashMap<String, Class<?>>();
-        map.put("boolean", Boolean.TYPE);
-        map.put("byte", Byte.TYPE);
-        map.put("char", Character.TYPE);
-        map.put("short", Short.TYPE);
-        map.put("int", Integer.TYPE);
-        map.put("long", Long.TYPE);
-        map.put("float", Float.TYPE);
-        map.put("double", Double.TYPE);
-        return map;
-    }
-
     private static final DefaultMemberAccessPolicy INSTANCE = new 
DefaultMemberAccessPolicy();
 
-    private DefaultMemberAccessPolicy() {
-    }
+    private final Set<Class<?>> whitelistRuleFinalClasses;
+    private final Set<Class<?>> whitelistRuleNonFinalClasses;
+    private final WhitelistMemberAccessPolicy whitelistMemberAccessPolicy;
+    private final BlacklistMemberAccessPolicy blacklistMemberAccessPolicy;
 
     /**
      * Returns the singleton that's compatible with the given incompatible 
improvements version.
@@ -117,24 +61,127 @@ public final class DefaultMemberAccessPolicy implements 
MemberAccessPolicy {
         return INSTANCE;
     }
 
-    public ClassMemberAccessPolicy forClass(Class<?> containingClass) {
-        return CLASS_MEMBER_ACCESS_POLICY_INSTANCE;
-    }
+    private DefaultMemberAccessPolicy() {
+        try {
+            ClassLoader classLoader = 
DefaultMemberAccessPolicy.class.getClassLoader();
+
+            whitelistRuleFinalClasses = new HashSet<Class<?>>();
+            whitelistRuleNonFinalClasses = new HashSet<Class<?>>();
+            Set<Class<?>> typesWithBlacklistUnlistedRule = new 
HashSet<Class<?>>();
+            List<MemberSelector> whitelistMemberSelectors = new 
ArrayList<MemberSelector>();
+            for (String line : loadMemberSelectorFileLines()) {
+                line = line.trim();
+                if (!MemberSelector.isIgnoredLine(line)) {
+                    if (line.startsWith("@")) {
+                        String[] lineParts = line.split("\\s+");
+                        if (lineParts.length != 2) {
+                            throw new IllegalStateException("Malformed @ line: 
" + line);
+                        }
+                        String typeName = lineParts[1];
+                        Class<?> upperBoundType;
+                        try {
+                            upperBoundType = classLoader.loadClass(typeName);
+                        } catch (ClassNotFoundException e) {
+                            upperBoundType = null;
+                        }
+                        String rule = lineParts[0].substring(1);
+                        if (rule.equals("whitelistPolicyIfAssignable")) {
+                            if (upperBoundType != null) {
+                                Set<Class<?>> targetSet =
+                                        (upperBoundType.getModifiers() & 
Modifier.FINAL) != 0
+                                                ? whitelistRuleFinalClasses
+                                                : whitelistRuleNonFinalClasses;
+                                targetSet.add(upperBoundType);
+                            }
+                        } else if (rule.equals("blacklistUnlistedMembers")) {
+                            if (upperBoundType != null) {
+                                
typesWithBlacklistUnlistedRule.add(upperBoundType);
+                            }
+                        } else {
+                            throw new IllegalStateException("Unhandled rule: " 
+ rule);
+                        }
+                    } else {
+                        MemberSelector memberSelector =
+                                MemberSelector.parse(line, classLoader);
+                        Class<?> upperBoundType = 
memberSelector.getUpperBoundType();
+                        if (upperBoundType != null) {
+                            if 
(!whitelistRuleFinalClasses.contains(upperBoundType)
+                                    && 
!whitelistRuleNonFinalClasses.contains(upperBoundType)
+                                    && 
!typesWithBlacklistUnlistedRule.contains(upperBoundType)) {
+                                throw new IllegalStateException("Type without 
rule: " + upperBoundType.getName());
+                            }
+                            // We always do the same, as 
"blacklistUnlistedMembers" is also defined via a whitelist:
+                            whitelistMemberSelectors.add(memberSelector);
+                        }
+                    }
+                }
+            }
+
+            whitelistMemberAccessPolicy = new 
WhitelistMemberAccessPolicy(whitelistMemberSelectors);
 
-    private static final BacklistClassMemberAccessPolicy 
CLASS_MEMBER_ACCESS_POLICY_INSTANCE
-            = new BacklistClassMemberAccessPolicy();
-    private static class BacklistClassMemberAccessPolicy implements 
ClassMemberAccessPolicy {
+            // Generate blacklists based on the whitelist and the members of 
"blacklistUnlistedMembers" types:
+            List<MemberSelector> blacklistMemberSelectors = new 
ArrayList<MemberSelector>();
+            for (Class<?> blacklistUnlistedRuleType : 
typesWithBlacklistUnlistedRule) {
+                ClassMemberAccessPolicy classPolicy = 
whitelistMemberAccessPolicy.forClass(blacklistUnlistedRuleType);
+                for (Method method : blacklistUnlistedRuleType.getMethods()) {
+                    if (!classPolicy.isMethodExposed(method)) {
+                        blacklistMemberSelectors.add(new 
MemberSelector(blacklistUnlistedRuleType, method));
+                    }
+                }
+                for (Constructor<?> constructor : 
blacklistUnlistedRuleType.getConstructors()) {
+                    if (!classPolicy.isConstructorExposed(constructor)) {
+                        blacklistMemberSelectors.add(new 
MemberSelector(blacklistUnlistedRuleType, constructor));
+                    }
+                }
+                for (Field field : blacklistUnlistedRuleType.getFields()) {
+                    if (!classPolicy.isFieldExposed(field)) {
+                        blacklistMemberSelectors.add(new 
MemberSelector(blacklistUnlistedRuleType, field));
+                    }
+                }
+            }
+            blacklistMemberAccessPolicy = new 
BlacklistMemberAccessPolicy(blacklistMemberSelectors);
+        } catch (Exception e) {
+            throw new IllegalStateException("Couldn't init " + 
this.getClass().getName() + " instance", e);
+        }
+    }
 
-        public boolean isMethodExposed(Method method) {
-            return !UNSAFE_METHODS.contains(method);
+    private static List<String> loadMemberSelectorFileLines() throws 
IOException {
+        List<String> whitelist = new ArrayList<String>();
+        BufferedReader reader =
+                new BufferedReader(
+                        new InputStreamReader(
+                                
DefaultMemberAccessPolicy.class.getResourceAsStream("DefaultMemberAccessPolicy-rules"),
+                                "UTF-8"));
+        try {
+            String line;
+            while ((line = reader.readLine()) != null){
+                whitelist.add(line);
+            }
+        } finally {
+            reader.close();
         }
 
-        public boolean isConstructorExposed(Constructor<?> constructor) {
-            return true;
+        return whitelist;
+    }
+
+    public ClassMemberAccessPolicy forClass(Class<?> contextClass) {
+        if (isTypeWithWhitelistRule(contextClass)) {
+            return whitelistMemberAccessPolicy.forClass(contextClass);
+        } else {
+            return blacklistMemberAccessPolicy.forClass(contextClass);
         }
+    }
 
-        public boolean isFieldExposed(Field field) {
+    private boolean isTypeWithWhitelistRule(Class<?> contextClass) {
+        if (whitelistRuleFinalClasses.contains(contextClass)) {
             return true;
         }
+        for (Class<?> nonFinalClass : whitelistRuleNonFinalClasses) {
+            if (nonFinalClass.isAssignableFrom(contextClass)) {
+                return true;
+            }
+        }
+        return false;
     }
+
 }
diff --git a/src/main/java/freemarker/ext/beans/FieldMatcher.java 
b/src/main/java/freemarker/ext/beans/FieldMatcher.java
index f67bf14..179ea26 100644
--- a/src/main/java/freemarker/ext/beans/FieldMatcher.java
+++ b/src/main/java/freemarker/ext/beans/FieldMatcher.java
@@ -31,4 +31,9 @@ final class FieldMatcher extends MemberMatcher<Field, String> 
{
     protected String toMemberSignature(Field member) {
         return member.getName();
     }
+
+    @Override
+    protected boolean matchInUpperBoundTypeSubtypes() {
+        return true;
+    }
 }
diff --git a/src/main/java/freemarker/ext/beans/DefaultMemberAccessPolicy.java 
b/src/main/java/freemarker/ext/beans/LegacyDefaultMemberAccessPolicy.java
similarity index 64%
copy from src/main/java/freemarker/ext/beans/DefaultMemberAccessPolicy.java
copy to src/main/java/freemarker/ext/beans/LegacyDefaultMemberAccessPolicy.java
index 8c1186d..13b6c41 100644
--- a/src/main/java/freemarker/ext/beans/DefaultMemberAccessPolicy.java
+++ b/src/main/java/freemarker/ext/beans/LegacyDefaultMemberAccessPolicy.java
@@ -22,24 +22,22 @@ package freemarker.ext.beans;
 import java.lang.reflect.Constructor;
 import java.lang.reflect.Field;
 import java.lang.reflect.Method;
-import java.util.HashMap;
 import java.util.HashSet;
-import java.util.Map;
 import java.util.Properties;
 import java.util.Set;
 import java.util.StringTokenizer;
 
-import freemarker.template.Version;
-import freemarker.template._TemplateAPI;
 import freemarker.template.utility.ClassUtil;
 
 /**
- * Legacy black list based member access policy, used only to keep old 
behavior, as it can't provide meaningful safety.
- * Do not use it if you allow untrusted users to edit templates!
+ * Legacy blacklist based member access policy, used only to keep old 
behavior, as it can't provide meaningful safety.
+ * Do not use it if you allow untrusted users to edit templates! Use {@link 
WhitelistMemberAccessPolicy} then.
  *
  * @since 2.3.30
  */
-public final class DefaultMemberAccessPolicy implements MemberAccessPolicy {
+public final class LegacyDefaultMemberAccessPolicy implements 
MemberAccessPolicy {
+
+    public static final LegacyDefaultMemberAccessPolicy INSTANCE = new 
LegacyDefaultMemberAccessPolicy();
 
     private static final String UNSAFE_METHODS_PROPERTIES = 
"unsafeMethods.properties";
     private static final Set<Method> UNSAFE_METHODS = createUnsafeMethodsSet();
@@ -48,10 +46,9 @@ public final class DefaultMemberAccessPolicy implements 
MemberAccessPolicy {
         try {
             Properties props = ClassUtil.loadProperties(BeansWrapper.class, 
UNSAFE_METHODS_PROPERTIES);
             Set<Method> set = new HashSet<Method>(props.size() * 4 / 3, 1f);
-            Map<String, Class<?>> primClasses = createPrimitiveClassesMap();
             for (Object key : props.keySet()) {
                 try {
-                    set.add(parseMethodSpec((String) key, primClasses));
+                    set.add(parseMethodSpec((String) key));
                 } catch (ClassNotFoundException e) {
                     if (ClassIntrospector.DEVELOPMENT_MODE) {
                         throw e;
@@ -68,7 +65,7 @@ public final class DefaultMemberAccessPolicy implements 
MemberAccessPolicy {
         }
     }
 
-    private static Method parseMethodSpec(String methodSpec, Map<String, 
Class<?>> primClasses)
+    private static Method parseMethodSpec(String methodSpec)
     throws ClassNotFoundException,
         NoSuchMethodException {
         int brace = methodSpec.indexOf('(');
@@ -81,7 +78,7 @@ public final class DefaultMemberAccessPolicy implements 
MemberAccessPolicy {
         Class<?>[] argTypes = new Class[argcount];
         for (int i = 0; i < argcount; i++) {
             String argClassName = tok.nextToken();
-            argTypes[i] = primClasses.get(argClassName);
+            argTypes[i] = ClassUtil.resolveIfPrimitiveTypeName(argClassName);
             if (argTypes[i] == null) {
                 argTypes[i] = ClassUtil.forName(argClassName);
             }
@@ -89,41 +86,16 @@ public final class DefaultMemberAccessPolicy implements 
MemberAccessPolicy {
         return clazz.getMethod(methodName, argTypes);
     }
 
-    private static Map<String, Class<?>> createPrimitiveClassesMap() {
-        Map<String, Class<?>> map = new HashMap<String, Class<?>>();
-        map.put("boolean", Boolean.TYPE);
-        map.put("byte", Byte.TYPE);
-        map.put("char", Character.TYPE);
-        map.put("short", Short.TYPE);
-        map.put("int", Integer.TYPE);
-        map.put("long", Long.TYPE);
-        map.put("float", Float.TYPE);
-        map.put("double", Double.TYPE);
-        return map;
-    }
-
-    private static final DefaultMemberAccessPolicy INSTANCE = new 
DefaultMemberAccessPolicy();
-
-    private DefaultMemberAccessPolicy() {
-    }
-
-    /**
-     * Returns the singleton that's compatible with the given incompatible 
improvements version.
-     */
-    public static DefaultMemberAccessPolicy getInstance(Version 
incompatibleImprovements) {
-        _TemplateAPI.checkVersionNotNullAndSupported(incompatibleImprovements);
-        // All breakpoints here must occur in 
ClassIntrospectorBuilder.normalizeIncompatibleImprovementsVersion!
-        // Though currently we don't have any.
-        return INSTANCE;
+    private LegacyDefaultMemberAccessPolicy() {
     }
 
     public ClassMemberAccessPolicy forClass(Class<?> containingClass) {
         return CLASS_MEMBER_ACCESS_POLICY_INSTANCE;
     }
 
-    private static final BacklistClassMemberAccessPolicy 
CLASS_MEMBER_ACCESS_POLICY_INSTANCE
-            = new BacklistClassMemberAccessPolicy();
-    private static class BacklistClassMemberAccessPolicy implements 
ClassMemberAccessPolicy {
+    private static final BlacklistClassMemberAccessPolicy 
CLASS_MEMBER_ACCESS_POLICY_INSTANCE
+            = new BlacklistClassMemberAccessPolicy();
+    private static class BlacklistClassMemberAccessPolicy implements 
ClassMemberAccessPolicy {
 
         public boolean isMethodExposed(Method method) {
             return !UNSAFE_METHODS.contains(method);
diff --git a/src/main/java/freemarker/ext/beans/MemberAccessPolicy.java 
b/src/main/java/freemarker/ext/beans/MemberAccessPolicy.java
index c8de56d..eacb97d 100644
--- a/src/main/java/freemarker/ext/beans/MemberAccessPolicy.java
+++ b/src/main/java/freemarker/ext/beans/MemberAccessPolicy.java
@@ -19,26 +19,31 @@
 
 package freemarker.ext.beans;
 
+import freemarker.core.Environment;
 import freemarker.template.DefaultObjectWrapper;
 import freemarker.template.DefaultObjectWrapperBuilder;
 import freemarker.template.ObjectWrapper;
 import freemarker.template.TemplateModel;
 
 /**
- * Implement this to specify what class members are accessible from templates.
+ * Implement this to restrict what class members (methods, fields, 
constructors) are accessible from templates.
+ * Note, however, that {@link BeansWrapper} and its subclasses doesn't 
discover all members on the first place, and the
+ * {@link MemberAccessPolicy} just removes from that set of members, never 
adds to it. Practically speaking, it's the
+ * last filter in the chain.
  *
- * <p>The instance is usually set via {@link 
BeansWrapperBuilder#setMemberAccessPolicy(MemberAccessPolicy)} (or if
- * you use {@link DefaultObjectWrapper}, with
- * {@link 
DefaultObjectWrapperBuilder#setMemberAccessPolicy(MemberAccessPolicy)}).
+ * <p>{@link MemberAccessPolicy}-s meant to be used inside {@link 
ObjectWrapper}-s, and their existence is transparent
+ * for the rest of the system. The instance is usually set via
+ * {@link BeansWrapperBuilder#setMemberAccessPolicy(MemberAccessPolicy)} (or 
if you use {@link DefaultObjectWrapper},
+ * with {@link 
DefaultObjectWrapperBuilder#setMemberAccessPolicy(MemberAccessPolicy)}).
  *
  * <p>As {@link BeansWrapper}, and its subclasses like {@link 
DefaultObjectWrapper}, only discover public
- * members, it's pointless to whitelist non-public members. An {@link 
MemberAccessPolicy} is a filter applied to
- * the set of members that {@link BeansWrapper} intends to expose on the first 
place. (Also, while public members
- * declared in non-public classes are discovered by {@link BeansWrapper}, Java 
reflection will not allow accessing those
- * normally, so generally it's not useful to whitelist those either.)
+ * members, it's pointless to whitelist non-public members. (Also, while 
public members declared in non-public classes
+ * are discovered by {@link BeansWrapper}, Java reflection will not allow 
accessing those normally, so generally it's
+ * not useful to whitelist those either.)
  *
  * <p>Note that if you add {@link TemplateModel}-s directly to the data-model, 
those are not wrapped by the
- * {@link ObjectWrapper}, and so the {@link MemberAccessPolicy} won't affect 
those.
+ * {@link ObjectWrapper} (from {@link Environment#getObjectWrapper()}), and so 
the {@link MemberAccessPolicy} won't
+ * affect those.
  *
  * <p>Implementations must be thread-safe, and instances generally should be 
singletons on JVM level. FreeMarker
  * caches its class metadata in a global (static, JVM-scope) cache for shared 
use, and the {@link MemberAccessPolicy}
diff --git a/src/main/java/freemarker/ext/beans/MemberMatcher.java 
b/src/main/java/freemarker/ext/beans/MemberMatcher.java
index 2b52178..34ccc9c 100644
--- a/src/main/java/freemarker/ext/beans/MemberMatcher.java
+++ b/src/main/java/freemarker/ext/beans/MemberMatcher.java
@@ -45,6 +45,8 @@ abstract class MemberMatcher<M extends Member, S> {
      */
     protected abstract S toMemberSignature(M member);
 
+    protected abstract boolean matchInUpperBoundTypeSubtypes();
+
     /**
      * Adds a member that this {@link MemberMatcher} will match.
      *
@@ -85,7 +87,17 @@ abstract class MemberMatcher<M extends Member, S> {
         S memberSignature = toMemberSignature(member);
         Types upperBoundTypes = 
signaturesToUpperBoundTypes.get(memberSignature);
 
-        return upperBoundTypes != null && 
containsTypeOrSuperType(upperBoundTypes, contextClass);
+        return upperBoundTypes != null
+                && (matchInUpperBoundTypeSubtypes()
+                        ? containsTypeOrSuperType(upperBoundTypes, 
contextClass)
+                        : containsExactType(upperBoundTypes, contextClass));
+    }
+
+    private static boolean containsExactType(Types types, Class<?> c) {
+        if (c == null) {
+            return false;
+        }
+        return types.set.contains(c);
     }
 
     private static boolean containsTypeOrSuperType(Types types, Class<?> c) {
diff --git 
a/src/main/java/freemarker/ext/beans/WhitelistMemberAccessPolicy.java 
b/src/main/java/freemarker/ext/beans/MemberSelectorListMemberAccessPolicy.java
similarity index 77%
copy from src/main/java/freemarker/ext/beans/WhitelistMemberAccessPolicy.java
copy to 
src/main/java/freemarker/ext/beans/MemberSelectorListMemberAccessPolicy.java
index 5e8945a..93c2788 100644
--- a/src/main/java/freemarker/ext/beans/WhitelistMemberAccessPolicy.java
+++ 
b/src/main/java/freemarker/ext/beans/MemberSelectorListMemberAccessPolicy.java
@@ -19,6 +19,7 @@
 
 package freemarker.ext.beans;
 
+import java.lang.annotation.Annotation;
 import java.lang.reflect.Constructor;
 import java.lang.reflect.Field;
 import java.lang.reflect.Method;
@@ -28,55 +29,62 @@ import java.util.List;
 import java.util.StringTokenizer;
 
 import freemarker.log.Logger;
-import freemarker.template.ObjectWrapper;
 import freemarker.template.utility.ClassUtil;
 import freemarker.template.utility.NullArgumentException;
 
 /**
- * Whitelist-based member access policy, that is, only members that you have 
explicitly whitelisted will be accessible.
- * The whitelist content is application specific, and can be significant work 
to put together, but it's the only way
- * you can achieve any practical safety if you don't fully trust the users who 
can edit templates. Of course, this only
- * can deal with the {@link ObjectWrapper} aspect of safety; please check the 
Manual to see what else is needed. Also,
- * since this is related to security, read the documentation of {@link 
MemberAccessPolicy}, to know about the
- * pitfalls and edge cases related to {@link MemberAccessPolicy}-es in general.
+ * Superclass for member-selector-list-based member access policies, like 
{@link WhitelistMemberAccessPolicy}.
  *
- * <p>There are two ways you can add members to the whitelist:
+ * <p>There are two ways you can add members to the member selector list:
  * <ul>
  *     <li>Via a list of member selectors passed to the constructor
- *     <li>Via {@link TemplateAccessible} annotation
+ *     <li>Via annotation (concrete type depends on subclass)
  * </ul>
  *
- * <p>When a member is whitelisted, it's identified by the following data 
(with the example of
- * {@code com.example.MyClass.myMethod(int, int)} being whitelisted):
+ * <p>Members are identified with the following data (with the example of
+ * {@code com.example.MyClass.myMethod(int, int)}):
  * <ul>
  *    <li>Upper bound class ({@code com.example.MyClass} in the example)
  *    <li>Member name ({@code myMethod} in the example), except for 
constructors where it's unused
  *    <li>Parameter types ({@code int, int} in the example), except for fields 
where it's unused
  * </ul>
  *
- * <p>Once you have whitelisted a member in the upper bound class, it will be 
automatically whitelisted in all
- * subclasses of that, even if the whitelisted member is a field or 
constructor (which doesn't support overriding, but
- * it will be treated as such if the field name or constructor parameter types 
match).
- * It's called "upper bound" class, because the member will only be 
whitelisted in classes that are {@code instanceof}
- * the upper bound class. That restriction stands even if the member was 
inherited from another class or
- * interface, and it wasn't even overridden in the upper bound class; the 
member won't be whitelisted in the
- * class/interface where it was inherited from, if that type is more generic 
than the upper bound class.
+ * <p>If a method or field is matched in the upper bound type, it will be 
automatically matched in all subtypes of that.
+ * It's called "upper bound" type, because the member will only be matched in 
classes that are {@code instanceof}
+ * the upper bound class. That restriction stands even if the member was 
inherited from another type (class or
+ * interface), and it wasn't even overridden in the upper bound type; the 
member won't be matched in the
+ * type where it was inherited from, if that type is more generic than the 
upper bound type.
  *
- * <p>Note that the return type of methods aren't used in any way. So if you 
whitelist {@code myMethod(int, int)}, and
- * it has multiple variants with different return types (which is possible on 
the bytecode level), then you have
- * whitelisted all variants of it.
+ * <p>The above inheritance rule doesn't apply to constructors. That's 
consistent with the fact constructors aren't
+ * inherited in Java (or pretty much any other language). So for example, if 
you add {@code com.example.A.A()} to
+ * the member selector list, and {@code B extends A}, then {@code 
com.example.B.B()} is still not matched by that list.
+ * If you want it to be matched, you have to add {@code com.example.B.B()} to 
list explicitly.
+ *
+ * <p>Note that the return type of methods aren't used in any way. If {@code 
myMethod(int, int)} has multiple variants
+ * with different return types (which is possible on the bytecode level) but 
the same parameter types, then all
+ * variants of it is matched, or none is. Similarly, the type of fields isn't 
used either, only the name of the field
+ * matters.
  *
  * @since 2.3.30
  */
-public class WhitelistMemberAccessPolicy implements MemberAccessPolicy {
+public abstract class MemberSelectorListMemberAccessPolicy implements 
MemberAccessPolicy {
     private static final Logger LOG = Logger.getLogger("freemarker.beans");
 
+    enum ListType {
+        /** Only matched members will be exposed. */
+        WHITELIST,
+        /** Matched members will not be exposed. */
+        BLACKLIST
+    }
+
+    private final ListType listType;
     private final MethodMatcher methodMatcher;
     private final ConstructorMatcher constructorMatcher;
     private final FieldMatcher fieldMatcher;
+    private final Class<? extends Annotation> matchAnnotation;
 
     /**
-     * A condition that matches some type members. See {@link 
WhitelistMemberAccessPolicy} documentation for more.
+     * A condition that matches some type members. See {@link 
MemberSelectorListMemberAccessPolicy} documentation for more.
      * Exactly one of these will be non-{@code null}:
      * {@link #getMethod()}, {@link #getConstructor()}, {@link #getField()}, 
{@link #getException()}.
      *
@@ -262,7 +270,7 @@ public class WhitelistMemberAccessPolicy implements 
MemberAccessPolicy {
 
             if (hasArgList) {
                 if (cleanedStr.charAt(cleanedStr.length() - 1) != ')') {
-                    throw new IllegalArgumentException("Malformed whitelist 
entry (missing closing ')'): "
+                    throw new IllegalArgumentException("Malformed whitelist 
entry (should end with ')'): "
                             + memberSelectorString);
                 }
                 String argsSpec = cleanedStr.substring(postMemberNameIdx + 1, 
cleanedStr.length() - 1);
@@ -316,19 +324,46 @@ public class WhitelistMemberAccessPolicy implements 
MemberAccessPolicy {
         }
 
         /**
-         * Convenience method to parse all member selectors in the collection; 
see {@link #parse(String, ClassLoader)}.
+         * Convenience method to parse all member selectors in the collection 
(see {@link #parse(String, ClassLoader)}),
+         * while also filtering out blank and comment lines; see {@link 
#parse(String, ClassLoader)},
+         * and {@link #isIgnoredLine(String)}.
          */
         public static List<MemberSelector> parse(Collection<String> 
memberSelectors,
                 ClassLoader classLoader) {
             List<MemberSelector> parsedMemberSelectors = new 
ArrayList<MemberSelector>(memberSelectors.size());
             for (String memberSelector : memberSelectors) {
-                parsedMemberSelectors.add(parse(memberSelector, classLoader));
+                if (!isIgnoredLine(memberSelector)) {
+                    parsedMemberSelectors.add(parse(memberSelector, 
classLoader));
+                }
             }
             return parsedMemberSelectors;
         }
+
+        /**
+         * A line is ignored if it's blank or a comment. A line is be blank if 
it doesn't contain non-whitespace
+         * character. A line is a comment if it starts with {@code #}, or 
{@code //} (ignoring any amount of
+         * preceding whitespace).
+         */
+        public static boolean isIgnoredLine(String line) {
+            String trimmedLine = line.trim();
+            return trimmedLine.length() == 0 || trimmedLine.startsWith("#") || 
trimmedLine.startsWith("//");
+        }
     }
 
-    public WhitelistMemberAccessPolicy(Collection<MemberSelector> 
memberSelectors) {
+    /**
+     * @param memberSelectors
+     *      List of member selectors; see {@link 
MemberSelectorListMemberAccessPolicy} class-level documentation for
+     *      more.
+     * @param listType
+     *      Decides the "color" of the list
+     * @param matchAnnotation
+     */
+    MemberSelectorListMemberAccessPolicy(
+            Collection<MemberSelector> memberSelectors, ListType listType,
+            Class<? extends Annotation> matchAnnotation) {
+        this.listType = listType;
+        this.matchAnnotation = matchAnnotation;
+
         methodMatcher = new MethodMatcher();
         constructorMatcher = new ConstructorMatcher();
         fieldMatcher = new FieldMatcher();
@@ -351,26 +386,44 @@ public class WhitelistMemberAccessPolicy implements 
MemberAccessPolicy {
         }
     }
 
-    public ClassMemberAccessPolicy forClass(final Class<?> contextClass) {
+    public final ClassMemberAccessPolicy forClass(final Class<?> contextClass) 
{
         return new ClassMemberAccessPolicy() {
             public boolean isMethodExposed(Method method) {
-                return methodMatcher.matches(contextClass, method)
-                        || _MethodUtil.getInheritableAnnotation(contextClass, 
method, TemplateAccessible.class) != null;
+                return matchResultToIsExposedResult(
+                        methodMatcher.matches(contextClass, method)
+                        || matchAnnotation != null
+                                && 
_MethodUtil.getInheritableAnnotation(contextClass, method, matchAnnotation)
+                                        != null);
             }
 
             public boolean isConstructorExposed(Constructor<?> constructor) {
-                return constructorMatcher.matches(contextClass, constructor)
-                        || _MethodUtil.getInheritableAnnotation(contextClass, 
constructor, TemplateAccessible.class)
-                                != null;
+                return matchResultToIsExposedResult(
+                        constructorMatcher.matches(contextClass, constructor)
+                        || matchAnnotation != null
+                                && 
_MethodUtil.getInheritableAnnotation(contextClass, constructor, matchAnnotation)
+                                        != null);
             }
 
             public boolean isFieldExposed(Field field) {
-                return fieldMatcher.matches(contextClass, field)
-                        || _MethodUtil.getInheritableAnnotation(contextClass, 
field, TemplateAccessible.class) != null;
+                return matchResultToIsExposedResult(
+                        fieldMatcher.matches(contextClass, field)
+                        || matchAnnotation != null
+                                && 
_MethodUtil.getInheritableAnnotation(contextClass, field, matchAnnotation)
+                                        != null);
             }
         };
     }
 
+    private boolean matchResultToIsExposedResult(boolean matches) {
+        if (listType == ListType.WHITELIST) {
+            return matches;
+        }
+        if (listType == ListType.BLACKLIST) {
+            return !matches;
+        }
+        throw new AssertionError();
+    }
+
     private static boolean isWellFormedClassName(String s) {
         if (s.length() == 0) {
             return false;
diff --git a/src/main/java/freemarker/ext/beans/MethodMatcher.java 
b/src/main/java/freemarker/ext/beans/MethodMatcher.java
index 7df56be..b7a5b07 100644
--- a/src/main/java/freemarker/ext/beans/MethodMatcher.java
+++ b/src/main/java/freemarker/ext/beans/MethodMatcher.java
@@ -35,4 +35,9 @@ final class MethodMatcher extends MemberMatcher<Method, 
ExecutableMemberSignatur
     protected ExecutableMemberSignature toMemberSignature(Method member) {
         return new ExecutableMemberSignature(member);
     }
+
+    @Override
+    protected boolean matchInUpperBoundTypeSubtypes() {
+        return true;
+    }
 }
diff --git 
a/src/main/java/freemarker/ext/beans/WhitelistMemberAccessPolicy.java 
b/src/main/java/freemarker/ext/beans/WhitelistMemberAccessPolicy.java
index 5e8945a..9c33884 100644
--- a/src/main/java/freemarker/ext/beans/WhitelistMemberAccessPolicy.java
+++ b/src/main/java/freemarker/ext/beans/WhitelistMemberAccessPolicy.java
@@ -19,393 +19,36 @@
 
 package freemarker.ext.beans;
 
-import java.lang.reflect.Constructor;
-import java.lang.reflect.Field;
-import java.lang.reflect.Method;
-import java.util.ArrayList;
 import java.util.Collection;
-import java.util.List;
-import java.util.StringTokenizer;
 
-import freemarker.log.Logger;
 import freemarker.template.ObjectWrapper;
-import freemarker.template.utility.ClassUtil;
-import freemarker.template.utility.NullArgumentException;
 
 /**
- * Whitelist-based member access policy, that is, only members that you have 
explicitly whitelisted will be accessible.
- * The whitelist content is application specific, and can be significant work 
to put together, but it's the only way
- * you can achieve any practical safety if you don't fully trust the users who 
can edit templates. Of course, this only
- * can deal with the {@link ObjectWrapper} aspect of safety; please check the 
Manual to see what else is needed. Also,
- * since this is related to security, read the documentation of {@link 
MemberAccessPolicy}, to know about the
- * pitfalls and edge cases related to {@link MemberAccessPolicy}-es in general.
+ * Whitelist-based member access policy, that is, only members that are 
matched by the listing will be exposed.
+ * Note that {@link BeansWrapper} and its subclasses doesn't discover all 
members on the first place, and the
+ * {@link MemberAccessPolicy} just removes from that set of members, never 
adds to it.
  *
- * <p>There are two ways you can add members to the whitelist:
- * <ul>
- *     <li>Via a list of member selectors passed to the constructor
- *     <li>Via {@link TemplateAccessible} annotation
- * </ul>
+ * <p>The whitelist content is usually application specific, and can be 
significant work to put together, but it's the
+ * only way you can achieve any practical safety when you don't fully trust 
the users who can edit templates.
  *
- * <p>When a member is whitelisted, it's identified by the following data 
(with the example of
- * {@code com.example.MyClass.myMethod(int, int)} being whitelisted):
- * <ul>
- *    <li>Upper bound class ({@code com.example.MyClass} in the example)
- *    <li>Member name ({@code myMethod} in the example), except for 
constructors where it's unused
- *    <li>Parameter types ({@code int, int} in the example), except for fields 
where it's unused
- * </ul>
+ * <p>See more about the rules at {@link MemberSelectorListMemberAccessPolicy}.
+ * {@link TemplateAccessible} annotation may be used to add members to the 
whitelist.
  *
- * <p>Once you have whitelisted a member in the upper bound class, it will be 
automatically whitelisted in all
- * subclasses of that, even if the whitelisted member is a field or 
constructor (which doesn't support overriding, but
- * it will be treated as such if the field name or constructor parameter types 
match).
- * It's called "upper bound" class, because the member will only be 
whitelisted in classes that are {@code instanceof}
- * the upper bound class. That restriction stands even if the member was 
inherited from another class or
- * interface, and it wasn't even overridden in the upper bound class; the 
member won't be whitelisted in the
- * class/interface where it was inherited from, if that type is more generic 
than the upper bound class.
- *
- * <p>Note that the return type of methods aren't used in any way. So if you 
whitelist {@code myMethod(int, int)}, and
- * it has multiple variants with different return types (which is possible on 
the bytecode level), then you have
- * whitelisted all variants of it.
+ * <p>Of course, this only can deal with the {@link ObjectWrapper} aspect of 
safety; please check the Manual to see what
+ * else is needed. Also, since this is related to security, read the 
documentation of {@link MemberAccessPolicy}, to
+ * know about the pitfalls and edge cases related to {@link 
MemberAccessPolicy}-es in general.
  *
  * @since 2.3.30
  */
-public class WhitelistMemberAccessPolicy implements MemberAccessPolicy {
-    private static final Logger LOG = Logger.getLogger("freemarker.beans");
-
-    private final MethodMatcher methodMatcher;
-    private final ConstructorMatcher constructorMatcher;
-    private final FieldMatcher fieldMatcher;
+public class WhitelistMemberAccessPolicy extends 
MemberSelectorListMemberAccessPolicy {
 
     /**
-     * A condition that matches some type members. See {@link 
WhitelistMemberAccessPolicy} documentation for more.
-     * Exactly one of these will be non-{@code null}:
-     * {@link #getMethod()}, {@link #getConstructor()}, {@link #getField()}, 
{@link #getException()}.
-     *
-     * @since 2.3.30
+     * @param memberSelectors
+     *      List of member selectors; see {@link 
MemberSelectorListMemberAccessPolicy} class-level documentation for
+     *      more.
      */
-    public final static class MemberSelector {
-        private final Class<?> upperBoundType;
-        private final Method method;
-        private final Constructor<?> constructor;
-        private final Field field;
-        private final Exception exception;
-        private final String exceptionMemberSelectorString;
-
-        /**
-         * Use if you want to match methods similar to the specified one, in 
types that are {@code instanceof} of
-         * the specified upper bound type. When methods are matched, only the 
name and the parameter types matter.
-         */
-        public MemberSelector(Class<?> upperBoundType, Method method) {
-            NullArgumentException.check("upperBoundType", upperBoundType);
-            NullArgumentException.check("method", method);
-            this.upperBoundType = upperBoundType;
-            this.method = method;
-            this.constructor = null;
-            this.field = null;
-            this.exception = null;
-            this.exceptionMemberSelectorString = null;
-        }
-
-        /**
-         * Use if you want to match constructors similar to the specified one, 
in types that are {@code instanceof} of
-         * the specified upper bound type. When constructors are matched, only 
the parameter types matter.
-         */
-        public MemberSelector(Class<?> upperBoundType, Constructor<?> 
constructor) {
-            NullArgumentException.check("upperBoundType", upperBoundType);
-            NullArgumentException.check("constructor", constructor);
-            this.upperBoundType = upperBoundType;
-            this.method = null;
-            this.constructor = constructor;
-            this.field = null;
-            this.exception = null;
-            this.exceptionMemberSelectorString = null;
-        }
-
-        /**
-         * Use if you want to match fields similar to the specified one, in 
types that are {@code instanceof} of
-         * the specified upper bound type. When fields are matched, only the 
name matters.
-         */
-        public MemberSelector(Class<?> upperBoundType, Field field) {
-            NullArgumentException.check("upperBoundType", upperBoundType);
-            NullArgumentException.check("field", field);
-            this.upperBoundType = upperBoundType;
-            this.method = null;
-            this.constructor = null;
-            this.field = field;
-            this.exception = null;
-            this.exceptionMemberSelectorString = null;
-        }
-
-        /**
-         * Used to store the result of a parsing that's failed for a reason 
that we can skip on runtime (typically,
-         * when a missing class or member was referred).
-         *
-         * @param upperBoundType {@code null} if resolving the upper bound 
type itself failed.
-         * @param exception Not {@code null}
-         * @param exceptionMemberSelectorString Not {@code null}; the selector 
whose resolution has failed, used in
-         *      the log message.
-         */
-        public MemberSelector(Class<?> upperBoundType, Exception exception, 
String exceptionMemberSelectorString) {
-            NullArgumentException.check("exception", exception);
-            NullArgumentException.check("exceptionMemberSelectorString", 
exceptionMemberSelectorString);
-            this.upperBoundType = upperBoundType;
-            this.method = null;
-            this.constructor = null;
-            this.field = null;
-            this.exception = exception;
-            this.exceptionMemberSelectorString = exceptionMemberSelectorString;
-        }
-
-        /**
-         * Maybe {@code null} if {@link #getException()} is non-{@code null}.
-         */
-        public Class<?> getUpperBoundType() {
-            return upperBoundType;
-        }
-
-        /**
-         * Maybe {@code null};
-         * set if the selector matches methods similar to the returned one, 
and there was no exception.
-         */
-        public Method getMethod() {
-            return method;
-        }
-
-        /**
-         * Maybe {@code null};
-         * set if the selector matches constructors similar to the returned 
one, and there was no exception.
-         */
-        public Constructor<?> getConstructor() {
-            return constructor;
-        }
-
-        /**
-         * Maybe {@code null};
-         * set if the selector matches fields similar to the returned one, and 
there was no exception.
-         */
-        public Field getField() {
-            return field;
-        }
-
-        /**
-         * Maybe {@code null}
-         */
-        public Exception getException() {
-            return exception;
-        }
-
-        /**
-         * Maybe {@code null}
-         */
-        public String getExceptionMemberSelectorString() {
-            return exceptionMemberSelectorString;
-        }
-
-        /**
-         * Parses a member selector that was specified with a string.
-         *
-         * @param classLoader
-         *      Used to resolve class names in the member selectors. Generally 
you want to pick a class that belongs to
-         *      you application (not to a 3rd party library, like FreeMarker), 
and then call
-         *      {@link Class#getClassLoader()} on that. Note that the 
resolution of the classes is not lazy, and so the
-         *      {@link ClassLoader} won't be stored after this method returns.
-         * @param memberSelectorString
-         *      Describes the member (method, constructor, field) which you 
want to whitelist. Starts with the full
-         *      qualified name of the member, like {@code 
com.example.MyClass.myMember}. Unless it's a field, the
-         *      name is followed by comma separated list of the parameter 
types inside parentheses, like in
-         *      {@code com.example.MyClass.myMember(java.lang.String, 
boolean)}. The parameter type names must be
-         *      also full qualified names, except primitive type names. Array 
types must be indicated with one or
-         *      more {@code []}-s after the type name. Varargs arguments 
shouldn't be marked with {@code ...}, but with
-         *      {@code []}. In the member name, like {@code 
com.example.MyClass.myMember}, the class refers to the so
-         *      called "upper bound class". Regarding that and inheritance 
rules see the class level documentation.
-         *
-         * @return The {@link MemberSelector}, which might has non-{@code 
null} {@link MemberSelector#exception}.
-         */
-        public static MemberSelector parse(String memberSelectorString, 
ClassLoader classLoader) {
-            if (memberSelectorString.contains("<") || 
memberSelectorString.contains(">")
-                    || memberSelectorString.contains("...") || 
memberSelectorString.contains(";")) {
-                throw new IllegalArgumentException(
-                        "Malformed whitelist entry (shouldn't contain \"<\", 
\">\", \"...\", or \";\"): "
-                                + memberSelectorString);
-            }
-            String cleanedStr = 
memberSelectorString.trim().replaceAll("\\s*([\\.,\\(\\)\\[\\]])\\s*", "$1");
-
-            int postMemberNameIdx;
-            boolean hasArgList;
-            {
-                int openParenIdx = cleanedStr.indexOf('(');
-                hasArgList = openParenIdx != -1;
-                postMemberNameIdx = hasArgList ? openParenIdx : 
cleanedStr.length();
-            }
-
-            final int postClassDotIdx = cleanedStr.lastIndexOf('.', 
postMemberNameIdx);
-            if (postClassDotIdx == -1) {
-                throw new IllegalArgumentException("Malformed whitelist entry 
(missing dot): " + memberSelectorString);
-            }
-
-            Class<?> upperBoundClass;
-            String upperBoundClassStr = cleanedStr.substring(0, 
postClassDotIdx);
-            if (!isWellFormedClassName(upperBoundClassStr)) {
-                throw new IllegalArgumentException("Malformed whitelist entry 
(malformed upper bound class name): "
-                        + memberSelectorString);
-            }
-            try {
-                upperBoundClass = classLoader.loadClass(upperBoundClassStr);
-            } catch (ClassNotFoundException e) {
-                return new MemberSelector(null, e, cleanedStr);
-            }
-
-            String memberName = cleanedStr.substring(postClassDotIdx + 1, 
postMemberNameIdx);
-            if (!isWellFormedJavaIdentifier(memberName)) {
-                throw new IllegalArgumentException(
-                        "Malformed whitelist entry (malformed member name): " 
+ memberSelectorString);
-            }
-
-            if (hasArgList) {
-                if (cleanedStr.charAt(cleanedStr.length() - 1) != ')') {
-                    throw new IllegalArgumentException("Malformed whitelist 
entry (missing closing ')'): "
-                            + memberSelectorString);
-                }
-                String argsSpec = cleanedStr.substring(postMemberNameIdx + 1, 
cleanedStr.length() - 1);
-                StringTokenizer tok = new StringTokenizer(argsSpec, ",");
-                int argCount = tok.countTokens();
-                Class<?>[] argTypes = new Class[argCount];
-                for (int i = 0; i < argCount; i++) {
-                    String argClassName = tok.nextToken();
-                    int arrayDimensions = 0;
-                    while (argClassName.endsWith("[]")) {
-                        arrayDimensions++;
-                        argClassName = argClassName.substring(0, 
argClassName.length() - 2);
-                    }
-                    Class<?> argClass;
-                    Class<?> primArgClass = 
ClassUtil.resolveIfPrimitiveTypeName(argClassName);
-                    if (primArgClass != null) {
-                        argClass = primArgClass;
-                    } else {
-                        if (!isWellFormedClassName(argClassName)) {
-                            throw new IllegalArgumentException(
-                                    "Malformed whitelist entry (malformed 
argument class name): " + memberSelectorString);
-                        }
-                        try {
-                            argClass = classLoader.loadClass(argClassName);
-                        } catch (ClassNotFoundException e) {
-                            return new MemberSelector(upperBoundClass, e, 
cleanedStr);
-                        } catch (SecurityException e) {
-                            return new MemberSelector(upperBoundClass, e, 
cleanedStr);
-                        }
-                    }
-                    argTypes[i] = ClassUtil.getArrayClass(argClass, 
arrayDimensions);
-                }
-                try {
-                    return memberName.equals(upperBoundClass.getSimpleName())
-                            ? new MemberSelector(upperBoundClass, 
upperBoundClass.getConstructor(argTypes))
-                            : new MemberSelector(upperBoundClass, 
upperBoundClass.getMethod(memberName, argTypes));
-                } catch (NoSuchMethodException e) {
-                    return new MemberSelector(upperBoundClass, e, cleanedStr);
-                } catch (SecurityException e) {
-                    return new MemberSelector(upperBoundClass, e, cleanedStr);
-                }
-            } else {
-                try {
-                    return new MemberSelector(upperBoundClass, 
upperBoundClass.getField(memberName));
-                } catch (NoSuchFieldException e) {
-                    return new MemberSelector(upperBoundClass, e, cleanedStr);
-                } catch (SecurityException e) {
-                    return new MemberSelector(upperBoundClass, e, cleanedStr);
-                }
-            }
-        }
-
-        /**
-         * Convenience method to parse all member selectors in the collection; 
see {@link #parse(String, ClassLoader)}.
-         */
-        public static List<MemberSelector> parse(Collection<String> 
memberSelectors,
-                ClassLoader classLoader) {
-            List<MemberSelector> parsedMemberSelectors = new 
ArrayList<MemberSelector>(memberSelectors.size());
-            for (String memberSelector : memberSelectors) {
-                parsedMemberSelectors.add(parse(memberSelector, classLoader));
-            }
-            return parsedMemberSelectors;
-        }
-    }
-
     public WhitelistMemberAccessPolicy(Collection<MemberSelector> 
memberSelectors) {
-        methodMatcher = new MethodMatcher();
-        constructorMatcher = new ConstructorMatcher();
-        fieldMatcher = new FieldMatcher();
-        for (MemberSelector memberSelector : memberSelectors) {
-            Class<?> upperBoundClass = memberSelector.upperBoundType;
-            if (memberSelector.exception != null) {
-                if (LOG.isDebugEnabled()) {
-                    LOG.debug("Member selector ignored due to error: " + 
memberSelector.getExceptionMemberSelectorString(),
-                            memberSelector.exception);
-                }
-            } else if (memberSelector.constructor != null) {
-                constructorMatcher.addMatching(upperBoundClass, 
memberSelector.constructor);
-            } else if (memberSelector.method != null) {
-                methodMatcher.addMatching(upperBoundClass, 
memberSelector.method);
-            } else if (memberSelector.field != null) {
-                fieldMatcher.addMatching(upperBoundClass, 
memberSelector.field);
-            } else {
-                throw new AssertionError();
-            }
-        }
-    }
-
-    public ClassMemberAccessPolicy forClass(final Class<?> contextClass) {
-        return new ClassMemberAccessPolicy() {
-            public boolean isMethodExposed(Method method) {
-                return methodMatcher.matches(contextClass, method)
-                        || _MethodUtil.getInheritableAnnotation(contextClass, 
method, TemplateAccessible.class) != null;
-            }
-
-            public boolean isConstructorExposed(Constructor<?> constructor) {
-                return constructorMatcher.matches(contextClass, constructor)
-                        || _MethodUtil.getInheritableAnnotation(contextClass, 
constructor, TemplateAccessible.class)
-                                != null;
-            }
-
-            public boolean isFieldExposed(Field field) {
-                return fieldMatcher.matches(contextClass, field)
-                        || _MethodUtil.getInheritableAnnotation(contextClass, 
field, TemplateAccessible.class) != null;
-            }
-        };
-    }
-
-    private static boolean isWellFormedClassName(String s) {
-        if (s.length() == 0) {
-            return false;
-        }
-        int identifierStartIdx = 0;
-        for (int i = 0; i < s.length(); i++) {
-            char c = s.charAt(i);
-            if (i == identifierStartIdx) {
-                if (!Character.isJavaIdentifierStart(c)) {
-                    return false;
-                }
-            } else if (c == '.' && i != s.length() - 1) {
-                identifierStartIdx = i + 1;
-            } else {
-                if (!Character.isJavaIdentifierPart(c)) {
-                    return false;
-                }
-            }
-        }
-        return true;
-    }
-
-    private static boolean isWellFormedJavaIdentifier(String s) {
-        if (s.length() == 0) {
-            return false;
-        }
-        if (!Character.isJavaIdentifierStart(s.charAt(0))) {
-            return false;
-        }
-        for (int i = 1; i < s.length(); i++) {
-            if (!Character.isJavaIdentifierPart(s.charAt(i))) {
-                return false;
-            }
-        }
-        return true;
+        super(memberSelectors, ListType.WHITELIST, TemplateAccessible.class);
     }
 
 }
diff --git a/src/main/java/freemarker/template/DefaultObjectWrapper.java 
b/src/main/java/freemarker/template/DefaultObjectWrapper.java
index 8333a00..eac0198 100644
--- a/src/main/java/freemarker/template/DefaultObjectWrapper.java
+++ b/src/main/java/freemarker/template/DefaultObjectWrapper.java
@@ -34,6 +34,8 @@ import freemarker.ext.beans.BeansWrapper;
 import freemarker.ext.beans.BeansWrapperConfiguration;
 import freemarker.ext.beans.DefaultMemberAccessPolicy;
 import freemarker.ext.beans.EnumerationModel;
+import freemarker.ext.beans.LegacyDefaultMemberAccessPolicy;
+import freemarker.ext.beans.MemberAccessPolicy;
 import freemarker.ext.dom.NodeModel;
 import freemarker.log.Logger;
 
@@ -254,7 +256,8 @@ public class DefaultObjectWrapper extends 
freemarker.ext.beans.BeansWrapper {
      * or for a W3C DOM node. In its default implementation, W3C {@link 
Node}-s will be wrapped as {@link NodeModel}-s
      * (allows DOM tree traversal), Jython objects will be delegated to the 
{@code JythonWrapper}, others will be
      * wrapped using {@link BeansWrapper#wrap(Object)}. Note that if {@link 
#getMemberAccessPolicy()} doesn't return
-     * a {@link DefaultMemberAccessPolicy}, then Jython wrapper will be 
skipped for security reasons.
+     * a {@link DefaultMemberAccessPolicy} or {@link 
LegacyDefaultMemberAccessPolicy}, then Jython wrapper will be
+     * skipped for security reasons.
      * 
      * <p>
      * When you override this method, you should first decide if you want to 
wrap the object in a custom way (and if so
@@ -265,7 +268,9 @@ public class DefaultObjectWrapper extends 
freemarker.ext.beans.BeansWrapper {
         if (obj instanceof Node) {
             return wrapDomNode(obj);
         }
-        if (getMemberAccessPolicy() instanceof DefaultMemberAccessPolicy) {
+        MemberAccessPolicy memberAccessPolicy = getMemberAccessPolicy();
+        if (memberAccessPolicy instanceof DefaultMemberAccessPolicy
+                || memberAccessPolicy instanceof 
LegacyDefaultMemberAccessPolicy) {
             if (JYTHON_WRAPPER != null && JYTHON_OBJ_CLASS.isInstance(obj)) {
                 return JYTHON_WRAPPER.wrap(obj);
             }
diff --git 
a/src/main/resources/freemarker/ext/beans/DefaultMemberAccessPolicy-rules 
b/src/main/resources/freemarker/ext/beans/DefaultMemberAccessPolicy-rules
new file mode 100644
index 0000000..48001c2
--- /dev/null
+++ b/src/main/resources/freemarker/ext/beans/DefaultMemberAccessPolicy-rules
@@ -0,0 +1,582 @@
+# 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.
+
+# Used by DefaultMemberAccessPolicy (not by LegacyDefaultMemberAccessPolicy).
+# It does NOT provide enough safety if template authors aren't as trusted as 
the developers; you need to use a custom
+# whitelist then (see WhitelistMemberAccessPolicy).
+
+# Each member entry must have a upper bound type that already has a rule 
defined. The rules are associated with the
+# upper bound type in the lines starting with @. The possible rules are:
+# - whitelistPolicyIfAssignable: Members of the type and of its subtypes can 
only access members that were whitelisted.
+#   Thus, if you extend a such type, and add a new method, it won't be 
exposed, as it wasn't whitelisted.
+# - blacklistUnlistedMembers: Members of the type that are not listed will be 
blacklisted. Once a member was blacklisted,
+#   it will be blacklisted in subtypes as well. If you extend a type that has 
tris rule, and add a new method, it will
+#   be exposed, as it wasn't blacklisted.
+
+@blacklistUnlistedMembers java.lang.Object
+# Disallowed since 2.3.0: java.lang.Object.wait(long)
+# Disallowed since 2.3.0: java.lang.Object.wait(long,int)
+# Disallowed since 2.3.0: java.lang.Object.wait()
+java.lang.Object.equals(java.lang.Object)
+java.lang.Object.toString()
+java.lang.Object.hashCode()
+java.lang.Object.getClass()
+# Disallowed since 2.3.0: java.lang.Object.notify()
+# Disallowed since 2.3.0: java.lang.Object.notifyAll()
+
+@blacklistUnlistedMembers java.lang.Thread
+java.lang.Thread.getName()
+# Disallowed since 2.3.0, since 2.3.30 even when overridden: 
java.lang.Thread.run()
+java.lang.Thread.isInterrupted()
+# Disallowed since 2.3.30: java.lang.Thread.currentThread()
+# Disallowed since 2.3.30: java.lang.Thread.onSpinWait()
+# Disallowed since 2.3.0: java.lang.Thread.join(long,int)
+# Disallowed since 2.3.0: java.lang.Thread.join(long)
+# Disallowed since 2.3.0: java.lang.Thread.join()
+java.lang.Thread.getThreadGroup()
+# Disallowed since 2.3.0: 
java.lang.Thread.setContextClassLoader(java.lang.ClassLoader)
+java.lang.Thread.holdsLock(java.lang.Object)
+# Disallowed since 2.3.30: java.lang.Thread.getStackTrace()
+java.lang.Thread.checkAccess()
+# Disallowed since 2.3.30: java.lang.Thread.dumpStack()
+# Disallowed since 2.3.0: java.lang.Thread.setPriority(int)
+# Disallowed since 2.3.0: java.lang.Thread.setDaemon(boolean)
+# Disallowed since 2.3.0: java.lang.Thread.start()
+# Disallowed since 2.3.0: java.lang.Thread.sleep(long)
+# Disallowed since 2.3.0: java.lang.Thread.sleep(long,int)
+java.lang.Thread.isDaemon()
+java.lang.Thread.getPriority()
+# Disallowed since 2.3.0: java.lang.Thread.getContextClassLoader()
+# Disallowed since 2.3.0: java.lang.Thread.resume()
+# Disallowed since 2.3.0: java.lang.Thread.interrupt()
+java.lang.Thread.activeCount()
+# Disallowed since 2.3.30: java.lang.Thread.enumerate(java.lang.Thread[])
+java.lang.Thread.isAlive()
+# Disallowed since 2.3.30: 
java.lang.Thread.setDefaultUncaughtExceptionHandler(java.lang.Thread$UncaughtExceptionHandler)
+# Disallowed since 2.3.30: java.lang.Thread.getUncaughtExceptionHandler()
+# Disallowed since 2.3.30: java.lang.Thread.yield()
+# Disallowed since 2.3.0: java.lang.Thread.stop()
+java.lang.Thread.interrupted()
+# Disallowed since 2.3.0: java.lang.Thread.suspend()
+# Disallowed since 2.3.0: java.lang.Thread.setName(java.lang.String)
+java.lang.Thread.countStackFrames()
+# Disallowed since 2.3.30: java.lang.Thread.getAllStackTraces()
+java.lang.Thread.getId()
+java.lang.Thread.getState()
+# Disallowed since 2.3.30: 
java.lang.Thread.getDefaultUncaughtExceptionHandler()
+# Disallowed since 2.3.30: 
java.lang.Thread.setUncaughtExceptionHandler(java.lang.Thread$UncaughtExceptionHandler)
+
+@whitelistPolicyIfAssignable java.lang.ThreadGroup
+java.lang.ThreadGroup.getName()
+# Disallowed since 2.3.30: java.lang.ThreadGroup.list()
+java.lang.ThreadGroup.getParent()
+java.lang.ThreadGroup.checkAccess()
+# Disallowed since 2.3.0: java.lang.ThreadGroup.setDaemon(boolean)
+java.lang.ThreadGroup.isDaemon()
+# Disallowed since 2.3.0: java.lang.ThreadGroup.resume()
+# Disallowed since 2.3.0: java.lang.ThreadGroup.interrupt()
+java.lang.ThreadGroup.getMaxPriority()
+java.lang.ThreadGroup.activeCount()
+# Disallowed since 2.3.30: 
java.lang.ThreadGroup.enumerate(java.lang.ThreadGroup[],boolean)
+# Disallowed since 2.3.30: 
java.lang.ThreadGroup.enumerate(java.lang.ThreadGroup[])
+# Disallowed since 2.3.30: java.lang.ThreadGroup.enumerate(java.lang.Thread[])
+# Disallowed since 2.3.30: 
java.lang.ThreadGroup.enumerate(java.lang.Thread[],boolean)
+# Disallowed since 2.3.30: 
java.lang.ThreadGroup.uncaughtException(java.lang.Thread,java.lang.Throwable)
+# Disallowed since 2.3.0: java.lang.ThreadGroup.stop()
+# Disallowed since 2.3.0: java.lang.ThreadGroup.suspend()
+# Disallowed since 2.3.0: java.lang.ThreadGroup.setMaxPriority(int)
+java.lang.ThreadGroup.activeGroupCount()
+# Disallowed since 2.3.0: java.lang.ThreadGroup.destroy()
+java.lang.ThreadGroup.isDestroyed()
+java.lang.ThreadGroup.parentOf(java.lang.ThreadGroup)
+# Disallowed since 2.3.0: java.lang.ThreadGroup.allowThreadSuspension(boolean)
+
+@whitelistPolicyIfAssignable java.lang.Runtime
+# Disallowed since 2.3.30: java.lang.Runtime.getRuntime()
+# Disallowed since 2.3.0: java.lang.Runtime.exit(int)
+# Disallowed since 2.3.30: java.lang.Runtime.runFinalization()
+java.lang.Runtime.version()
+# Disallowed since 2.3.0: java.lang.Runtime.loadLibrary(java.lang.String)
+# Disallowed since 2.3.30: java.lang.Runtime.gc()
+# Disallowed since 2.3.0: java.lang.Runtime.load(java.lang.String)
+java.lang.Runtime.freeMemory()
+java.lang.Runtime.maxMemory()
+java.lang.Runtime.availableProcessors()
+# Disallowed since 2.3.0: java.lang.Runtime.halt(int)
+# Disallowed since 2.3.0: java.lang.Runtime.exec(java.lang.String[])
+# Disallowed since 2.3.0: 
java.lang.Runtime.exec(java.lang.String,java.lang.String[],java.io.File)
+# Disallowed since 2.3.0: java.lang.Runtime.exec(java.lang.String)
+# Disallowed since 2.3.0: 
java.lang.Runtime.exec(java.lang.String[],java.lang.String[])
+# Disallowed since 2.3.0: 
java.lang.Runtime.exec(java.lang.String[],java.lang.String[],java.io.File)
+# Disallowed since 2.3.0: 
java.lang.Runtime.exec(java.lang.String,java.lang.String[])
+# Disallowed since 2.3.0: java.lang.Runtime.addShutdownHook(java.lang.Thread)
+# Disallowed since 2.3.0: 
java.lang.Runtime.removeShutdownHook(java.lang.Thread)
+java.lang.Runtime.totalMemory()
+# Disallowed since 2.3.0: java.lang.Runtime.traceInstructions(boolean)
+# Disallowed since 2.3.0: java.lang.Runtime.traceMethodCalls(boolean)
+
+@whitelistPolicyIfAssignable java.lang.System
+# Disallowed since 2.3.0: java.lang.System.exit(int)
+# Disallowed since 2.3.0: java.lang.System.runFinalization()
+# Disallowed since 2.3.0: java.lang.System.runFinalizersOnExit(boolean)
+java.lang.System.getProperty(java.lang.String)
+java.lang.System.getProperty(java.lang.String,java.lang.String)
+java.lang.System.identityHashCode(java.lang.Object)
+java.lang.System.currentTimeMillis()
+java.lang.System.nanoTime()
+# Disallowed since 2.3.30: 
java.lang.System.arraycopy(java.lang.Object,int,java.lang.Object,int,int)
+# Disallowed since 2.3.30: java.lang.System.getSecurityManager()
+java.lang.System.mapLibraryName(java.lang.String)
+# Disallowed since 2.3.0: java.lang.System.loadLibrary(java.lang.String)
+# Disallowed since 2.3.30: java.lang.System.console()
+# Disallowed since 2.3.30: java.lang.System.inheritedChannel()
+# Disallowed since 2.3.0: 
java.lang.System.setSecurityManager(java.lang.SecurityManager)
+java.lang.System.lineSeparator()
+# Disallowed since 2.3.0: 
java.lang.System.setProperty(java.lang.String,java.lang.String)
+java.lang.System.getenv(java.lang.String)
+java.lang.System.getenv()
+# Disallowed since 2.3.30: 
java.lang.System.getLogger(java.lang.String,java.util.ResourceBundle)
+# Disallowed since 2.3.30: java.lang.System.getLogger(java.lang.String)
+# Disallowed since 2.3.30: java.lang.System.gc()
+# Disallowed since 2.3.0: java.lang.System.setIn(java.io.InputStream)
+# Disallowed since 2.3.0: java.lang.System.setOut(java.io.PrintStream)
+# Disallowed since 2.3.0: java.lang.System.setErr(java.io.PrintStream)
+java.lang.System.getProperties()
+# Disallowed since 2.3.0: java.lang.System.setProperties(java.util.Properties)
+# Disallowed since 2.3.0: java.lang.System.clearProperty(java.lang.String)
+# Disallowed since 2.3.0: java.lang.System.load(java.lang.String)
+
+@whitelistPolicyIfAssignable java.lang.ClassLoader
+java.lang.ClassLoader.getName()
+# Disallowed since 2.3.30: java.lang.ClassLoader.loadClass(java.lang.String)
+# Disallowed since 2.3.30: java.lang.ClassLoader.getPlatformClassLoader()
+# Disallowed since 2.3.30: java.lang.ClassLoader.getSystemClassLoader()
+# Disallowed since 2.3.30: 
java.lang.ClassLoader.getSystemResourceAsStream(java.lang.String)
+# Disallowed since 2.3.30: 
java.lang.ClassLoader.getResourceAsStream(java.lang.String)
+# Disallowed since 2.3.30: 
java.lang.ClassLoader.getSystemResource(java.lang.String)
+# Disallowed since 2.3.30: java.lang.ClassLoader.getResource(java.lang.String)
+# Disallowed since 2.3.30: java.lang.ClassLoader.getResources(java.lang.String)
+# Disallowed since 2.3.30: 
java.lang.ClassLoader.getDefinedPackage(java.lang.String)
+# Disallowed since 2.3.30: java.lang.ClassLoader.resources(java.lang.String)
+# Disallowed since 2.3.30: 
java.lang.ClassLoader.isRegisteredAsParallelCapable()
+# Disallowed since 2.3.30: 
java.lang.ClassLoader.getSystemResources(java.lang.String)
+# Disallowed since 2.3.30: java.lang.ClassLoader.getParent()
+# Disallowed since 2.3.30: java.lang.ClassLoader.getUnnamedModule()
+# Disallowed since 2.3.30: java.lang.ClassLoader.getDefinedPackages()
+# Disallowed since 2.3.30: 
java.lang.ClassLoader.setDefaultAssertionStatus(boolean)
+# Disallowed since 2.3.30: 
java.lang.ClassLoader.setPackageAssertionStatus(java.lang.String,boolean)
+# Disallowed since 2.3.30: 
java.lang.ClassLoader.setClassAssertionStatus(java.lang.String,boolean)
+# Disallowed since 2.3.30: java.lang.ClassLoader.clearAssertionStatus()
+
+@whitelistPolicyIfAssignable java.security.ProtectionDomain
+# Disallowed since 2.3.30: java.security.ProtectionDomain.getClassLoader()
+# Disallowed since 2.3.30: java.security.ProtectionDomain.getCodeSource()
+# Disallowed since 2.3.30: 
java.security.ProtectionDomain.implies(java.security.Permission)
+# Disallowed since 2.3.30: java.security.ProtectionDomain.getPermissions()
+# Disallowed since 2.3.30: java.security.ProtectionDomain.getPrincipals()
+# Disallowed since 2.3.30: 
java.security.ProtectionDomain.staticPermissionsOnly()
+
+@whitelistPolicyIfAssignable java.lang.Class
+java.lang.Class.getName()
+# Disallowed since 2.3.30: 
java.lang.Class.forName(java.lang.Module,java.lang.String)
+# Disallowed since 2.3.0: 
java.lang.Class.forName(java.lang.String,boolean,java.lang.ClassLoader)
+# Disallowed since 2.3.0: java.lang.Class.forName(java.lang.String)
+# Disallowed since 2.3.30: java.lang.Class.getModule()
+java.lang.Class.getProtectionDomain()
+java.lang.Class.isAssignableFrom(java.lang.Class)
+java.lang.Class.isInstance(java.lang.Object)
+java.lang.Class.getModifiers()
+java.lang.Class.isInterface()
+java.lang.Class.isArray()
+java.lang.Class.isPrimitive()
+java.lang.Class.getSuperclass()
+java.lang.Class.cast(java.lang.Object)
+java.lang.Class.componentType()
+java.lang.Class.componentType()
+java.lang.Class.describeConstable()
+java.lang.Class.getComponentType()
+java.lang.Class.isAnnotation()
+java.lang.Class.isEnum()
+java.lang.Class.getTypeParameters()
+# Disallowed since 2.3.0: java.lang.Class.getClassLoader()
+# Disallowed since 2.3.0: java.lang.Class.newInstance()
+java.lang.Class.getInterfaces()
+java.lang.Class.getEnclosingClass()
+java.lang.Class.getSimpleName()
+java.lang.Class.getCanonicalName()
+# Disallowed since 2.3.30: 
java.lang.Class.getResourceAsStream(java.lang.String)
+# Disallowed since 2.3.30: java.lang.Class.getResource(java.lang.String)
+java.lang.Class.getPackageName()
+java.lang.Class.desiredAssertionStatus()
+java.lang.Class.getMethod(java.lang.String,java.lang.Class[])
+java.lang.Class.isAnnotationPresent(java.lang.Class)
+java.lang.Class.descriptorString()
+java.lang.Class.arrayType()
+java.lang.Class.toGenericString()
+java.lang.Class.isSynthetic()
+java.lang.Class.getGenericSuperclass()
+java.lang.Class.getPackage()
+java.lang.Class.getGenericInterfaces()
+# Disallowed since 2.3.30: java.lang.Class.getSigners()
+java.lang.Class.getEnclosingMethod()
+java.lang.Class.getEnclosingConstructor()
+java.lang.Class.getDeclaringClass()
+java.lang.Class.getTypeName()
+java.lang.Class.isAnonymousClass()
+java.lang.Class.isLocalClass()
+java.lang.Class.isMemberClass()
+java.lang.Class.getClasses()
+java.lang.Class.getFields()
+java.lang.Class.getMethods()
+java.lang.Class.getConstructors()
+java.lang.Class.getField(java.lang.String)
+java.lang.Class.getConstructor(java.lang.Class[])
+java.lang.Class.getDeclaredClasses()
+java.lang.Class.getDeclaredFields()
+java.lang.Class.getDeclaredMethods()
+java.lang.Class.getDeclaredConstructors()
+java.lang.Class.getDeclaredField(java.lang.String)
+java.lang.Class.getDeclaredMethod(java.lang.String,java.lang.Class[])
+java.lang.Class.getDeclaredConstructor(java.lang.Class[])
+java.lang.Class.getEnumConstants()
+java.lang.Class.asSubclass(java.lang.Class)
+java.lang.Class.getAnnotation(java.lang.Class)
+java.lang.Class.getAnnotationsByType(java.lang.Class)
+java.lang.Class.getAnnotations()
+java.lang.Class.getDeclaredAnnotation(java.lang.Class)
+java.lang.Class.getDeclaredAnnotationsByType(java.lang.Class)
+java.lang.Class.getDeclaredAnnotations()
+java.lang.Class.getAnnotatedSuperclass()
+java.lang.Class.getAnnotatedInterfaces()
+java.lang.Class.getNestHost()
+java.lang.Class.isNestmateOf(java.lang.Class)
+java.lang.Class.getNestMembers()
+
+@whitelistPolicyIfAssignable java.lang.Package
+java.lang.Package.getName()
+java.lang.Package.isAnnotationPresent(java.lang.Class)
+java.lang.Package.getPackage(java.lang.String)
+java.lang.Package.getAnnotation(java.lang.Class)
+java.lang.Package.getAnnotationsByType(java.lang.Class)
+java.lang.Package.getAnnotations()
+java.lang.Package.getDeclaredAnnotation(java.lang.Class)
+java.lang.Package.getDeclaredAnnotationsByType(java.lang.Class)
+java.lang.Package.getDeclaredAnnotations()
+java.lang.Package.getPackages()
+java.lang.Package.isSealed()
+java.lang.Package.isSealed(java.net.URL)
+java.lang.Package.getSpecificationTitle()
+java.lang.Package.getSpecificationVersion()
+java.lang.Package.getSpecificationVendor()
+java.lang.Package.getImplementationTitle()
+java.lang.Package.getImplementationVersion()
+java.lang.Package.getImplementationVendor()
+java.lang.Package.isCompatibleWith(java.lang.String)
+
+@whitelistPolicyIfAssignable java.lang.reflect.Method
+# Disallowed since 2.3.0: 
java.lang.reflect.Method.invoke(java.lang.Object,java.lang.Object[])
+java.lang.reflect.Method.getName()
+java.lang.reflect.Method.getModifiers()
+java.lang.reflect.Method.getTypeParameters()
+java.lang.reflect.Method.getReturnType()
+java.lang.reflect.Method.getParameterTypes()
+java.lang.reflect.Method.toGenericString()
+java.lang.reflect.Method.isSynthetic()
+java.lang.reflect.Method.getDeclaringClass()
+java.lang.reflect.Method.getAnnotation(java.lang.Class)
+java.lang.reflect.Method.getDeclaredAnnotations()
+# Disallowed since 2.3.0: java.lang.reflect.Method.setAccessible(boolean)
+java.lang.reflect.Method.isVarArgs()
+java.lang.reflect.Method.getParameterCount()
+java.lang.reflect.Method.getParameterAnnotations()
+java.lang.reflect.Method.getGenericParameterTypes()
+java.lang.reflect.Method.getGenericExceptionTypes()
+java.lang.reflect.Method.isDefault()
+java.lang.reflect.Method.getGenericReturnType()
+java.lang.reflect.Method.getExceptionTypes()
+java.lang.reflect.Method.isBridge()
+java.lang.reflect.Method.getDefaultValue()
+java.lang.reflect.Method.getAnnotatedReturnType()
+java.lang.reflect.Method.getAnnotationsByType(java.lang.Class)
+java.lang.reflect.Method.getAnnotatedParameterTypes()
+java.lang.reflect.Method.getParameters()
+java.lang.reflect.Method.getAnnotatedReceiverType()
+java.lang.reflect.Method.getAnnotatedExceptionTypes()
+java.lang.reflect.Method.isAnnotationPresent(java.lang.Class)
+java.lang.reflect.Method.getAnnotations()
+java.lang.reflect.Method.getDeclaredAnnotation(java.lang.Class)
+java.lang.reflect.Method.getDeclaredAnnotationsByType(java.lang.Class)
+# Disallowed since 2.3.0: 
java.lang.reflect.Method.setAccessible(java.lang.reflect.AccessibleObject[],boolean)
+# Disallowed since 2.3.0: java.lang.reflect.Method.trySetAccessible()
+java.lang.reflect.Method.isAccessible()
+java.lang.reflect.Method.canAccess(java.lang.Object)
+
+@whitelistPolicyIfAssignable java.lang.reflect.Constructor
+java.lang.reflect.Constructor.getName()
+java.lang.reflect.Constructor.getModifiers()
+java.lang.reflect.Constructor.getTypeParameters()
+# Disallowed since 2.3.0: 
java.lang.reflect.Constructor.newInstance(java.lang.Object[])
+java.lang.reflect.Constructor.getParameterTypes()
+java.lang.reflect.Constructor.toGenericString()
+java.lang.reflect.Constructor.isSynthetic()
+java.lang.reflect.Constructor.getDeclaringClass()
+java.lang.reflect.Constructor.getAnnotation(java.lang.Class)
+java.lang.reflect.Constructor.getDeclaredAnnotations()
+# Disallowed since 2.3.0: java.lang.reflect.Constructor.setAccessible(boolean)
+java.lang.reflect.Constructor.isVarArgs()
+java.lang.reflect.Constructor.getParameterCount()
+java.lang.reflect.Constructor.getParameterAnnotations()
+java.lang.reflect.Constructor.getGenericParameterTypes()
+java.lang.reflect.Constructor.getGenericExceptionTypes()
+java.lang.reflect.Constructor.getExceptionTypes()
+java.lang.reflect.Constructor.getAnnotatedReturnType()
+java.lang.reflect.Constructor.getAnnotatedReceiverType()
+java.lang.reflect.Constructor.getAnnotationsByType(java.lang.Class)
+java.lang.reflect.Constructor.getAnnotatedParameterTypes()
+java.lang.reflect.Constructor.getParameters()
+java.lang.reflect.Constructor.getAnnotatedExceptionTypes()
+java.lang.reflect.Constructor.isAnnotationPresent(java.lang.Class)
+java.lang.reflect.Constructor.getAnnotations()
+java.lang.reflect.Constructor.getDeclaredAnnotation(java.lang.Class)
+java.lang.reflect.Constructor.getDeclaredAnnotationsByType(java.lang.Class)
+# Disallowed since 2.3.0: 
java.lang.reflect.Constructor.setAccessible(java.lang.reflect.AccessibleObject[],boolean)
+# Disallowed since 2.3.0: java.lang.reflect.Constructor.trySetAccessible()
+java.lang.reflect.Constructor.isAccessible()
+java.lang.reflect.Constructor.canAccess(java.lang.Object)
+
+@whitelistPolicyIfAssignable java.lang.reflect.Field
+java.lang.reflect.Field.getName()
+java.lang.reflect.Field.getModifiers()
+# Disallowed since 2.3.30: java.lang.reflect.Field.get(java.lang.Object)
+# Disallowed since 2.3.30: java.lang.reflect.Field.getBoolean(java.lang.Object)
+# Disallowed since 2.3.30: java.lang.reflect.Field.getByte(java.lang.Object)
+# Disallowed since 2.3.30: java.lang.reflect.Field.getShort(java.lang.Object)
+# Disallowed since 2.3.30: java.lang.reflect.Field.getChar(java.lang.Object)
+# Disallowed since 2.3.30: java.lang.reflect.Field.getInt(java.lang.Object)
+# Disallowed since 2.3.30: java.lang.reflect.Field.getLong(java.lang.Object)
+# Disallowed since 2.3.30: java.lang.reflect.Field.getFloat(java.lang.Object)
+# Disallowed since 2.3.30: java.lang.reflect.Field.getDouble(java.lang.Object)
+java.lang.reflect.Field.toGenericString()
+java.lang.reflect.Field.isSynthetic()
+java.lang.reflect.Field.getDeclaringClass()
+java.lang.reflect.Field.getAnnotation(java.lang.Class)
+java.lang.reflect.Field.getAnnotationsByType(java.lang.Class)
+java.lang.reflect.Field.getDeclaredAnnotations()
+# Disallowed since 2.3.0: 
java.lang.reflect.Field.set(java.lang.Object,java.lang.Object)
+# Disallowed since 2.3.0: java.lang.reflect.Field.setAccessible(boolean)
+java.lang.reflect.Field.getGenericType()
+java.lang.reflect.Field.getType()
+# Disallowed since 2.3.0: 
java.lang.reflect.Field.setBoolean(java.lang.Object,boolean)
+# Disallowed since 2.3.0: 
java.lang.reflect.Field.setByte(java.lang.Object,byte)
+# Disallowed since 2.3.0: 
java.lang.reflect.Field.setChar(java.lang.Object,char)
+# Disallowed since 2.3.0: 
java.lang.reflect.Field.setShort(java.lang.Object,short)
+# Disallowed since 2.3.0: java.lang.reflect.Field.setInt(java.lang.Object,int)
+# Disallowed since 2.3.0: 
java.lang.reflect.Field.setLong(java.lang.Object,long)
+# Disallowed since 2.3.0: 
java.lang.reflect.Field.setFloat(java.lang.Object,float)
+# Disallowed since 2.3.0: 
java.lang.reflect.Field.setDouble(java.lang.Object,double)
+java.lang.reflect.Field.isEnumConstant()
+java.lang.reflect.Field.getAnnotatedType()
+java.lang.reflect.Field.isAnnotationPresent(java.lang.Class)
+java.lang.reflect.Field.getAnnotations()
+java.lang.reflect.Field.getDeclaredAnnotation(java.lang.Class)
+java.lang.reflect.Field.getDeclaredAnnotationsByType(java.lang.Class)
+# Disallowed since 2.3.0: 
java.lang.reflect.Field.setAccessible(java.lang.reflect.AccessibleObject[],boolean)
+# Disallowed since 2.3.0: java.lang.reflect.Field.trySetAccessible()
+java.lang.reflect.Field.isAccessible()
+java.lang.reflect.Field.canAccess(java.lang.Object)
+
+@blacklistUnlistedMembers java.lang.reflect.AccessibleObject
+java.lang.reflect.AccessibleObject.isAnnotationPresent(java.lang.Class)
+java.lang.reflect.AccessibleObject.getAnnotation(java.lang.Class)
+java.lang.reflect.AccessibleObject.getAnnotationsByType(java.lang.Class)
+java.lang.reflect.AccessibleObject.getAnnotations()
+java.lang.reflect.AccessibleObject.getDeclaredAnnotation(java.lang.Class)
+java.lang.reflect.AccessibleObject.getDeclaredAnnotationsByType(java.lang.Class)
+java.lang.reflect.AccessibleObject.getDeclaredAnnotations()
+# Disallowed since 2.3.0: 
java.lang.reflect.AccessibleObject.setAccessible(boolean)
+# Disallowed since 2.3.0: 
java.lang.reflect.AccessibleObject.setAccessible(java.lang.reflect.AccessibleObject[],boolean)
+# Disallowed since 2.3.30: 
java.lang.reflect.AccessibleObject.trySetAccessible()
+java.lang.reflect.AccessibleObject.isAccessible()
+java.lang.reflect.AccessibleObject.canAccess(java.lang.Object)
+
+@whitelistPolicyIfAssignable java.lang.reflect.Member
+java.lang.reflect.Member.getName()
+java.lang.reflect.Member.getModifiers()
+java.lang.reflect.Member.isSynthetic()
+java.lang.reflect.Member.getDeclaringClass()
+
+@whitelistPolicyIfAssignable java.lang.reflect.GenericDeclaration
+java.lang.reflect.GenericDeclaration.getTypeParameters()
+java.lang.reflect.GenericDeclaration.isAnnotationPresent(java.lang.Class)
+java.lang.reflect.GenericDeclaration.getAnnotation(java.lang.Class)
+java.lang.reflect.GenericDeclaration.getAnnotationsByType(java.lang.Class)
+java.lang.reflect.GenericDeclaration.getAnnotations()
+java.lang.reflect.GenericDeclaration.getDeclaredAnnotation(java.lang.Class)
+java.lang.reflect.GenericDeclaration.getDeclaredAnnotationsByType(java.lang.Class)
+java.lang.reflect.GenericDeclaration.getDeclaredAnnotations()
+
+@whitelistPolicyIfAssignable java.lang.reflect.Executable
+java.lang.reflect.Executable.getName()
+java.lang.reflect.Executable.getModifiers()
+java.lang.reflect.Executable.getTypeParameters()
+java.lang.reflect.Executable.getParameterTypes()
+java.lang.reflect.Executable.toGenericString()
+java.lang.reflect.Executable.isSynthetic()
+java.lang.reflect.Executable.getDeclaringClass()
+java.lang.reflect.Executable.getAnnotation(java.lang.Class)
+java.lang.reflect.Executable.getAnnotationsByType(java.lang.Class)
+java.lang.reflect.Executable.getDeclaredAnnotations()
+java.lang.reflect.Executable.isVarArgs()
+java.lang.reflect.Executable.getAnnotatedParameterTypes()
+java.lang.reflect.Executable.getParameterCount()
+java.lang.reflect.Executable.getParameterAnnotations()
+java.lang.reflect.Executable.getGenericParameterTypes()
+java.lang.reflect.Executable.getGenericExceptionTypes()
+java.lang.reflect.Executable.getExceptionTypes()
+java.lang.reflect.Executable.getAnnotatedReturnType()
+java.lang.reflect.Executable.getParameters()
+java.lang.reflect.Executable.getAnnotatedReceiverType()
+java.lang.reflect.Executable.getAnnotatedExceptionTypes()
+java.lang.reflect.Executable.isAnnotationPresent(java.lang.Class)
+java.lang.reflect.Executable.getAnnotations()
+java.lang.reflect.Executable.getDeclaredAnnotation(java.lang.Class)
+java.lang.reflect.Executable.getDeclaredAnnotationsByType(java.lang.Class)
+# Disallowed since 2.3.0: java.lang.reflect.Executable.setAccessible(boolean)
+# Disallowed since 2.3.0: 
java.lang.reflect.Executable.setAccessible(java.lang.reflect.AccessibleObject[],boolean)
+# Disallowed since 2.3.0: java.lang.reflect.Executable.trySetAccessible()
+java.lang.reflect.Executable.isAccessible()
+java.lang.reflect.Executable.canAccess(java.lang.Object)
+
+@whitelistPolicyIfAssignable java.lang.reflect.TypeVariable
+java.lang.reflect.TypeVariable.getName()
+java.lang.reflect.TypeVariable.getBounds()
+java.lang.reflect.TypeVariable.getGenericDeclaration()
+java.lang.reflect.TypeVariable.getAnnotatedBounds()
+java.lang.reflect.TypeVariable.getTypeName()
+java.lang.reflect.TypeVariable.isAnnotationPresent(java.lang.Class)
+java.lang.reflect.TypeVariable.getAnnotation(java.lang.Class)
+java.lang.reflect.TypeVariable.getAnnotationsByType(java.lang.Class)
+java.lang.reflect.TypeVariable.getAnnotations()
+java.lang.reflect.TypeVariable.getDeclaredAnnotation(java.lang.Class)
+java.lang.reflect.TypeVariable.getDeclaredAnnotationsByType(java.lang.Class)
+java.lang.reflect.TypeVariable.getDeclaredAnnotations()
+
+@whitelistPolicyIfAssignable java.lang.reflect.AnnotatedType
+java.lang.reflect.AnnotatedType.getType()
+java.lang.reflect.AnnotatedType.getAnnotatedOwnerType()
+java.lang.reflect.AnnotatedType.isAnnotationPresent(java.lang.Class)
+java.lang.reflect.AnnotatedType.getAnnotation(java.lang.Class)
+java.lang.reflect.AnnotatedType.getAnnotationsByType(java.lang.Class)
+java.lang.reflect.AnnotatedType.getAnnotations()
+java.lang.reflect.AnnotatedType.getDeclaredAnnotation(java.lang.Class)
+java.lang.reflect.AnnotatedType.getDeclaredAnnotationsByType(java.lang.Class)
+java.lang.reflect.AnnotatedType.getDeclaredAnnotations()
+
+@whitelistPolicyIfAssignable java.lang.reflect.Type
+java.lang.reflect.Type.getTypeName()
+
+@whitelistPolicyIfAssignable java.lang.reflect.Parameter
+java.lang.reflect.Parameter.getName()
+java.lang.reflect.Parameter.getModifiers()
+java.lang.reflect.Parameter.isSynthetic()
+java.lang.reflect.Parameter.getAnnotation(java.lang.Class)
+java.lang.reflect.Parameter.getAnnotationsByType(java.lang.Class)
+java.lang.reflect.Parameter.getAnnotations()
+java.lang.reflect.Parameter.getDeclaredAnnotation(java.lang.Class)
+java.lang.reflect.Parameter.getDeclaredAnnotationsByType(java.lang.Class)
+java.lang.reflect.Parameter.getDeclaredAnnotations()
+java.lang.reflect.Parameter.getType()
+java.lang.reflect.Parameter.getAnnotatedType()
+java.lang.reflect.Parameter.getParameterizedType()
+java.lang.reflect.Parameter.isVarArgs()
+java.lang.reflect.Parameter.isNamePresent()
+java.lang.reflect.Parameter.getDeclaringExecutable()
+java.lang.reflect.Parameter.isImplicit()
+java.lang.reflect.Parameter.isAnnotationPresent(java.lang.Class)
+
+@whitelistPolicyIfAssignable java.lang.annotation.Annotation
+java.lang.annotation.Annotation.annotationType()
+
+@whitelistPolicyIfAssignable java.lang.constant.ClassDesc
+java.lang.constant.ClassDesc.isArray()
+java.lang.constant.ClassDesc.isPrimitive()
+java.lang.constant.ClassDesc.componentType()
+# Disallowed since 2.3.30: java.lang.constant.ClassDesc.of(java.lang.String)
+# Disallowed since 2.3.30: 
java.lang.constant.ClassDesc.of(java.lang.String,java.lang.String)
+java.lang.constant.ClassDesc.packageName()
+java.lang.constant.ClassDesc.descriptorString()
+# Disallowed since 2.3.30: 
java.lang.constant.ClassDesc.ofDescriptor(java.lang.String)
+java.lang.constant.ClassDesc.arrayType()
+java.lang.constant.ClassDesc.arrayType()
+java.lang.constant.ClassDesc.arrayType(int)
+java.lang.constant.ClassDesc.displayName()
+java.lang.constant.ClassDesc.isClassOrInterface()
+# Disallowed since 2.3.30: 
java.lang.constant.ClassDesc.nested(java.lang.String,java.lang.String[])
+# Disallowed since 2.3.30: 
java.lang.constant.ClassDesc.nested(java.lang.String)
+# Disallowed since 2.3.30: 
java.lang.constant.ClassDesc.resolveConstantDesc(java.lang.invoke.MethodHandles$Lookup)
+
+@whitelistPolicyIfAssignable java.net.URL
+# Disallowed since 2.3.30: java.net.URL.openStream()
+java.net.URL.getHost()
+java.net.URL.getPort()
+java.net.URL.getDefaultPort()
+java.net.URL.sameFile(java.net.URL)
+java.net.URL.toExternalForm()
+# Disallowed since 2.3.30: java.net.URL.openConnection()
+# Disallowed since 2.3.30: java.net.URL.openConnection(java.net.Proxy)
+# Disallowed since 2.3.30: java.net.URL.getContent()
+# Disallowed since 2.3.30: java.net.URL.getContent(java.lang.Class[])
+java.net.URL.getProtocol()
+java.net.URL.getAuthority()
+java.net.URL.getFile()
+java.net.URL.getRef()
+java.net.URL.getQuery()
+java.net.URL.getPath()
+java.net.URL.getUserInfo()
+java.net.URL.toURI()
+# Disallowed since 2.3.30: 
java.net.URL.setURLStreamHandlerFactory(java.net.URLStreamHandlerFactory)
+
+@whitelistPolicyIfAssignable java.lang.constant.ClassDesc
+
+@whitelistPolicyIfAssignable java.net.URI
+java.net.URI.getRawAuthority()
+java.net.URI.compareTo(java.lang.Object)
+java.net.URI.compareTo(java.net.URI)
+java.net.URI.isAbsolute()
+java.net.URI.resolve(java.net.URI)
+java.net.URI.resolve(java.lang.String)
+java.net.URI.normalize()
+java.net.URI.getScheme()
+java.net.URI.isOpaque()
+java.net.URI.getRawFragment()
+java.net.URI.getRawQuery()
+java.net.URI.getRawPath()
+java.net.URI.getHost()
+java.net.URI.getPort()
+java.net.URI.create(java.lang.String)
+java.net.URI.getAuthority()
+java.net.URI.getQuery()
+java.net.URI.getPath()
+java.net.URI.getUserInfo()
+java.net.URI.toURL()
+java.net.URI.relativize(java.net.URI)
+java.net.URI.getRawSchemeSpecificPart()
+java.net.URI.parseServerAuthority()
+java.net.URI.getSchemeSpecificPart()
+java.net.URI.getRawUserInfo()
+java.net.URI.getFragment()
+java.net.URI.toASCIIString()
diff --git a/src/main/resources/freemarker/ext/beans/unsafeMethods.properties 
b/src/main/resources/freemarker/ext/beans/unsafeMethods.properties
index 05c1981..a8025af 100644
--- a/src/main/resources/freemarker/ext/beans/unsafeMethods.properties
+++ b/src/main/resources/freemarker/ext/beans/unsafeMethods.properties
@@ -15,6 +15,14 @@
 # specific language governing permissions and limitations
 # under the License.
 
+# Used by LegacyDefaultMemberAccessPolicy (not by DefaultMemberAccessPolicy).
+# It does NOT provide enough safety if template authors aren't as trusted as 
the developers; you need to use a custom
+# whitelist then (see WhitelistMemberAccessPolicy).
+
+# This is a blacklist, that is, methods mentioned here will be not be 
accessible, but everything else will be.
+# Furthermore, overridden version of the blacklisted methods will be 
accessible (which is strange, but we kept backward
+# compatibility).
+
 java.lang.Object.wait()
 java.lang.Object.wait(long)
 java.lang.Object.wait(long,int)
diff --git a/src/manual/en_US/book.xml b/src/manual/en_US/book.xml
index f736f8a..433ac9a 100644
--- a/src/manual/en_US/book.xml
+++ b/src/manual/en_US/book.xml
@@ -28953,8 +28953,7 @@ TemplateModel x = env.getVariable("x");  // get 
variable x</programlisting>
                 by the <literal>MemberAccessPolicy</literal> also won't be
                 visible with <literal>?api</literal> (assuming you are using a
                 well behaving <literal>ObjectWrapper</literal>, like
-                <literal>DefaultObjectWrapper</literal> is, hopefully.)
-                </para>
+                <literal>DefaultObjectWrapper</literal> is, hopefully.)</para>
 
                 <para>Last not least, some maybe aware of that the standard
                 object wrappers filters out some well known
@@ -29013,7 +29012,7 @@ TemplateModel x = env.getVariable("x");  // get 
variable x</programlisting>
                 <literal>BeansWrapper</literal> or a subclass of it (typically
                 <literal>DefaultObjectWrapper</literal>), constructors not
                 allowed by the <literal>MemberAccessPolicy</literal> also
-                won't be accessible for <literal>?new</literal>. </para>
+                won't be accessible for <literal>?new</literal>.</para>
               </listitem>
 
               <listitem>
@@ -29213,6 +29212,26 @@ TemplateModel x = env.getVariable("x");  // get 
variable x</programlisting>
 
           <itemizedlist>
             <listitem>
+              <para><link
+              
xlink:href="https://issues.apache.org/jira/browse/FREEMARKER-124";>FREEMARKER-124</link>:
+              Made the default filtering of class members more restrictive
+              (when you are using <literal>BeansWrapper</literal>, or its
+              subclasses like <literal>DefaultObjectWrapper</literal>). This
+              is not strictly backward compatible, but unlikely to break any
+              real-world applications; see
+              
<literal>src/main/resources/freemarker/ext/beans/DefaultMemberAccessPolicy-rules</literal>
+              to see what was changed. This change was made for security
+              reasons, but the default behavior will never be safe enough if
+              untrusted users will edit templates; see <link
+              linkend="faq_template_uploading_security">in the FAQ</link>. In
+              the unlikely case this change breaks your application, then you
+              can still use the old behavior by setting the
+              <literal>memberAccessPolicy</literal> property of the object
+              wrapper to
+              
<literal>LegacyDefaultMemberAccessPolicy.INSTANCE</literal>.</para>
+            </listitem>
+
+            <listitem>
               <para>Added
               <literal>freemarker.ext.beans.MemberAccessPolicy</literal>
               interface, and the <literal>memberAccessPolicy</literal>
diff --git 
a/src/test/java/freemarker/ext/beans/DefaultMemberAccessPolicyTest.java 
b/src/test/java/freemarker/ext/beans/DefaultMemberAccessPolicyTest.java
new file mode 100644
index 0000000..324abd1
--- /dev/null
+++ b/src/test/java/freemarker/ext/beans/DefaultMemberAccessPolicyTest.java
@@ -0,0 +1,143 @@
+/*
+ * 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.ext.beans;
+
+import static org.junit.Assert.*;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.net.URL;
+import java.security.ProtectionDomain;
+import java.util.Arrays;
+
+import org.junit.Test;
+
+import freemarker.template.Configuration;
+
+public class DefaultMemberAccessPolicyTest {
+
+    private static final DefaultMemberAccessPolicy POLICY = 
DefaultMemberAccessPolicy.getInstance(Configuration.VERSION_2_3_30);
+
+    @Test
+    public void testWhitelistRuleWithNoMembers() throws NoSuchMethodException {
+        ClassMemberAccessPolicy classPolicy = 
POLICY.forClass(ProtectionDomain.class);
+        
assertFalse(classPolicy.isMethodExposed(ProtectionDomain.class.getMethod("getClassLoader")));
+    }
+
+    @Test
+    public void testWhitelistRuleWithSomeMembers() throws 
NoSuchMethodException {
+        ClassMemberAccessPolicy classPolicy = POLICY.forClass(URL.class);
+        
assertFalse(classPolicy.isMethodExposed(URL.class.getMethod("openStream")));
+        
assertFalse(classPolicy.isMethodExposed(URL.class.getMethod("getContent", 
Class[].class)));
+        
assertTrue(classPolicy.isMethodExposed(URL.class.getMethod("getHost")));
+        assertTrue(classPolicy.isMethodExposed(URL.class.getMethod("sameFile", 
URL.class)));
+    }
+
+    @Test
+    public void testWhitelistRuleOnSubclass() throws NoSuchMethodException {
+        ClassMemberAccessPolicy classPolicy = 
POLICY.forClass(CustomClassLoader.class);
+        
assertFalse(classPolicy.isMethodExposed(CustomClassLoader.class.getMethod("loadClass",
 String.class)));
+        
assertFalse(classPolicy.isMethodExposed(CustomClassLoader.class.getMethod("m1")));
+    }
+
+    @Test
+    public void testBlacklistUnlistedRule() throws NoSuchMethodException {
+        for (Class<?> testedClass : Arrays.asList(
+                Object.class, Thread.class, ThreadSubclass.class, 
ProtectionDomain.class, CustomClassLoader.class,
+                UserClass.class)) {
+            ClassMemberAccessPolicy classPolicy = POLICY.forClass(testedClass);
+            
assertFalse(classPolicy.isMethodExposed(testedClass.getMethod("wait")));
+            assertTrue(testedClass.getName(), 
classPolicy.isMethodExposed(testedClass.getMethod("toString")));
+        }
+
+        ClassMemberAccessPolicy classPolicy = POLICY.forClass(UserClass.class);
+        
assertTrue(classPolicy.isMethodExposed(UserClass.class.getMethod("foo")));
+    }
+
+    @Test
+    public void testBlacklistUnlistedRuleOnSubclass() throws 
NoSuchMethodException {
+        ClassMemberAccessPolicy classPolicy = 
POLICY.forClass(ThreadSubclass.class);
+        
assertFalse(classPolicy.isMethodExposed(ThreadSubclass.class.getMethod("run")));
+        
assertTrue(classPolicy.isMethodExposed(ThreadSubclass.class.getMethod("getName")));
+        
assertTrue(classPolicy.isMethodExposed(ThreadSubclass.class.getMethod("m1")));
+    }
+
+    @Test
+    public void testWellKnownUnsafeMethodsAreBanned() throws 
NoSuchMethodException {
+        {
+            ClassMemberAccessPolicy classPolicy = POLICY.forClass(Class.class);
+            
assertFalse(classPolicy.isMethodExposed(Class.class.getMethod("forName", 
String.class)));
+            
assertFalse(classPolicy.isMethodExposed(Class.class.getMethod("newInstance")));
+            
assertFalse(classPolicy.isMethodExposed(Class.class.getMethod("getClassLoader")));
+            
assertFalse(classPolicy.isMethodExposed(Class.class.getMethod("getResourceAsStream",
 String.class)));
+            
assertFalse(classPolicy.isMethodExposed(Class.class.getMethod("getResource", 
String.class)));
+            
assertTrue(classPolicy.isMethodExposed(Class.class.getMethod("getProtectionDomain")));
 // Allowed
+        }
+        {
+            ClassMemberAccessPolicy classPolicy = 
POLICY.forClass(ProtectionDomain.class);
+            
assertFalse(classPolicy.isMethodExposed(ProtectionDomain.class.getMethod("getClassLoader")));
+        }
+        {
+            ClassMemberAccessPolicy classPolicy = 
POLICY.forClass(ClassLoader.class);
+            
assertFalse(classPolicy.isMethodExposed(ClassLoader.class.getMethod("loadClass",
 String.class)));
+        }
+        {
+            ClassMemberAccessPolicy classPolicy = 
POLICY.forClass(Method.class);
+            
assertFalse(classPolicy.isMethodExposed(Method.class.getMethod("invoke", 
Object.class, Object[].class)));
+        }
+        {
+            ClassMemberAccessPolicy classPolicy = 
POLICY.forClass(Constructor.class);
+            
assertFalse(classPolicy.isMethodExposed(Constructor.class.getMethod("newInstance",
 Object[].class)));
+        }
+        {
+            ClassMemberAccessPolicy classPolicy = POLICY.forClass(Field.class);
+            
assertFalse(classPolicy.isMethodExposed(Field.class.getMethod("get", 
Object.class)));
+        }
+        {
+            ClassMemberAccessPolicy classPolicy = 
POLICY.forClass(Object.class);
+            
assertTrue(classPolicy.isMethodExposed(Field.class.getMethod("getClass"))); // 
Allowed by design
+            
assertFalse(classPolicy.isMethodExposed(Field.class.getMethod("wait")));
+        }
+        {
+            ClassMemberAccessPolicy classPolicy = POLICY.forClass(URL.class);
+            
assertFalse(classPolicy.isMethodExposed(URL.class.getMethod("openConnection")));
+        }
+    }
+
+    public static class ThreadSubclass extends Thread {
+        @Override
+        public void run() {
+            super.run();
+        }
+
+        public void m1() {}
+    }
+
+    public static class UserClass {
+        public void foo() {
+        }
+    }
+
+    public static class CustomClassLoader extends ClassLoader {
+        public void m1() {}
+    }
+
+}
diff --git 
a/src/test/java/freemarker/ext/beans/DefaultObjectWrapperMemberAccessPolicyTest.java
 
b/src/test/java/freemarker/ext/beans/DefaultObjectWrapperMemberAccessPolicyTest.java
index eafbaf2..f9fcf47 100644
--- 
a/src/test/java/freemarker/ext/beans/DefaultObjectWrapperMemberAccessPolicyTest.java
+++ 
b/src/test/java/freemarker/ext/beans/DefaultObjectWrapperMemberAccessPolicyTest.java
@@ -51,16 +51,18 @@ import freemarker.template.TemplateNumberModel;
 
 public class DefaultObjectWrapperMemberAccessPolicyTest {
 
+    private final DefaultObjectWrapper dow
+            = new 
DefaultObjectWrapperBuilder(Configuration.VERSION_2_3_30).build();
+
     @Test
     public void testMethodsWithDefaultMemberAccessPolicy() throws 
TemplateModelException {
-        DefaultObjectWrapper ow = 
createDefaultMemberAccessPolicyObjectWrapper();
-        TemplateHashModel objM = (TemplateHashModel) ow.wrap(new C());
+        TemplateHashModel objM = (TemplateHashModel) dow.wrap(new C());
 
         assertNotNull(objM.get("m1"));
-        assertEquals("m2(true)", exec(ow, objM.get("m2"), true));
-        assertEquals("staticM()", exec(ow, objM.get("staticM")));
+        assertEquals("m2(true)", exec(dow, objM.get("m2"), true));
+        assertEquals("staticM()", exec(dow, objM.get("staticM")));
 
-        assertEquals("x", getHashValue(ow, objM, "x"));
+        assertEquals("x", getHashValue(dow, objM, "x"));
         assertNotNull(objM.get("getX"));
         assertNotNull(objM.get("setX"));
 
@@ -68,12 +70,9 @@ public class DefaultObjectWrapperMemberAccessPolicyTest {
 
         assertNull(objM.get("notify"));
 
-        // Because it was overridden, we allow it historically.
-        assertNotNull(objM.get("run"));
-
-        assertEquals("safe wait(1)", exec(ow, objM.get("wait"), 1L));
+        assertEquals("safe wait(1)", exec(dow, objM.get("wait"), 1L));
         try {
-            exec(ow, objM.get("wait")); // 0 arg overload is not visible, a 
it's "unsafe"
+            exec(dow, objM.get("wait")); // 0 arg overload is not visible, a 
it's "unsafe"
             fail();
         } catch (TemplateModelException e) {
             assertThat(e.getMessage(), containsString("wait(int)"));
@@ -82,8 +81,7 @@ public class DefaultObjectWrapperMemberAccessPolicyTest {
 
     @Test
     public void testFieldsWithDefaultMemberAccessPolicy() throws 
TemplateModelException {
-        DefaultObjectWrapper ow = 
createDefaultMemberAccessPolicyObjectWrapper();
-        TemplateHashModel objM = (TemplateHashModel) ow.wrap(new C());
+        TemplateHashModel objM = (TemplateHashModel) dow.wrap(new C());
         assertFieldsNotExposed(objM);
     }
 
@@ -103,27 +101,33 @@ public class DefaultObjectWrapperMemberAccessPolicyTest {
 
     @Test
     public void testGenericGetWithDefaultMemberAccessPolicy() throws 
TemplateModelException {
-        DefaultObjectWrapper ow = 
createDefaultMemberAccessPolicyObjectWrapper();
+        TemplateHashModel objM = (TemplateHashModel) dow.wrap(new 
CWithGenericGet());
 
-        TemplateHashModel objM = (TemplateHashModel) ow.wrap(new 
CWithGenericGet());
+        assertEquals("get(x)", getHashValue(dow, objM, "x"));
+    }
 
-        assertEquals("get(x)", getHashValue(ow, objM, "x"));
+    @Test
+    public void testBlacklistRuleWithDefaultMemberAccessPolicy() throws 
TemplateModelException {
+        TemplateHashModel objM = (TemplateHashModel) dow.wrap(new CThread());
+
+        assertNull(getHashValue(dow, objM, "run")); // blacklisted in Thread
+        assertNotNull(getHashValue(dow, objM, "m1")); // As Thread doesn't use 
whitelisted rule
+        assertNotNull(getHashValue(dow, objM, "toString"));
     }
 
     @Test
     public void testConstructorsWithDefaultMemberAccessPolicy() throws 
TemplateModelException {
-        DefaultObjectWrapper ow = 
createDefaultMemberAccessPolicyObjectWrapper();
-        assertNonPublicConstructorNotExposed(ow);
+        assertNonPublicConstructorNotExposed(dow);
 
-        assertEquals(CWithConstructor.class, 
ow.newInstance(CWithConstructor.class, Collections.emptyList())
+        assertEquals(CWithConstructor.class, 
dow.newInstance(CWithConstructor.class, Collections.emptyList())
                 .getClass());
 
         assertEquals(CWithOverloadedConstructor.class,
-                ow.newInstance(CWithOverloadedConstructor.class, 
Collections.emptyList())
+                dow.newInstance(CWithOverloadedConstructor.class, 
Collections.emptyList())
                         .getClass());
 
         assertEquals(CWithOverloadedConstructor.class,
-                ow.newInstance(CWithOverloadedConstructor.class, 
Collections.singletonList(new SimpleNumber(1)))
+                dow.newInstance(CWithOverloadedConstructor.class, 
Collections.singletonList(new SimpleNumber(1)))
                         .getClass());
     }
 
@@ -357,7 +361,7 @@ public class DefaultObjectWrapperMemberAccessPolicyTest {
     }
 
     @Test
-    public void testMemberAccessPolicyAndNewBI() throws IOException, 
TemplateException {
+    public void testMemberAccessPolicyAndNewBI() throws IOException, 
TemplateException, NoSuchMethodException {
         DefaultObjectWrapperBuilder owb = new 
DefaultObjectWrapperBuilder(Configuration.VERSION_2_3_30);
         owb.setMemberAccessPolicy(new MemberAccessPolicy() {
             public ClassMemberAccessPolicy forClass(Class<?> contextClass) {
@@ -392,7 +396,11 @@ public class DefaultObjectWrapperMemberAccessPolicyTest {
             assertEquals("1 failed", out.toString());
         }
 
-        cfg.setObjectWrapper(new 
DefaultObjectWrapperBuilder(Configuration.VERSION_2_3_30).build());
+        DefaultObjectWrapper dow = new 
DefaultObjectWrapperBuilder(Configuration.VERSION_2_3_30).build();
+        MemberAccessPolicy pol = dow.getMemberAccessPolicy();
+        ClassMemberAccessPolicy cpol = pol.forClass(CustomModel.class);
+        
assertTrue(cpol.isConstructorExposed(CustomModel.class.getConstructor()));
+        cfg.setObjectWrapper(dow);
         {
             StringWriter out = new StringWriter();
             template.process(null, out);
@@ -400,10 +408,6 @@ public class DefaultObjectWrapperMemberAccessPolicyTest {
         }
     }
 
-    private static DefaultObjectWrapper 
createDefaultMemberAccessPolicyObjectWrapper() {
-        return new 
DefaultObjectWrapperBuilder(Configuration.VERSION_2_3_30).build();
-    }
-
     private static Object getHashValue(ObjectWrapperAndUnwrapper ow, 
TemplateHashModel objM, String key)
             throws TemplateModelException {
         return ow.unwrap(objM.get(key));
@@ -423,7 +427,7 @@ public class DefaultObjectWrapperMemberAccessPolicyTest {
         return returnValue instanceof TemplateModel ? 
ow.unwrap((TemplateModel) returnValue) : returnValue;
     }
 
-    public static class C extends Thread {
+    public static class C {
         public static final int STATIC_FIELD = 1;
         public int publicField1 = 1;
         public int publicField2 = 2;
@@ -471,18 +475,13 @@ public class DefaultObjectWrapperMemberAccessPolicyTest {
         public String wait(int otherOverload) {
             return "safe wait(" + otherOverload + ")";
         }
-
-        @Override
-        public void run() {
-            return;
-        }
     }
 
     public static class CExtended extends C {
         public int publicField3 = 3;
     }
 
-    public static class CWithGenericGet extends Thread {
+    public static class CWithGenericGet {
         public String get(String key) {
             return "get(" + key + ")";
         }
@@ -493,6 +492,13 @@ public class DefaultObjectWrapperMemberAccessPolicyTest {
         }
     }
 
+    public static class CThread extends Thread {
+        @Override
+        public void run() {}
+
+        public void m1() {}
+    }
+
     public static class CWithOverloadedConstructor implements TemplateModel {
         public CWithOverloadedConstructor() {
         }
diff --git 
a/src/test/java/freemarker/ext/beans/LegacyDefaultMemberAccessPolicyTest.java 
b/src/test/java/freemarker/ext/beans/LegacyDefaultMemberAccessPolicyTest.java
new file mode 100644
index 0000000..e301ddf
--- /dev/null
+++ 
b/src/test/java/freemarker/ext/beans/LegacyDefaultMemberAccessPolicyTest.java
@@ -0,0 +1,73 @@
+/*
+ * 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.ext.beans;
+
+import static org.junit.Assert.*;
+
+import org.junit.Test;
+
+public class LegacyDefaultMemberAccessPolicyTest {
+
+    @Test
+    public void testBasic() throws NoSuchMethodException, NoSuchFieldException 
{
+        ClassMemberAccessPolicy classPolicy = 
LegacyDefaultMemberAccessPolicy.INSTANCE.forClass(UserClass.class);
+        
assertFalse(classPolicy.isMethodExposed(UserClass.class.getMethod("wait")));
+        
assertTrue(classPolicy.isMethodExposed(UserClass.class.getMethod("foo")));
+        
assertTrue(classPolicy.isConstructorExposed(UserClass.class.getConstructor()));
+        assertTrue(classPolicy.isFieldExposed(UserClass.class.getField("f1")));
+    }
+
+    @Test
+    public void testThread() throws NoSuchMethodException, 
NoSuchFieldException {
+        {
+            ClassMemberAccessPolicy classPolicy = 
LegacyDefaultMemberAccessPolicy.INSTANCE.forClass(Thread.class);
+            
assertFalse(classPolicy.isMethodExposed(Thread.class.getMethod("run")));
+        }
+        {
+            ClassMemberAccessPolicy classPolicy = 
LegacyDefaultMemberAccessPolicy.INSTANCE.forClass(ThreadSubclass.class);
+            // Strange glitch that we reproduce for backward compatibility:
+            
assertTrue(classPolicy.isMethodExposed(ThreadSubclass.class.getMethod("run")));
+        }
+    }
+
+    @Test
+    public void testClass() throws NoSuchMethodException, NoSuchFieldException 
{
+        ClassMemberAccessPolicy classPolicy = 
LegacyDefaultMemberAccessPolicy.INSTANCE.forClass(Class.class);
+        
assertFalse(classPolicy.isMethodExposed(Class.class.getMethod("getClassLoader")));
+        
assertTrue(classPolicy.isMethodExposed(Class.class.getMethod("getName")));
+    }
+
+    public static class ThreadSubclass extends Thread {
+        @Override
+        public void run() {
+            super.run();
+        }
+
+        public void m1() {}
+    }
+
+    public static class UserClass {
+        public String f1;
+
+        public void foo() {
+        }
+    }
+
+}
diff --git 
a/src/test/java/freemarker/ext/beans/WhitelistMemberAccessPolicyTest.java 
b/src/test/java/freemarker/ext/beans/MemberSelectorListAccessPolicyTest.java
similarity index 84%
rename from 
src/test/java/freemarker/ext/beans/WhitelistMemberAccessPolicyTest.java
rename to 
src/test/java/freemarker/ext/beans/MemberSelectorListAccessPolicyTest.java
index 0edee07..1250c2a 100644
--- a/src/test/java/freemarker/ext/beans/WhitelistMemberAccessPolicyTest.java
+++ b/src/test/java/freemarker/ext/beans/MemberSelectorListAccessPolicyTest.java
@@ -27,11 +27,7 @@ import java.util.Arrays;
 
 import org.junit.Test;
 
-import freemarker.template.Configuration;
-import freemarker.template.TemplateException;
-import freemarker.template.TemplateHashModel;
-
-public class WhitelistMemberAccessPolicyTest {
+public class MemberSelectorListAccessPolicyTest {
 
     @Test
     public void testEmpty() throws NoSuchMethodException, NoSuchFieldException 
{
@@ -92,7 +88,7 @@ public class WhitelistMemberAccessPolicyTest {
 
         
assertFalse(c1Policy.isConstructorExposed(C1.class.getConstructor(int.class)));
         
assertTrue(c2Policy.isConstructorExposed(C2.class.getConstructor(int.class)));
-        
assertTrue(c3Policy.isConstructorExposed(C3.class.getConstructor(int.class)));
+        
assertFalse(c3Policy.isConstructorExposed(C3.class.getConstructor(int.class))); 
// Not inherited
 
         assertFalse(c1Policy.isMethodExposed(C1.class.getMethod("m1")));
         assertTrue(c2Policy.isMethodExposed(C2.class.getMethod("m1")));
@@ -263,6 +259,75 @@ public class WhitelistMemberAccessPolicyTest {
     }
 
     @Test
+    public void testBlacklist1() throws NoSuchMethodException, 
NoSuchFieldException {
+        BlacklistMemberAccessPolicy policy = newBlacklistMemberAccessPolicy(
+                C1.class.getName() + ".m1()",
+                C1.class.getName() + ".f1",
+                C1.class.getName() + "." + C1.class.getSimpleName() + "()"
+        );
+
+        for (Class<?> cl : new Class[] { C1.class, C2.class, C3.class }) {
+            ClassMemberAccessPolicy classPolicy = policy.forClass(cl);
+            assertFalse(classPolicy.isMethodExposed(cl.getMethod("m1")));
+            assertTrue(classPolicy.isMethodExposed(cl.getMethod("m2", 
int.class)));
+            assertTrue(classPolicy.isMethodExposed(cl.getMethod("m3")));
+            assertFalse(classPolicy.isFieldExposed(cl.getField("f1")));
+            assertTrue(classPolicy.isFieldExposed(cl.getField("f2")));
+            if (cl != C2.class) {
+                assertEquals(cl != C1.class, 
classPolicy.isConstructorExposed(cl.getConstructor()));
+            }
+            
assertTrue(classPolicy.isConstructorExposed(cl.getConstructor(int.class)));
+        }
+    }
+
+    @Test
+    public void testBlacklist2() throws NoSuchMethodException, 
NoSuchFieldException {
+        BlacklistMemberAccessPolicy policy = newBlacklistMemberAccessPolicy(
+                C2.class.getName() + ".m1()",
+                C2.class.getName() + ".f1",
+                C2.class.getName() + "." + C2.class.getSimpleName() + "(int)"
+        );
+
+        {
+            Class<C1> lc = C1.class;
+            ClassMemberAccessPolicy classPolicy = policy.forClass(lc);
+            assertTrue(classPolicy.isMethodExposed(lc.getMethod("m1")));
+            assertTrue(classPolicy.isFieldExposed(lc.getField("f1")));
+            
assertTrue(classPolicy.isConstructorExposed(lc.getConstructor(int.class)));
+        }
+
+        {
+            Class<C2> lc = C2.class;
+            ClassMemberAccessPolicy classPolicy = policy.forClass(lc);
+            assertFalse(classPolicy.isMethodExposed(lc.getMethod("m1")));
+            assertFalse(classPolicy.isFieldExposed(lc.getField("f1")));
+            
assertFalse(classPolicy.isConstructorExposed(lc.getConstructor(int.class)));
+        }
+
+        {
+            Class<C3> lc = C3.class;
+            ClassMemberAccessPolicy classPolicy = policy.forClass(lc);
+            assertFalse(classPolicy.isMethodExposed(lc.getMethod("m1")));
+            assertFalse(classPolicy.isFieldExposed(lc.getField("f1")));
+            
assertTrue(classPolicy.isConstructorExposed(lc.getConstructor(int.class)));
+        }
+    }
+
+    @Test
+    public void testBlacklistIgnoredAnnotation() throws NoSuchMethodException, 
NoSuchFieldException {
+        BlacklistMemberAccessPolicy policy = newBlacklistMemberAccessPolicy(
+                CAnnotationsTest1.class.getName() + ".m5()",
+                CAnnotationsTest1.class.getName() + ".f5",
+                CAnnotationsTest1.class.getName() + "." + 
CAnnotationsTest1.class.getSimpleName() + "()"
+        );
+
+        ClassMemberAccessPolicy classPolicy = 
policy.forClass(CAnnotationsTest1.class);
+        
assertFalse(classPolicy.isMethodExposed(CAnnotationsTest1.class.getMethod("m5")));
+        
assertFalse(classPolicy.isFieldExposed(CAnnotationsTest1.class.getField("f5")));
+        
assertFalse(classPolicy.isConstructorExposed(CAnnotationsTest1.class.getConstructor()));
+    }
+
+    @Test
     public void memberSelectorParserIgnoresWhitespace() throws 
NoSuchMethodException {
         WhitelistMemberAccessPolicy policy = newWhitelistMemberAccessPolicy(
                 (CArrayArgs.class.getName() + 
".m1(java.lang.String)").replace(".", "\n\t. "),
@@ -307,7 +372,7 @@ public class WhitelistMemberAccessPolicyTest {
             newWhitelistMemberAccessPolicy("java.util.Date.toString(");
             fail();
         } catch (IllegalArgumentException e) {
-            assertThat(e.getMessage(), containsString("missing closing ')'"));
+            assertThat(e.getMessage(), containsString("should end with ')'"));
         }
         try {
             newWhitelistMemberAccessPolicy("java.util.Date.m(com.x-y)");
@@ -361,31 +426,18 @@ public class WhitelistMemberAccessPolicyTest {
                 CAnnotationsTest2.class.getConstructor(int.class, int.class, 
int.class, int.class)));
     }
 
-    public static final MemberAccessPolicy CONFIG_TEST_MEMBER_ACCESS_POLICY =
-            newWhitelistMemberAccessPolicy(
-                    C1.class.getName() + ".m1()",
-                    C1.class.getName() + ".m3()");
-
-    @Test
-    public void stringBasedConfigurationTest() throws TemplateException {
-        Configuration cfg = new Configuration(Configuration.VERSION_2_3_30);
-        cfg.setSetting(
-                "objectWrapper",
-                "DefaultObjectWrapper(2.3.30, " +
-                        "memberAccessPolicy="
-                        + WhitelistMemberAccessPolicyTest.class.getName() + 
".CONFIG_TEST_MEMBER_ACCESS_POLICY"
-                        + ")");
-        TemplateHashModel m = (TemplateHashModel) 
cfg.getObjectWrapper().wrap(new C1());
-        assertNotNull(m.get("m1"));
-        assertNull(m.get("m2"));
-        assertNotNull(m.get("m3"));
-    }
-
     private static WhitelistMemberAccessPolicy 
newWhitelistMemberAccessPolicy(String... memberSelectors) {
         return new WhitelistMemberAccessPolicy(
-                WhitelistMemberAccessPolicy.MemberSelector.parse(
+                MemberSelectorListMemberAccessPolicy.MemberSelector.parse(
                         Arrays.asList(memberSelectors),
-                        
WhitelistMemberAccessPolicyTest.class.getClassLoader()));
+                        
MemberSelectorListAccessPolicyTest.class.getClassLoader()));
+    }
+
+    private static BlacklistMemberAccessPolicy 
newBlacklistMemberAccessPolicy(String... memberSelectors) {
+        return new BlacklistMemberAccessPolicy(
+                MemberSelectorListMemberAccessPolicy.MemberSelector.parse(
+                        Arrays.asList(memberSelectors),
+                        
MemberSelectorListAccessPolicyTest.class.getClassLoader()));
     }
 
     public static class C1 {
@@ -421,6 +473,10 @@ public class WhitelistMemberAccessPolicyTest {
             super(x);
         }
 
+        @Override
+        public void m2(int x) {
+        }
+
         public void m2(boolean x) {
         }
 
diff --git a/src/test/java/freemarker/template/ConfigurationTest.java 
b/src/test/java/freemarker/template/ConfigurationTest.java
index c18e47c..4b77eef 100644
--- a/src/test/java/freemarker/template/ConfigurationTest.java
+++ b/src/test/java/freemarker/template/ConfigurationTest.java
@@ -22,6 +22,7 @@ package freemarker.template;
 import static org.hamcrest.Matchers.*;
 import static org.junit.Assert.*;
 
+import java.io.File;
 import java.io.IOException;
 import java.io.StringWriter;
 import java.lang.reflect.Field;
@@ -80,7 +81,11 @@ import freemarker.core.XHTMLOutputFormat;
 import freemarker.core.XMLOutputFormat;
 import freemarker.core._CoreStringUtils;
 import freemarker.ext.beans.BeansWrapperBuilder;
+import freemarker.ext.beans.LegacyDefaultMemberAccessPolicy;
+import freemarker.ext.beans.MemberAccessPolicy;
+import freemarker.ext.beans.MemberSelectorListMemberAccessPolicy;
 import freemarker.ext.beans.StringModel;
+import freemarker.ext.beans.WhitelistMemberAccessPolicy;
 import freemarker.template.utility.DateUtil;
 import freemarker.template.utility.NullArgumentException;
 import freemarker.template.utility.NullWriter;
@@ -1896,6 +1901,39 @@ public class ConfigurationTest extends TestCase {
         assertFalse(cfg.getFallbackOnNullLoopVariable());
     }
 
+    public static final MemberAccessPolicy CONFIG_TEST_MEMBER_ACCESS_POLICY =
+            new 
WhitelistMemberAccessPolicy(MemberSelectorListMemberAccessPolicy.MemberSelector.parse(
+                    ImmutableList.<String>of(
+                            File.class.getName() + ".getName()",
+                            File.class.getName() + ".isFile()"),
+                    ConfigurationTest.class.getClassLoader()));
+
+    @Test
+    public void testMemberAccessPolicySetting() throws TemplateException {
+        Configuration cfg = new Configuration(Configuration.VERSION_2_3_30);
+        cfg.setSetting(
+                "objectWrapper",
+                "DefaultObjectWrapper(2.3.30, "
+                        + "memberAccessPolicy="
+                        + ConfigurationTest.class.getName() + 
".CONFIG_TEST_MEMBER_ACCESS_POLICY"
+                        + ")");
+        TemplateHashModel m = (TemplateHashModel) 
cfg.getObjectWrapper().wrap(new File("x"));
+        assertNotNull(m.get("getName"));
+        assertNotNull(m.get("isFile"));
+        assertNull(m.get("delete"));
+    }
+
+    @Test
+    public void testMemberAccessPolicySetting2() throws TemplateException {
+        Configuration cfg = new Configuration(Configuration.VERSION_2_3_30);
+        cfg.setSetting(
+                "objectWrapper",
+                "DefaultObjectWrapper(2.3.30, "
+                        + "memberAccessPolicy=" + 
LegacyDefaultMemberAccessPolicy.class.getName() + "())");
+        assertSame(((DefaultObjectWrapper) 
cfg.getObjectWrapper()).getMemberAccessPolicy(),
+                LegacyDefaultMemberAccessPolicy.INSTANCE);
+    }
+
     @Test
     public void testGetSettingNamesAreSorted() throws Exception {
         Configuration cfg = new Configuration(Configuration.VERSION_2_3_22);

Reply via email to