CodeQL 文档

缺少超克隆

ID: java/missing-call-to-super-clone
Kind: problem
Security severity: 
Severity: error
Precision: medium
Tags:
   - reliability
   - maintainability
   - external/cwe/cwe-580
Query suites:
   - java-security-and-quality.qls

单击以在 CodeQL 存储库中查看查询

在子类中重写的 clone 方法应调用 super.clone。如果不这样做,子类 clone 将返回错误类型的对象,这违反了 Cloneable 的约定。

Java API 规范指出,对于对象 xclone 方法的一般目的是满足以下三个属性

  • x.clone() != x(克隆对象是不同的对象实例)

  • x.clone().getClass() == x.getClass()(克隆对象与源对象类型相同)

  • x.clone().equals(x)(克隆对象与源对象“内容”相同)要使克隆对象与源对象类型相同,非 final 类必须调用 super.clone,并且该调用最终必须到达 Object.clone,它创建一个正确类型的实例。如果它使用构造函数创建一个新对象,则未实现 clone 方法的子类将返回错误类型的对象。此外,所有也重写 clone 的类的超类型都必须调用 super.clone。否则,它永远不会到达 Object.clone,并创建一个类型不正确的对象。

但是,由于 Object.clone 仅对对象的字段进行浅层复制,因此任何具有“深层结构”的 Cloneable 对象(例如,使用数组或 Collection 的对象)都必须获取从调用 super.clone 生成的克隆,并将显式创建的结构副本分配给克隆的字段。这意味着克隆实例不与其源对象共享其内部状态。如果它确实共享其内部状态,则在克隆对象中进行的任何更改也会影响源对象的内部状态,这可能会导致意外的行为。

另一个复杂之处在于 clone 无法修改 final 字段中的值,这些值已由调用 super.clone 设置。必须将某些字段设为非 final 才能正确实现 clone 方法。

建议

每个克隆方法都应该始终使用 super.clone 来构造克隆对象。这确保克隆对象最终由 Object.clone 构造,后者使用反射来确保创建正确运行时类型的对象。

示例

在以下示例中,尝试克隆 WrongEmployee 失败,因为 super.clone 在其超类 WrongPerson 中实现不正确。

class WrongPerson implements Cloneable {
    private String name;
    public WrongPerson(String name) { this.name = name; }
    // BAD: 'clone' does not call 'super.clone'.
    public WrongPerson clone() {
        return new WrongPerson(this.name);
    }
}

class WrongEmployee extends WrongPerson {
    public WrongEmployee(String name) {
        super(name);
    }
    // ALMOST RIGHT: 'clone' correctly calls 'super.clone',
    // but 'super.clone' is implemented incorrectly.
    public WrongEmployee clone() {
    	return (WrongEmployee)super.clone();
    }
}

public class MissingCallToSuperClone {
    public static void main(String[] args) {
        WrongEmployee e = new WrongEmployee("John Doe");
        WrongEmployee eclone = e.clone(); // Causes a ClassCastException
    }
}

然而,在以下修改后的示例中,尝试克隆 Employee 成功,因为 super.clone 在其超类 Person 中实现正确。

class Person implements Cloneable {
    private String name;
    public Person(String name) { this.name = name; }
    // GOOD: 'clone' correctly calls 'super.clone'
    public Person clone() {
        try {
            return (Person)super.clone();
        } catch (CloneNotSupportedException e) {
            throw new AssertionError("Should never happen");
        }
    }
}

class Employee extends Person {
    public Employee(String name) {
        super(name);
    }
    // GOOD: 'clone' correctly calls 'super.clone'
    public Employee clone() {
    	return (Employee)super.clone();
    }
}

public class MissingCallToSuperClone {
    public static void main(String[] args) {
        Employee e2 = new Employee("Jane Doe");
        Employee e2clone = e2.clone(); // 'clone' correctly returns an object of type 'Employee'
    }
}

参考

  • J. Bloch,Effective Java(第二版),第 11 项。Addison-Wesley,2008 年。

  • Java API 规范:Object.clone()

  • 通用弱点枚举:CWE-580

  • ©GitHub, Inc.
  • 条款
  • 隐私