This is an automated email from the ASF dual-hosted git repository.

tkobayas pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/incubator-kie-drools.git


The following commit(s) were added to refs/heads/main by this push:
     new e1e51c1057 [incubator-kie-issues-2235] Drools null pointer shows up 
when executing rules with exists() clauses (#6585)
e1e51c1057 is described below

commit e1e51c1057b7f5cc454c23d8e9b08cc5a02639b0
Author: Toshiya Kobayashi <[email protected]>
AuthorDate: Mon Feb 9 09:45:50 2026 +0900

    [incubator-kie-issues-2235] Drools null pointer shows up when executing 
rules with exists() clauses (#6585)
---
 .../org/drools/core/reteoo/TupleToObjectNode.java  |   4 +-
 .../execmodel/SubnetworkSharingExistsTest.java     | 105 +++++++++++++++++++++
 2 files changed, 108 insertions(+), 1 deletion(-)

diff --git 
a/drools-core/src/main/java/org/drools/core/reteoo/TupleToObjectNode.java 
b/drools-core/src/main/java/org/drools/core/reteoo/TupleToObjectNode.java
index 4684e2a81b..60a8bed351 100644
--- a/drools-core/src/main/java/org/drools/core/reteoo/TupleToObjectNode.java
+++ b/drools-core/src/main/java/org/drools/core/reteoo/TupleToObjectNode.java
@@ -247,7 +247,8 @@ public class TupleToObjectNode extends ObjectSource
     }
 
     private int calculateHashCode() {
-        return this.tupleSource.hashCode() * 17 + ((this.tupleMemoryEnabled) ? 
1234 : 4321);
+        return (this.tupleSource.hashCode() * 17 + ((this.tupleMemoryEnabled) 
? 1234 : 4321)) * 31
+               + this.startTupleSource.hashCode();
     }
 
     @Override
@@ -259,6 +260,7 @@ public class TupleToObjectNode extends ObjectSource
         return ((NetworkNode) object).getType() == 
NodeTypeEnums.TupleToObjectNode && this.hashCode() == object
                 .hashCode() &&
                this.tupleSource.getId() == ((TupleToObjectNode) 
object).tupleSource.getId() &&
+               this.startTupleSource.getId() == ((TupleToObjectNode) 
object).startTupleSource.getId() &&
                this.tupleMemoryEnabled == ((TupleToObjectNode) 
object).tupleMemoryEnabled;
     }
 
diff --git 
a/drools-model/drools-model-codegen/src/test/java/org/drools/model/codegen/execmodel/SubnetworkSharingExistsTest.java
 
b/drools-model/drools-model-codegen/src/test/java/org/drools/model/codegen/execmodel/SubnetworkSharingExistsTest.java
new file mode 100644
index 0000000000..c3dc74eb59
--- /dev/null
+++ 
b/drools-model/drools-model-codegen/src/test/java/org/drools/model/codegen/execmodel/SubnetworkSharingExistsTest.java
@@ -0,0 +1,105 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.drools.model.codegen.execmodel;
+
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.MethodSource;
+import org.kie.api.runtime.KieSession;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * Reproducer for NPE in BetaNode.getStartTuple() when two rules share
+ * subnetwork nodes via exists() with overlapping patterns but different
+ * subnetwork entry points.
+ */
+public class SubnetworkSharingExistsTest extends BaseModelTest {
+
+    public static class TextField {
+
+        private String name;
+        private String value;
+
+        public TextField(String name, String value) {
+            this.name = name;
+            this.value = value;
+        }
+
+        public String getName() {
+            return name;
+        }
+
+        public String getValue() {
+            return value;
+        }
+
+        public void setValue(String value) {
+            this.value = value;
+        }
+    }
+
+    /**
+     * Rule 1 has field1 in the main path and exists(field3 && field2).
+     * Rule 2 has exists(field1 && field3 && field2).
+     * The overlapping subnetwork suffix (field3 && field2) causes the
+     * TupleToObjectNodes to be incorrectly shared, leading to NPE in
+     * BetaNode.getStartTuple() when searching the peer chain.
+     */
+    @ParameterizedTest
+    @MethodSource("parameters")
+    public void testExistsWithDifferentSubnetworkEntryPoints(RUN_TYPE runType) 
{
+        String str =
+                "import " + TextField.class.getCanonicalName() + ";\n" +
+                "rule \"Rule 1\"\n" +
+                "no-loop true\n" +
+                "    when\n" +
+                "        $outputField: TextField(name == \"outputField\")\n" +
+                "        TextField(name == \"field1\" && value == \"A\")\n" +
+                "        exists(\n" +
+                "            TextField(name == \"field3\" && value == 
\"1\")\n" +
+                "            && TextField(name == \"field2\" && value == 
\"20\")\n" +
+                "        )\n" +
+                "    then\n" +
+                "    end\n" +
+                "\n" +
+                "rule \"Rule 2\"\n" +
+                "no-loop true\n" +
+                "    when\n" +
+                "        $outputField: TextField(name == \"outputField\")\n" +
+                "        exists(\n" +
+                "            TextField(name == \"field1\" && value == 
\"A\")\n" +
+                "            && TextField(name == \"field3\" && value == 
\"1\")\n" +
+                "            && TextField(name == \"field2\" && value == 
\"20\")\n" +
+                "        )\n" +
+                "    then\n" +
+                "    end\n";
+
+        KieSession ksession = getKieSession(runType, str);
+
+        ksession.insert(new TextField("field1", "A"));
+        ksession.insert(new TextField("field2", "20"));
+        ksession.insert(new TextField("field3", "1"));
+        ksession.insert(new TextField("outputField", ""));
+
+        int fired = ksession.fireAllRules();
+        assertThat(fired).isEqualTo(2);
+
+        ksession.dispose();
+    }
+}


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to