From 875ec27a7fe9e832d735d068ac28d6da63f7216a Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 12 Feb 2026 14:37:15 +0000 Subject: [PATCH 1/4] Initial plan From 4cce891433ba8fee090061a73e5a3a374321c8b8 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 12 Feb 2026 14:40:53 +0000 Subject: [PATCH 2/4] Fix race condition in enforce() causing ArrayIndexOutOfBoundsException Co-authored-by: hsluoyz <3787410+hsluoyz@users.noreply.github.com> --- .../org/casbin/jcasbin/main/CoreEnforcer.java | 2 +- .../jcasbin/main/ConcurrentEnforcerTest.java | 81 +++++++++++++++++++ 2 files changed, 82 insertions(+), 1 deletion(-) create mode 100644 src/test/java/org/casbin/jcasbin/main/ConcurrentEnforcerTest.java diff --git a/src/main/java/org/casbin/jcasbin/main/CoreEnforcer.java b/src/main/java/org/casbin/jcasbin/main/CoreEnforcer.java index 23c7ab32..206cb7d2 100644 --- a/src/main/java/org/casbin/jcasbin/main/CoreEnforcer.java +++ b/src/main/java/org/casbin/jcasbin/main/CoreEnforcer.java @@ -623,7 +623,7 @@ private EnforceResult enforce(String matcher, Object... rvals) { policyEffects = new Effect[policyLen]; matcherResults = new float[policyLen]; - for (int i = 0; i < policy.size(); i++) { + for (int i = 0; i < policyLen; i++) { List pvals = policy.get(i); Map parameters = new HashMap<>(rvals.length + pTokens.length); getPTokens(parameters, pType, pvals, pTokens); diff --git a/src/test/java/org/casbin/jcasbin/main/ConcurrentEnforcerTest.java b/src/test/java/org/casbin/jcasbin/main/ConcurrentEnforcerTest.java new file mode 100644 index 00000000..0dde3409 --- /dev/null +++ b/src/test/java/org/casbin/jcasbin/main/ConcurrentEnforcerTest.java @@ -0,0 +1,81 @@ +// Copyright 2024 The casbin Authors. All Rights Reserved. +// +// Licensed 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.casbin.jcasbin.main; + +import org.testng.annotations.Test; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.TimeUnit; + +import static org.testng.Assert.assertFalse; +import static org.testng.Assert.assertTrue; + +/** + * Test for concurrent access to enforcer to verify race condition fixes. + */ +public class ConcurrentEnforcerTest { + + @Test + public void testConcurrentEnforceAndAddPolicy() throws InterruptedException { + // This test reproduces the race condition where policy.size() is read + // multiple times during enforcement, potentially causing ArrayIndexOutOfBoundsException + + DistributedEnforcer enforcer = new DistributedEnforcer("examples/rbac_model.conf", "examples/rbac_policy.csv"); + + int numThreads = 10; + int iterations = 100; + ExecutorService executor = Executors.newFixedThreadPool(numThreads); + CountDownLatch latch = new CountDownLatch(numThreads); + AtomicBoolean errorOccurred = new AtomicBoolean(false); + + // Half threads add policies, half enforce + for (int t = 0; t < numThreads; t++) { + final int threadId = t; + executor.submit(() -> { + try { + for (int i = 0; i < iterations; i++) { + if (threadId % 2 == 0) { + // Add and remove policies to change policy list size + String user = "user" + threadId + "_" + i; + String resource = "data" + (i % 3); + enforcer.addPolicy(user, resource, "read"); + enforcer.removePolicy(user, resource, "read"); + } else { + // Continuously enforce to trigger the race condition + try { + enforcer.enforce("alice", "data1", "read"); + } catch (ArrayIndexOutOfBoundsException e) { + errorOccurred.set(true); + System.err.println("ArrayIndexOutOfBoundsException caught: " + e.getMessage()); + } + } + } + } catch (Exception e) { + e.printStackTrace(); + } finally { + latch.countDown(); + } + }); + } + + assertTrue(latch.await(30, TimeUnit.SECONDS), "Test timeout"); + executor.shutdown(); + + // With the fix, no ArrayIndexOutOfBoundsException should occur + assertFalse(errorOccurred.get(), "ArrayIndexOutOfBoundsException occurred during concurrent access"); + } +} From 633f0b13b1d3f9636cd3cf4eb438f08876b97537 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 12 Feb 2026 14:42:58 +0000 Subject: [PATCH 3/4] Remove System.err.println from test per code review feedback Co-authored-by: hsluoyz <3787410+hsluoyz@users.noreply.github.com> --- .../java/org/casbin/jcasbin/main/ConcurrentEnforcerTest.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/test/java/org/casbin/jcasbin/main/ConcurrentEnforcerTest.java b/src/test/java/org/casbin/jcasbin/main/ConcurrentEnforcerTest.java index 0dde3409..68fefde8 100644 --- a/src/test/java/org/casbin/jcasbin/main/ConcurrentEnforcerTest.java +++ b/src/test/java/org/casbin/jcasbin/main/ConcurrentEnforcerTest.java @@ -60,7 +60,6 @@ public void testConcurrentEnforceAndAddPolicy() throws InterruptedException { enforcer.enforce("alice", "data1", "read"); } catch (ArrayIndexOutOfBoundsException e) { errorOccurred.set(true); - System.err.println("ArrayIndexOutOfBoundsException caught: " + e.getMessage()); } } } From 7297e4bb0cebd5c8f5510ba1754575b4b95a7828 Mon Sep 17 00:00:00 2001 From: Yang Luo Date: Thu, 12 Feb 2026 22:52:59 +0800 Subject: [PATCH 4/4] Delete src/test/java/org/casbin/jcasbin/main/ConcurrentEnforcerTest.java --- .../jcasbin/main/ConcurrentEnforcerTest.java | 80 ------------------- 1 file changed, 80 deletions(-) delete mode 100644 src/test/java/org/casbin/jcasbin/main/ConcurrentEnforcerTest.java diff --git a/src/test/java/org/casbin/jcasbin/main/ConcurrentEnforcerTest.java b/src/test/java/org/casbin/jcasbin/main/ConcurrentEnforcerTest.java deleted file mode 100644 index 68fefde8..00000000 --- a/src/test/java/org/casbin/jcasbin/main/ConcurrentEnforcerTest.java +++ /dev/null @@ -1,80 +0,0 @@ -// Copyright 2024 The casbin Authors. All Rights Reserved. -// -// Licensed 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.casbin.jcasbin.main; - -import org.testng.annotations.Test; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.concurrent.TimeUnit; - -import static org.testng.Assert.assertFalse; -import static org.testng.Assert.assertTrue; - -/** - * Test for concurrent access to enforcer to verify race condition fixes. - */ -public class ConcurrentEnforcerTest { - - @Test - public void testConcurrentEnforceAndAddPolicy() throws InterruptedException { - // This test reproduces the race condition where policy.size() is read - // multiple times during enforcement, potentially causing ArrayIndexOutOfBoundsException - - DistributedEnforcer enforcer = new DistributedEnforcer("examples/rbac_model.conf", "examples/rbac_policy.csv"); - - int numThreads = 10; - int iterations = 100; - ExecutorService executor = Executors.newFixedThreadPool(numThreads); - CountDownLatch latch = new CountDownLatch(numThreads); - AtomicBoolean errorOccurred = new AtomicBoolean(false); - - // Half threads add policies, half enforce - for (int t = 0; t < numThreads; t++) { - final int threadId = t; - executor.submit(() -> { - try { - for (int i = 0; i < iterations; i++) { - if (threadId % 2 == 0) { - // Add and remove policies to change policy list size - String user = "user" + threadId + "_" + i; - String resource = "data" + (i % 3); - enforcer.addPolicy(user, resource, "read"); - enforcer.removePolicy(user, resource, "read"); - } else { - // Continuously enforce to trigger the race condition - try { - enforcer.enforce("alice", "data1", "read"); - } catch (ArrayIndexOutOfBoundsException e) { - errorOccurred.set(true); - } - } - } - } catch (Exception e) { - e.printStackTrace(); - } finally { - latch.countDown(); - } - }); - } - - assertTrue(latch.await(30, TimeUnit.SECONDS), "Test timeout"); - executor.shutdown(); - - // With the fix, no ArrayIndexOutOfBoundsException should occur - assertFalse(errorOccurred.get(), "ArrayIndexOutOfBoundsException occurred during concurrent access"); - } -}