[Soot-list] [soot] AbstractFlowSet equals/hashCode contract issues (#11)

Ondřej Lhoták olhotak at uwaterloo.ca
Fri Nov 9 12:08:03 EST 2012


I don't know. Those implementations and design decisions are from before
my time.

On Thu, Nov 08, 2012 at 09:55:35PM +0100, Eric Bodden wrote:
> Hi Tarsis.
> 
> Thanks a lot for those hints. I think personally think your assessment is
> correct. Having said that, Soot has quite some code that depends on those set
> implementations and there may be some good reason for seeing the implementation
> we have today. (For instance I seem to remember that there were some parts of
> Spark that use instance-equality for performance reasons.
> 
> Does anyone remember? Ondrej or Patrick maybe?
> 
> Cheers,
> Eric
> 
> 
> On 8 November 2012 21:23, Tarsis Toledo <notifications at github.com> wrote:
> 
> 
>     Hello,
> 
>     First of all, thanks for the wonderful open source framework.
> 
>     I'd like to draw your attention to the current implementation of the
>     AbstractFlowSet implementation and its subclasses wrt to the equals/
>     hashCode contract. I've encountered several issues:
> 
>     1) Violation of the hashCode contract (equal objects must have the same
>     hashCode).
>     For example, take the following snippet:
> 
>     ArraySparseSet ars1 = new ArraySparseSet();
>     ars1.add("a");
>     ars1.add("b");
> 
>     ArraySparseSet ars2 = new ArraySparseSet();
>     ars2.add("b");
>     ars2.add("a");
> 
>     System.out.println(ars1.equals(ars2)); // true
>     System.out.println(ars1.hashCode() == ars2.hashCode()); // false; violates the hashCode contract
> 
>     AbstractFlowSet also accept intertype equality, but fails to comply to the
>     hashCode contract:
> 
>     ArrayPackedSet aps = new ArrayPackedSet(new CollectionFlowUniverse<String>(Arrays.asList("a","b")));
>     aps.add("b");
>     aps.add("a");
> 
>     System.out.println(ars1.equals(aps)); // true
>     System.out.println(ars1.hashCode() == aps.hashCode()); // false; violates the hashCode contract
> 
>     This happens because the hashCode is calculated based on the iteration
>     order, whereas the equals checks for equality independent of order.
> 
>     One way to fix this could be to relax the hashCode implementation below:
> 
>     public int hashCode() {
>         final int PRIME = 31;
>         int result = 1;
>         Iterator iter = iterator();
>         while(iter.hasNext()) {
>             Object o = iter.next();
>             result = PRIME * result + o.hashCode();
>         }
>         return result;
>     }
> 
>     to something like:
> 
>     public int hashCode() {
>         int result = 1;
>         Iterator iter = iterator();
>         while(iter.hasNext()) {
>             Object o = iter.next();
>             result += o.hashCode();
>         }
>         return result;
>     }
> 
>     2) One consequence of allowing intertype equality (like the comparisson
>     between ArrayPackedSet and ArraySparseSet above) is that it is very hard to
>     comply with the simmetry clause of the equals contract:
> 
>     ToppedSet ts1 = new ToppedSet(ars1);
>     System.out.println(ts1.equals(ars1)); // false
>     System.out.println(ars1.equals(ts1)); // true; violates the simmetry clause of the equals contract
> 
>     3) and also to comply with the transitivity clause. Suppose the following
>     class:
> 
>     class MyFlowSet extends ArraySparseSet {
>         private int myState = 0;
> 
>         public void setState(int state) {
>             myState = state;
>         }
> 
>         @Override
>         public boolean equals(Object otherFlow) {
>             if (otherFlow instanceof MyFlowSet) {
>                 MyFlowSet other = (MyFlowSet) otherFlow;
>                 if (other.numElements != this.numElements || other.myState != this.myState)
>                     return false;
> 
>                 for(int i = 0; i < this.numElements; i++)
>                     if(!other.contains(this.elements[i]))
>                         return false;
>                 return true;
>             }
> 
>             return super.equals(otherFlow);
>         }
> 
>         @Override
>         public List toList() {
>             return super.toList();
>         }
>     }
> 
>     Because intertype equality is allowed, the following case can emerge:
> 
>     MyFlowSet mfs1 = new MyFlowSet();
>     mfs1.add("a");
>     mfs1.add("b");
> 
>     MyFlowSet mfs2 = new MyFlowSet();
>     mfs2.add("a");
>     mfs2.add("b");
>     mfs2.setState(1);
> 
>     System.out.println(mfs1.equals(ars1)); // true
>     System.out.println(mfs2.equals(ars1)); // true
>     System.out.println(mfs1.equals(mfs2)); // false; violates the transitivity clause of the equals contract
> 
>     I believe that many of these issues could be avoided by separating the
>     object equality from the "content equality", maybe using the EquivTo
>     interface, or by disallowing intertype equality. Both of these alternatives
>     will most likely break backwards compatibility wth existing code.
> 
>     Are any of these two alternatives viable options? What are the developers
>     position on this?
> 
>     Thanks!
> 
>>     Reply to this email directly or view it on GitHub.
> 
> 
> 
> 
> --
> Eric Bodden, Ph.D., http://sse.ec-spride.de/ http://bodden.de/
> Head of Secure Software Engineering Group at EC SPRIDE
> Tel: +49 6151 16-75422    Fax: +49 6151 16-72051
> Room 3.2.14, Mornewegstr. 30, 64293 Darmstadt


More information about the Soot-list mailing list