[Jython-checkins] jython: Fixed #2101 and #1996. This required to rewrite parts of PyType.createAllSlots.

stefan.richthofer jython-checkins at python.org
Fri Mar 3 04:33:12 EST 2017


https://hg.python.org/jython/rev/4884658aa644
changeset:   8041:4884658aa644
user:        Stefan Richthofer <stefan.richthofer at gmx.de>
date:        Fri Mar 03 10:32:43 2017 +0100
summary:
  Fixed #2101 and #1996. This required to rewrite parts of PyType.createAllSlots.

files:
  Lib/test/test_slots_jy.py       |   44 ++++++-
  NEWS                            |    5 +-
  src/org/python/core/PyType.java |  134 +++++++++++++++++--
  3 files changed, 164 insertions(+), 19 deletions(-)


diff --git a/Lib/test/test_slots_jy.py b/Lib/test/test_slots_jy.py
--- a/Lib/test/test_slots_jy.py
+++ b/Lib/test/test_slots_jy.py
@@ -229,11 +229,53 @@
         self.assertIn("__weakref__", dir(self.make_class(HashMap, "__weakref__")()))
 
 
+class MultiInheritanceSlotsTestCase(unittest.TestCase):
+
+    def test_diamond_multi_inheritance_second_branch(self):
+        # see issue bugs.jython.org/issue2101
+        # also related: bugs.jython.org/issue1996
+        result = []
+
+        class A(object):
+            def method(self):
+                pass
+
+        class B(A):
+            __slots__ = ('b')
+            def method(self):
+                result.append('B.method begin')
+                super(B, self).method()
+                self.b = 'b'
+                result.append('B.method end')
+
+        class C(A):
+            def method(self):
+                result.append('C.method begin')
+                super(C, self).method()
+                self.c = 'c'
+                result.append('C.method end')
+
+        class D1(B, C):
+            def method(self): super(D1, self).method()
+
+        class D2(C, B):
+            def method(self): super(D2, self).method()
+
+        D1().method()
+        self.assertEqual(result,
+                ['B.method begin', 'C.method begin', 'C.method end', 'B.method end'])
+        result = []
+        D2().method()
+        self.assertEqual(result,
+                ['C.method begin', 'B.method begin', 'B.method end', 'C.method end'])
+
+
 def test_main():
     test_support.run_unittest(SlottedTestCase,
                               SlottedWithDictTestCase,
                               SlottedWithWeakrefTestCase,
-                              SpecialSlotsBaseTestCase)
+                              SpecialSlotsBaseTestCase,
+                              MultiInheritanceSlotsTestCase)
 
 
 if __name__ == '__main__':
diff --git a/NEWS b/NEWS
--- a/NEWS
+++ b/NEWS
@@ -4,6 +4,8 @@
 
 Jython 2.7.1rc1
   Bugs fixed
+    - [ 1996 ] Core slots array out of bounds with multiple inheritance
+    - [ 2101 ] Diamond-style multiple inheritance fails when using __slots__ on the second branch
     - [ 2552 ] installing scandir via pip fails (breaks e.g. installing pathlib2 via pip)
     - [ 2534 ] os.getlogin() returns a wrong user or returns an exception
     - [ 2553 ] sys.getwindowsversion not implemented (breaks pathlib on Windows)
@@ -34,8 +36,7 @@
     - [ 2446 ] Support SNI for SSL/TLS client sockets
     - [ 2455 ] Java classes in packages with __init__.py not found
     - [ 2481 ] Update urllib2.py from 2.7.11
-    - [ 2514 ] Jython Class.__subclasses__() does not match Python output
-               (not in load order)
+    - [ 2514 ] Jython Class.__subclasses__() does not match Python output (not in load order)
     - [ 2413 ] ElementTree.write doesn't close files if used with invalid encoding
     - [ 2443 ] java.util.Map derived classes lack iterkeys, itervalues methods
     - [ 2516 ] _get_open_ssl_key_manager tries to validate that the private and
diff --git a/src/org/python/core/PyType.java b/src/org/python/core/PyType.java
--- a/src/org/python/core/PyType.java
+++ b/src/org/python/core/PyType.java
@@ -8,6 +8,9 @@
 import java.util.Iterator;
 import java.util.List;
 import java.util.Set;
+import java.util.ArrayList;
+import java.util.LinkedHashSet;
+import java.util.IdentityHashMap;
 import java.util.concurrent.ConcurrentMap;
 import java.util.concurrent.atomic.AtomicReferenceArray;
 
@@ -90,9 +93,12 @@
     /** MethodCacheEntry version tag. */
     private volatile Object versionTag = new Object();
 
-    /** The number of __slots__ defined. */
+    /** The number of __slots__ defined by this type + bases. */
     private int numSlots;
 
+    /** The number of __slots__ defined by this type itself. */
+    private int ownSlots = 0;
+
     private transient ReferenceQueue<PyType> subclasses_refq = new ReferenceQueue<PyType>();
     private Set<WeakReference<PyType>> subclasses = Generic.linkedHashSet();
 
@@ -218,25 +224,105 @@
     }
 
     /**
+     * Used internally by {@link #createAllSlots()}.
+     * Builds a naive pseudo mro used to collect all slot names relevant for this type.
+     * 
+     * @param tp type to be investigated
+     * @param dest list collecting all ancestors
+     * @param slotsMap map linking each type to its slots
+     * @return position of first ancestor that is not equal to or ancestor of primary base
+     */
+    private static int findSlottedAncestors(PyType tp, List<PyType> dest,
+            IdentityHashMap<PyType, PyObject> slotsMap) {
+        int baseEnd = 0;
+        if (tp.base != null && tp.base.numSlots > 0 && !slotsMap.containsKey(tp.base)) {
+            findSlottedAncestors(tp.base, dest, slotsMap);
+        }
+        baseEnd = dest.size();
+        PyObject slots = tp.dict.__finditem__("__slots__");
+        if (slots != null) {
+            dest.add(tp); // to keep track of order
+            slotsMap.put(tp, slots);
+        }
+        if (tp.bases.length > 1) {
+            for (PyObject base: tp.bases) {
+                if (base == tp.base || !(base instanceof PyType) || ((PyType) base).numSlots == 0
+                        || slotsMap.containsKey((PyType) base)) {
+                    continue;
+                }
+                findSlottedAncestors((PyType) base, dest, slotsMap);
+            }
+        }
+        return baseEnd;
+    }
+
+    /**
+     * Used internally by {@link #createAllSlots()}.
+     * Adds all names in {@code slots} to {@code dest}.
+     *
+     * @param slots names to be added as slots
+     * @param dest set collecting all slots
+     */
+    private static void insertSlots(PyObject slots, Set<String> dest) {
+        if (slots instanceof PyString) {
+            slots = new PyTuple(slots);
+        }
+        // Check for valid slot names and create them.
+        for (PyObject slot: slots.asIterable()) {
+            String slotName = confirmIdentifier(slot);
+            if (slotName.equals("__dict__") || slotName.equals("__weakref__")) {
+                continue;
+            }
+            dest.add(slotName);
+        }
+    }
+
+    /**
      * Create all slots and related descriptors.
      *
      * @param mayAddDict whether a __dict__ descriptor is allowed on this type
      * @param mayAddWeak whether a __weakref__ descriptor is allowed on this type
      */
     private void createAllSlots(boolean mayAddDict, boolean mayAddWeak) {
-        numSlots = base.numSlots;
+        List<PyType> slottedAncestors = new ArrayList<>(base.mro.length+(bases.length-1)*3+1);
+        IdentityHashMap<PyType, PyObject> slotsMap =
+                new IdentityHashMap<>(slottedAncestors.size());
+        /* Here we would need the mro to search for slots (also in secondary bases) properly,
+           but mro hasn't been set up yet. So we quickly (?) build a pseudo mro sufficient to
+           find all slots. */
+        int baseEnd = findSlottedAncestors(this, slottedAncestors, slotsMap);
+        // baseEnd is the first position of an ancestor not equal to or ancestor of primary base
+        int slots_tmp = 0; // used for various purpose, first to accumulate maximal slot count
+        for (PyType anc: slottedAncestors) {
+            slots_tmp += anc.numSlots;
+        }
+        /* In allSlots we collect slots of primary base first, then of this type,
+           then of secondary bases.
+           At any time we prevent it from containing __dict__ or __weakref__. */
+        
+        // we know the required capacity, so the set likely won't be resized
+        LinkedHashSet<String> allSlots = new LinkedHashSet<>(2*slots_tmp);
+        if (baseEnd > 0) {
+            for (int i = 0; i < baseEnd; ++i) {
+                insertSlots(slotsMap.get(slottedAncestors.get(i)), allSlots);
+            }
+        }
+        assert allSlots.size() == base.numSlots;
+
         boolean wantDict = false;
         boolean wantWeak = false;
         PyObject slots = dict.__finditem__("__slots__");
-
+        ownSlots = 0; // to keep track of slots defined by this type itself for isSolidBase
+        /* from now on, slots_tmp stores position where other ancestors than primary base
+           begin (points to this type if it defines own slots) */
         if (slots == null) {
             wantDict = mayAddDict;
             wantWeak = mayAddWeak;
+            slots_tmp = baseEnd;
         } else {
             if (slots instanceof PyString) {
                 slots = new PyTuple(slots);
             }
-
             // Check for valid slot names and create them. Handle two special cases
             for (PyObject slot : slots.asIterable()) {
                 String slotName = confirmIdentifier(slot);
@@ -264,16 +350,13 @@
                         continue;
                     }
                 }
-
-                slotName = mangleName(name, slotName);
-                if (dict.__finditem__(slotName) == null) {
-                    dict.__setitem__(slotName, new PySlot(this, slotName, numSlots++));
+                if (allSlots.add(slotName)) {
+                    ++ownSlots;
                 }
             }
-
-            // Secondary bases may provide weakrefs or dict
-            if (bases.length > 1
-                && ((mayAddDict && !wantDict) || (mayAddWeak && !wantWeak))) {
+            if (bases.length > 1 &&
+                    ((mayAddDict && !wantDict) || (mayAddWeak && !wantWeak))) {
+                // Secondary bases may provide weakrefs or dict
                 for (PyObject base : bases) {
                     if (base == this.base) {
                         // Skip primary base
@@ -291,7 +374,7 @@
                         break;
                     }
 
-                    PyType baseType = (PyType)base;
+                    PyType baseType = (PyType) base;
                     if (mayAddDict && !wantDict && baseType.needs_userdict) {
                         wantDict = true;
                     }
@@ -304,7 +387,28 @@
                     }
                 }
             }
+            slots_tmp = baseEnd + 1;
         }
+        for (int i = slots_tmp; i < slottedAncestors.size(); ++i) {
+            insertSlots(slotsMap.get(slottedAncestors.get(i)), allSlots);
+        }
+        numSlots = allSlots.size();
+        int slotPos = 0;
+        Iterator<String> slotIter = allSlots.iterator();
+        // skip slot names belonging to primary base (i.e. first base.numSlots ones)
+        for (; slotPos < base.numSlots; ++slotPos) {
+            slotIter.next();
+        }
+        while (slotIter.hasNext()) {
+            String slotName = slotIter.next();
+            slotName = mangleName(name, slotName);
+            if (dict.__finditem__(slotName) == null) {
+                dict.__setitem__(slotName, new PySlot(this, slotName, slotPos++));
+            } else {
+                --numSlots;
+            }
+        }
+        assert slotPos == numSlots;
 
         if (wantDict) {
             createDictSlot();
@@ -1127,9 +1231,7 @@
     }
 
     private static boolean isSolidBase(PyType type) {
-        return type.underlying_class != null ||
-                (type.numSlots - (type.base != null ? type.base.numSlots : 0) != 0 &&
-                !type.needs_userdict);
+        return type.underlying_class != null || (type.ownSlots != 0 && !type.needs_userdict);
     }
 
     /**

-- 
Repository URL: https://hg.python.org/jython


More information about the Jython-checkins mailing list