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();
