Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

XML ToolInformation.components and Authors serialization/deserialization for 2nd component/author generated wrong? #562

Closed
andrew-m-leonard opened this issue Dec 3, 2024 · 5 comments · Fixed by #568
Labels
bug Something isn't working
Milestone

Comments

@andrew-m-leonard
Copy link
Contributor

When adding a new Component to an existing ToolInformation.components array, eg: say existing is:

<?xml version="1.0" encoding="UTF-8"?>
<bom serialNumber="urn:uuid:7696daee-b58c-4c11-b45b-2cc8f97477ef" version="1" xmlns="http://cyclonedx.org/schema/bom/1.6">
  <metadata>
    <timestamp>2024-12-03T15:49:05Z</timestamp>
    <tools>
      <components>
        <component type="application">
          <name>MacOS Compiler</name>
          <version>clang (clang/LLVM from Xcode 15.2)</version>
        </component>
      </components>
    </tools>
    <authors>
      <author>
        <name>Adoptium Temurin</name>
      </author>
    </authors>
    <component type="framework">
      <name>Eclipse Temurin</name>
      <version>21.0.6-beta+5-202412031542</version>
      <description>Eclipse Temurin components</description>
    </component>
    <manufacture>
      <name>Eclipse Foundation</name>
      <url>https://www.eclipse.org/</url>
    </manufacture>
    <properties>
      <property name="OS version">Darwin 23.6.0</property>
      <property name="OS architecture">arm64</property>
    </properties>
  </metadata>
  <formulation>
    <formula bom-ref="CycloneDX">
      <components>
        <component type="framework">
          <name>CycloneDX jar SHAs</name>
        </component>
        <component type="framework">
          <name>CycloneDX jar versions</name>
        </component>
      </components>
    </formula>
  </formulation>
</bom>

Then the following code cannot add a new Component when there is ALREADY one in the XML list, as the backing Array is non-resizeable, this only happens for XML, JSON deserializing seems fine:

        Component tool = new Component();
        tool.setType(Component.Type.APPLICATION);
        tool.setName(toolName);
        tool.setVersion(version);

        // Create ToolInformation if not already
        ToolInformation tools = meta.getToolChoice();
        if (tools == null) {
            tools = new ToolInformation();
        }

        // Create components array if not already
        List<Component> components = tools.getComponents();
        if (components == null) {
            components = new LinkedList<Component>();
        }

        components.add(tool); // Exception: java.lang.UnsupportedOperationException
@andrew-m-leonard
Copy link
Contributor Author

andrew-m-leonard commented Dec 3, 2024

I thought I could workaround the XML deserialization problem, by always creating a new sizeable List, like this:

        // Create new components array, add existing to it
        List<Component> components = new LinkedList<Component>();
        if (tools.getComponents() != null) {
            components.addAll(tools.getComponents());
        }
        components.add(tool);
        tools.setComponents(components);

However, I then get many spurious entries in the XML:

<tools>
      <components>
        <component type="application">
          <name>BOOTJDK</name>
          <version>20.0.2+9</version>
        </component>
      </components>
      <components>
        <component/>
      </components>
      <components>
        <component/>
      </components>
      <components>
        <component/>
      </components>
      <components>
        <component type="application">
          <name>GCC</name>
          <version>10.1</version>
        </component>
      </components>
      <components>
        <component/>
      </components>
      <components>
        <component/>
      </components>
      <components>
        <component/>
      </components>
    </tools>

JSON seems fine:

"tools" : {
      "components" : [
        {
          "type" : "application",
          "name" : "BOOTJDK",
          "version" : "20.0.2+9"
        },
        {
          "type" : "application",
          "name" : "GCC",
          "version" : "10.1"
        }
      ]
    }

@andrew-m-leonard
Copy link
Contributor Author

andrew-m-leonard commented Dec 4, 2024

Seems the problem is to do with XML Deserialization and/or Serialization of Arrays, as i've found it affects "authors" list as well.
Please try this unittest that demonstrates the problem, it produces Issue562_SBOM.json/xml, then reads the file back in again and re-serializes it to Issue562_SBOM_2.json/xml.
As can be seen in the Issue562_SBOM_2.xml, the contents of ToolInformation.components and authors it's quite wrong.

import org.cyclonedx.exception.GeneratorException;
import org.cyclonedx.generators.json.BomJsonGenerator;
import org.cyclonedx.generators.xml.BomXmlGenerator;
import org.cyclonedx.model.Bom;
import org.cyclonedx.model.Component;
import org.cyclonedx.model.Metadata;
import org.cyclonedx.model.metadata.ToolInformation;
import org.cyclonedx.model.OrganizationalContact;
import org.cyclonedx.parsers.JsonParser;
import org.cyclonedx.parsers.XmlParser;
import org.cyclonedx.Version;
import java.io.FileWriter;
import java.io.FileReader;
import java.util.List;
import java.util.LinkedList;
import java.util.UUID;

public final class Issue562 {

    public static void main(final String[] args) {
        try {
            Bom bom = new Bom();
            bom.setSerialNumber("urn:uuid:" + UUID.randomUUID());

            Metadata meta = new Metadata();

            // ToolInformation test
            Component tool1 = new Component();
            tool1.setType(Component.Type.APPLICATION);
            tool1.setName("TOOL 1");
            tool1.setVersion("v1");

            Component tool2 = new Component();
            tool2.setType(Component.Type.APPLICATION);
            tool2.setName("TOOL 2");
            tool2.setVersion("v2");

            ToolInformation tools = new ToolInformation();
            List<Component> components = new LinkedList<Component>();
            components.add(tool1);
            components.add(tool2);
            tools.setComponents(components);
            meta.setToolChoice(tools);

            // Author test
            OrganizationalContact auth1 = new OrganizationalContact();
            auth1.setName("Author 1");
            meta.addAuthor(auth1);

            OrganizationalContact auth2 = new OrganizationalContact();
            auth2.setName("Author 2");
            meta.addAuthor(auth2);

            bom.setMetadata(meta);

            // Serialize...
            writeJSONfile(bom, "Issue562_SBOM.json");
            writeXMLfile(bom, "Issue562_SBOM.xml");

            // Deserialize...
            Bom bomJson = readJSONfile("Issue562_SBOM.json");
            Bom bomXml  = readXMLfile("Issue562_SBOM.xml");

            // Serialize again...
            writeJSONfile(bomJson, "Issue562_SBOM_2.json");
            writeXMLfile(bomXml, "Issue562_SBOM_2.xml");
        } catch(Exception e) {
            e.printStackTrace();
            System.exit(1);
        }
    }

    static String generateBomJson(final Bom bom) throws GeneratorException {
        BomJsonGenerator bomGen = new BomJsonGenerator(bom, Version.VERSION_16);
        String json = bomGen.toJsonString();
        return json;
    }

    static String generateBomXml(final Bom bom) throws GeneratorException {
        BomXmlGenerator bomGen = new BomXmlGenerator(bom, Version.VERSION_16);
        String xml = bomGen.toXmlString();
        return xml;
    }

    // Writes the BOM object to the specified file.
    static void writeJSONfile(final Bom bom, final String fileName) {
        FileWriter file;
        try {
            String json = generateBomJson(bom);

            file = new FileWriter(fileName);
            file.write(json);
            file.close();
        } catch (Exception e) {
            e.printStackTrace();
            System.exit(1);
        }
    }

    // Writes the BOM object to the specified XML file.
    static void writeXMLfile(final Bom bom, final String fileName) {
        FileWriter file;
        try {
            String xml = generateBomXml(bom);

            file = new FileWriter(fileName);
            file.write(xml);
            file.close();
        } catch (Exception e) {
            e.printStackTrace();
            System.exit(1);
        }
    }

    // Returns a parsed BOM object from the specified file.
    static Bom readJSONfile(final String fileName) {
        Bom bom = null;
        try {
            FileReader reader = new FileReader(fileName);
            JsonParser parser = new JsonParser();
            bom = parser.parse(reader);
        } catch (Exception e) {
            e.printStackTrace();
            System.exit(1);
        } finally {
           return bom;
        }
    }

    // Returns a parsed BOM object from the specified file.
    static Bom readXMLfile(final String fileName) {
        Bom bom = null;
        try {
            FileReader reader = new FileReader(fileName);
            XmlParser parser = new XmlParser();
            bom = parser.parse(reader);
        } catch (Exception e) {
            e.printStackTrace();
            System.exit(1);
        } finally {
           return bom;
        }
    }
}
  • First serialize: Issue562_SBOM.xml.
    (Has 2 "components" and 2 "authors" lists)
<?xml version="1.0" encoding="UTF-8"?>
<bom serialNumber="urn:uuid:52d8a234-6125-4ddb-8c18-644a82819f0e" version="1" xmlns="http://cyclonedx.org/schema/bom/1.6">
  <metadata>
    <timestamp>2024-12-04T11:10:42Z</timestamp>
    <tools>
      <components>
        <component type="application">
          <name>TOOL 1</name>
          <version>v1</version>
        </component>
      </components>
      <components>
        <component type="application">
          <name>TOOL 2</name>
          <version>v2</version>
        </component>
      </components>
    </tools>
    <authors>
      <author>
        <name>Author 1</name>
      </author>
    </authors>
    <authors>
      <author>
        <name>Author 2</name>
      </author>
    </authors>
  </metadata>
</bom>
  • Second deserialize/serialize: Issue562_SBOM_2.xml
    (Is quite messed up with lots of duplicates and some empty...)
<?xml version="1.0" encoding="UTF-8"?>
<bom serialNumber="urn:uuid:52d8a234-6125-4ddb-8c18-644a82819f0e" version="1" xmlns="http://cyclonedx.org/schema/bom/1.6">
  <metadata>
    <timestamp>2024-12-04T11:10:42Z</timestamp>
    <tools>
      <components>
        <component type="application">
          <name>TOOL 1</name>
          <version>v1</version>
        </component>
      </components>
      <components>
        <component/>
      </components>
      <components>
        <component type="application">
          <name>TOOL 2</name>
          <version>v2</version>
        </component>
      </components>
      <components>
        <component/>
      </components>
    </tools>
    <authors>
      <author/>
    </authors>
    <authors>
      <author/>
    </authors>
  </metadata>
</bom>
  • Issue562_SBOM.json and Issue562_SBOM_2.json are identical

@andrew-m-leonard andrew-m-leonard changed the title XML ToolInformation.getComponents().add(component) fails java.lang.UnsupportedOperationException on 2nd component XML ToolInformation.components and Authors serialization/deserialization for 2nd component/author generated wrong? Dec 4, 2024
@sschuberth
Copy link
Contributor

We're seeing a similar problem in ORT when adding a second component here, though getting a different errors message:

Invalid content was found starting with element '{"http://cyclonedx.org/schema/bom/1.5":components}'. One of '{"http://cyclonedx.org/schema/bom/1.5":services}' is expected.

JSON serialization indeed works.

@andrew-m-leonard
Copy link
Contributor Author

I think this issue is related to #492

@mr-zepol
Copy link
Contributor

mr-zepol commented Dec 9, 2024

@andrew-m-leonard I am looking into this

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
Development

Successfully merging a pull request may close this issue.

4 participants