RFR (JAXP) JDK-8067170: Enable security manager on JAXP unit tests

Frank Yuan frank.yuan at oracle.com
Wed Jul 27 09:27:42 UTC 2016


Hi Daniel

Would you like to have a look at the following changes before I finish all rework? 
It shows runWithAllPerm, and the handling for user.dir property in FilePolicy.java. The code like runWithTmpPermission is still
kept, please ignore them, I will remove them finally.

diff -r 1bfe60e61bad test/javax/xml/jaxp/libs/jaxp/library/FilePolicy.java
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/javax/xml/jaxp/libs/jaxp/library/FilePolicy.java	Wed Jul 27 02:23:58 2016 -0700
@@ -0,0 +1,44 @@
+/*
+ * Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package jaxp.library;
+
+import static jaxp.library.JAXPTestUtilities.getSystemProperty;
+
+import java.io.FilePermission;
+
+import org.testng.ITestContext;
+
+/**
+ * This policy can access local XML files.
+ */
+public class FilePolicy extends BasePolicy {
+
+    @Override
+    public void onStart(ITestContext arg0) {
+        JAXPPolicyManager policyManager = JAXPPolicyManager.getJAXPPolicyManager(true);
+        String userdir = getSystemProperty("user.dir");
+        policyManager.addPermission(new FilePermission(userdir + "/-", "read, write, delete"));
+        policyManager.addPermission(new FilePermission(System.getProperty("test.src") + "/-", "read"));
+        policyManager.addPermission(new FilePermission(userdir, "read"));
+    }
+}
diff -r 1bfe60e61bad test/javax/xml/jaxp/libs/jaxp/library/JAXPPolicyManager.java
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/javax/xml/jaxp/libs/jaxp/library/JAXPPolicyManager.java	Wed Jul 27 02:23:58 2016 -0700
@@ -0,0 +1,312 @@
+/*
+ * Copyright (c) 2015, 2016, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package jaxp.library;
+
+
+import java.lang.reflect.ReflectPermission;
+import java.security.CodeSource;
+import java.security.Permission;
+import java.security.PermissionCollection;
+import java.security.Permissions;
+import java.security.Policy;
+import java.security.ProtectionDomain;
+import java.security.SecurityPermission;
+import java.util.ArrayList;
+import java.util.Enumeration;
+import java.util.List;
+import java.util.PropertyPermission;
+import java.util.StringJoiner;
+
+
+/*
+ * This is a base class that every test class must extend if it needs to be run
+ * with security mode.
+ */
+public class JAXPPolicyManager {
+    /*
+     * Backing up policy.
+     */
+    private Policy policyBackup;
+
+    /*
+     * Backing up security manager.
+     */
+    private SecurityManager smBackup;
+
+    /*
+     * Current policy.
+     */
+    private TestPolicy policy = new TestPolicy();
+
+    /*
+     * JAXPPolicyManager singleton.
+     */
+    private static JAXPPolicyManager policyManager = null;
+
+    /*
+     * Install a SecurityManager along with a default Policy to allow testNG to
+     * run when there is a security manager.
+     */
+    private JAXPPolicyManager() {
+        // Backing up policy and security manager for restore
+        policyBackup = Policy.getPolicy();
+        smBackup = System.getSecurityManager();
+
+        // Set customized policy
+        setDefaultPermissions();
+        Policy.setPolicy(policy);
+        System.setSecurityManager(new SecurityManager());
+    }
+    
+    static synchronized JAXPPolicyManager getJAXPPolicyManager(boolean createIfNone) {
+        if (policyManager == null & createIfNone)
+            policyManager = new JAXPPolicyManager();
+        return policyManager;
+    }
+    
+    private void teardown() throws Exception {
+        System.setSecurityManager(smBackup);
+        Policy.setPolicy(policyBackup);
+    }
+
+    /*
+     * Restore the original Policy and SecurityManager.
+     */
+    static synchronized void teardownPolicyManager() throws Exception {
+        if (policyManager != null) {
+            policyManager.teardown();
+            policyManager = null;
+        }
+    }
+
+    /*
+     * Set default permissions, sub-class of JAXPBaseTest should override this
+     * method.
+     */
+    private void setDefaultPermissions() {
+        //Permissions to set security manager and policy
+        addPermission(new SecurityPermission("getPolicy"));
+        addPermission(new SecurityPermission("setPolicy"));
+        addPermission(new RuntimePermission("setSecurityManager"));
+        //Properties that jtreg and TestNG require
+        addPermission(new PropertyPermission("testng.show.stack.frames", "read"));
+        addPermission(new PropertyPermission("test.src", "read"));
+        addPermission(new PropertyPermission("test.classes", "read"));
+        addPermission(new PropertyPermission("dataproviderthreadcount", "read"));
+        addPermission(new PropertyPermission("experimental", "read"));
+        
+        //addPermission(new PropertyPermission("fileStringBuffer", "read"));
+        /*
+        addPermission(new RuntimePermission("getClassLoader"));
+        addPermission(new RuntimePermission("createClassLoader"));
+        addPermission(new RuntimePermission("createSecurityManager"));
+        addPermission(new RuntimePermission("modifyThread"));
+        addPermission(new PropertyPermission("*", "read, write"));
+        addPermission(new ReflectPermission("suppressAccessChecks"));
+        addPermission(new RuntimePermission("setIO"));
+        addPermission(new RuntimePermission("setContextClassLoader"));
+        addPermission(new RuntimePermission("accessDeclaredMembers"));*/
+    }
+
+    /*
+     * Add permission to the TestPolicy.
+     * 
+     * @param permission to be added.
+     */
+    void addPermission(Permission p) {
+        policy.addPermission(p);
+    }
+
+    /*
+     * Add a temporary permission in current thread context. This won't impact
+     * global policy and doesn't support permission combination.
+     * 
+     * @param permission
+     *            to add.
+     * @return index of the added permission.
+     */
+    int addTmpPermission(Permission p) {
+        return policy.addTmpPermission(p);
+    }
+
+    /*
+     * set allowAll in current thread context.
+     */
+    void setAllowAll(boolean allow) {
+        policy.setAllowAll(allow);
+    }
+
+    /*
+     * Remove a temporary permission from current thread context.
+     * 
+     * @param index to remove.
+     *
+     * @throws RuntimeException if no temporary permission list in current
+     *             thread context or no permission correlated to the index.
+     */
+    void removeTmpPermission(int index) {
+        policy.removeTmpPermission(index);
+    }
+
+
+}
+
+/*
+ * Simple Policy class that supports the required Permissions to validate the
+ * JAXP concrete classes.
+ */
+class TestPolicy extends Policy {
+    private final PermissionCollection permissions = new Permissions();
+
+    private ThreadLocal<List<Permission>> transientPermissions = new ThreadLocal<>();
+    private ThreadLocal<Boolean> allowAll = new ThreadLocal<>();
+
+    private static Policy defaultPolicy = Policy.getPolicy();
+
+    /*
+     * Add permission to this policy.
+     * 
+     * @param permission to be added.
+     */
+    void addPermission(Permission p) {
+        permissions.add(p);
+    }
+
+    /*
+     * Set all permissions. Caution: this should not called carefully unless
+     * it's really needed.
+     * 
+     * private void setAllPermissions() { permissions.add(new AllPermission());
+     * }
+     */
+
+    /*
+     * Overloaded methods from the Policy class.
+     */
+    @Override
+    public String toString() {
+        StringJoiner sj = new StringJoiner("\n", "policy: ", "");
+        Enumeration<Permission> perms = permissions.elements();
+        while (perms.hasMoreElements()) {
+            sj.add(perms.nextElement().toString());
+        }
+        return sj.toString();
+
+    }
+
+    @Override
+    public PermissionCollection getPermissions(ProtectionDomain domain) {
+        return permissions;
+    }
+
+    @Override
+    public PermissionCollection getPermissions(CodeSource codesource) {
+        return permissions;
+    }
+
+    @Override
+    public boolean implies(ProtectionDomain domain, Permission perm) {
+        if (allowAll())
+            return true;
+
+        if (defaultPolicy.implies(domain, perm))
+            return true;
+
+        if (permissions.implies(perm))
+            return true;
+        else
+            return tmpImplies(perm);
+    }
+
+    /*
+     * Add a temporary permission in current thread context. This won't impact
+     * global policy and doesn't support permission combination.
+     * 
+     * @param permission to add.
+     * @return index of the added permission.
+     */
+    int addTmpPermission(Permission p) {
+        List<Permission> tmpPermissions = transientPermissions.get();
+        if (tmpPermissions == null)
+            tmpPermissions = new ArrayList<>();
+
+        tmpPermissions.add(p);
+        transientPermissions.set(tmpPermissions);
+        return tmpPermissions.size() - 1;
+    }
+
+    /*
+     * Remove a temporary permission from current thread context.
+     * 
+     * @param index to remove.
+     *
+     * @throws RuntimeException if no temporary permission list in current
+     *             thread context or no permission correlated to the index.
+     */
+    void removeTmpPermission(int index) {
+        try {
+            List<Permission> tmpPermissions = transientPermissions.get();
+            tmpPermissions.remove(index);
+        } catch (NullPointerException | IndexOutOfBoundsException e) {
+            throw new RuntimeException("Tried to delete a non-existent temporary permission", e);
+        }
+    }
+
+    /*
+     * Checks to see if the specified permission is implied by temporary
+     * permission list in current thread context.
+     *
+     * @param permission the Permission object to compare.
+     *
+     * @return true if "permission" is implied by any permission in the
+     *         temporary permission list, false if not.
+     */
+    private boolean tmpImplies(Permission perm) {
+        List<Permission> tmpPermissions = transientPermissions.get();
+        if (tmpPermissions != null) {
+            for (Permission p : tmpPermissions) {
+                if (p.implies(perm))
+                    return true;
+            }
+        }
+        return false;
+    }
+
+    /*
+     * Checks to see if allow all permission requests in current thread context.
+     */
+    private boolean allowAll() {
+        Boolean allow = allowAll.get();
+        if (allow != null) {
+            return allow;
+        }
+        return false;
+    }
+
+    /*
+     * set allowAll in current thread context.
+     */
+    void setAllowAll(boolean allow) {
+        allowAll.set(allow);
+    }
+}
diff -r 1bfe60e61bad test/javax/xml/jaxp/libs/jaxp/library/JAXPTestUtilities.java
--- a/test/javax/xml/jaxp/libs/jaxp/library/JAXPTestUtilities.java	Mon Apr 04 14:54:38 2016 -0700
+++ b/test/javax/xml/jaxp/libs/jaxp/library/JAXPTestUtilities.java	Wed Jul 27 02:23:58 2016 -0700
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2014, 2015, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2014, 2016, Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
@@ -22,6 +22,8 @@
  */
 package jaxp.library;
 
+import static org.testng.Assert.fail;
+
 import java.io.ByteArrayInputStream;
 import java.io.File;
 import java.io.IOException;
@@ -34,12 +36,18 @@
 import java.nio.charset.UnsupportedCharsetException;
 import java.nio.file.Files;
 import java.nio.file.Paths;
+import java.security.Permission;
+import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.Optional;
 import java.util.concurrent.ConcurrentHashMap;
+import java.util.function.Supplier;
 import java.util.regex.Pattern;
 import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
 import javax.xml.parsers.DocumentBuilder;
 import javax.xml.parsers.DocumentBuilderFactory;
 import javax.xml.parsers.ParserConfigurationException;
@@ -48,7 +56,7 @@
 import javax.xml.transform.TransformerFactory;
 import javax.xml.transform.dom.DOMSource;
 import javax.xml.transform.stream.StreamResult;
-import static org.testng.Assert.fail;
+
 import org.w3c.dom.Document;
 import org.w3c.dom.Node;
 import org.xml.sax.SAXException;
@@ -74,12 +82,6 @@
     public static final String FILE_SEP = "/";
 
     /**
-     * Current test directory.
-     */
-    public static final String USER_DIR =
-            System.getProperty("user.dir", ".") + FILE_SEP;;
-
-    /**
      * A map storing every test's current test file pointer. File number should
      * be incremental and it's a thread-safe reading on this file number.
      */
@@ -89,7 +91,7 @@
     /**
      * BOM table for storing BOM header.
      */
-    private final static Map<String, byte[]> bom = new HashMap<>();
+    private final static Map<String, byte[]> bom = new HashMap();
 
     /**
      * Initialize all BOM headers.
@@ -130,8 +132,15 @@
      */
     public static boolean compareWithGold(String goldfile, String outputfile,
              Charset cs) throws IOException {
-        return Files.readAllLines(Paths.get(goldfile)).
+        boolean isSame = Files.readAllLines(Paths.get(goldfile)).
                 equals(Files.readAllLines(Paths.get(outputfile), cs));
+        if (!isSame) {
+            System.err.println("Golden file " + goldfile + " :");
+            Files.readAllLines(Paths.get(goldfile)).forEach(System.err::println);
+            System.err.println("Output file " + outputfile + " :");
+            Files.readAllLines(Paths.get(outputfile), cs).forEach(System.err::println);
+        }
+        return isSame;
     }
 
     /**
@@ -308,10 +317,10 @@
         int nextNumber = currentFileNumber.contains(clazz)
                 ? currentFileNumber.get(clazz) + 1 : 1;
         Integer i = currentFileNumber.putIfAbsent(clazz, nextNumber);
-        if (i != null && i != nextNumber) {
+        if (i != null) {
             do {
                 nextNumber = currentFileNumber.get(clazz) + 1;
-            } while (currentFileNumber.replace(clazz, nextNumber -1, nextNumber));
+            } while (!currentFileNumber.replace(clazz, nextNumber - 1, nextNumber));
         }
         return USER_DIR + clazz.getName() + nextNumber + ".out";
     }
@@ -332,4 +341,146 @@
                 toAbsolutePath().toString();
         return normalizedPath.replace("\\", FILE_SEP) + FILE_SEP;
     }
+
+
+    /**
+     * Run the RunnableWithException with creating a JAXPPolicyManager and
+     * assigning temporary permissions. It's not thread-safe to use this
+     * function.
+     * 
+     * @param r
+     *            RunnableWithException to execute
+     * @param ps
+     *            assigning permissions to add.
+     */
+    public static void tryRunWithPolicyManager(RunnableWithException r, Permission... ps) throws Exception {
+        JAXPPolicyManager policyManager = JAXPPolicyManager.getJAXPPolicyManager(true);
+        if (policyManager != null)
+            Stream.of(ps).forEach(p -> policyManager.addTmpPermission(p));
+        try {
+            r.run();
+        } finally {
+            JAXPPolicyManager.teardownPolicyManager();
+        }
+    }
+
+    /**
+     * Run the runnable with assigning temporary permissions. This won't impact
+     * global policy.
+     * 
+     * @param r
+     *            Runnable to run
+     * @param ps
+     *            assigning permissions to add.
+     */
+    public static void runWithTmpPermission(Runnable r, Permission... ps) {
+        JAXPPolicyManager policyManager = JAXPPolicyManager.getJAXPPolicyManager(false);
+        List<Integer> tmpPermissionIndexes = new ArrayList();
+        if (policyManager != null)
+            Stream.of(ps).forEach(p -> tmpPermissionIndexes.add(policyManager.addTmpPermission(p)));
+        try {
+            r.run();
+        } finally {
+            tmpPermissionIndexes.forEach(index -> policyManager.removeTmpPermission(index));
+        }
+    }
+
+    /**
+     * Run the supplier with all permissions. This won't impact global policy.
+     * 
+     * @param s
+     *            Supplier to run
+     */
+    public static <T> T runWithAllPerm(Supplier<T> s) {
+        Optional<JAXPPolicyManager> policyManager = Optional.ofNullable(JAXPPolicyManager
+                .getJAXPPolicyManager(false));
+        policyManager.ifPresent(manager -> manager.setAllowAll(true));
+        try {
+            return s.get();
+        } finally {
+            policyManager.ifPresent(manager -> manager.setAllowAll(false));
+        }
+    }
+    
+    /**
+     * Run the Runnable with all permissions. This won't impact global policy.
+     * 
+     * @param s
+     *            Supplier to run
+     */
+    public static void runWithAllPerm(Runnable r) {
+        Optional<JAXPPolicyManager> policyManager = Optional.ofNullable(JAXPPolicyManager
+                .getJAXPPolicyManager(false));
+        policyManager.ifPresent(manager -> manager.setAllowAll(true));
+        try {
+            r.run();
+        } finally {
+            policyManager.ifPresent(manager -> manager.setAllowAll(false));
+        }
+    }
+
+    /**
+     * Acquire a system property.
+     * 
+     * @param name
+     *            System property name to be acquired.
+     * @return property value
+     */
+    public static String getSystemProperty(String name) {
+        return runWithAllPerm(() -> System.getProperty(name));
+    }
+
+    /**
+     * Set a system property by given system value.
+     *
+     * @param name
+     *            System property name to be set.
+     * @param value
+     *            System property value to be set.
+     */
+    public static void setSystemProperty(String name, String value) {
+        runWithAllPerm(() -> System.setProperty(name, value));
+    }
+
+    /**
+     * Clear a system property.
+     * 
+     * @param name
+     *            System property name to be cleared.
+     */
+    public static void clearSystemProperty(String name) {
+        runWithAllPerm(() -> clearSystemProperty(name));
+    }
+
+    /**
+     * Run the RunnableWithException with assigning temporary permissions. This
+     * won't impact global policy.
+     * 
+     * @param r
+     *            RunnableWithException to execute
+     * @param ps
+     *            assigning permissions to add.
+     */
+    public static void tryRunWithTmpPermission(RunnableWithException r, Permission... ps) throws Exception {
+        JAXPPolicyManager policyManager = JAXPPolicyManager.getJAXPPolicyManager(false);
+        List<Integer> tmpPermissionIndexes = new ArrayList();
+        if (policyManager != null)
+            Stream.of(ps).forEach(p -> tmpPermissionIndexes.add(policyManager.addTmpPermission(p)));
+        try {
+            r.run();
+        } finally {
+            tmpPermissionIndexes.forEach(index -> policyManager.removeTmpPermission(index));
+        }
+    }
+
+    @FunctionalInterface
+    public interface RunnableWithException {
+        void run() throws Exception;
+    }
+
+    /**
+     * Current test directory.
+     */
+    public static final String USER_DIR = getSystemProperty("user.dir") + FILE_SEP;;
+
 }


diff -r 1bfe60e61bad test/javax/xml/jaxp/unittest/transform/Bug6490921.java
--- a/test/javax/xml/jaxp/unittest/transform/Bug6490921.java	Mon Apr 04 14:54:38 2016 -0700
+++ b/test/javax/xml/jaxp/unittest/transform/Bug6490921.java	Wed Jul 27 02:22:40 2016 -0700
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2014, 2015, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2014, 2016, Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
@@ -23,6 +23,8 @@
 
 package transform;
 
+import static jaxp.library.JAXPTestUtilities.setSystemProperty;
+
 import java.io.IOException;
 import java.io.StringReader;
 import java.io.StringWriter;
@@ -37,6 +39,7 @@
 import javax.xml.transform.stream.StreamResult;
 
 import org.testng.Assert;
+import org.testng.annotations.Listeners;
 import org.testng.annotations.Test;
 import org.xml.sax.InputSource;
 import org.xml.sax.SAXException;
@@ -46,6 +49,7 @@
  * @bug 6490921
  * @summary Test property org.xml.sax.driver is always applied in transformer API.
  */
+ at Listeners({jaxp.library.BasePolicy.class})
 public class Bug6490921 {
 
     public static class ReaderStub extends XMLFilterImpl {
@@ -71,7 +75,7 @@
     public void test01() {
         String xml = "<?xml version='1.0'?><root/>";
         ReaderStub.used = false;
-        System.setProperty("org.xml.sax.driver", "");
+        setSystemProperty("org.xml.sax.driver", "");
 
         // Don't set 'org.xml.sax.driver' here, just use default
         try {
@@ -91,7 +95,7 @@
     public void test02() {
         String xml = "<?xml version='1.0'?><root/>";
         ReaderStub.used = false;
-        System.setProperty("org.xml.sax.driver", ReaderStub.class.getName());
+        setSystemProperty("org.xml.sax.driver", ReaderStub.class.getName());
         try {
             TransformerFactory transFactory = TransformerFactory.newInstance();
             Transformer transformer = transFactory.newTransformer();
@@ -111,7 +115,7 @@
                 + "   <xsl:template match='/'>Hello World!</xsl:template>\n" + "</xsl:stylesheet>\n";
 
         ReaderStub.used = false;
-        System.setProperty("org.xml.sax.driver", ReaderStub.class.getName());
+        setSystemProperty("org.xml.sax.driver", ReaderStub.class.getName());
         try {
             TransformerFactory transFactory = TransformerFactory.newInstance();
             if (transFactory.getFeature(SAXTransformerFactory.FEATURE) == false) {

Thanks
Frank

-----Original Message-----
From: Daniel Fuchs [mailto:daniel.fuchs at oracle.com] 
Sent: Tuesday, July 26, 2016 3:46 PM
To: Frank Yuan; 'huizhe wang'
Cc: 'Amy Lu'; 'core-libs-dev'
Subject: Re: RFR (JAXP) JDK-8067170: Enable security manager on JAXP unit tests

On 26/07/16 04:24, Frank Yuan wrote:
> Thank you very much for your suggestions! Now I fully understand the rule(at least I think so :P)
> I will use a runWithAllPerm block surrounding the user setup code as Daniel's way. Btw, Daniel, ThreadLocal should not need Atomic
> any more, correct?
>

Hi Frank,

runWithAllPerm is another way to do it.
It uses a ThreadLocal<Permissions>, right?

I agree it's adequate. Just be careful of what might
happen if you run runWithAllPerm inside runWithPermissions,
or runWithPermissions(runnable, a,b,c) with a runnable that
later calls runWithPermission(runnable2, a, d, e) further down
the road.

At the moment I'm not sure whether your code will work correctly
in the presence of such nested invocation (maybe it does),
but because it seems to be index based it's not immediately
obvious (I'm not asking you to change it - just to verify and
confirm that it's something you have taken into account, and
that you're confident that it works).


Best regards,

-- daniel



More information about the core-libs-dev mailing list