Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.

...

Do not violate any of these five conditions while overriding the equals() method. Mistakes resulting from a violation of the first condition are infrequent; it is consequently omitted from this discussion. The second and third conditions are highlighted. The rule for consistency implies that mutable objects may not satisfy the equals() contract. It is good practice to avoid defining equals() implementations that use unreliable data sources such as IP addresses and caches. The final condition about the comparison with null is typically violated when the equals() code throws an exception instead of returning false. When this constitutes a security vulnerability (in the form of denial of service), it can be trivially fixed by returning false.

Noncompliant Code Example

This noncompliant code example violates the second condition in the contract (symmetry). This requirement means that if one object is equal to another then the other must also be equal to this one. Consider a CaseInsensitiveString class that defines a String and overrides the equals() method. The CaseInsensitiveString knows about ordinary strings but the String class has no idea about case-insensitive strings. As a result, s.equalsIgnoreCase(((CaseInsensitiveString)o).s) returns true but s.equalsIgnoreCase((String)o) always returns false.

Code Block
bgColor#FFCCCC
public final class CaseInsensitiveString {
  private String s;

  public CaseInsensitiveString(String s) {
    if (s == null) {
      throw new NullPointerException();
    } 
    this.s = s;
  }

  //This method violates asymmetry
  public boolean equals(Object o) {
    if (o instanceof CaseInsensitiveString) {
      return s.equalsIgnoreCase(((CaseInsensitiveString)o).s);
    }

    if (o instanceof String) {
      return s.equalsIgnoreCase((String)o);
    }
    return false;
  }

  public static void main(String[] args) {
    CaseInsensitiveString cis = new CaseInsensitiveString("Java");
    String s = "java";
    System.out.println(cis.equals(s)); // Returns true
    System.out.println(s.equals(cis)); // Returns false
  }
}

Compliant Solution

Do not try to inter-operate with String from within the equals() method. The new equals() method is highlighted in this compliant solution.

Code Block
bgColor#ccccff
public final class CaseInsensitiveString {
  private String s;

  public CaseInsensitiveString(String s) {
    if (s == null) {
      throw new NullPointerException();
    }
    this.s = s;
  }

  public boolean equals(Object o) {
    return o instanceof CaseInsensitiveString &&
    ((CaseInsensitiveString)o).s.equalsIgnoreCase(s);
  }

  public static void main(String[] args) {
    CaseInsensitiveString cis = new CaseInsensitiveString("Java");
    String s = "java";
    System.out.println(cis.equals(s)); // Returns false now
    System.out.println(s.equals(cis)); // Returns false now
  }
}

Noncompliant Code Example

This noncompliant code example violates transitivity though it satisfies the symmetry condition. In the first print statement, the comparison between p1 and p2 returns true, in the second, the comparison between p2 and p3 also returns true but in the third, the comparison between p1 and p3 returns false. This contradicts the transitivity rule.

Code Block
bgColor#FFCCCC
public class Card {
  private final int number;

  public Card(int number) {
    this.number = number;
  }

  public boolean equals(Object o) {
    if (!(o instanceof Card)) {
      return false;
    } 
    
    Card c = (Card)o;
    return c.number == number;
  }
}

class XCard extends Card {
  private String type;
  public XCard(int number, String type) {
    super(number);
    this.type = type;
  }

  public boolean equals(Object o) {
    if (!(o instanceof Card)) {
      return false;
    } 

    // Normal Card, do not compare type 
    if (!(o instanceof XCard)) {
      return o.equals(this);
    } 

    // It is an XCard, compare type as well
    XCard xc = (XCard)o;
    return super.equals(o) && xc.type == type;
  }	  
  
  public static void main(String[] args) {
    XCard p1 = new XCard(1, "type1"); 
    Card p2 = new Card(1);
    XCard p3 = new XCard(1, "type2");
    System.out.println(p1.equals(p2)); // Returns true
    System.out.println(p2.equals(p3)); // Returns true
    System.out.println(p1.equals(p3)); // Returns false, violating transitivity
  }
}

Compliant Solution

Wiki Markup
It is currently not possible to extend an instantiable class (as opposed to an {{abstract}} class) and add a value or field in the subclass while preserving the {{equals()}} contract. This implies that composition must be preferred over inheritance. This technique does qualify as a reasonable workaround \[[Bloch 08|AA. Java References#Bloch 08]\]. It can be implemented by giving the {{XCard}} class a private {{card}} field and providing a {{public}} {{viewCard()}} method. 

...

Wiki Markup
"There are some classes in the Java platform libraries that do extend an instantiable class and add a value component. For example, {{java.sql.Timestamp}} extends {{java.util.Date}} and adds a nanoseconds field. The {{equals}} implementation for {{Timestamp}} does violate symmetry and can cause erratic behavior if {{Timestamp}} and {{Date}} objects are used in the same collection or are otherwise intermixed." \[[Bloch 08|AA. Java References#Bloch 08]\]

Risk Assessment

Violating the general contract when overriding the equals() method can lead to unexpected results.

Rule

Severity

Likelihood

Remediation Cost

Priority

Level

MET30- J

low

unlikely

medium

P2

L3

Automated Detection

TODO

Related Vulnerabilities

Search for vulnerabilities resulting from the violation of this rule on the CERT website.

References

Wiki Markup
\[[API 06|AA. Java References#API 06]\] [method equals()|http://java.sun.com/j2se/1.4.2/docs/api/java/lang/Object.html#equals(java.lang.Object)]
\[[Bloch 08|AA. Java References#Bloch 08]\] Item 8: Obey the general contract when overriding equals
\[[Darwin 04|AA. Java References#Darwin 04]\] 9.2 Overriding the equals method

...