8148886: SEGV in sun.java2d.marlin.Renderer._endRendering
authorlbourges
Tue, 23 Feb 2016 22:07:27 +0100
changeset 36458 25786a73a5fc
parent 36457 2daf75025304
child 36459 a34194491737
8148886: SEGV in sun.java2d.marlin.Renderer._endRendering Summary: handle reentrancy in both AAShapePipe and MarlinRenderingEngine using new sun.java2d.ReentrantContextProvider implementations Reviewed-by: flar, prr
jdk/src/java.desktop/share/classes/sun/java2d/ReentrantContext.java
jdk/src/java.desktop/share/classes/sun/java2d/ReentrantContextProvider.java
jdk/src/java.desktop/share/classes/sun/java2d/ReentrantContextProviderCLQ.java
jdk/src/java.desktop/share/classes/sun/java2d/ReentrantContextProviderTL.java
jdk/src/java.desktop/share/classes/sun/java2d/marlin/ByteArrayCache.java
jdk/src/java.desktop/share/classes/sun/java2d/marlin/FloatArrayCache.java
jdk/src/java.desktop/share/classes/sun/java2d/marlin/IntArrayCache.java
jdk/src/java.desktop/share/classes/sun/java2d/marlin/MarlinCache.java
jdk/src/java.desktop/share/classes/sun/java2d/marlin/MarlinRenderingEngine.java
jdk/src/java.desktop/share/classes/sun/java2d/marlin/RendererContext.java
jdk/src/java.desktop/share/classes/sun/java2d/marlin/Version.java
jdk/src/java.desktop/share/classes/sun/java2d/pipe/AAShapePipe.java
jdk/test/sun/java2d/marlin/CrashPaintTest.java
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/java.desktop/share/classes/sun/java2d/ReentrantContext.java	Tue Feb 23 22:07:27 2016 +0100
@@ -0,0 +1,43 @@
+/*
+ * 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.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * 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 sun.java2d;
+
+import java.lang.ref.Reference;
+
+/**
+ * ReentrantContext is a base class to hold thread-local data supporting
+ * reentrancy in either a ThreadLocal or a ConcurrentLinkedQueue
+ *
+ * @see ReentrantContextProvider
+ */
+public class ReentrantContext {
+    // usage stored as a byte
+    byte usage = ReentrantContextProvider.USAGE_TL_INACTIVE;
+    /*
+     * Reference to this instance (hard, soft or weak).
+     * @see ReentrantContextProvider#refType
+     */
+    Reference<? extends ReentrantContext> reference = null;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/java.desktop/share/classes/sun/java2d/ReentrantContextProvider.java	Tue Feb 23 22:07:27 2016 +0100
@@ -0,0 +1,169 @@
+/*
+ * 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.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * 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 sun.java2d;
+
+import java.lang.ref.Reference;
+import java.lang.ref.SoftReference;
+import java.lang.ref.WeakReference;
+
+/**
+ * This abstract ReentrantContextProvider helper class manages the creation,
+ * storage, and retrieval of concrete ReentrantContext instances which can be
+ * subclassed to hold cached contextual data.
+ *
+ * It supports reentrancy as every call to acquire() provides a new unique context
+ * instance that must later be returned for reuse by a call to release(ctx)
+ * (typically in a try/finally block).
+ *
+ * It has a couple of abstract implementations which store references in a queue
+ * and/or thread-local storage.
+ * The Providers can be configured to hold ReentrantContext instances in memory
+ * using hard, soft or weak references.
+ *
+ * The acquire() and release() methods are used to retrieve and return the contexts.
+ *
+ * The {@code newContext()} method remains abstract in all implementations and
+ * must be provided by the module to create a new subclass of ReentrantContext
+ * with the appropriate contextual data in it.
+ *
+ * Sample Usage:
+ * - create a subclass ReentrantContextImpl to hold the thread state:
+ *
+ * static final class ReentrantContextImpl extends ReentrantContext {
+ *     // specific cached data
+ * }
+ *
+ * - create the appropriate ReentrantContextProvider:
+ *
+ * private static final ReentrantContextProvider<ReentrantContextImpl> contextProvider =
+ *     new ReentrantContextProviderTL<ReentrantContextImpl>(ReentrantContextProvider.REF_WEAK)
+ *     {
+ *         @Override
+ *         protected ReentrantContextImpl newContext() {
+ *             return new ReentrantContextImpl();
+ *         }
+ *     };
+ * ...
+ * void someMethod() {
+ *     ReentrantContextImpl ctx = contextProvider.acquire();
+ *     try {
+ *         // use the context
+ *     } finally {
+ *         contextProvider.release(ctx);
+ *     }
+ * }
+ *
+ * @param <K> ReentrantContext subclass
+ *
+ * @see ReentrantContext
+ */
+public abstract class ReentrantContextProvider<K extends ReentrantContext>
+{
+    // thread-local storage: inactive
+    static final byte USAGE_TL_INACTIVE = 0;
+    // thread-local storage: in use
+    static final byte USAGE_TL_IN_USE = 1;
+    // CLQ storage
+    static final byte USAGE_CLQ = 2;
+
+    // hard reference
+    public static final int REF_HARD = 0;
+    // soft reference
+    public static final int REF_SOFT = 1;
+    // weak reference
+    public static final int REF_WEAK = 2;
+
+    /* members */
+    // internal reference type
+    private final int refType;
+
+    /**
+     * Create a new ReentrantContext provider using the given reference type
+     * among hard, soft or weak
+     *
+     * @param refType reference type
+     */
+    protected ReentrantContextProvider(final int refType) {
+        this.refType = refType;
+    }
+
+    /**
+     * Create a new ReentrantContext instance
+     *
+     * @return new ReentrantContext instance
+     */
+    protected abstract K newContext();
+
+    /**
+     * Give a ReentrantContext instance for the current thread
+     *
+     * @return ReentrantContext instance
+     */
+    public abstract K acquire();
+
+    /**
+     * Restore the given ReentrantContext instance for reuse
+     *
+     * @param ctx ReentrantContext instance
+     */
+    public abstract void release(K ctx);
+
+    @SuppressWarnings("unchecked")
+    protected final Reference<K> getOrCreateReference(final K ctx) {
+        if (ctx.reference == null) {
+            // Create the reference:
+            switch (refType) {
+                case REF_HARD:
+                    ctx.reference = new HardReference<K>(ctx);
+                    break;
+                case REF_SOFT:
+                    ctx.reference = new SoftReference<K>(ctx);
+                    break;
+                default:
+                case REF_WEAK:
+                    ctx.reference = new WeakReference<K>(ctx);
+                    break;
+            }
+        }
+        return (Reference<K>) ctx.reference;
+    }
+
+    /* Missing HardReference implementation */
+    static final class HardReference<V> extends WeakReference<V> {
+        // kept strong reference:
+        private final V strongRef;
+
+        HardReference(final V referent) {
+            // no referent needed for the parent WeakReference:
+            super(null);
+            this.strongRef = referent;
+        }
+
+        @Override
+        public V get() {
+            return strongRef;
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/java.desktop/share/classes/sun/java2d/ReentrantContextProviderCLQ.java	Tue Feb 23 22:07:27 2016 +0100
@@ -0,0 +1,89 @@
+/*
+ * 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.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * 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 sun.java2d;
+
+import java.lang.ref.Reference;
+import java.util.concurrent.ConcurrentLinkedQueue;
+
+/**
+ * This ReentrantContextProvider implementation uses one ConcurrentLinkedQueue
+ * to store all ReentrantContext instances (thread and its child contexts)
+ *
+ * Note: this implementation keeps less contexts in memory depending on the
+ * concurrent active threads in contrary to a ThreadLocal provider. However,
+ * it is slower in highly concurrent workloads.
+ *
+ * @param <K> ReentrantContext subclass
+ */
+public abstract class ReentrantContextProviderCLQ<K extends ReentrantContext>
+    extends ReentrantContextProvider<K>
+{
+    // ReentrantContext queue to store all contexts
+    private final ConcurrentLinkedQueue<Reference<K>> ctxQueue
+        = new ConcurrentLinkedQueue<Reference<K>>();
+
+    /**
+     * Create a new ReentrantContext provider using the given reference type
+     * among hard, soft or weak based using a ConcurrentLinkedQueue storage
+     *
+     * @param refType reference type
+     */
+    public ReentrantContextProviderCLQ(final int refType) {
+        super(refType);
+    }
+
+    /**
+     * Give a ReentrantContext instance for the current thread
+     *
+     * @return ReentrantContext instance
+     */
+    @Override
+    public final K acquire() {
+        K ctx = null;
+        // Drain queue if all referent are null:
+        Reference<K> ref = null;
+        while ((ctx == null) && ((ref = ctxQueue.poll()) != null)) {
+            ctx = ref.get();
+        }
+        if (ctx == null) {
+            // create a new ReentrantContext if none is available
+            ctx = newContext();
+            ctx.usage = USAGE_CLQ;
+        }
+        return ctx;
+    }
+
+    /**
+     * Restore the given ReentrantContext instance for reuse
+     *
+     * @param ctx ReentrantContext instance
+     */
+    @Override
+    public final void release(final K ctx) {
+        if (ctx.usage == USAGE_CLQ) {
+            ctxQueue.offer(getOrCreateReference(ctx));
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/java.desktop/share/classes/sun/java2d/ReentrantContextProviderTL.java	Tue Feb 23 22:07:27 2016 +0100
@@ -0,0 +1,123 @@
+/*
+ * 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.  Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * 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 sun.java2d;
+
+import java.lang.ref.Reference;
+
+/**
+* This ReentrantContextProvider implementation uses a ThreadLocal to hold
+ * the first ReentrantContext per thread and a ReentrantContextProviderCLQ to
+ * store child ReentrantContext instances needed during recursion.
+ *
+ * Note: this implementation may keep up to one context in memory per thread.
+ * Child contexts for recursive uses are stored in the queue using a WEAK
+ * reference by default unless specified in the 2 argument constructor.
+ *
+ * @param <K> ReentrantContext subclass
+ */
+public abstract class ReentrantContextProviderTL<K extends ReentrantContext>
+    extends ReentrantContextProvider<K>
+{
+    // Thread-local storage:
+    private final ThreadLocal<Reference<K>> ctxTL
+        = new ThreadLocal<Reference<K>>();
+
+    // ReentrantContext CLQ provider for child contexts:
+    private final ReentrantContextProviderCLQ<K> ctxProviderCLQ;
+
+    /**
+     * Create a new ReentrantContext provider using the given reference type
+     * among hard, soft or weak.
+     * It uses weak reference for the child contexts.
+     *
+     * @param refType reference type
+     */
+    public ReentrantContextProviderTL(final int refType) {
+        this(refType, REF_WEAK);
+    }
+
+    /**
+     * Create a new ReentrantContext provider using the given reference types
+     * among hard, soft or weak
+     *
+     * @param refTypeTL reference type used by ThreadLocal
+     * @param refTypeCLQ reference type used by ReentrantContextProviderCLQ
+     */
+    public ReentrantContextProviderTL(final int refTypeTL, final int refTypeCLQ)
+    {
+        super(refTypeTL);
+
+        final ReentrantContextProviderTL<K> parent = this;
+
+        this.ctxProviderCLQ = new ReentrantContextProviderCLQ<K>(refTypeCLQ) {
+            @Override
+            protected K newContext() {
+                return parent.newContext();
+            }
+        };
+    }
+
+    /**
+     * Give a ReentrantContext instance for the current thread
+     *
+     * @return ReentrantContext instance
+     */
+    @Override
+    public final K acquire() {
+        K ctx = null;
+        final Reference<K> ref = ctxTL.get();
+        if (ref != null) {
+            ctx = ref.get();
+        }
+        if (ctx == null) {
+            // create a new ReentrantContext if none is available
+            ctx = newContext();
+            // update thread local reference:
+            ctxTL.set(getOrCreateReference(ctx));
+        }
+        // Check reentrance:
+        if (ctx.usage == USAGE_TL_INACTIVE) {
+           ctx.usage = USAGE_TL_IN_USE;
+        } else {
+            // get or create another ReentrantContext from CLQ provider:
+            ctx = ctxProviderCLQ.acquire();
+        }
+        return ctx;
+    }
+
+    /**
+     * Restore the given ReentrantContext instance for reuse
+     *
+     * @param ctx ReentrantContext instance
+     */
+    @Override
+    public final void release(final K ctx) {
+        if (ctx.usage == USAGE_TL_IN_USE) {
+           ctx.usage = USAGE_TL_INACTIVE;
+        } else {
+            ctxProviderCLQ.release(ctx);
+        }
+    }
+}
--- a/jdk/src/java.desktop/share/classes/sun/java2d/marlin/ByteArrayCache.java	Tue Feb 23 09:11:19 2016 -0800
+++ b/jdk/src/java.desktop/share/classes/sun/java2d/marlin/ByteArrayCache.java	Tue Feb 23 22:07:27 2016 +0100
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
+ * 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
@@ -126,7 +126,7 @@
         }
 
         if (doChecks) {
-            check(array, 0, array.length, value);
+            check(array, fromIndex, toIndex, value);
         }
     }
 
@@ -135,9 +135,10 @@
     {
         if (doChecks) {
             // check zero on full array:
-            for (int i = fromIndex; i < toIndex; i++) {
+            for (int i = 0; i < array.length; i++) {
                 if (array[i] != value) {
-                    logException("Invalid array value at " + i + "\n"
+                    logException("Invalid value at: " + i + " = " + array[i]
+                            + " from: " + fromIndex + " to: " + toIndex + "\n"
                             + Arrays.toString(array), new Throwable());
 
                     // ensure array is correctly filled:
--- a/jdk/src/java.desktop/share/classes/sun/java2d/marlin/FloatArrayCache.java	Tue Feb 23 09:11:19 2016 -0800
+++ b/jdk/src/java.desktop/share/classes/sun/java2d/marlin/FloatArrayCache.java	Tue Feb 23 22:07:27 2016 +0100
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
+ * 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
@@ -127,7 +127,7 @@
         }
 
         if (doChecks) {
-            check(array, 0, array.length, value);
+            check(array, fromIndex, toIndex, value);
         }
     }
 
@@ -136,9 +136,10 @@
     {
         if (doChecks) {
             // check zero on full array:
-            for (int i = fromIndex; i < toIndex; i++) {
+            for (int i = 0; i < array.length; i++) {
                 if (array[i] != value) {
-                    logException("Invalid array value at " + i + "\n"
+                    logException("Invalid value at: " + i + " = " + array[i]
+                            + " from: " + fromIndex + " to: " + toIndex + "\n"
                             + Arrays.toString(array), new Throwable());
 
                     // ensure array is correctly filled:
--- a/jdk/src/java.desktop/share/classes/sun/java2d/marlin/IntArrayCache.java	Tue Feb 23 09:11:19 2016 -0800
+++ b/jdk/src/java.desktop/share/classes/sun/java2d/marlin/IntArrayCache.java	Tue Feb 23 22:07:27 2016 +0100
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
+ * 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
@@ -126,7 +126,7 @@
         }
 
         if (doChecks) {
-            check(array, 0, array.length, value);
+            check(array, fromIndex, toIndex, value);
         }
     }
 
@@ -135,9 +135,10 @@
     {
         if (doChecks) {
             // check zero on full array:
-            for (int i = fromIndex; i < toIndex; i++) {
+            for (int i = 0; i < array.length; i++) {
                 if (array[i] != value) {
-                    logException("Invalid array value at " + i + "\n"
+                    logException("Invalid value at: " + i + " = " + array[i]
+                            + " from: " + fromIndex + " to: " + toIndex + "\n"
                             + Arrays.toString(array), new Throwable());
 
                     // ensure array is correctly filled:
--- a/jdk/src/java.desktop/share/classes/sun/java2d/marlin/MarlinCache.java	Tue Feb 23 09:11:19 2016 -0800
+++ b/jdk/src/java.desktop/share/classes/sun/java2d/marlin/MarlinCache.java	Tue Feb 23 22:07:27 2016 +0100
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2007, 2015, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2007, 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
@@ -590,8 +590,8 @@
             alphaRow[to + 1] = 0;
         }
         if (doChecks) {
-            IntArrayCache.check(blkFlags, 0, blkFlags.length, 0);
-            IntArrayCache.check(alphaRow, 0, alphaRow.length, 0);
+            IntArrayCache.check(blkFlags, blkW, blkE, 0);
+            IntArrayCache.check(alphaRow, from, px1 - bboxX0, 0);
         }
 
         if (doMonitors) {
--- a/jdk/src/java.desktop/share/classes/sun/java2d/marlin/MarlinRenderingEngine.java	Tue Feb 23 09:11:19 2016 -0800
+++ b/jdk/src/java.desktop/share/classes/sun/java2d/marlin/MarlinRenderingEngine.java	Tue Feb 23 22:07:27 2016 +0100
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2007, 2015, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2007, 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
@@ -30,11 +30,12 @@
 import java.awt.geom.AffineTransform;
 import java.awt.geom.Path2D;
 import java.awt.geom.PathIterator;
-import java.lang.ref.Reference;
 import java.security.AccessController;
-import java.util.concurrent.ConcurrentLinkedQueue;
 import static sun.java2d.marlin.MarlinUtils.logInfo;
 import sun.awt.geom.PathConsumer2D;
+import sun.java2d.ReentrantContextProvider;
+import sun.java2d.ReentrantContextProviderCLQ;
+import sun.java2d.ReentrantContextProviderTL;
 import sun.java2d.pipe.AATileGenerator;
 import sun.java2d.pipe.Region;
 import sun.java2d.pipe.RenderingEngine;
@@ -882,46 +883,50 @@
     // use ThreadLocal or ConcurrentLinkedQueue to get one RendererContext
     private static final boolean useThreadLocal;
 
-    // hard reference
-    static final int REF_HARD = 0;
-    // soft reference
-    static final int REF_SOFT = 1;
-    // weak reference
-    static final int REF_WEAK = 2;
-
     // reference type stored in either TL or CLQ
     static final int REF_TYPE;
 
     // Per-thread RendererContext
-    private static final ThreadLocal<Object> rdrCtxThreadLocal;
-    // RendererContext queue when ThreadLocal is disabled
-    private static final ConcurrentLinkedQueue<Object> rdrCtxQueue;
+    private static final ReentrantContextProvider<RendererContext> rdrCtxProvider;
 
     // Static initializer to use TL or CLQ mode
     static {
-        // CLQ mode by default:
         useThreadLocal = MarlinProperties.isUseThreadLocal();
-        rdrCtxThreadLocal = (useThreadLocal) ? new ThreadLocal<Object>()
-                                             : null;
-        rdrCtxQueue = (!useThreadLocal) ? new ConcurrentLinkedQueue<Object>()
-                                        : null;
 
         // Soft reference by default:
-        String refType = AccessController.doPrivileged(
+        final String refType = AccessController.doPrivileged(
                             new GetPropertyAction("sun.java2d.renderer.useRef",
                             "soft"));
         switch (refType) {
             default:
             case "soft":
-                REF_TYPE = REF_SOFT;
+                REF_TYPE = ReentrantContextProvider.REF_SOFT;
                 break;
             case "weak":
-                REF_TYPE = REF_WEAK;
+                REF_TYPE = ReentrantContextProvider.REF_WEAK;
                 break;
             case "hard":
-                REF_TYPE = REF_HARD;
+                REF_TYPE = ReentrantContextProvider.REF_HARD;
                 break;
         }
+
+        if (useThreadLocal) {
+            rdrCtxProvider = new ReentrantContextProviderTL<RendererContext>(REF_TYPE)
+                {
+                    @Override
+                    protected RendererContext newContext() {
+                        return RendererContext.createContext();
+                    }
+                };
+        } else {
+            rdrCtxProvider = new ReentrantContextProviderCLQ<RendererContext>(REF_TYPE)
+                {
+                    @Override
+                    protected RendererContext newContext() {
+                        return RendererContext.createContext();
+                    }
+                };
+        }
     }
 
     private static boolean settingsLogged = !enableLogs;
@@ -936,13 +941,13 @@
         String refType;
         switch (REF_TYPE) {
             default:
-            case REF_HARD:
+            case ReentrantContextProvider.REF_HARD:
                 refType = "hard";
                 break;
-            case REF_SOFT:
+            case ReentrantContextProvider.REF_SOFT:
                 refType = "soft";
                 break;
-            case REF_WEAK:
+            case ReentrantContextProvider.REF_WEAK:
                 refType = "weak";
                 break;
         }
@@ -1025,22 +1030,7 @@
      */
     @SuppressWarnings({"unchecked"})
     static RendererContext getRendererContext() {
-        RendererContext rdrCtx = null;
-        final Object ref = (useThreadLocal) ? rdrCtxThreadLocal.get()
-                           : rdrCtxQueue.poll();
-        if (ref != null) {
-            // resolve reference:
-            rdrCtx = (REF_TYPE == REF_HARD) ? ((RendererContext) ref)
-                     : ((Reference<RendererContext>) ref).get();
-        }
-        // create a new RendererContext if none is available
-        if (rdrCtx == null) {
-            rdrCtx = RendererContext.createContext();
-            if (useThreadLocal) {
-                // update thread local reference:
-                rdrCtxThreadLocal.set(rdrCtx.reference);
-            }
-        }
+        final RendererContext rdrCtx = rdrCtxProvider.acquire();
         if (doMonitors) {
             RendererContext.stats.mon_pre_getAATileGenerator.start();
         }
@@ -1057,8 +1047,6 @@
         if (doMonitors) {
             RendererContext.stats.mon_pre_getAATileGenerator.stop();
         }
-        if (!useThreadLocal) {
-            rdrCtxQueue.offer(rdrCtx.reference);
-        }
+        rdrCtxProvider.release(rdrCtx);
     }
 }
--- a/jdk/src/java.desktop/share/classes/sun/java2d/marlin/RendererContext.java	Tue Feb 23 09:11:19 2016 -0800
+++ b/jdk/src/java.desktop/share/classes/sun/java2d/marlin/RendererContext.java	Tue Feb 23 22:07:27 2016 +0100
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
+ * 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
@@ -26,9 +26,10 @@
 package sun.java2d.marlin;
 
 import java.awt.geom.Path2D;
-import java.lang.ref.SoftReference;
 import java.lang.ref.WeakReference;
 import java.util.concurrent.atomic.AtomicInteger;
+import sun.java2d.ReentrantContext;
+import sun.java2d.ReentrantContextProvider;
 import static sun.java2d.marlin.ArrayCache.*;
 import sun.java2d.marlin.MarlinRenderingEngine.NormalizingPathIterator;
 import static sun.java2d.marlin.MarlinUtils.logInfo;
@@ -36,7 +37,7 @@
 /**
  * This class is a renderer context dedicated to a single thread
  */
-final class RendererContext implements MarlinConst {
+final class RendererContext extends ReentrantContext implements MarlinConst {
 
     // RendererContext creation counter
     private static final AtomicInteger contextCount = new AtomicInteger(1);
@@ -45,7 +46,7 @@
                                        ? RendererStats.getInstance(): null;
 
     private static final boolean USE_CACHE_HARD_REF = doStats
-        || (MarlinRenderingEngine.REF_TYPE == MarlinRenderingEngine.REF_WEAK);
+        || (MarlinRenderingEngine.REF_TYPE == ReentrantContextProvider.REF_WEAK);
 
     /**
      * Create a new renderer context
@@ -55,6 +56,7 @@
     static RendererContext createContext() {
         final RendererContext newCtx = new RendererContext("ctx"
                     + Integer.toString(contextCount.getAndIncrement()));
+
         if (RendererContext.stats != null) {
             RendererContext.stats.allContexts.add(newCtx);
         }
@@ -63,11 +65,6 @@
 
     // context name (debugging purposes)
     final String name;
-    /*
-     * Reference to this instance (hard, soft or weak).
-     * @see MarlinRenderingEngine#REF_TYPE
-     */
-    final Object reference;
     // Smallest object used as Cleaner's parent reference
     final Object cleanerObj = new Object();
     // dirty flag indicating an exception occured during pipeline in pathTo()
@@ -101,7 +98,7 @@
     /**
      * Constructor
      *
-     * @param name
+     * @param name context name (debugging)
      */
     RendererContext(final String name) {
         if (logCreateContext) {
@@ -124,20 +121,6 @@
 
         stroker = new Stroker(this);
         dasher = new Dasher(this);
-
-        // Create the reference to this instance (hard, soft or weak):
-        switch (MarlinRenderingEngine.REF_TYPE) {
-            default:
-            case MarlinRenderingEngine.REF_HARD:
-                reference = this;
-                break;
-            case MarlinRenderingEngine.REF_SOFT:
-                reference = new SoftReference<RendererContext>(this);
-                break;
-            case MarlinRenderingEngine.REF_WEAK:
-                reference = new WeakReference<RendererContext>(this);
-                break;
-        }
     }
 
     /**
--- a/jdk/src/java.desktop/share/classes/sun/java2d/marlin/Version.java	Tue Feb 23 09:11:19 2016 -0800
+++ b/jdk/src/java.desktop/share/classes/sun/java2d/marlin/Version.java	Tue Feb 23 22:07:27 2016 +0100
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
+ * 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
@@ -27,7 +27,7 @@
 
 public final class Version {
 
-    private static final String version = "marlin-0.7.3-Unsafe-OpenJDK";
+    private static final String version = "marlin-0.7.3.2-Unsafe-OpenJDK";
 
     public static String getVersion() {
         return version;
--- a/jdk/src/java.desktop/share/classes/sun/java2d/pipe/AAShapePipe.java	Tue Feb 23 09:11:19 2016 -0800
+++ b/jdk/src/java.desktop/share/classes/sun/java2d/pipe/AAShapePipe.java	Tue Feb 23 22:07:27 2016 +0100
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 1997, 2015, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 1997, 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
@@ -28,7 +28,11 @@
 import java.awt.Rectangle;
 import java.awt.Shape;
 import java.awt.geom.Rectangle2D;
+import java.util.concurrent.ConcurrentLinkedQueue;
 import sun.awt.SunHints;
+import sun.java2d.ReentrantContext;
+import sun.java2d.ReentrantContextProvider;
+import sun.java2d.ReentrantContextProviderTL;
 import sun.java2d.SunGraphics2D;
 
 /**
@@ -38,28 +42,31 @@
  * This class sets up the Generator and computes the alpha tiles
  * and then passes them on to a CompositePipe object for painting.
  */
-public class AAShapePipe
+public final class AAShapePipe
     implements ShapeDrawPipe, ParallelogramPipe
 {
-    static RenderingEngine renderengine = RenderingEngine.getInstance();
+    static final RenderingEngine renderengine = RenderingEngine.getInstance();
 
     // Per-thread TileState (~1K very small so do not use any Weak Reference)
-    private static final ThreadLocal<TileState> tileStateThreadLocal =
-            new ThreadLocal<TileState>() {
-        @Override
-        protected TileState initialValue() {
-            return new TileState();
-        }
-    };
+    private static final ReentrantContextProvider<TileState> tileStateProvider =
+            new ReentrantContextProviderTL<TileState>(
+                    ReentrantContextProvider.REF_HARD)
+            {
+                @Override
+                protected TileState newContext() {
+                    return new TileState();
+                }
+            };
 
-    CompositePipe outpipe;
+    final CompositePipe outpipe;
 
     public AAShapePipe(CompositePipe pipe) {
         outpipe = pipe;
     }
 
+    @Override
     public void draw(SunGraphics2D sg, Shape s) {
-        BasicStroke bs;
+        final BasicStroke bs;
 
         if (sg.stroke instanceof BasicStroke) {
             bs = (BasicStroke) sg.stroke;
@@ -71,10 +78,12 @@
         renderPath(sg, s, bs);
     }
 
+    @Override
     public void fill(SunGraphics2D sg, Shape s) {
         renderPath(sg, s, null);
     }
 
+    @Override
     public void fillParallelogram(SunGraphics2D sg,
                                   double ux1, double uy1,
                                   double ux2, double uy2,
@@ -82,21 +91,23 @@
                                   double dx1, double dy1,
                                   double dx2, double dy2)
     {
-        Region clip = sg.getCompClip();
-        final TileState ts = tileStateThreadLocal.get();
-        final int[] abox = ts.abox;
+        final TileState ts = tileStateProvider.acquire();
+        try {
+            final int[] abox = ts.abox;
 
-        AATileGenerator aatg =
-            renderengine.getAATileGenerator(x, y, dx1, dy1, dx2, dy2, 0, 0,
-                                            clip, abox);
-        if (aatg == null) {
-            // Nothing to render
-            return;
+            final AATileGenerator aatg =
+                renderengine.getAATileGenerator(x, y, dx1, dy1, dx2, dy2, 0, 0,
+                                                sg.getCompClip(), abox);
+            if (aatg != null) {
+                renderTiles(sg, ts.computeBBox(ux1, uy1, ux2, uy2),
+                            aatg, abox, ts);
+            }
+        } finally {
+            tileStateProvider.release(ts);
         }
-
-        renderTiles(sg, ts.computeBBox(ux1, uy1, ux2, uy2), aatg, abox, ts);
     }
 
+    @Override
     public void drawParallelogram(SunGraphics2D sg,
                                   double ux1, double uy1,
                                   double ux2, double uy2,
@@ -105,52 +116,61 @@
                                   double dx2, double dy2,
                                   double lw1, double lw2)
     {
-        Region clip = sg.getCompClip();
-        final TileState ts = tileStateThreadLocal.get();
-        final int[] abox = ts.abox;
+        final TileState ts = tileStateProvider.acquire();
+        try {
+            final int[] abox = ts.abox;
 
-        AATileGenerator aatg =
-            renderengine.getAATileGenerator(x, y, dx1, dy1, dx2, dy2, lw1, lw2,
-                                            clip, abox);
-        if (aatg == null) {
-            // Nothing to render
-            return;
+            final AATileGenerator aatg =
+                renderengine.getAATileGenerator(x, y, dx1, dy1, dx2, dy2, lw1,
+                                                lw2, sg.getCompClip(), abox);
+            if (aatg != null) {
+                // Note that bbox is of the original shape, not the wide path.
+                // This is appropriate for handing to Paint methods...
+                renderTiles(sg, ts.computeBBox(ux1, uy1, ux2, uy2),
+                            aatg, abox, ts);
+            }
+        } finally {
+            tileStateProvider.release(ts);
         }
-
-        // Note that bbox is of the original shape, not the wide path.
-        // This is appropriate for handing to Paint methods...
-        renderTiles(sg, ts.computeBBox(ux1, uy1, ux2, uy2), aatg, abox, ts);
     }
 
     public void renderPath(SunGraphics2D sg, Shape s, BasicStroke bs) {
-        boolean adjust = (bs != null &&
+        final boolean adjust = (bs != null &&
                           sg.strokeHint != SunHints.INTVAL_STROKE_PURE);
-        boolean thin = (sg.strokeState <= SunGraphics2D.STROKE_THINDASHED);
+        final boolean thin = (sg.strokeState <= SunGraphics2D.STROKE_THINDASHED);
 
-        Region clip = sg.getCompClip();
-        final TileState ts = tileStateThreadLocal.get();
-        final int[] abox = ts.abox;
+        final TileState ts = tileStateProvider.acquire();
+        try {
+            final int[] abox = ts.abox;
 
-        AATileGenerator aatg =
-            renderengine.getAATileGenerator(s, sg.transform, clip,
-                                            bs, thin, adjust, abox);
-        if (aatg == null) {
-            // Nothing to render
-            return;
+            final AATileGenerator aatg =
+                renderengine.getAATileGenerator(s, sg.transform, sg.getCompClip(),
+                                                bs, thin, adjust, abox);
+            if (aatg != null) {
+                renderTiles(sg, s, aatg, abox, ts);
+            }
+        } finally {
+            tileStateProvider.release(ts);
         }
-
-        renderTiles(sg, s, aatg, abox, ts);
     }
 
     public void renderTiles(SunGraphics2D sg, Shape s,
-                            AATileGenerator aatg, int abox[], TileState ts)
+                            final AATileGenerator aatg,
+                            final int[] abox, final TileState ts)
     {
         Object context = null;
         try {
+            // reentrance: outpipe may also use AAShapePipe:
             context = outpipe.startSequence(sg, s,
                                             ts.computeDevBox(abox),
                                             abox);
 
+            // copy of int[] abox as local variables for performance:
+            final int x0 = abox[0];
+            final int y0 = abox[1];
+            final int x1 = abox[2];
+            final int y1 = abox[3];
+
             final int tw = aatg.getTileWidth();
             final int th = aatg.getTileHeight();
 
@@ -158,16 +178,15 @@
             final byte[] alpha = ts.getAlphaTile(tw * th);
             byte[] atile;
 
-            for (int y = abox[1]; y < abox[3]; y += th) {
-                int h = Math.min(th, abox[3] - y);
+            for (int y = y0; y < y1; y += th) {
+                final int h = Math.min(th, y1 - y);
 
-                for (int x = abox[0]; x < abox[2]; x += tw) {
-                    int w = Math.min(tw, abox[2] - x);
+                for (int x = x0; x < x1; x += tw) {
+                    final int w = Math.min(tw, x1 - x);
 
-                    int a = aatg.getTypicalAlpha();
-                    if (a == 0x00 ||
-                        outpipe.needTile(context, x, y, w, h) == false)
-                    {
+                    final int a = aatg.getTypicalAlpha();
+
+                    if (a == 0x00 || !outpipe.needTile(context, x, y, w, h)) {
                         aatg.nextTile();
                         outpipe.skipTile(context, x, y);
                         continue;
@@ -180,8 +199,7 @@
                         aatg.getAlpha(alpha, 0, tw);
                     }
 
-                    outpipe.renderPathTile(context, atile, 0, tw,
-                                           x, y, w, h);
+                    outpipe.renderPathTile(context, atile, 0, tw, x, y, w, h);
                 }
             }
         } finally {
@@ -193,7 +211,7 @@
     }
 
     // Tile state used by AAShapePipe
-    static final class TileState {
+    static final class TileState extends ReentrantContext {
         // cached tile (32 x 32 tile by default)
         private byte[] theTile = new byte[32 * 32];
         // dirty aabox array
@@ -240,5 +258,4 @@
             return box;
         }
     }
-
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/sun/java2d/marlin/CrashPaintTest.java	Tue Feb 23 22:07:27 2016 +0100
@@ -0,0 +1,205 @@
+/*
+ * 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.
+ */
+
+import java.awt.Color;
+import java.awt.Graphics2D;
+import java.awt.Paint;
+import java.awt.PaintContext;
+import java.awt.Rectangle;
+import java.awt.RenderingHints;
+import java.awt.TexturePaint;
+import java.awt.geom.AffineTransform;
+import java.awt.geom.Ellipse2D;
+import java.awt.geom.Rectangle2D;
+import java.awt.image.BufferedImage;
+import java.awt.image.ColorModel;
+import java.awt.image.Raster;
+import java.io.File;
+import java.io.IOException;
+import java.util.Locale;
+import java.util.logging.Handler;
+import java.util.logging.LogRecord;
+import java.util.logging.Logger;
+import javax.imageio.ImageIO;
+
+/**
+ * @test
+ * @bug 8148886
+ * @summary Verifies that Marlin supports reentrant operations (ThreadLocal)
+ * like in custom Paint or custom Composite
+ * @run main CrashPaintTest
+ */
+public class CrashPaintTest {
+
+    static final boolean SAVE_IMAGE = false;
+
+    public static void main(String argv[]) {
+        Locale.setDefault(Locale.US);
+
+        // initialize j.u.l Looger:
+        final Logger log = Logger.getLogger("sun.java2d.marlin");
+        log.addHandler(new Handler() {
+            @Override
+            public void publish(LogRecord record) {
+                Throwable th = record.getThrown();
+                // detect any Throwable:
+                if (th != null) {
+                    System.out.println("Test failed:\n" + record.getMessage());
+                    th.printStackTrace(System.out);
+
+                    throw new RuntimeException("Test failed: ", th);
+                }
+            }
+
+            @Override
+            public void flush() {
+            }
+
+            @Override
+            public void close() throws SecurityException {
+            }
+        });
+
+        // enable Marlin logging & internal checks:
+        System.setProperty("sun.java2d.renderer.log", "true");
+        System.setProperty("sun.java2d.renderer.useLogger", "true");
+        System.setProperty("sun.java2d.renderer.doChecks", "true");
+
+        // Force using thread-local storage:
+        System.setProperty("sun.java2d.renderer.useThreadLocal", "true");
+        // Force smaller pixelsize to force using array caches:
+        System.setProperty("sun.java2d.renderer.pixelsize", "256");
+
+        final int width = 300;
+        final int height = 300;
+
+        final BufferedImage image = new BufferedImage(width, height,
+                BufferedImage.TYPE_INT_ARGB);
+
+        final Graphics2D g2d = (Graphics2D) image.getGraphics();
+        try {
+            g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
+                    RenderingHints.VALUE_ANTIALIAS_ON);
+
+            g2d.setBackground(Color.WHITE);
+            g2d.clearRect(0, 0, width, height);
+
+            final Ellipse2D.Double ellipse
+                = new Ellipse2D.Double(0, 0, width, height);
+
+            final Paint paint = new CustomPaint(100);
+
+            for (int i = 0; i < 20; i++) {
+                final long start = System.nanoTime();
+                g2d.setPaint(paint);
+                g2d.fill(ellipse);
+
+                g2d.setColor(Color.GREEN);
+                g2d.draw(ellipse);
+
+                final long time = System.nanoTime() - start;
+                System.out.println("paint: duration= " + (1e-6 * time) + " ms.");
+            }
+
+            if (SAVE_IMAGE) {
+                try {
+                    final File file = new File("CrashPaintTest.png");
+                    System.out.println("Writing file: "
+                            + file.getAbsolutePath());
+                    ImageIO.write(image, "PNG", file);
+                } catch (IOException ex) {
+                    System.out.println("Writing file failure:");
+                    ex.printStackTrace();
+                }
+            }
+
+            // Check image on few pixels:
+            final Raster raster = image.getData();
+
+            // 170, 175 = blue
+            checkPixel(raster, 170, 175, Color.BLUE.getRGB());
+            // 50, 50 = blue
+            checkPixel(raster, 50, 50, Color.BLUE.getRGB());
+
+            // 190, 110 = pink
+            checkPixel(raster, 190, 110, Color.PINK.getRGB());
+            // 280, 210 = pink
+            checkPixel(raster, 280, 210, Color.PINK.getRGB());
+
+        } finally {
+            g2d.dispose();
+        }
+    }
+
+    private static void checkPixel(final Raster raster,
+                                   final int x, final int y,
+                                   final int expected) {
+
+        final int[] rgb = (int[]) raster.getDataElements(x, y, null);
+
+        if (rgb[0] != expected) {
+            throw new IllegalStateException("bad pixel at (" + x + ", " + y
+                + ") = " + rgb[0] + " expected: " + expected);
+        }
+    }
+
+    private static class CustomPaint extends TexturePaint {
+        private int size;
+
+        CustomPaint(final int size) {
+            super(new BufferedImage(size, size,
+                    BufferedImage.TYPE_INT_ARGB),
+                    new Rectangle2D.Double(0, 0, size, size)
+            );
+            this.size = size;
+        }
+
+        @Override
+        public PaintContext createContext(ColorModel cm,
+                                          Rectangle deviceBounds,
+                                          Rectangle2D userBounds,
+                                          AffineTransform at,
+                                          RenderingHints hints) {
+
+            // Fill bufferedImage using
+            final Graphics2D g2d = (Graphics2D) getImage().getGraphics();
+            try {
+                g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
+                        RenderingHints.VALUE_ANTIALIAS_ON);
+                g2d.setBackground(Color.PINK);
+                g2d.clearRect(0, 0, size, size);
+
+                g2d.setColor(Color.BLUE);
+                g2d.drawRect(0, 0, size, size);
+
+                g2d.fillOval(size / 10, size / 10,
+                             size * 8 / 10, size * 8 / 10);
+
+            } finally {
+                g2d.dispose();
+            }
+
+            return super.createContext(cm, deviceBounds, userBounds, at, hints);
+        }
+    }
+}