PsiElementVisitor (@NotNull ProblemsHolder holder, boolean isOnTheFly) { return new JavaElementVisitor() { @Override public void visitMethod(@NotNull PsiMethod method) { if (INVOKE.methodMatches(method)) { check(method); } if (method.getReturnType() instanceof PsiClassType) { PsiParameterList list = method.getParameterList(); if (list.getParametersCount() == 3) { PsiParameter[] parameters = list.getParameters(); if (EntryStream.zip(parameters, HANDLER_ARGUMENT_TYPES).allMatch((p, t) -> TypeUtils.typeEquals(t, p.getType())) && SyntaxTraverser.psiTraverser(holder.getFile()).filter(PsiMethodReferenceExpression.class) .filter(ref -> ref.isReferenceTo(method) && TypeUtils.typeEquals(HANDLER_CLASS, ref.getFunctionalInterfaceType())) .first() != null) { check(method); } } } } @Override public void visitLambdaExpression(@NotNull PsiLambdaExpression lambda) { if (lambda.getParameterList().getParametersCount() != 3) return; PsiType type = lambda.getFunctionalInterfaceType(); if (!InheritanceUtil.isInheritor(type, HANDLER_CLASS)) return; check(lambda); } private void check(PsiParameterListOwner method) { PsiElement body = method.getBody(); if (body == null) return; PsiParameter methodParameter = method.getParameterList().getParameter(1); if (methodParameter == null) return; if (body instanceof PsiCodeBlock && !ControlFlowUtils.containsReturn(body)) return; if (!VariableAccessUtils.variableIsUsed(methodParameter, body)) { holder.registerProblem(Objects.requireNonNull(methodParameter.getNameIdentifier()), JavaAnalysisBundle.message("suspicious.invocation.handler.implementation.method.unused.message")); return; } InvocationHandlerAnalysisRunner runner = new InvocationHandlerAnalysisRunner(holder, body, methodParameter); DfaVariableValue methodName = runner.myDfaMethodName; if (methodName == null) return; Map<String, Map<PsiExpression, DfType>> returnMap = new TreeMap<>(); RunnerResult result = runner.analyzeMethod(body, new JavaDfaListener() { @Override public void beforeValueReturn(@NotNull DfaValue value, @Nullable PsiExpression expression, @NotNull PsiElement context, @NotNull DfaMemoryState state) { if (context != method) return; String name = state.getDfType(methodName).getConstantOfType(String.class); if (name == null) { runner.cancel(); return; } DfType type = state.getDfType(value); if (type instanceof DfPrimitiveType) { type = DfTypes.typedObject(((DfPrimitiveType)type).getPsiType().getBoxedType(body), Nullability.NOT_NULL); } returnMap.computeIfAbsent(name, k -> new HashMap<>()).merge(expression, type, DfType::join); } }); if (result != RunnerResult.OK) return; Set<PsiExpression> reportedAnchors = new HashSet<>(); returnMap.forEach((name, map) -> { DfType reduced = map.values().stream().reduce(DfType.BOTTOM, DfType::join); PsiType wantedType = getWantedType(body, name); if (wantedType == null) return; TypeConstraint wantedConstraint = TypeConstraints.exact(wantedType); if (reduced.meet(wantedConstraint.asDfType().meet(DfTypes.NOT_NULL_OBJECT)) != DfType.BOTTOM && StreamEx.ofValues(map).map(DfaNullability::fromDfType).anyMatch( n -> n != DfaNullability.NULLABLE && n != DfaNullability.NULL)) { return; } map.forEach((expression, type) -> { if (reportedAnchors.contains(expression)) return; String message = null; TypeConstraint constraint = TypeConstraint.fromDfType(type); if (wantedConstraint.meet(constraint) == TypeConstraints.BOTTOM) { message = JavaAnalysisBundle.message("suspicious.invocation.handler.implementation.type.mismatch.message", name, wantedConstraint.getPresentationText(null), constraint.getPresentationText(null)); } DfaNullability nullability = DfaNullability.fromDfType(type); if (nullability == DfaNullability.NULL) { message = name.equals("toString") ? JavaAnalysisBundle.message("suspicious.invocation.handler.implementation.null.returned.for.toString.message") : JavaAnalysisBundle.message("suspicious.invocation.handler.implementation.null.returned.message", name); } if (message != null) { reportedAnchors.add(expression); holder.registerProblem(expression, message); } }); }); } private static @Nullable PsiType getWantedType(PsiElement body, String name) { return switch (name) { case "equals" -> PsiTypes.booleanType().getBoxedType(body); case "hashCode" -> PsiTypes.intType().getBoxedType(body); case "toString" -> PsiType.getJavaLangString(body.getManager(), body.getResolveScope()); default -> null; }; } }; }