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

Augmenting an existing XML Bom Tools causes java.lang.UnsupportedOperationException #571

Closed
andrew-m-leonard opened this issue Dec 12, 2024 · 7 comments
Labels
bug Something isn't working
Milestone

Comments

@andrew-m-leonard
Copy link
Contributor

andrew-m-leonard commented Dec 12, 2024

Using release 10.0.0 with the fix for #562 the tools are now being serialized correctly, however if an existing Bom containing a "single" existing Tool is Deserialized and then an attempt to add a new Tool to the Components, we get

java.lang.UnsupportedOperationException
	at java.base/java.util.AbstractList.add(AbstractList.java:153)
	at java.base/java.util.AbstractList.add(AbstractList.java:111)
	at Issue571.main(Issue571.java:69)

The problem only occurs if the existing Bom only has a "single" existing Tool, it seems if the existing Bom has two existing Tools the Deserialization then constructs a List which supports the add() operation.

Test case:

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 Issue571 {

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

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

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

            bom.setMetadata(meta);

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

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

            // Now augment Boms with Tool2 and Author2

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

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

            // Add Tool2
            bomJson.getMetadata().getToolChoice().getComponents().add(tool2);
            bomXml.getMetadata().getToolChoice().getComponents().add(tool2);

            // Add Author2
            bomJson.getMetadata().addAuthor(auth2);
            bomXml.getMetadata().addAuthor(auth2);

            // Serialize again...
            writeJSONfile(bomJson, "Issue571_SBOM_2.json");
            writeXMLfile(bomXml, "Issue571_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;
        }
    }
}
@andrew-m-leonard
Copy link
Contributor Author

@mr-zepol fyi, the fix for #562 is now generating the correct xml, but the Deserialization use case when the XML being deserialized contains only 1 existing Tool creates a non-modifiable List.

@mr-zepol
Copy link
Contributor

mr-zepol commented Dec 13, 2024

@mr-zepol fyi, the fix for #562 is now generating the correct xml, but the Deserialization use case when the XML being deserialized contains only 1 existing Tool creates a non-modifiable List.

Can you please share the SBOM you are using to test? or the one you are getting the issue with? or what part of the code you sent is causing the issue?

I am loading SBOM with tools (2) then I add a new one after being deserialized and it allows me to do so

@andrew-m-leonard
Copy link
Contributor Author

@mr-zepol apologies I had an error in the above testcase... i've Editted it and corrected it.. Here is the output and files from running it:

anleonar@anleonar-mac cyclonedx-lib % java -cp .:build/jar/cyclonedx-core-java.jar:build/jar/jackson-core.jar:build/jar/jackson-dataformat-xml.jar:build/jar/jackson-databind.jar:build/jar/jackson-annotations.jar:build/jar/json-schema-validator.jar:build/jar/commons-codec.jar:build/jar/commons-io.jar:build/jar/github-package-url.jar:build/jar/webpki.org-libext-1.00.jar:build/jar/temurin-sign-sbom.jar:build/jar/commons-collections4.jar:build/jar/commons-lang3.jar:build/jar/stax2-api.jar:build/jar/woodstox-core.jar Issue571
java.lang.UnsupportedOperationException
	at java.base/java.util.AbstractList.add(AbstractList.java:153)
	at java.base/java.util.AbstractList.add(AbstractList.java:111)
	at Issue571.main(Issue571.java:69)

Issue571_SBOM.xml :

<?xml version="1.0" encoding="UTF-8"?>
<bom serialNumber="urn:uuid:49c9de22-2638-40e8-aa7b-049c774a4bac" version="1" xmlns="http://cyclonedx.org/schema/bom/1.6">
  <metadata>
    <timestamp>2024-12-13T12:57:18Z</timestamp>
    <tools>
      <components>
        <component type="application">
          <name>TOOL 1</name>
          <version>v1</version>
        </component>
      </components>
    </tools>
    <authors>
      <author>
        <name>Author 1</name>
      </author>
    </authors>
  </metadata>
</bom>

@andrew-m-leonard
Copy link
Contributor Author

@mr-zepol

I am loading SBOM with tools (2) then I add a new one after being deserialized and it allows me to do so

Yeah this scenario works :-)
Try loading SBOM with tools (1), it then fails... I am suspecting the Deserialization when there is only 1 item in the array is creating a non-modifiable Singleton ?

@mr-zepol
Copy link
Contributor

@mr-zepol

I am loading SBOM with tools (2) then I add a new one after being deserialized and it allows me to do so

Yeah this scenario works :-) Try loading SBOM with tools (1), it then fails... I am suspecting the Deserialization when there is only 1 item in the array is creating a non-modifiable Singleton ?

PR to fix this issue #574 , you were right a non-modifiable singleton was creating when the deserializer for vulnerabilities was added

@nscuro nscuro added the bug Something isn't working label Dec 21, 2024
@nscuro
Copy link
Member

nscuro commented Dec 21, 2024

Fixed via #574.

@andrew-m-leonard You could give this a spin using the latest SNAPSHOT build: https://oss.sonatype.org/content/repositories/snapshots/org/cyclonedx/cyclonedx-core-java/10.0.1-SNAPSHOT/

@nscuro nscuro closed this as completed Dec 21, 2024
@nscuro nscuro added this to the 10.1.0 milestone Dec 21, 2024
@andrew-m-leonard
Copy link
Contributor Author

@nscuro Hi Niklas, i've just tested it, and it now passes all our tests. thank you for fixing 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

No branches or pull requests

3 participants