freemarker-notifications mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From ddek...@apache.org
Subject [freemarker] 01/02: Forward ported from 2.3-gae: FREEMARKER-124: Changed DefaultMemberAccessPolicy to block more methods. 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
Date Sat, 11 Jan 2020 18:30:26 GMT
This is an automated email from the ASF dual-hosted git repository.

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

commit aad5477771df4a7e375945c584bca644fb9f2721
Author: ddekany <ddekany@apache.org>
AuthorDate: Sat Jan 11 19:27:35 2020 +0100

    Forward ported from 2.3-gae: FREEMARKER-124: Changed DefaultMemberAccessPolicy to block more methods.
    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
---
 .../apache/freemarker/core/ConfigurationTest.java  |  28 +
 .../model/impl/DefaultMemberAccessPolicyTest.java  | 143 +++++
 ...DefaultObjectWrapperMemberAccessPolicyTest.java | 208 +++++---
 ...ava => MemberSelectorListAccessPolicyTest.java} | 116 +++-
 .../freemarker/core/ProcessingConfiguration.java   |   6 +
 .../model/impl/BlacklistMemberAccessPolicy.java    |  48 ++
 .../core/model/impl/ConstructorMatcher.java        |   5 +
 .../core/model/impl/DefaultMemberAccessPolicy.java | 204 +++++---
 .../freemarker/core/model/impl/FieldMatcher.java   |   5 +
 .../core/model/impl/MemberAccessPolicy.java        |  23 +-
 .../freemarker/core/model/impl/MemberMatcher.java  |  14 +-
 ...a => MemberSelectorListMemberAccessPolicy.java} | 127 +++--
 .../freemarker/core/model/impl/MethodMatcher.java  |   5 +
 .../model/impl/WhitelistMemberAccessPolicy.java    | 388 +-------------
 .../model/impl/DefaultMemberAccessPolicy-rules     | 582 +++++++++++++++++++++
 .../core/model/impl/unsafeMethods.properties       |  98 ----
 16 files changed, 1289 insertions(+), 711 deletions(-)

diff --git a/freemarker-core-test/src/test/java/org/apache/freemarker/core/ConfigurationTest.java b/freemarker-core-test/src/test/java/org/apache/freemarker/core/ConfigurationTest.java
index 708bae8..56d5390 100644
--- a/freemarker-core-test/src/test/java/org/apache/freemarker/core/ConfigurationTest.java
+++ b/freemarker-core-test/src/test/java/org/apache/freemarker/core/ConfigurationTest.java
@@ -25,6 +25,7 @@ import static org.apache.freemarker.test.hamcerst.Matchers.*;
 import static org.hamcrest.Matchers.*;
 import static org.junit.Assert.*;
 
+import java.io.File;
 import java.io.IOException;
 import java.io.Serializable;
 import java.io.StringWriter;
@@ -46,10 +47,14 @@ import java.util.TimeZone;
 import java.util.TreeSet;
 
 import org.apache.freemarker.core.Configuration.*;
+import org.apache.freemarker.core.model.TemplateHashModel;
 import org.apache.freemarker.core.model.TemplateStringModel;
 import org.apache.freemarker.core.model.impl.DefaultObjectWrapper;
+import org.apache.freemarker.core.model.impl.MemberAccessPolicy;
+import org.apache.freemarker.core.model.impl.MemberSelectorListMemberAccessPolicy;
 import org.apache.freemarker.core.model.impl.RestrictedObjectWrapper;
 import org.apache.freemarker.core.model.impl.SimpleString;
+import org.apache.freemarker.core.model.impl.WhitelistMemberAccessPolicy;
 import org.apache.freemarker.core.outputformat.MarkupOutputFormat;
 import org.apache.freemarker.core.outputformat.OutputFormat;
 import org.apache.freemarker.core.outputformat.UnregisteredOutputFormatException;
@@ -728,6 +733,29 @@ public class ConfigurationTest {
         assertEquals(1000L * 60 * 60 * 2, (Object) cfgB.getTemplateUpdateDelayMilliseconds());
     }
 
+    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.Builder(Configuration.VERSION_3_0_0)
+                .setting(
+                        "objectWrapper",
+                        "DefaultObjectWrapper(3.0.0, "
+                                + "memberAccessPolicy="
+                                + ConfigurationTest.class.getName() + ".CONFIG_TEST_MEMBER_ACCESS_POLICY"
+                                + ")")
+                .build();
+        TemplateHashModel m = (TemplateHashModel) cfg.getObjectWrapper().wrap(new File("x"));
+        assertNotNull(m.get("getName"));
+        assertNotNull(m.get("isFile"));
+        assertNull(m.get("delete"));
+    }
+
     @Test
     public void testGetSettingNamesAreSorted() throws Exception {
         List<String> names = new ArrayList<>(Builder.getSettingNames());
diff --git a/freemarker-core-test/src/test/java/org/apache/freemarker/core/model/impl/DefaultMemberAccessPolicyTest.java b/freemarker-core-test/src/test/java/org/apache/freemarker/core/model/impl/DefaultMemberAccessPolicyTest.java
new file mode 100644
index 0000000..42cd905
--- /dev/null
+++ b/freemarker-core-test/src/test/java/org/apache/freemarker/core/model/impl/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 org.apache.freemarker.core.model.impl;
+
+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.apache.freemarker.core.Configuration;
+import org.junit.Test;
+
+public class DefaultMemberAccessPolicyTest {
+
+    private static final DefaultMemberAccessPolicy POLICY =
+            DefaultMemberAccessPolicy.getInstance(Configuration.VERSION_3_0_0);
+
+    @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/freemarker-core-test/src/test/java/org/apache/freemarker/core/model/impl/DefaultObjectWrapperMemberAccessPolicyTest.java b/freemarker-core-test/src/test/java/org/apache/freemarker/core/model/impl/DefaultObjectWrapperMemberAccessPolicyTest.java
index 7d4826c..4747f3e 100644
--- a/freemarker-core-test/src/test/java/org/apache/freemarker/core/model/impl/DefaultObjectWrapperMemberAccessPolicyTest.java
+++ b/freemarker-core-test/src/test/java/org/apache/freemarker/core/model/impl/DefaultObjectWrapperMemberAccessPolicyTest.java
@@ -43,16 +43,18 @@ import com.google.common.collect.ImmutableMap;
 
 public class DefaultObjectWrapperMemberAccessPolicyTest {
 
+    private final DefaultObjectWrapper dow
+            = new DefaultObjectWrapper.Builder(Configuration.VERSION_3_0_0).build();
+
     @Test
     public void testMethodsWithDefaultMemberAccessPolicy() throws TemplateException {
-        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"));
 
@@ -60,12 +62,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 (TemplateException e) {
             assertThat(e.getMessage(), containsString("wait(int)"));
@@ -74,8 +73,7 @@ public class DefaultObjectWrapperMemberAccessPolicyTest {
 
     @Test
     public void testFieldsWithDefaultMemberAccessPolicy() throws TemplateException {
-        DefaultObjectWrapper ow = createDefaultMemberAccessPolicyObjectWrapper();
-        TemplateHashModel objM = (TemplateHashModel) ow.wrap(new C());
+        TemplateHashModel objM = (TemplateHashModel) dow.wrap(new C());
         assertFieldsNotExposed(objM);
     }
 
@@ -89,34 +87,39 @@ public class DefaultObjectWrapperMemberAccessPolicyTest {
         assertNull(objM.get("nonPublicField1"));
         assertNull(objM.get("nonPublicField2"));
 
-        // Strangely, public static fields are banned historically, while static methods aren't.
+        // Strangely, static fields are banned historically, while static methods aren't.
         assertNull(objM.get("STATIC_FIELD"));
     }
 
     @Test
     public void testGenericGetWithDefaultMemberAccessPolicy() throws TemplateException {
-        DefaultObjectWrapper ow = createDefaultMemberAccessPolicyObjectWrapper();
+        TemplateHashModel objM = (TemplateHashModel) dow.wrap(new CWithGenericGet());
 
-        TemplateHashModel objM = (TemplateHashModel) ow.wrap(new CWithGenericGet());
+        assertEquals("get(x)", getHashValue(dow, objM, "x"));
+    }
+
+    @Test
+    public void testBlacklistRuleWithDefaultMemberAccessPolicy() throws TemplateException {
+        TemplateHashModel objM = (TemplateHashModel) dow.wrap(new CThread());
 
-        assertEquals("get(x)", getHashValue(ow, objM, "x"));
+        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 TemplateException {
-        DefaultObjectWrapper ow = createDefaultMemberAccessPolicyObjectWrapper();
-        assertNonPublicConstructorNotExposed(ow);
+        assertNonPublicConstructorNotExposed(dow);
 
-        assertEquals(CWithConstructor.class,
-                ow.newInstance(CWithConstructor.class, new TemplateModel[0], null)
-                        .getClass());
+        assertEquals(CWithConstructor.class, dow.newInstance(CWithConstructor.class, new TemplateModel[0], null)
+                .getClass());
 
         assertEquals(CWithOverloadedConstructor.class,
-                ow.newInstance(CWithOverloadedConstructor.class, new TemplateModel[0], null)
+                dow.newInstance(CWithOverloadedConstructor.class, new TemplateModel[0], null)
                         .getClass());
 
         assertEquals(CWithOverloadedConstructor.class,
-                ow.newInstance(CWithOverloadedConstructor.class, new TemplateModel[] {new SimpleNumber(1)}, null)
+                dow.newInstance(CWithOverloadedConstructor.class, new TemplateModel[] { new SimpleNumber(1) }, null)
                         .getClass());
     }
 
@@ -167,8 +170,10 @@ public class DefaultObjectWrapperMemberAccessPolicyTest {
     public void testMethodsWithCustomMemberAccessPolicy() throws TemplateException {
         DefaultObjectWrapper.Builder owb = new DefaultObjectWrapper.Builder(Configuration.VERSION_3_0_0);
         owb.setMemberAccessPolicy(new MemberAccessPolicy() {
+            @Override
             public ClassMemberAccessPolicy forClass(Class<?> contextClass) {
                 return new ClassMemberAccessPolicy() {
+                    @Override
                     public boolean isMethodExposed(Method method) {
                         String name = method.getName();
                         Class<?>[] paramTypes = method.getParameterTypes();
@@ -177,10 +182,12 @@ public class DefaultObjectWrapperMemberAccessPolicyTest {
                                 && (paramTypes.length == 0 || paramTypes[0].equals(boolean.class)));
                     }
 
+                    @Override
                     public boolean isConstructorExposed(Constructor<?> constructor) {
                         return true;
                     }
 
+                    @Override
                     public boolean isFieldExposed(Field field) {
                         return true;
                     }
@@ -209,16 +216,20 @@ public class DefaultObjectWrapperMemberAccessPolicyTest {
         DefaultObjectWrapper.Builder owb = new DefaultObjectWrapper.Builder(Configuration.VERSION_3_0_0);
         owb.setExposeFields(true);
         owb.setMemberAccessPolicy(new MemberAccessPolicy() {
+            @Override
             public ClassMemberAccessPolicy forClass(Class<?> contextClass) {
                 return new ClassMemberAccessPolicy() {
+                    @Override
                     public boolean isMethodExposed(Method method) {
                         return true;
                     }
 
+                    @Override
                     public boolean isConstructorExposed(Constructor<?> constructor) {
                         return true;
                     }
 
+                    @Override
                     public boolean isFieldExposed(Field field) {
                         return field.getName().equals("publicField1")
                                 || field.getName().equals("nonPublicField1");
@@ -239,16 +250,20 @@ public class DefaultObjectWrapperMemberAccessPolicyTest {
     public void testGenericGetWithCustomMemberAccessPolicy() throws TemplateException {
         DefaultObjectWrapper.Builder owb = new DefaultObjectWrapper.Builder(Configuration.VERSION_3_0_0);
         owb.setMemberAccessPolicy(new MemberAccessPolicy() {
+            @Override
             public ClassMemberAccessPolicy forClass(Class<?> contextClass) {
                 return new ClassMemberAccessPolicy() {
+                    @Override
                     public boolean isMethodExposed(Method method) {
                         return false;
                     }
 
+                    @Override
                     public boolean isConstructorExposed(Constructor<?> constructor) {
                         return true;
                     }
 
+                    @Override
                     public boolean isFieldExposed(Field field) {
                         return true;
                     }
@@ -265,17 +280,21 @@ public class DefaultObjectWrapperMemberAccessPolicyTest {
     public void testConstructorsWithCustomMemberAccessPolicy() throws TemplateException {
         DefaultObjectWrapper.Builder owb = new DefaultObjectWrapper.Builder(Configuration.VERSION_3_0_0);
         owb.setMemberAccessPolicy(new MemberAccessPolicy() {
+            @Override
             public ClassMemberAccessPolicy forClass(Class<?> contextClass) {
                 return new ClassMemberAccessPolicy() {
+                    @Override
                     public boolean isMethodExposed(Method method) {
                         return true;
                     }
 
+                    @Override
                     public boolean isConstructorExposed(Constructor<?> constructor) {
                         return constructor.getDeclaringClass() == CWithOverloadedConstructor.class
                                 && constructor.getParameterTypes().length == 1;
                     }
 
+                    @Override
                     public boolean isFieldExposed(Field field) {
                         return true;
                     }
@@ -302,54 +321,55 @@ public class DefaultObjectWrapperMemberAccessPolicyTest {
         }
 
         assertEquals(CWithOverloadedConstructor.class,
-                ow.newInstance(CWithOverloadedConstructor.class,
-                        new TemplateModel[] {new SimpleNumber(1)}, null).getClass());
+                ow.newInstance(CWithOverloadedConstructor.class, new TemplateModel[] { new SimpleNumber(1) }, null)
+                        .getClass());
     }
 
     @Test
     public void testMemberAccessPolicyAndApiBI() throws IOException, TemplateException {
-        DefaultObjectWrapper ow = new DefaultObjectWrapper.Builder(Configuration.VERSION_3_0_0)
-                .memberAccessPolicy(new MemberAccessPolicy() {
+        DefaultObjectWrapper.Builder owb = new DefaultObjectWrapper.Builder(Configuration.VERSION_3_0_0);
+        owb.setMemberAccessPolicy(new MemberAccessPolicy() {
+            @Override
+            public ClassMemberAccessPolicy forClass(Class<?> contextClass) {
+                return new ClassMemberAccessPolicy() {
                     @Override
-                    public ClassMemberAccessPolicy forClass(Class<?> contextClass) {
-                        return new ClassMemberAccessPolicy() {
-                            public boolean isMethodExposed(Method method) {
-                                return method.getName().equals("size");
-                            }
-
-                            public boolean isConstructorExposed(Constructor<?> constructor) {
-                                return true;
-                            }
-
-                            public boolean isFieldExposed(Field field) {
-                                return true;
-                            }
-                        };
+                    public boolean isMethodExposed(Method method) {
+                        return method.getName().equals("size");
                     }
-                })
-                .build();
 
-        Map<String, Object> dataModel = ImmutableMap.<String, Object>of("m", ImmutableMap.of("k", "v"));
+                    @Override
+                    public boolean isConstructorExposed(Constructor<?> constructor) {
+                        return true;
+                    }
 
-        String templateSource = "size=${m?api.size()} get=${(m?api.get('k'))!'hidden'}";
+                    @Override
+                    public boolean isFieldExposed(Field field) {
+                        return true;
+                    }
+                };
+            }
+        });
+        DefaultObjectWrapper ow = owb.build();
 
+        Map<String, Object> dataModel = ImmutableMap.of("m", ImmutableMap.of("k", "v"));
+
+        String templateSourceCode = "size=${m?api.size()} get=${(m?api.get('k'))!'hidden'}";
         {
             Configuration cfg = new Configuration.Builder(Configuration.VERSION_3_0_0)
                     .objectWrapper(ow)
                     .apiBuiltinEnabled(true)
                     .build();
-            Template template = new Template(null, templateSource, cfg);
+            Template template = new Template(null, templateSourceCode, cfg);
             StringWriter out = new StringWriter();
             template.process(dataModel, out);
             assertEquals("size=1 get=hidden", out.toString());
         }
-
         {
             Configuration cfg = new Configuration.Builder(Configuration.VERSION_3_0_0)
                     .objectWrapper(new DefaultObjectWrapper.Builder(Configuration.VERSION_3_0_0).build())
                     .apiBuiltinEnabled(true)
                     .build();
-            Template template = new Template(null, templateSource, cfg);
+            Template template = new Template(null, templateSourceCode, cfg);
             StringWriter out = new StringWriter();
             template.process(dataModel, out);
             assertEquals("size=1 get=v", out.toString());
@@ -357,53 +377,62 @@ public class DefaultObjectWrapperMemberAccessPolicyTest {
     }
 
     @Test
-    public void testMemberAccessPolicyAndNewBI() throws IOException, TemplateException {
-        DefaultObjectWrapper ow = new DefaultObjectWrapper.Builder(Configuration.VERSION_3_0_0)
-                .memberAccessPolicy(new MemberAccessPolicy() {
-                        public ClassMemberAccessPolicy forClass(Class<?> contextClass) {
-                            return new ClassMemberAccessPolicy() {
-                                public boolean isMethodExposed(Method method) {
-                                    return true;
-                                }
-
-                                public boolean isConstructorExposed(Constructor<?> constructor) {
-                                    return constructor.getDeclaringClass().equals(CustomModel.class);
-                                }
-
-                                public boolean isFieldExposed(Field field) {
-                                    return true;
-                                }
-                            };
-                        }
-                    })
-                .build();
-
-        String templateSource = "${'" + CustomModel.class.getName() + "'?new()} "
+    public void testMemberAccessPolicyAndNewBI() throws IOException, TemplateException, NoSuchMethodException {
+        DefaultObjectWrapper.Builder owb = new DefaultObjectWrapper.Builder(Configuration.VERSION_3_0_0);
+        owb.setMemberAccessPolicy(new MemberAccessPolicy() {
+            @Override
+            public ClassMemberAccessPolicy forClass(Class<?> contextClass) {
+                return new ClassMemberAccessPolicy() {
+                    @Override
+                    public boolean isMethodExposed(Method method) {
+                        return true;
+                    }
+
+                    @Override
+                    public boolean isConstructorExposed(Constructor<?> constructor) {
+                        return constructor.getDeclaringClass().equals(CustomModel.class);
+                    }
+
+                    @Override
+                    public boolean isFieldExposed(Field field) {
+                        return true;
+                    }
+                };
+            }
+        });
+        DefaultObjectWrapper ow = owb.build();
+
+        String templateSourceCode = "${'" + CustomModel.class.getName() + "'?new()} "
                 + "<#attempt>${'" + OtherCustomModel.class.getName() + "'?new()}<#recover>failed</#attempt>";
         {
             Configuration cfg = new Configuration.Builder(Configuration.VERSION_3_0_0)
                     .objectWrapper(ow)
                     .apiBuiltinEnabled(true)
                     .build();
-            Template template = new Template(null, templateSource, cfg);
+            Template template = new Template(null, templateSourceCode, cfg);
+
             StringWriter out = new StringWriter();
             template.process(null, out);
             assertEquals("1 failed", out.toString());
         }
-
         {
-            Configuration cfg = new Configuration.Builder(Configuration.VERSION_3_0_0).build();
-            Template template = new Template(null, templateSource, cfg);
+            DefaultObjectWrapper dow = new DefaultObjectWrapper.Builder(Configuration.VERSION_3_0_0).build();
+            MemberAccessPolicy pol = dow.getMemberAccessPolicy();
+            ClassMemberAccessPolicy cpol = pol.forClass(CustomModel.class);
+            assertTrue(cpol.isConstructorExposed(CustomModel.class.getConstructor()));
+
+            Configuration cfg = new Configuration.Builder(Configuration.VERSION_3_0_0)
+                    .objectWrapper(dow)
+                    .apiBuiltinEnabled(true)
+                    .build();
+            Template template = new Template(null, templateSourceCode, cfg);
+
             StringWriter out = new StringWriter();
             template.process(null, out);
             assertEquals("1 2", out.toString());
         }
     }
 
-    private static DefaultObjectWrapper createDefaultMemberAccessPolicyObjectWrapper() {
-        return new DefaultObjectWrapper.Builder(Configuration.VERSION_3_0_0).build();
-    }
-
     private static Object getHashValue(ObjectWrapperAndUnwrapper ow, TemplateHashModel objM, String key)
             throws TemplateException {
         return ow.unwrap(objM.get(key));
@@ -413,7 +442,8 @@ public class DefaultObjectWrapperMemberAccessPolicyTest {
         assertThat(objM, instanceOf(TemplateFunctionModel.class));
         TemplateModel[] argModels = new TemplateModel[args.length];
         for (int i = 0; i < args.length; i++) {
-            argModels[i] = ow.wrap(args[i]);
+            Object arg = args[i];
+            argModels[i] = ow.wrap(arg);
         }
         Object returnValue = ((TemplateFunctionModel) objM).execute(argModels, null, null);
         return unwrap(ow, returnValue);
@@ -423,7 +453,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 +501,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 +518,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() {
         }
@@ -502,14 +534,18 @@ public class DefaultObjectWrapperMemberAccessPolicyTest {
     }
 
     public static class CustomModel implements TemplateNumberModel {
+        @Override
         public Number getAsNumber() {
             return 1;
         }
     }
 
     public static class OtherCustomModel implements TemplateNumberModel {
+        @Override
         public Number getAsNumber() {
             return 2;
         }
     }
+
 }
+
diff --git a/freemarker-core-test/src/test/java/org/apache/freemarker/core/model/impl/WhitelistMemberAccessPolicyTest.java b/freemarker-core-test/src/test/java/org/apache/freemarker/core/model/impl/MemberSelectorListAccessPolicyTest.java
similarity index 84%
rename from freemarker-core-test/src/test/java/org/apache/freemarker/core/model/impl/WhitelistMemberAccessPolicyTest.java
rename to freemarker-core-test/src/test/java/org/apache/freemarker/core/model/impl/MemberSelectorListAccessPolicyTest.java
index 9ed20be..1e667b2 100644
--- a/freemarker-core-test/src/test/java/org/apache/freemarker/core/model/impl/WhitelistMemberAccessPolicyTest.java
+++ b/freemarker-core-test/src/test/java/org/apache/freemarker/core/model/impl/MemberSelectorListAccessPolicyTest.java
@@ -25,12 +25,9 @@ import static org.junit.Assert.*;
 import java.io.Serializable;
 import java.util.Arrays;
 
-import org.apache.freemarker.core.Configuration;
-import org.apache.freemarker.core.TemplateException;
-import org.apache.freemarker.core.model.TemplateHashModel;
 import org.junit.Test;
 
-public class WhitelistMemberAccessPolicyTest {
+public class MemberSelectorListAccessPolicyTest {
 
     @Test
     public void testEmpty() throws NoSuchMethodException, NoSuchFieldException {
@@ -91,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")));
@@ -262,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. "),
@@ -306,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)");
@@ -360,32 +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.Builder(Configuration.VERSION_3_0_0)
-                .setting(
-                        "objectWrapper",
-                        "DefaultObjectWrapper(3.0.0, " +
-                                "memberAccessPolicy="
-                                + WhitelistMemberAccessPolicyTest.class.getName() + ".CONFIG_TEST_MEMBER_ACCESS_POLICY"
-                                + ")")
-                .build();
-        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) {
         }
 
@@ -448,6 +504,7 @@ public class WhitelistMemberAccessPolicyTest {
     }
 
     public static class E1 implements I1Sub {
+        @Override
         public void m1() {
 
         }
@@ -550,6 +607,7 @@ public class WhitelistMemberAccessPolicyTest {
         @TemplateAccessible
         public void m4() {}
 
+        @Override
         public void m5() {}
 
         public void m6() {}
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/ProcessingConfiguration.java b/freemarker-core/src/main/java/org/apache/freemarker/core/ProcessingConfiguration.java
index 230dfed..e3215cf 100644
--- a/freemarker-core/src/main/java/org/apache/freemarker/core/ProcessingConfiguration.java
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/ProcessingConfiguration.java
@@ -33,6 +33,8 @@ import java.util.TimeZone;
 
 import org.apache.freemarker.core.arithmetic.ArithmeticEngine;
 import org.apache.freemarker.core.arithmetic.impl.BigDecimalArithmeticEngine;
+import org.apache.freemarker.core.model.ObjectWrapper;
+import org.apache.freemarker.core.model.impl.MemberAccessPolicy;
 import org.apache.freemarker.core.pluggablebuiltin.TruncateBuiltinAlgorithm;
 import org.apache.freemarker.core.pluggablebuiltin.impl.DefaultTruncateBuiltinAlgorithm;
 import org.apache.freemarker.core.valueformat.TemplateDateFormatFactory;
@@ -508,6 +510,10 @@ public interface ProcessingConfiguration {
      * called to resolve the <code>"com.example.SomeClassName"</code> string to a class. The default value is {@link
      * TemplateClassResolver#UNRESTRICTED}. If you allow users to upload templates, it's important to use a
      * custom restrictive {@link TemplateClassResolver} or {@link TemplateClassResolver#ALLOW_NOTHING}.
+     *
+     * <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.
      */
     TemplateClassResolver getNewBuiltinClassResolver();
 
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/BlacklistMemberAccessPolicy.java b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/BlacklistMemberAccessPolicy.java
new file mode 100644
index 0000000..5bb0d62
--- /dev/null
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/BlacklistMemberAccessPolicy.java
@@ -0,0 +1,48 @@
+/*
+ * 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 org.apache.freemarker.core.model.impl;
+
+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 DefaultObjectWrapper} 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.
+ */
+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/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/ConstructorMatcher.java b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/ConstructorMatcher.java
index 0300fd4..6d90470 100644
--- a/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/ConstructorMatcher.java
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/ConstructorMatcher.java
@@ -29,4 +29,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/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/DefaultMemberAccessPolicy.java b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/DefaultMemberAccessPolicy.java
index 6899efe..4713f2a 100644
--- a/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/DefaultMemberAccessPolicy.java
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/DefaultMemberAccessPolicy.java
@@ -19,88 +19,36 @@
 
 package org.apache.freemarker.core.model.impl;
 
+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.nio.charset.StandardCharsets;
+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 org.apache.freemarker.core.Version;
 import org.apache.freemarker.core._CoreAPI;
-import org.apache.freemarker.core.util._ClassUtils;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
+import org.apache.freemarker.core.model.impl.MemberSelectorListMemberAccessPolicy.MemberSelector;
 
 /**
- * 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; it can't provide safety in practice, if you allow
+ * untrusted users to edit templates! Use {@link WhitelistMemberAccessPolicy} if you need stricter control.
  */
 public final class DefaultMemberAccessPolicy implements MemberAccessPolicy {
-    private static final Logger LOG = LoggerFactory.getLogger(DefaultMemberAccessPolicy.class);
-    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 = _ClassUtils.loadProperties(DefaultObjectWrapper.class, UNSAFE_METHODS_PROPERTIES);
-            Set<Method> set = new HashSet<>(props.size() * 4 / 3, 1f);
-            Map<String, Class<?>> primClasses = createPrimitiveClassesMap();
-            for (Object key : props.keySet()) {
-                try {
-                    set.add(parseMethodSpec((String) key, primClasses));
-                } catch (ClassNotFoundException | NoSuchMethodException e) {
-                    LOG.debug("Failed to get unsafe method", 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 = _ClassUtils.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] = _ClassUtils.forName(argClassName);
-            }
-        }
-        return clazz.getMethod(methodName, argTypes);
-    }
-
-    private static Map<String, Class<?>> createPrimitiveClassesMap() {
-        Map<String, Class<?>> map = new HashMap<>();
-        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 DefaultMemberAccessPolicy() {
-    }
 
     private static final DefaultMemberAccessPolicy INSTANCE = new 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.
      */
@@ -111,24 +59,124 @@ 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();
 
-    private static final BacklistClassMemberAccessPolicy CLASS_MEMBER_ACCESS_POLICY_INSTANCE
-            = new BacklistClassMemberAccessPolicy();
-    private static class BacklistClassMemberAccessPolicy implements ClassMemberAccessPolicy {
+            whitelistRuleFinalClasses = new HashSet<>();
+            whitelistRuleNonFinalClasses = new HashSet<>();
+            Set<Class<?>> typesWithBlacklistUnlistedRule = new HashSet<>();
+            List<MemberSelector> whitelistMemberSelectors = new ArrayList<>();
+            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);
 
-        public boolean isMethodExposed(Method method) {
-            return !UNSAFE_METHODS.contains(method);
+            // 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 isConstructorExposed(Constructor<?> constructor) {
-            return true;
+    private static List<String> loadMemberSelectorFileLines() throws IOException {
+        List<String> whitelist = new ArrayList<String>();
+        try (BufferedReader reader = new BufferedReader(
+                new InputStreamReader(
+                        DefaultMemberAccessPolicy.class.getResourceAsStream("DefaultMemberAccessPolicy-rules"),
+                        StandardCharsets.UTF_8))) {
+            String line;
+            while ((line = reader.readLine()) != null) {
+                whitelist.add(line);
+            }
         }
 
-        public boolean isFieldExposed(Field field) {
+        return whitelist;
+    }
+
+    @Override
+    public ClassMemberAccessPolicy forClass(Class<?> contextClass) {
+        if (isTypeWithWhitelistRule(contextClass)) {
+            return whitelistMemberAccessPolicy.forClass(contextClass);
+        } else {
+            return blacklistMemberAccessPolicy.forClass(contextClass);
+        }
+    }
+
+    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/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/FieldMatcher.java b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/FieldMatcher.java
index dda187f..a61d54c 100644
--- a/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/FieldMatcher.java
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/FieldMatcher.java
@@ -29,4 +29,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/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/MemberAccessPolicy.java b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/MemberAccessPolicy.java
index c5fa8b6..c00dbde 100644
--- a/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/MemberAccessPolicy.java
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/MemberAccessPolicy.java
@@ -19,24 +19,29 @@
 
 package org.apache.freemarker.core.model.impl;
 
+import org.apache.freemarker.core.Environment;
 import org.apache.freemarker.core.model.ObjectWrapper;
 import org.apache.freemarker.core.model.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 DefaultObjectWrapper} 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 DefaultObjectWrapper.Builder#setMemberAccessPolicy(MemberAccessPolicy)} (or if
- * you use {@link DefaultObjectWrapper}, with
- * {@link DefaultObjectWrapper.Builder#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 DefaultObjectWrapper.Builder#setMemberAccessPolicy(MemberAccessPolicy)} (or if you use
+ * {@link DefaultObjectWrapper}, with {@link DefaultObjectWrapper.Builder#setMemberAccessPolicy(MemberAccessPolicy)}).
  *
  * <p>As {@link DefaultObjectWrapper}, 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 DefaultObjectWrapper} intends to expose on the first place. (Also, while public members
- * declared in non-public classes are discovered by {@link DefaultObjectWrapper}, 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 DefaultObjectWrapper}, 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/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/MemberMatcher.java b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/MemberMatcher.java
index 76080a1..c1fbe7c 100644
--- a/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/MemberMatcher.java
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/MemberMatcher.java
@@ -43,6 +43,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.
      *
@@ -83,7 +85,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/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/WhitelistMemberAccessPolicy.java b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/MemberSelectorListMemberAccessPolicy.java
similarity index 76%
copy from freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/WhitelistMemberAccessPolicy.java
copy to freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/MemberSelectorListMemberAccessPolicy.java
index 64ef7a9..48e48e4 100644
--- a/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/WhitelistMemberAccessPolicy.java
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/MemberSelectorListMemberAccessPolicy.java
@@ -19,6 +19,7 @@
 
 package org.apache.freemarker.core.model.impl;
 
+import java.lang.annotation.Annotation;
 import java.lang.reflect.Constructor;
 import java.lang.reflect.Field;
 import java.lang.reflect.Method;
@@ -27,59 +28,64 @@ import java.util.Collection;
 import java.util.List;
 import java.util.StringTokenizer;
 
-import org.apache.freemarker.core.model.ObjectWrapper;
 import org.apache.freemarker.core.util._ClassUtils;
 import org.apache.freemarker.core.util._NullArgumentException;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 /**
- * 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.
  */
-public class WhitelistMemberAccessPolicy implements MemberAccessPolicy {
-    private static final Logger LOG = LoggerFactory.getLogger(WhitelistMemberAccessPolicy.class);
+public abstract class MemberSelectorListMemberAccessPolicy implements MemberAccessPolicy {
+    private static final Logger LOG = LoggerFactory.getLogger(MemberSelectorListMemberAccessPolicy.class);
+
+    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()}.
-     *
-     * @since 2.3.30
      */
     public final static class MemberSelector {
         private final Class<?> upperBoundType;
@@ -261,7 +267,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);
@@ -309,19 +315,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<>(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();
@@ -345,29 +378,47 @@ public class WhitelistMemberAccessPolicy implements MemberAccessPolicy {
     }
 
     @Override
-    public ClassMemberAccessPolicy forClass(final Class<?> contextClass) {
+    public final ClassMemberAccessPolicy forClass(final Class<?> contextClass) {
         return new ClassMemberAccessPolicy() {
             @Override
             public boolean isMethodExposed(Method method) {
-                return methodMatcher.matches(contextClass, method)
-                        || _MethodUtils.getInheritableAnnotation(contextClass, method, TemplateAccessible.class) != null;
+                return matchResultToIsExposedResult(
+                        methodMatcher.matches(contextClass, method)
+                                || matchAnnotation != null
+                                && _MethodUtils.getInheritableAnnotation(contextClass, method, matchAnnotation)
+                                != null);
             }
 
             @Override
             public boolean isConstructorExposed(Constructor<?> constructor) {
-                return constructorMatcher.matches(contextClass, constructor)
-                        || _MethodUtils.getInheritableAnnotation(contextClass, constructor, TemplateAccessible.class)
-                        != null;
+                return matchResultToIsExposedResult(
+                        constructorMatcher.matches(contextClass, constructor)
+                                || matchAnnotation != null
+                                && _MethodUtils.getInheritableAnnotation(contextClass, constructor, matchAnnotation)
+                                != null);
             }
 
             @Override
             public boolean isFieldExposed(Field field) {
-                return fieldMatcher.matches(contextClass, field)
-                        || _MethodUtils.getInheritableAnnotation(contextClass, field, TemplateAccessible.class) != null;
+                return matchResultToIsExposedResult(
+                        fieldMatcher.matches(contextClass, field)
+                                || matchAnnotation != null
+                                && _MethodUtils.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/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/MethodMatcher.java b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/MethodMatcher.java
index ce66f4c..88711d6 100644
--- a/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/MethodMatcher.java
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/MethodMatcher.java
@@ -33,4 +33,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/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/WhitelistMemberAccessPolicy.java b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/WhitelistMemberAccessPolicy.java
index 64ef7a9..25e4801 100644
--- a/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/WhitelistMemberAccessPolicy.java
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/WhitelistMemberAccessPolicy.java
@@ -19,390 +19,34 @@
 
 package org.apache.freemarker.core.model.impl;
 
-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 org.apache.freemarker.core.model.ObjectWrapper;
-import org.apache.freemarker.core.util._ClassUtils;
-import org.apache.freemarker.core.util._NullArgumentException;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
 
 /**
- * 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 DefaultObjectWrapper} 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.
  */
-public class WhitelistMemberAccessPolicy implements MemberAccessPolicy {
-    private static final Logger LOG = LoggerFactory.getLogger(WhitelistMemberAccessPolicy.class);
-
-    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 = _ClassUtils.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 | SecurityException e) {
-                            return new MemberSelector(upperBoundClass, e, cleanedStr);
-                        }
-                    }
-                    argTypes[i] = _ClassUtils.getArrayClass(argClass, arrayDimensions);
-                }
-                try {
-                    return memberName.equals(upperBoundClass.getSimpleName())
-                            ? new MemberSelector(upperBoundClass, upperBoundClass.getConstructor(argTypes))
-                            : new MemberSelector(upperBoundClass, upperBoundClass.getMethod(memberName, argTypes));
-                } catch (NoSuchMethodException | SecurityException e) {
-                    return new MemberSelector(upperBoundClass, e, cleanedStr);
-                }
-            } else {
-                try {
-                    return new MemberSelector(upperBoundClass, upperBoundClass.getField(memberName));
-                } catch (NoSuchFieldException | 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<>(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();
-            }
-        }
-    }
-
-    @Override
-    public ClassMemberAccessPolicy forClass(final Class<?> contextClass) {
-        return new ClassMemberAccessPolicy() {
-            @Override
-            public boolean isMethodExposed(Method method) {
-                return methodMatcher.matches(contextClass, method)
-                        || _MethodUtils.getInheritableAnnotation(contextClass, method, TemplateAccessible.class) != null;
-            }
-
-            @Override
-            public boolean isConstructorExposed(Constructor<?> constructor) {
-                return constructorMatcher.matches(contextClass, constructor)
-                        || _MethodUtils.getInheritableAnnotation(contextClass, constructor, TemplateAccessible.class)
-                        != null;
-            }
-
-            @Override
-            public boolean isFieldExposed(Field field) {
-                return fieldMatcher.matches(contextClass, field)
-                        || _MethodUtils.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);
     }
 
-}
+}
\ No newline at end of file
diff --git a/freemarker-core/src/main/resources/org/apache/freemarker/core/model/impl/DefaultMemberAccessPolicy-rules b/freemarker-core/src/main/resources/org/apache/freemarker/core/model/impl/DefaultMemberAccessPolicy-rules
new file mode 100644
index 0000000..474b831
--- /dev/null
+++ b/freemarker-core/src/main/resources/org/apache/freemarker/core/model/impl/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.
+# 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/freemarker-core/src/main/resources/org/apache/freemarker/core/model/impl/unsafeMethods.properties b/freemarker-core/src/main/resources/org/apache/freemarker/core/model/impl/unsafeMethods.properties
deleted file mode 100644
index 05c1981..0000000
--- a/freemarker-core/src/main/resources/org/apache/freemarker/core/model/impl/unsafeMethods.properties
+++ /dev/null
@@ -1,98 +0,0 @@
-# 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.
-
-java.lang.Object.wait()
-java.lang.Object.wait(long)
-java.lang.Object.wait(long,int)
-java.lang.Object.notify()
-java.lang.Object.notifyAll()
-
-java.lang.Class.getClassLoader()
-java.lang.Class.newInstance()
-java.lang.Class.forName(java.lang.String)
-java.lang.Class.forName(java.lang.String,boolean,java.lang.ClassLoader)
-
-java.lang.reflect.Constructor.newInstance([Ljava.lang.Object;)
-
-java.lang.reflect.Method.invoke(java.lang.Object,[Ljava.lang.Object;)
-
-java.lang.reflect.Field.set(java.lang.Object,java.lang.Object)
-java.lang.reflect.Field.setBoolean(java.lang.Object,boolean)
-java.lang.reflect.Field.setByte(java.lang.Object,byte)
-java.lang.reflect.Field.setChar(java.lang.Object,char)
-java.lang.reflect.Field.setDouble(java.lang.Object,double)
-java.lang.reflect.Field.setFloat(java.lang.Object,float)
-java.lang.reflect.Field.setInt(java.lang.Object,int)
-java.lang.reflect.Field.setLong(java.lang.Object,long)
-java.lang.reflect.Field.setShort(java.lang.Object,short)
-
-java.lang.reflect.AccessibleObject.setAccessible([Ljava.lang.reflect.AccessibleObject;,boolean)
-java.lang.reflect.AccessibleObject.setAccessible(boolean)
-
-java.lang.Thread.destroy()
-java.lang.Thread.getContextClassLoader()
-java.lang.Thread.interrupt()
-java.lang.Thread.join()
-java.lang.Thread.join(long)
-java.lang.Thread.join(long,int)
-java.lang.Thread.resume()
-java.lang.Thread.run()
-java.lang.Thread.setContextClassLoader(java.lang.ClassLoader)
-java.lang.Thread.setDaemon(boolean)
-java.lang.Thread.setName(java.lang.String)
-java.lang.Thread.setPriority(int)
-java.lang.Thread.sleep(long)
-java.lang.Thread.sleep(long,int)
-java.lang.Thread.start()
-java.lang.Thread.stop()
-java.lang.Thread.stop(java.lang.Throwable)
-java.lang.Thread.suspend()
-
-java.lang.ThreadGroup.allowThreadSuspension(boolean)
-java.lang.ThreadGroup.destroy()
-java.lang.ThreadGroup.interrupt()
-java.lang.ThreadGroup.resume()
-java.lang.ThreadGroup.setDaemon(boolean)
-java.lang.ThreadGroup.setMaxPriority(int)
-java.lang.ThreadGroup.stop()
-java.lang.Thread.suspend()
-
-java.lang.Runtime.addShutdownHook(java.lang.Thread)
-java.lang.Runtime.exec(java.lang.String)
-java.lang.Runtime.exec([Ljava.lang.String;)
-java.lang.Runtime.exec([Ljava.lang.String;,[Ljava.lang.String;)
-java.lang.Runtime.exec([Ljava.lang.String;,[Ljava.lang.String;,java.io.File)
-java.lang.Runtime.exec(java.lang.String,[Ljava.lang.String;)
-java.lang.Runtime.exec(java.lang.String,[Ljava.lang.String;,java.io.File)
-java.lang.Runtime.exit(int)
-java.lang.Runtime.halt(int)
-java.lang.Runtime.load(java.lang.String)
-java.lang.Runtime.loadLibrary(java.lang.String)
-java.lang.Runtime.removeShutdownHook(java.lang.Thread)
-java.lang.Runtime.traceInstructions(boolean)
-java.lang.Runtime.traceMethodCalls(boolean)
-
-java.lang.System.exit(int)
-java.lang.System.load(java.lang.String)
-java.lang.System.loadLibrary(java.lang.String)
-java.lang.System.runFinalizersOnExit(boolean)
-java.lang.System.setErr(java.io.PrintStream)
-java.lang.System.setIn(java.io.InputStream)
-java.lang.System.setOut(java.io.PrintStream)
-java.lang.System.setProperties(java.util.Properties)
-java.lang.System.setProperty(java.lang.String,java.lang.String)
-java.lang.System.setSecurityManager(java.lang.SecurityManager)


Mime
View raw message