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
| Auswirkung | Details |
|---|---|
| Integrität | Bereich: Integrität Unerwarteter Zustand - Geklonte Objekte haben den falschen Typ, was Typfehlanpassungen und falsches Verhalten in Klassenhierarchien verursacht. |
| Sonstige | Bereich: 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
- MITRE Corporation. "CWE-580: clone() Method Without super.clone()." https://cwe.mitre.org/data/definitions/580.html
- Joshua Bloch. "Effective Java" - Item 13: Override clone judiciously.
- Oracle. "Object.clone() Documentation."