/**
 * @name Hashed value without GetHashCode definition
 * @description Finds uses of hashing on types that define 'Equals(...)' but not 'GetHashCode()'.
 * @kind problem
 * @problem.severity warning
 * @precision high
 * @id cs/gethashcode-is-not-defined
 * @tags reliability
 *       maintainability
 */
import csharp
import semmle.code.csharp.frameworks.System

predicate dictionary(ConstructedType constructed)
{
  exists(UnboundGenericType dict |
    dict.hasQualifiedName("System.Collections.Generic", "Dictionary<,>") and
    constructed = dict.getAConstructedGeneric())
}

predicate hashtable(Class c)
{
  c.hasQualifiedName("System.Collections", "Hashtable")
}

predicate hashstructure(Type t)
{
  hashtable(t) or dictionary(t)
}

predicate hashAdd(Expr e)
{
  exists(MethodCall mc, string name |
    (name = "Add" or name = "ContainsKey") and
    mc.getArgument(0) = e and
    mc.getTarget().hasName(name) and
    hashstructure(mc.getTarget().getDeclaringType()))
}

predicate eqWithoutHash(RefType t)
{
  t.getAMethod() instanceof EqualsMethod
  and not t.getAMethod() instanceof GetHashCodeMethod
}

predicate hashCall(Expr e)
{
  exists(MethodCall mc |
    mc.getQualifier() = e and
    mc.getTarget() instanceof GetHashCodeMethod)
}

from Expr e, Type t
where (hashAdd(e) or hashCall(e))
  and e.getType() = t
  and eqWithoutHash(t)
select e, "This expression is hashed, but type '" + t.getName()
  + "' only defines Equals(...) not GetHashCode()."
