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]