clone()-Methode ohne super.clone()

Beschreibung

clone()-Methode ohne super.clone() ist ein Programmierfehler in Java, bei dem eine Klasse die clone()-Methode implementiert, aber beim Erstellen des geklonten Objekts super.clone() nicht aufruft. Gemäß der Java-Klonkonvention sollten alle clone()-Implementierungen das neue Objekt durch Aufruf von super.clone() erhalten. Dies stellt sicher, dass die Klonkette ordnungsgemäß Object.clone() aufruft, welches eine neue Instanz des korrekten Laufzeittyps erstellt. Wenn eine Klasse diese Konvention verletzt, indem sie Konstruktoren statt super.clone() verwendet, gibt die clone()-Methode jeder Unterklasse ein Objekt des falschen Typs zurück - den Elternklassentyp statt des tatsächlichen Unterklassentyps.

Risiko

Das Nichtaufrufen von super.clone() führt zu subtilen aber ernsthaften Fehlern in Klassenhierarchien. Wenn Unterklassen sich auf die clone()-Methode der Elternklasse verlassen, erhalten sie Objekte des Elterntyps, nicht ihres eigenen Typs. Dies verursacht ClassCastException zur Laufzeit, wenn der Klon in den erwarteten Unterklassentyp gecastet wird. Der Fehler manifestiert sich möglicherweise erst, wenn Unterklassen erstellt werden, was ihn zu einem latenten Defekt macht. Anwendungen erleben unerwartete Laufzeitfehler, die schwer auf die fehlerhafte clone-Implementierung zurückzuführen sind. Zusätzlich können geklonte Objekte falsches Verhalten aufweisen, wenn unterklassenspezifische Felder nicht ordnungsgemäß initialisiert werden.

Lösung

Rufen Sie immer super.clone() als ersten Schritt in clone()-Implementierungen auf. Die Object.clone()-Methode, erreicht durch die super.clone()-Kette, erstellt eine flache Kopie des korrekten Laufzeittyps. Nach Erhält des Klons führen Sie notwendiges tiefes Kopieren von veränderbaren Feldern durch. Alternativ erwägen Sie, clone() gänzlich zu vermeiden und Kopierkonstruktoren oder statische Factory-Methoden zu verwenden, die expliziter und weniger fehleranfällig sind. Bei Verwendung von clone() stellen Sie sicher, dass die Klasse das Cloneable-Interface implementiert, um CloneNotSupportedException zu vermeiden. Dokumentieren Sie das Klonverhalten klar für Unterklassen.

Häufige Auswirkungen

AuswirkungDetails
IntegritätBereich: Integrität

Unerwarteter Zustand - Geklonte Objekte haben den falschen Typ, was Typfehlanpassungen und falsches Verhalten in Klassenhierarchien verursacht.
SonstigeBereich: Sonstige

Qualitätsverschlechterung - Anwendungen schlagen zur Laufzeit mit ClassCastException fehl, wenn Unterklassen versuchen, die fehlerhafte clone-Implementierung zu verwenden.

Beispielcode

Verwundbarer Code

// Verwundbar: clone() ohne super.clone()
public class Kibitzer implements Cloneable {
    private String name;
    private int rating;

    public Kibitzer(String name, int rating) {
        this.name = name;
        this.rating = rating;
    }

    // Verwundbar: Konstruktor statt super.clone() verwenden
    @Override
    public Object clone() throws CloneNotSupportedException {
        // Falsch! Erstellt Kibitzer, nicht Unterklassentyp
        return new Kibitzer(this.name, this.rating);
    }

    public String getName() { return name; }
    public int getRating() { return rating; }
}

// Unterklasse die fehlerhafte clone erbt
public class FancyKibitzer extends Kibitzer {
    private String title;

    public FancyKibitzer(String name, int rating, String title) {
        super(name, rating);
        this.title = title;
    }

    // Diese clone() wird fehlschlagen!
    @Override
    public Object clone() throws CloneNotSupportedException {
        // Ruft Eltern-clone auf die Kibitzer zurückgibt, nicht FancyKibitzer!
        FancyKibitzer copy = (FancyKibitzer) super.clone();  // ClassCastException!
        // title-Feld wird nie kopiert
        return copy;
    }

    public String getTitle() { return title; }
}

// Verwundbar: Komplexeres Beispiel mit veränderbaren Feldern
public class Person implements Cloneable {
    private String name;
    private Date birthDate;
    private List<String> nicknames;

    // Verwundbar: Konstruktor-basiertes Klonen
    @Override
    public Object clone() {
        // Falscher Ansatz - bricht Unterklassen-Klonen
        Person copy = new Person();
        copy.name = this.name;
        copy.birthDate = new Date(this.birthDate.getTime());
        copy.nicknames = new ArrayList<>(this.nicknames);
        return copy;
    }
}

public class Employee extends Person {
    private String employeeId;
    private Department department;

    @Override
    public Object clone() {
        // Dies wird fehlschlagen - Person.clone() gibt Person zurück, nicht Employee
        Employee copy = (Employee) super.clone();  // ClassCastException!
        copy.employeeId = this.employeeId;
        // department wird nie ordnungsgemäß geklont
        return copy;
    }
}

// Verwundbar: this.getClass() verwenden ist immer noch falsch
public class BadCloner implements Cloneable {
    private int value;

    @Override
    public Object clone() throws CloneNotSupportedException {
        try {
            // Immer noch falsch - verlässt sich auf Reflection und argumentlosen Konstruktor
            BadCloner copy = this.getClass().getDeclaredConstructor().newInstance();
            copy.value = this.value;
            return copy;
        } catch (Exception e) {
            throw new CloneNotSupportedException();
        }
    }
}

// Unterklasse mit erforderlichen Konstruktorparametern schlägt fehl
public class RequiredArgsBadCloner extends BadCloner {
    private String required;

    public RequiredArgsBadCloner(String required) {
        this.required = required;
    }

    // Kein argumentloser Konstruktor - Eltern-clone() schlägt fehl!
}

Lösungscode

// Behoben: Ordnungsgemäße clone() mit super.clone()
public class Kibitzer implements Cloneable {
    private String name;
    private int rating;

    public Kibitzer(String name, int rating) {
        this.name = name;
        this.rating = rating;
    }

    // Behoben: Immer super.clone() verwenden
    @Override
    public Object clone() throws CloneNotSupportedException {
        // Korrekt! Object.clone() erstellt Instanz des tatsächlichen Laufzeittyps
        return super.clone();
        // Für diese Klasse mit nur Primitiven und unveränderlichem String
        // reicht flache Kopie von Object.clone() aus
    }

    public String getName() { return name; }
    public int getRating() { return rating; }
}

// Unterklasse funktioniert jetzt korrekt
public class FancyKibitzer extends Kibitzer {
    private String title;

    public FancyKibitzer(String name, int rating, String title) {
        super(name, rating);
        this.title = title;
    }

    @Override
    public Object clone() throws CloneNotSupportedException {
        // Funktioniert jetzt! super.clone() gibt FancyKibitzer zurück
        FancyKibitzer copy = (FancyKibitzer) super.clone();
        // title ist String (unveränderlich) - flache Kopie ist ausreichend
        return copy;
    }

    public String getTitle() { return title; }
}

// Behoben: Ordnungsgemäßes Klonen mit veränderbaren Feldern
public class Person implements Cloneable {
    private String name;
    private Date birthDate;
    private List<String> nicknames;

    public Person() {}

    public Person(String name, Date birthDate) {
        this.name = name;
        this.birthDate = birthDate;
        this.nicknames = new ArrayList<>();
    }

    // Behoben: super.clone() verwenden und veränderbare Felder tief kopieren
    @Override
    public Object clone() throws CloneNotSupportedException {
        // Zuerst ordnungsgemäß typisierte flache Kopie holen
        Person copy = (Person) super.clone();

        // Veränderbare Felder tief kopieren
        if (this.birthDate != null) {
            copy.birthDate = new Date(this.birthDate.getTime());
        }
        if (this.nicknames != null) {
            copy.nicknames = new ArrayList<>(this.nicknames);
        }

        return copy;
    }

    // Getter und Setter...
}

// Unterklasse funktioniert jetzt korrekt
public class Employee extends Person {
    private String employeeId;
    private Department department;  // Annahme: Department ist klonbar

    @Override
    public Object clone() throws CloneNotSupportedException {
        // Funktioniert! Person.clone() verwendet super.clone() das Employee zurückgibt
        Employee copy = (Employee) super.clone();

        // Veränderbare Felder spezifisch für Employee tief kopieren
        if (this.department != null) {
            copy.department = (Department) this.department.clone();
        }

        return copy;
    }
}

// Behoben: Vollständiges Beispiel mit ordnungsgemäßem Klonen
public class Document implements Cloneable {
    private String title;
    private Date createdDate;
    private Date modifiedDate;
    private List<Section> sections;
    private Map<String, String> metadata;

    @Override
    public Document clone() {
        try {
            // super.clone() für korrekten Typ verwenden
            Document copy = (Document) super.clone();

            // Alle veränderbaren Felder tief kopieren
            copy.createdDate = (Date) this.createdDate.clone();
            copy.modifiedDate = (Date) this.modifiedDate.clone();

            // Collections tief kopieren
            copy.sections = new ArrayList<>();
            for (Section section : this.sections) {
                copy.sections.add(section.clone());  // Section muss Cloneable sein
            }

            copy.metadata = new HashMap<>(this.metadata);

            return copy;

        } catch (CloneNotSupportedException e) {
            // Sollte für Cloneable-Klasse nie passieren
            throw new AssertionError("Clone nicht unterstützt", e);
        }
    }
}

// Alternative: Kopierkonstruktor (oft clone vorzuziehen)
public class SafePerson {
    private final String name;
    private final Date birthDate;
    private final List<String> nicknames;

    public SafePerson(String name, Date birthDate) {
        this.name = name;
        this.birthDate = new Date(birthDate.getTime());  // Defensive Kopie
        this.nicknames = new ArrayList<>();
    }

    // Kopierkonstruktor - explizit und klar
    public SafePerson(SafePerson other) {
        this.name = other.name;
        this.birthDate = new Date(other.birthDate.getTime());
        this.nicknames = new ArrayList<>(other.nicknames);
    }

    // Statische Factory-Methode als Alternative
    public static SafePerson copyOf(SafePerson other) {
        return new SafePerson(other);
    }
}

// Unterklasse mit Kopierkonstruktor
public class SafeEmployee extends SafePerson {
    private final String employeeId;
    private final Department department;

    public SafeEmployee(String name, Date birthDate,
                        String employeeId, Department department) {
        super(name, birthDate);
        this.employeeId = employeeId;
        this.department = department;  // Annahme: unveränderlich oder Kopie
    }

    // Kopierkonstruktor - klare Vererbung
    public SafeEmployee(SafeEmployee other) {
        super(other);  // Elternfelder kopieren
        this.employeeId = other.employeeId;
        this.department = new Department(other.department);  // Tiefe Kopie
    }

    public static SafeEmployee copyOf(SafeEmployee other) {
        return new SafeEmployee(other);
    }
}

// Builder-Pattern für komplexe Objekte verwenden (eine weitere Alternative)
public class ComplexDocument {
    private final String title;
    private final List<Section> sections;

    private ComplexDocument(Builder builder) {
        this.title = builder.title;
        this.sections = new ArrayList<>(builder.sections);
    }

    // Kopie über Builder erstellen
    public Builder toBuilder() {
        return new Builder()
            .title(this.title)
            .sections(new ArrayList<>(this.sections));
    }

    public static class Builder {
        private String title;
        private List<Section> sections = new ArrayList<>();

        public Builder title(String title) {
            this.title = title;
            return this;
        }

        public Builder sections(List<Section> sections) {
            this.sections = sections;
            return this;
        }

        public ComplexDocument build() {
            return new ComplexDocument(this);
        }
    }
}

CVE-Beispiele

Keine spezifischen CVEs werden dieser CWE üblicherweise zugeordnet, da sie primär die Anwendungskorrektheit betrifft statt Sicherheit.


Referenzen

  1. MITRE Corporation. "CWE-580: clone() Method Without super.clone()." https://cwe.mitre.org/data/definitions/580.html
  2. Joshua Bloch. "Effective Java" - Item 13: Override clone judiciously.
  3. Oracle. "Object.clone() Documentation."