/** * Returns the (non-static) fields that have the invariant annotation and are not yet initialized * in a given store. */ public List<VariableTree> getUninitializedInvariantFields( Store store, TreePath path, boolean isStatic, List<? extends AnnotationMirror> receiverAnnotations) { ClassTree currentClass = TreeUtils.enclosingClass(path); List<VariableTree> fields = InitializationChecker.getAllFields(currentClass); List<VariableTree> violatingFields = new ArrayList<>(); AnnotationMirror invariant = getFieldInvariantAnnotation(); for (VariableTree field : fields) { if (isUnused(field, receiverAnnotations)) { continue; // don't consider unused fields } VariableElement fieldElem = TreeUtils.elementFromDeclaration(field); if (ElementUtils.isStatic(fieldElem) == isStatic) { // Does this field need to satisfy the invariant? if (getAnnotatedType(field).hasEffectiveAnnotation(invariant)) { // Has the field been initialized? if (!store.isFieldInitialized(fieldElem)) { violatingFields.add(field); } } } } return violatingFields; }
public RegexQualifiedTypeFactory(QualifierContext<Regex> checker) { super(checker); patternCompile = TreeUtils.getMethod( "java.util.regex.Pattern", "compile", 1, getContext().getProcessingEnvironment()); patternMatcher = TreeUtils.getMethod( "java.util.regex.Pattern", "matcher", 1, getContext().getProcessingEnvironment()); }
public Element getElement() { Element el; if (tree instanceof IdentifierTree) { el = TreeUtils.elementFromUse((IdentifierTree) tree); } else { assert tree instanceof VariableTree; el = TreeUtils.elementFromDeclaration((VariableTree) tree); } return el; }
public NullnessVisitor(BaseTypeChecker checker, boolean useFbc) { super(checker); NONNULL = atypeFactory.NONNULL; NULLABLE = atypeFactory.NULLABLE; MONOTONIC_NONNULL = atypeFactory.MONOTONIC_NONNULL; stringType = elements.getTypeElement("java.lang.String").asType(); ProcessingEnvironment env = checker.getProcessingEnvironment(); this.collectionSize = TreeUtils.getMethod(java.util.Collection.class.getName(), "size", 0, env); this.collectionToArray = TreeUtils.getMethod(java.util.Collection.class.getName(), "toArray", 1, env); checkForAnnotatedJdk(); }
/** * Tests whether a method invocation is an invocation of {@link Comparable#compareTo}. * * <p> * * @param node a method invocation node * @return true iff {@code node} is a invocation of {@code compareTo()} */ private boolean isInvocationOfCompareTo(MethodInvocationTree node) { ExecutableElement method = TreeUtils.elementFromUse(node); return (method.getParameters().size() == 1 && method.getReturnType().getKind() == TypeKind.INT // method symbols only have simple names && method.getSimpleName().contentEquals("compareTo")); }
protected void setSelfTypeInInitializationCode( Tree tree, AnnotatedDeclaredType selfType, TreePath path) { ClassTree enclosingClass = TreeUtils.enclosingClass(path); Type classType = ((JCTree) enclosingClass).type; AnnotationMirror annotation = null; // If all fields are committed-only, and they are all initialized, // then it is save to switch to @UnderInitialization(CurrentClass). if (areAllFieldsCommittedOnly(enclosingClass)) { Store store = getStoreBefore(tree); if (store != null) { List<AnnotationMirror> annos = Collections.emptyList(); if (getUninitializedInvariantFields(store, path, false, annos).size() == 0) { if (useFbc) { annotation = createFreeAnnotation(classType); } else { annotation = createUnclassifiedAnnotation(classType); } } } } if (annotation == null) { annotation = getFreeOrRawAnnotationOfSuperType(classType); } selfType.replaceAnnotation(annotation); }
@Override public Void visitMethodInvocation(MethodInvocationTree tree, AnnotatedTypeMirror type) { if (isUnderlyingTypeAValue(type) && methodIsStaticallyExecutable(TreeUtils.elementFromUse(tree))) { // Get argument values List<? extends ExpressionTree> arguments = tree.getArguments(); ArrayList<List<?>> argValues; if (arguments.size() > 0) { argValues = new ArrayList<List<?>>(); for (ExpressionTree argument : arguments) { AnnotatedTypeMirror argType = getAnnotatedType(argument); List<?> values = getValues(argType, argType.getUnderlyingType()); if (values.isEmpty()) { // values aren't known, so don't try to evaluate the // method return null; } argValues.add(values); } } else { argValues = null; } // Get receiver values AnnotatedTypeMirror receiver = getReceiverType(tree); List<?> receiverValues; if (receiver != null && !ElementUtils.isStatic(TreeUtils.elementFromUse(tree))) { receiverValues = getValues(receiver, receiver.getUnderlyingType()); if (receiverValues.isEmpty()) { // values aren't known, so don't try to evaluate the // method return null; } } else { receiverValues = null; } // Evaluate method List<?> returnValues = evalutator.evaluteMethodCall(argValues, receiverValues, tree); AnnotationMirror returnType = resultAnnotationHandler(type.getUnderlyingType(), returnValues, tree); type.replaceAnnotation(returnType); } return null; }
/** Case 1: Check for null dereferencing */ @Override public Void visitMemberSelect(MemberSelectTree node, Void p) { boolean isType = node.getExpression().getKind() == Kind.PARAMETERIZED_TYPE; if (!TreeUtils.isSelfAccess(node) && !isType) { checkForNullability(node.getExpression(), DEREFERENCE_OF_NULLABLE); } return super.visitMemberSelect(node, p); }
@Override public Void visitMethod(MethodTree node, AnnotatedTypeMirror p) { Void result = super.visitMethod(node, p); if (TreeUtils.isConstructor(node)) { assert p instanceof AnnotatedExecutableType; AnnotatedExecutableType exeType = (AnnotatedExecutableType) p; DeclaredType underlyingType = (DeclaredType) exeType.getReturnType().getUnderlyingType(); AnnotationMirror a = getFreeOrRawAnnotationOfSuperType(underlyingType); exeType.getReturnType().replaceAnnotation(a); } return result; }
/** * Returns the (non-static) fields that have the invariant annotation and are initialized in a * given store. */ public List<VariableTree> getInitializedInvariantFields(Store store, TreePath path) { // TODO: Instead of passing the TreePath around, can we use // getCurrentClassTree? ClassTree currentClass = TreeUtils.enclosingClass(path); List<VariableTree> fields = InitializationChecker.getAllFields(currentClass); List<VariableTree> initializedFields = new ArrayList<>(); AnnotationMirror invariant = getFieldInvariantAnnotation(); for (VariableTree field : fields) { VariableElement fieldElem = TreeUtils.elementFromDeclaration(field); if (!ElementUtils.isStatic(fieldElem)) { // Does this field need to satisfy the invariant? if (getAnnotatedType(field).hasEffectiveAnnotation(invariant)) { // Has the field been initialized? if (store.isFieldInitialized(fieldElem)) { initializedFields.add(field); } } } } return initializedFields; }
/** Returns true if a class overrides Object.equals */ private boolean overridesEquals(ClassTree node) { List<? extends Tree> members = node.getMembers(); for (Tree member : members) { if (member instanceof MethodTree) { MethodTree mTree = (MethodTree) member; ExecutableElement enclosing = TreeUtils.elementFromDeclaration(mTree); if (overrides(enclosing, Object.class, "equals")) { return true; } } } return false; }
private boolean isNewArrayInToArray(NewArrayTree node) { if (node.getDimensions().size() != 1) { return false; } ExpressionTree dim = node.getDimensions().get(0); ProcessingEnvironment env = checker.getProcessingEnvironment(); if (!TreeUtils.isMethodInvocation(dim, collectionSize, env)) { return false; } ExpressionTree rcvsize = ((MethodInvocationTree) dim).getMethodSelect(); if (!(rcvsize instanceof MemberSelectTree)) { return false; } rcvsize = ((MemberSelectTree) rcvsize).getExpression(); if (!(rcvsize instanceof IdentifierTree)) { return false; } Tree encl = getCurrentPath().getParentPath().getLeaf(); if (!TreeUtils.isMethodInvocation(encl, collectionToArray, env)) { return false; } ExpressionTree rcvtoarray = ((MethodInvocationTree) encl).getMethodSelect(); if (!(rcvtoarray instanceof MemberSelectTree)) { return false; } rcvtoarray = ((MemberSelectTree) rcvtoarray).getExpression(); if (!(rcvtoarray instanceof IdentifierTree)) { return false; } return ((IdentifierTree) rcvsize).getName() == ((IdentifierTree) rcvtoarray).getName(); }
/* * Method to implement the @UsesObjectEquals functionality. * If a class is marked @UsesObjectEquals, it must: * * -not override .equals(Object) * -be a subclass of Object or another class marked @UsesObjectEquals * * If a class is not marked @UsesObjectEquals, it must: * * -not have a superclass marked @UsesObjectEquals * * * @see org.checkerframework.common.basetype.BaseTypeVisitor#visitClass(com.sun.source.tree.ClassTree, java.lang.Object) */ @Override public Void visitClass(ClassTree node, Void p) { // Looking for an @UsesObjectEquals class declaration TypeElement elt = TreeUtils.elementFromDeclaration(node); UsesObjectEquals annotation = elt.getAnnotation(UsesObjectEquals.class); Tree superClass = node.getExtendsClause(); Element elmt = null; if (superClass != null && (superClass instanceof IdentifierTree || superClass instanceof MemberSelectTree)) { elmt = TreeUtils.elementFromUse((ExpressionTree) superClass); } // if it's there, check to make sure does not override equals // and supertype is Object or @UsesObjectEquals if (annotation != null) { // check methods to ensure no .equals if (overridesEquals(node)) { checker.report(Result.failure("overrides.equals"), node); } if (!(superClass == null || (elmt != null && elmt.getAnnotation(UsesObjectEquals.class) != null))) { checker.report(Result.failure("superclass.unmarked"), node); } } else { // the class is not marked @UsesObjectEquals -> make sure its superclass isn't either. // this is impossible after design change making @UsesObjectEquals inherited? // check left in case of future design change back to non-inherited. if (superClass != null && (elmt != null && elmt.getAnnotation(UsesObjectEquals.class) != null)) { checker.report(Result.failure("superclass.marked"), node); } } return super.visitClass(node, p); }
public void handle(MethodInvocationTree tree, AnnotatedExecutableType method) { if (TreeUtils.isMethodInvocation(tree, systemGetProperty, env)) { List<? extends ExpressionTree> args = tree.getArguments(); assert args.size() == 1; ExpressionTree arg = args.get(0); if (arg.getKind() == Tree.Kind.STRING_LITERAL) { String literal = (String) ((LiteralTree) arg).getValue(); if (systemProperties.contains(literal)) { AnnotatedTypeMirror type = method.getReturnType(); type.replaceAnnotation(factory.NONNULL); } } } }
/** * In the first enclosing class, find the top-level member that contains tree. TODO: should we * look whether these elements are enclosed within another class that is itself under * construction. * * <p>Are there any other type of top level objects? */ private Tree findTopLevelClassMemberForTree(TreePath path) { ClassTree enclosingClass = TreeUtils.enclosingClass(path); if (enclosingClass != null) { List<? extends Tree> classMembers = enclosingClass.getMembers(); TreePath searchPath = path; while (searchPath.getParentPath() != null && searchPath.getParentPath() != enclosingClass) { searchPath = searchPath.getParentPath(); if (classMembers.contains(searchPath.getLeaf())) { return searchPath.getLeaf(); } } } return null; }
/** * Checks that the annotations on the type arguments supplied to a type or a method invocation are * within the bounds of the type variables as declared, and issues the * "type.argument.type.incompatible" error if they are not. * * <p>This method used to be visitParameterizedType, which incorrectly handles the main annotation * on generic types. */ protected Void visitParameterizedType(AnnotatedDeclaredType type, ParameterizedTypeTree tree) { // System.out.printf("TypeValidator.visitParameterizedType: type: %s, tree: %s\n", // type, tree); if (TreeUtils.isDiamondTree(tree)) return null; final TypeElement element = (TypeElement) type.getUnderlyingType().asElement(); if (checker.shouldSkipUses(element)) return null; List<AnnotatedTypeVariable> typevars = atypeFactory.typeVariablesFromUse(type, element); visitor.checkTypeArguments(tree, typevars, type.getTypeArguments(), tree.getTypeArguments()); return null; }
@Override protected void commonAssignmentCheck( Tree varTree, ExpressionTree valueExp, /*@CompilerMessageKey*/ String errorKey) { // allow MonotonicNonNull to be initialized to null at declaration if (varTree.getKind() == Tree.Kind.VARIABLE) { Element elem = TreeUtils.elementFromDeclaration((VariableTree) varTree); if (atypeFactory.fromElement(elem).hasEffectiveAnnotation(MONOTONIC_NONNULL) && !checker.getLintOption( AbstractNullnessChecker.LINT_NOINITFORMONOTONICNONNULL, AbstractNullnessChecker.LINT_DEFAULT_NOINITFORMONOTONICNONNULL)) { return; } } super.commonAssignmentCheck(varTree, valueExp, errorKey); }
@Override public AnnotatedDeclaredType getSelfType(Tree tree) { AnnotatedDeclaredType selfType = super.getSelfType(tree); TreePath path = getPath(tree); Tree topLevelMember = findTopLevelClassMemberForTree(path); if (topLevelMember != null) { if (topLevelMember.getKind() != Kind.METHOD || TreeUtils.isConstructor((MethodTree) topLevelMember)) { setSelfTypeInInitializationCode(tree, selfType, path); } } return selfType; }
@Override public Void visitMemberSelect(MemberSelectTree tree, AnnotatedTypeMirror type) { if (TreeUtils.isFieldAccess(tree) && isUnderlyingTypeAValue(type)) { VariableElement elem = (VariableElement) InternalUtils.symbol(tree); Object value = elem.getConstantValue(); if (value != null) { // compile time constant type.replaceAnnotation( resultAnnotationHandler( type.getUnderlyingType(), Collections.singletonList(value), tree)); return null; } if (ElementUtils.isStatic(elem) && ElementUtils.isFinal(elem)) { Element e = InternalUtils.symbol(tree.getExpression()); if (e != null) { String classname = ElementUtils.getQualifiedClassName(e).toString(); String fieldName = tree.getIdentifier().toString(); value = evalutator.evaluateStaticFieldAccess(classname, fieldName, tree); if (value != null) type.replaceAnnotation( resultAnnotationHandler( type.getUnderlyingType(), Collections.singletonList(value), tree)); return null; } } if (tree.getIdentifier().toString().equals("length")) { AnnotatedTypeMirror receiverType = getAnnotatedType(tree.getExpression()); if (receiverType.getKind() == TypeKind.ARRAY) { AnnotationMirror arrayAnno = receiverType.getAnnotation(ArrayLen.class); if (arrayAnno != null) { // array.length, where array : @ArrayLen(x) List<Integer> lengths = ValueAnnotatedTypeFactory.getArrayLength(arrayAnno); type.replaceAnnotation(createNumberAnnotationMirror(new ArrayList<Number>(lengths))); return null; } } } } return null; }
/** Returns whether the field {@code f} is unused, given the annotations on the receiver. */ private boolean isUnused( VariableTree field, Collection<? extends AnnotationMirror> receiverAnnos) { if (receiverAnnos.isEmpty()) { return false; } AnnotationMirror unused = getDeclAnnotation(TreeUtils.elementFromDeclaration(field), Unused.class); if (unused == null) { return false; } Name when = AnnotationUtils.getElementValueClassName(unused, "when", false); for (AnnotationMirror anno : receiverAnnos) { Name annoName = ((TypeElement) anno.getAnnotationType().asElement()).getQualifiedName(); if (annoName.contentEquals(when)) { return true; } } return false; }
/** Are all fields committed-only? */ protected boolean areAllFieldsCommittedOnly(ClassTree classTree) { if (!useFbc) { // In the rawness type system, no fields can store not fully // initialized objects. return true; } for (Tree member : classTree.getMembers()) { if (!member.getKind().equals(Tree.Kind.VARIABLE)) { continue; } VariableTree var = (VariableTree) member; VariableElement varElt = TreeUtils.elementFromDeclaration(var); // var is not committed-only if (getDeclAnnotation(varElt, NotOnlyInitialized.class) != null) { // var is not static -- need a check of initializer blocks, // not of constructor which is where this is used if (!varElt.getModifiers().contains(Modifier.STATIC)) { return false; } } } return true; }
@Override protected void checkMethodInvocability( AnnotatedExecutableType method, MethodInvocationTree node) { if (!TreeUtils.isSelfAccess(node) && // Static methods don't have a receiver method.getReceiverType() != null) { // TODO: should all or some constructors be excluded? // method.getElement().getKind() != ElementKind.CONSTRUCTOR) { Set<AnnotationMirror> recvAnnos = atypeFactory.getReceiverType(node).getAnnotations(); AnnotatedTypeMirror methodReceiver = method.getReceiverType().getErased(); AnnotatedTypeMirror treeReceiver = methodReceiver.shallowCopy(false); AnnotatedTypeMirror rcv = atypeFactory.getReceiverType(node); treeReceiver.addAnnotations(rcv.getEffectiveAnnotations()); // If receiver is Nullable, then we don't want to issue a warning // about method invocability (we'd rather have only the // "dereference.of.nullable" message). if (treeReceiver.hasAnnotation(NULLABLE) || recvAnnos.contains(MONOTONIC_NONNULL)) { return; } } super.checkMethodInvocability(method, node); }
@Override public Void visitNewClass(NewClassTree tree, AnnotatedTypeMirror type) { boolean wrapperClass = TypesUtils.isBoxedPrimitive(type.getUnderlyingType()) || TypesUtils.isDeclaredOfName(type.getUnderlyingType(), "java.lang.String"); if (wrapperClass || (isUnderlyingTypeAValue(type) && methodIsStaticallyExecutable(TreeUtils.elementFromUse(tree)))) { // get arugment values List<? extends ExpressionTree> arguments = tree.getArguments(); ArrayList<List<?>> argValues; if (arguments.size() > 0) { argValues = new ArrayList<List<?>>(); for (ExpressionTree argument : arguments) { AnnotatedTypeMirror argType = getAnnotatedType(argument); List<?> values = getValues(argType, argType.getUnderlyingType()); if (values.isEmpty()) { // values aren't known, so don't try to evaluate the // method return null; } argValues.add(values); } } else { argValues = null; } // Evaluate method List<?> returnValues = evalutator.evaluteConstrutorCall(argValues, tree, type.getUnderlyingType()); AnnotationMirror returnType = resultAnnotationHandler(type.getUnderlyingType(), returnValues, tree); type.replaceAnnotation(returnType); } return null; }
public SystemGetPropertyHandler(ProcessingEnvironment env, NullnessAnnotatedTypeFactory factory) { this.env = env; this.factory = factory; systemGetProperty = TreeUtils.getMethod("java.lang.System", "getProperty", 1, env); }
/** * Pattern matches to prevent false positives of the form {@code (a == b || a.compareTo(b) == 0)}. * Returns true iff the given node fits this pattern. * * @param node * @return true iff the node fits the pattern (a == b || a.compareTo(b) == 0) */ private boolean suppressEarlyCompareTo(final BinaryTree node) { // Only handle == binary trees if (node.getKind() != Tree.Kind.EQUAL_TO) return false; Tree left = node.getLeftOperand(); Tree right = node.getRightOperand(); // Only valid if we're comparing identifiers. if (!(left.getKind() == Tree.Kind.IDENTIFIER && right.getKind() == Tree.Kind.IDENTIFIER)) { return false; } final Element lhs = TreeUtils.elementFromUse((IdentifierTree) left); final Element rhs = TreeUtils.elementFromUse((IdentifierTree) right); // looking for ((a == b || a.compareTo(b) == 0) Heuristics.Matcher matcher = new Heuristics.Matcher() { @Override public Boolean visitBinary(BinaryTree tree, Void p) { if (tree.getKind() == Tree.Kind.EQUAL_TO) { // a.compareTo(b) == 0 ExpressionTree leftTree = tree.getLeftOperand(); // looking for a.compareTo(b) or b.compareTo(a) ExpressionTree rightTree = tree.getRightOperand(); // looking for 0 if (rightTree.getKind() != Tree.Kind.INT_LITERAL) { return false; } LiteralTree rightLiteral = (LiteralTree) rightTree; if (!rightLiteral.getValue().equals(0)) { return false; } return visit(leftTree, p); } else { // a == b || a.compareTo(b) == 0 ExpressionTree leftTree = tree.getLeftOperand(); // looking for a==b ExpressionTree rightTree = tree.getRightOperand(); // looking for a.compareTo(b) == 0 or b.compareTo(a) == 0 if (leftTree != node) { return false; } if (rightTree.getKind() != Tree.Kind.EQUAL_TO) { return false; } return visit(rightTree, p); } } @Override public Boolean visitMethodInvocation(MethodInvocationTree tree, Void p) { if (!isInvocationOfCompareTo(tree)) { return false; } List<? extends ExpressionTree> args = tree.getArguments(); if (args.size() != 1) { return false; } ExpressionTree arg = args.get(0); if (arg.getKind() != Tree.Kind.IDENTIFIER) { return false; } Element argElt = TreeUtils.elementFromUse(arg); ExpressionTree exp = tree.getMethodSelect(); if (exp.getKind() != Tree.Kind.MEMBER_SELECT) { return false; } MemberSelectTree member = (MemberSelectTree) exp; if (member.getExpression().getKind() != Tree.Kind.IDENTIFIER) { return false; } Element refElt = TreeUtils.elementFromUse(member.getExpression()); if (!((refElt.equals(lhs) && argElt.equals(rhs)) || ((refElt.equals(rhs) && argElt.equals(lhs))))) { return false; } return true; } }; boolean okay = Heuristics.Matchers.withIn(Heuristics.Matchers.ofKind(Tree.Kind.CONDITIONAL_OR, matcher)) .match(getCurrentPath()); return okay; }
// TODO: handle != comparisons too! private boolean suppressInsideComparison(final BinaryTree node) { // Only handle == binary trees if (node.getKind() != Tree.Kind.EQUAL_TO) return false; Tree left = node.getLeftOperand(); Tree right = node.getRightOperand(); // Only valid if we're comparing identifiers. if (!(left.getKind() == Tree.Kind.IDENTIFIER && right.getKind() == Tree.Kind.IDENTIFIER)) return false; // If we're not directly in an if statement in a method (ignoring // parens and blocks), terminate. // TODO: only if it's the first statement in the block if (!Heuristics.matchParents(getCurrentPath(), Tree.Kind.IF, Tree.Kind.METHOD)) return false; ExecutableElement enclosing = TreeUtils.elementFromDeclaration(visitorState.getMethodTree()); assert enclosing != null; final Element lhs = TreeUtils.elementFromUse((IdentifierTree) left); final Element rhs = TreeUtils.elementFromUse((IdentifierTree) right); // Matcher to check for if statement that returns zero Heuristics.Matcher matcher = new Heuristics.Matcher() { @Override public Boolean visitIf(IfTree tree, Void p) { return visit(tree.getThenStatement(), p); } @Override public Boolean visitBlock(BlockTree tree, Void p) { if (tree.getStatements().size() > 0) return visit(tree.getStatements().get(0), p); return false; } @Override public Boolean visitReturn(ReturnTree tree, Void p) { ExpressionTree expr = tree.getExpression(); return (expr != null && expr.getKind() == Tree.Kind.INT_LITERAL && ((LiteralTree) expr).getValue().equals(0)); } }; // Determine whether or not the "then" statement of the if has a single // "return 0" statement (for the Comparator.compare heuristic). if (overrides(enclosing, Comparator.class, "compare")) { final boolean returnsZero = Heuristics.Matchers.withIn(Heuristics.Matchers.ofKind(Tree.Kind.IF, matcher)) .match(getCurrentPath()); if (!returnsZero) return false; assert enclosing.getParameters().size() == 2; Element p1 = enclosing.getParameters().get(0); Element p2 = enclosing.getParameters().get(1); return (p1.equals(lhs) && p2.equals(rhs)) || (p2.equals(lhs) && p1.equals(rhs)); } else if (overrides(enclosing, Object.class, "equals")) { assert enclosing.getParameters().size() == 1; Element param = enclosing.getParameters().get(0); Element thisElt = getThis(trees.getScope(getCurrentPath())); assert thisElt != null; return (thisElt.equals(lhs) && param.equals(rhs)) || (param.equals(lhs) && thisElt.equals(rhs)); } else if (overrides(enclosing, Comparable.class, "compareTo")) { final boolean returnsZero = Heuristics.Matchers.withIn(Heuristics.Matchers.ofKind(Tree.Kind.IF, matcher)) .match(getCurrentPath()); if (!returnsZero) { return false; } assert enclosing.getParameters().size() == 1; Element param = enclosing.getParameters().get(0); Element thisElt = getThis(trees.getScope(getCurrentPath())); assert thisElt != null; return (thisElt.equals(lhs) && param.equals(rhs)) || (param.equals(lhs) && thisElt.equals(rhs)); } return false; }