/** * @name Equals method does not inspect argument type * @description An implementation of 'equals' that does not check the type * of its argument may lead to failing casts. * @kind problem * @problem.severity error * @precision high * @id java/unchecked-cast-in-equals * @tags reliability * correctness */ import semmle.code.java.Member import semmle.code.java.JDK /** A cast inside a try-catch block that catches `ClassCastException`. */ class CheckedCast extends CastExpr { CheckedCast() { exists(TryStmt try, RefType cce | this.getEnclosingStmt().getParent+() = try and try.getACatchClause().getVariable().getType() = cce and cce.getQualifiedName() = "java.lang.ClassCastException" ) } Variable castVariable() { result.getAnAccess() = this.getExpr() } } /** * An `equals` method with a body of either `return o == this;` * or `return o == field;` */ class ReferenceEquals extends EqualsMethod { ReferenceEquals() { exists(Block b, ReturnStmt ret, EQExpr eq | this.getBody() = b and b.getStmt(0) = ret and ( ret.getResult() = eq or exists(ParExpr pe | ret.getResult() = pe and pe.getExpr() = eq) ) and eq.getAnOperand() = this.getAParameter().getAnAccess() and (eq.getAnOperand() instanceof ThisAccess or eq.getAnOperand() instanceof FieldAccess) ) } } from EqualsMethod m where // The parameter is accessed at least once ... exists(VarAccess va | va.getVariable() = m.getParameter()) and // ... but its type is not checked using `instanceof`. not exists(InstanceOfExpr e | e.getEnclosingCallable() = m and e.getExpr().(VarAccess).getVariable() = m.getParameter() ) and // Exclude cases that are probably OK. not exists(MethodAccess ma, Method c | ma.getEnclosingCallable() = m and ma.getMethod() = c | c.hasName("getClass") or c.hasName("compareTo") or c.hasName("equals") or c.hasName("isInstance") or c.hasName("reflectionEquals") or // If both `this` and the argument are passed to another method, // or if the argument is passed to a method declared or inherited by `this` type, // that method may do the right thing. ma.getAnArgument() = m.getParameter().getAnAccess() and ( ma.getAnArgument() instanceof ThisAccess or exists(Method delegate | delegate.getSourceDeclaration() = ma.getMethod() | m.getDeclaringType().inherits(delegate) ) ) ) and not m.getDeclaringType() instanceof Interface and // Exclude checked casts (casts inside `try`-blocks). not exists(CheckedCast cast | cast.castVariable() = m.getAParameter()) and // Exclude `equals` methods that implement reference-equality. not m instanceof ReferenceEquals select m, "equals() method does not seem to check argument type."