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

coheigea pushed a commit to branch coheigea/dv
in repository https://gitbox.apache.org/repos/asf/cxf-xjc-utils.git

commit 6b0f52764af5befe3694a14caa332843bf35aac1
Author: Colm O hEigeartaigh <[email protected]>
AuthorDate: Thu Oct 15 14:35:40 2026 +0100

    Avoid recursive field initializers in DefaultValuePlugin
---
 dv-test/pom.xml                                    | 20 ++++++
 .../org/apache/cxf/xjc/dv/RecursiveTypeTest.java   | 63 +++++++++++++++++++
 .../resources/schemas/configuration/recursive.xsd  | 63 +++++++++++++++++++
 .../org/apache/cxf/xjc/dv/DefaultValuePlugin.java  | 73 +++++++++++++++++++++-
 4 files changed, 216 insertions(+), 3 deletions(-)

diff --git a/dv-test/pom.xml b/dv-test/pom.xml
index d80a2f0..ec7f5d9 100644
--- a/dv-test/pom.xml
+++ b/dv-test/pom.xml
@@ -102,6 +102,26 @@
                             </extensions>
                         </configuration>
                     </execution>
+                    <execution>
+                        <id>recursive</id>
+                        <goals>
+                            <goal>xsdtojava-tests</goal>
+                        </goals>
+                        <configuration>
+                            <xsdOptions>
+                                <xsdOption>
+                                    <extension>true</extension>
+                                    
<xsd>${basedir}/src/test/resources/schemas/configuration/recursive.xsd</xsd>
+                                    <extensionArgs>
+                                        <arg>-Xdv:optional</arg>
+                                    </extensionArgs>
+                                </xsdOption>
+                            </xsdOptions>
+                            <extensions>
+                                
<extension>org.apache.cxf.xjcplugins:cxf-xjc-dv:${project.version}</extension>
+                            </extensions>
+                        </configuration>
+                    </execution>
                 </executions>
             </plugin>
         </plugins>
diff --git a/dv-test/src/test/java/org/apache/cxf/xjc/dv/RecursiveTypeTest.java 
b/dv-test/src/test/java/org/apache/cxf/xjc/dv/RecursiveTypeTest.java
new file mode 100644
index 0000000..7679d25
--- /dev/null
+++ b/dv-test/src/test/java/org/apache/cxf/xjc/dv/RecursiveTypeTest.java
@@ -0,0 +1,63 @@
+/**
+ * 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.apache.cxf.xjc.dv;
+
+import org.apache.cxf.configuration.foo_simple.Not;
+import org.apache.cxf.configuration.foo_simple.Query;
+import org.apache.cxf.configuration.recursive.Base;
+import org.apache.cxf.configuration.recursive.Derived;
+import org.apache.cxf.configuration.recursive.Holder;
+import org.apache.cxf.configuration.recursive.Node;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+/**
+ * Field initializers must not construct the owning class again, which would
+ * recurse until StackOverflowError.
+ */
+public class RecursiveTypeTest extends Assert {
+
+    @Test
+    public void testRequiredMutualReference() {
+        // query -> not -> query / not
+        assertNull(new Query().getNot());
+        assertNull(new Not().getQuery());
+        assertNull(new Not().getNot());
+    }
+
+    @Test
+    public void testOptionalSelfReference() {
+        Node node = new Node();
+        assertNull(node.getNext());
+        assertEquals("x", node.getLabel());
+    }
+
+    @Test
+    public void testSubclassReference() {
+        assertNull(new Base().getDerived());
+        assertNull(new Derived().getDerived());
+    }
+
+    @Test
+    public void testNonRecursiveReferenceIsInitialized() {
+        assertNotNull(new Holder().getLeaf());
+    }
+}
diff --git a/dv-test/src/test/resources/schemas/configuration/recursive.xsd 
b/dv-test/src/test/resources/schemas/configuration/recursive.xsd
new file mode 100644
index 0000000..fbc7bba
--- /dev/null
+++ b/dv-test/src/test/resources/schemas/configuration/recursive.xsd
@@ -0,0 +1,63 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  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.
+-->
+<!--
+  Types whose complex-type field initializers would construct the owning
+  class again; compiled with -Xdv:optional.
+-->
+<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"; 
xmlns:tns="http://cxf.apache.org/configuration/recursive";
+    targetNamespace="http://cxf.apache.org/configuration/recursive"; 
elementFormDefault="qualified">
+
+    <!-- optional reference to its own type, which contains a default value -->
+    <xs:complexType name="node">
+        <xs:sequence>
+            <xs:element name="label" type="xs:string" default="x" 
minOccurs="0"/>
+            <xs:element name="next" type="tns:node" minOccurs="0"/>
+        </xs:sequence>
+    </xs:complexType>
+
+    <!-- a subclass constructs its base class -->
+    <xs:complexType name="base">
+        <xs:sequence>
+            <xs:element name="derived" type="tns:derived"/>
+        </xs:sequence>
+    </xs:complexType>
+    <xs:complexType name="derived">
+        <xs:complexContent>
+            <xs:extension base="tns:base">
+                <xs:sequence>
+                    <xs:element name="value" type="xs:int"/>
+                </xs:sequence>
+            </xs:extension>
+        </xs:complexContent>
+    </xs:complexType>
+
+    <!-- not recursive, so still initialized -->
+    <xs:complexType name="holder">
+        <xs:sequence>
+            <xs:element name="leaf" type="tns:leaf"/>
+        </xs:sequence>
+    </xs:complexType>
+    <xs:complexType name="leaf">
+        <xs:sequence>
+            <xs:element name="value" type="xs:int" default="1"/>
+        </xs:sequence>
+    </xs:complexType>
+
+</xs:schema>
diff --git a/dv/src/main/java/org/apache/cxf/xjc/dv/DefaultValuePlugin.java 
b/dv/src/main/java/org/apache/cxf/xjc/dv/DefaultValuePlugin.java
index 6a5698a..6464705 100644
--- a/dv/src/main/java/org/apache/cxf/xjc/dv/DefaultValuePlugin.java
+++ b/dv/src/main/java/org/apache/cxf/xjc/dv/DefaultValuePlugin.java
@@ -21,9 +21,12 @@ package org.apache.cxf.xjc.dv;
 
 import java.io.IOException;
 import java.lang.reflect.Field;
+import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
+import java.util.Map;
 import java.util.Set;
 import java.util.logging.Level;
 import java.util.logging.Logger;
@@ -119,6 +122,68 @@ public class DefaultValuePlugin {
         return ret;
     }
 
+    /**
+     * A field to be initialized with a new instance of its (complex) type.
+     */
+    private static final class PendingInit {
+        final JDefinedClass owner;
+        final JFieldVar var;
+        final JType type;
+
+        PendingInit(JDefinedClass owner, JFieldVar var, JType type) {
+            this.owner = owner;
+            this.var = var;
+            this.type = type;
+        }
+    }
+
+    /*
+     * Initialize the fields with "new Type()", except where constructing Type 
would,
+     * through further initializers or its superclasses, construct the owning 
class
+     * again, e.g. a type with a required (or, with -Xdv:optional, an 
optional) element
+     * of its own type. Such an initializer would recurse until 
StackOverflowError.
+     */
+    private void initComplexTypeFields(List<PendingInit> pendingInits) {
+        Map<JType, List<JType>> constructs = new HashMap<>();
+        for (PendingInit p : pendingInits) {
+            constructs.computeIfAbsent(p.owner, k -> new 
ArrayList<>()).add(p.type);
+        }
+        for (PendingInit p : pendingInits) {
+            if (constructs(constructs, p.type, p.owner, new HashSet<>())) {
+                LOG.warning("Not initializing " + p.owner.fullName() + "." + 
p.var.name()
+                            + " as constructing " + p.type.fullName() + " 
would recurse");
+            } else {
+                p.var.init(JExpr._new(p.type));
+            }
+        }
+    }
+
+    // whether constructing "type" runs the field initializers of "target"
+    private static boolean constructs(Map<JType, List<JType>> constructs, 
JType type, JType target,
+                                      Set<JType> seen) {
+        if (type == target) {
+            return true;
+        }
+        if (!seen.add(type)) {
+            return false;
+        }
+        if (type instanceof JDefinedClass) {
+            JClass superClass = ((JDefinedClass)type)._extends();
+            if (superClass != null && constructs(constructs, superClass, 
target, seen)) {
+                return true;
+            }
+        }
+        List<JType> types = constructs.get(type);
+        if (types != null) {
+            for (JType t : types) {
+                if (constructs(constructs, t, target, seen)) {
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+
     private boolean isAbstract(Outline outline, FieldOutline field) {
         for (ClassOutline classOutline : outline.getClasses()) {
             if (classOutline.implClass == field.getRawType() 
@@ -179,6 +244,7 @@ public class DefaultValuePlugin {
             return true;
         }
         LOG.fine("Running default value plugin.");
+        List<PendingInit> pendingInits = new ArrayList<>();
         for (ClassOutline co : outline.getClasses()) {
             for (FieldOutline f : co.getDeclaredFields()) {
 
@@ -220,10 +286,10 @@ public class DefaultValuePlugin {
                         if (rawType instanceof JClass) {
                             final JClass jclazz = (JClass) rawType;
                             if (!jclazz.isAbstract() && !jclazz.isInterface()) 
{
-                                var.init(JExpr._new(rawType));
+                                pendingInits.add(new PendingInit(co.implClass, 
var, rawType));
                             }
                         } else {
-                            var.init(JExpr._new(rawType));
+                            pendingInits.add(new PendingInit(co.implClass, 
var, rawType));
                         }
                     }
                 }
@@ -266,7 +332,8 @@ public class DefaultValuePlugin {
                 }
             }
         }
-        
+        initComplexTypeFields(pendingInits);
+
         for (PackageOutline po :outline.getAllPackageContexts()) {
             //also fixup some unecessary casts
             JDefinedClass cls = po.objectFactoryGenerator().getObjectFactory();

Reply via email to