2626import static com .google .errorprone .fixes .SuggestedFix .emptyFix ;
2727import static com .google .errorprone .fixes .SuggestedFixes .replaceIncludingComments ;
2828import static com .google .errorprone .matchers .Matchers .SERIALIZATION_METHODS ;
29+ import static com .google .errorprone .matchers .method .MethodMatchers .instanceMethod ;
30+ import static com .google .errorprone .matchers .method .MethodMatchers .staticMethod ;
2931import static com .google .errorprone .suppliers .Suppliers .typeFromString ;
3032import static com .google .errorprone .util .ASTHelpers .canBeRemoved ;
33+ import static com .google .errorprone .util .ASTHelpers .constValue ;
3134import static com .google .errorprone .util .ASTHelpers .getEnclosedElements ;
35+ import static com .google .errorprone .util .ASTHelpers .getReceiver ;
3236import static com .google .errorprone .util .ASTHelpers .getSymbol ;
3337import static com .google .errorprone .util .ASTHelpers .getType ;
3438import static com .google .errorprone .util .ASTHelpers .hasAnnotation ;
3539import static com .google .errorprone .util .ASTHelpers .isGeneratedConstructor ;
3640import static com .google .errorprone .util .ASTHelpers .isRecord ;
41+ import static com .google .errorprone .util .ASTHelpers .isSameType ;
3742import static com .google .errorprone .util .ASTHelpers .isSubtype ;
3843import static com .google .errorprone .util .MoreAnnotations .asStrings ;
3944import static com .google .errorprone .util .MoreAnnotations .getAnnotationValue ;
4348import static javax .lang .model .element .Modifier .FINAL ;
4449
4550import com .google .common .base .Ascii ;
51+ import com .google .common .collect .ImmutableList ;
4652import com .google .common .collect .ImmutableListMultimap ;
4753import com .google .common .collect .ImmutableSet ;
4854import com .google .errorprone .BugPattern ;
4955import com .google .errorprone .VisitorState ;
5056import com .google .errorprone .bugpatterns .BugChecker .CompilationUnitTreeMatcher ;
5157import com .google .errorprone .fixes .SuggestedFix ;
5258import com .google .errorprone .matchers .Description ;
59+ import com .google .errorprone .matchers .Matcher ;
5360import com .google .errorprone .suppliers .Supplier ;
5461import com .google .errorprone .suppliers .Suppliers ;
5562import com .google .errorprone .util .ASTHelpers ;
5865import com .sun .source .tree .ClassTree ;
5966import com .sun .source .tree .CompilationUnitTree ;
6067import com .sun .source .tree .DeconstructionPatternTree ;
68+ import com .sun .source .tree .ExpressionTree ;
6169import com .sun .source .tree .IdentifierTree ;
6270import com .sun .source .tree .MemberReferenceTree ;
6371import com .sun .source .tree .MemberSelectTree ;
7179import com .sun .tools .javac .code .Symbol ;
7280import com .sun .tools .javac .code .Symbol .ClassSymbol ;
7381import com .sun .tools .javac .code .Symbol .MethodSymbol ;
82+ import com .sun .tools .javac .code .Symbol .VarSymbol ;
7483import com .sun .tools .javac .code .Type ;
7584import com .sun .tools .javac .tree .JCTree .JCAnnotation ;
7685import com .sun .tools .javac .tree .JCTree .JCAssign ;
8493import javax .inject .Inject ;
8594import javax .lang .model .element .Modifier ;
8695import javax .lang .model .element .Name ;
96+ import org .jspecify .annotations .Nullable ;
8797
8898/** Bugpattern to detect unused declarations. */
8999@ BugPattern (
@@ -99,6 +109,29 @@ public final class UnusedMethod extends BugChecker implements CompilationUnitTre
99109 private static final Supplier <Type > JUNIT_PARAMS_ANNOTATION_TYPE =
100110 Suppliers .typeFromString ("junitparams.Parameters" );
101111
112+ private static final Matcher <ExpressionTree > LOOKUP_FIND_METHOD =
113+ instanceMethod ()
114+ .onDescendantOf ("java.lang.invoke.MethodHandles.Lookup" )
115+ .namedAnyOf ("findStatic" , "findVirtual" , "findSpecial" );
116+
117+ private static final Matcher <ExpressionTree > LOOKUP_FIND_CONSTRUCTOR =
118+ instanceMethod ()
119+ .onDescendantOf ("java.lang.invoke.MethodHandles.Lookup" )
120+ .named ("findConstructor" );
121+
122+ private static final Matcher <ExpressionTree > CLASS_GET_METHOD =
123+ instanceMethod ()
124+ .onDescendantOf ("java.lang.Class" )
125+ .namedAnyOf ("getMethod" , "getDeclaredMethod" );
126+
127+ private static final Matcher <ExpressionTree > CLASS_GET_CONSTRUCTOR =
128+ instanceMethod ()
129+ .onDescendantOf ("java.lang.Class" )
130+ .namedAnyOf ("getConstructor" , "getDeclaredConstructor" );
131+
132+ private static final Matcher <ExpressionTree > METHOD_TYPE_FACTORY =
133+ staticMethod ().onClass ("java.lang.invoke.MethodType" ).named ("methodType" );
134+
102135 /**
103136 * Class annotations which exempt methods within the annotated class from findings.
104137 *
@@ -272,6 +305,7 @@ public Void visitMemberReference(MemberReferenceTree tree, Void unused) {
272305 @ Override
273306 public Void visitMethodInvocation (MethodInvocationTree tree , Void unused ) {
274307 handle (getSymbol (tree ));
308+ handleReflectiveAccess (tree );
275309 return super .visitMethodInvocation (tree , null );
276310 }
277311
@@ -312,6 +346,122 @@ private void handle(Symbol symbol) {
312346 }
313347 }
314348
349+ private void handleReflectiveAccess (MethodInvocationTree tree ) {
350+ if (LOOKUP_FIND_METHOD .matches (tree , state )) {
351+ // findStatic/findVirtual/findSpecial(Class<?> refc, String name, MethodType type, ...)
352+ List <? extends ExpressionTree > args = tree .getArguments ();
353+ Type ownerType = extractTypeFromClassLiteral (args .get (0 ));
354+ String name = constValue (args .get (1 ), String .class );
355+ ImmutableList <Type > paramTypes = extractParamTypesFromMethodType (args .get (2 ));
356+ if (ownerType == null || name == null || paramTypes == null ) {
357+ return ;
358+ }
359+ removeReflectivelyAccessedMethod (name , ownerType , paramTypes );
360+ } else if (LOOKUP_FIND_CONSTRUCTOR .matches (tree , state )) {
361+ // findConstructor(Class<?> refc, MethodType type)
362+ List <? extends ExpressionTree > args = tree .getArguments ();
363+ Type ownerType = extractTypeFromClassLiteral (args .get (0 ));
364+ ImmutableList <Type > paramTypes = extractParamTypesFromMethodType (args .get (1 ));
365+ if (ownerType == null || paramTypes == null ) {
366+ return ;
367+ }
368+ removeReflectivelyAccessedMethod (null , ownerType , paramTypes );
369+ } else if (CLASS_GET_METHOD .matches (tree , state )) {
370+ // getMethod/getDeclaredMethod(String name, Class<?>... parameterTypes)
371+ List <? extends ExpressionTree > args = tree .getArguments ();
372+ Type ownerType = extractClassOwnerFromReceiver (tree );
373+ String name = constValue (args .get (0 ), String .class );
374+ ImmutableList <Type > paramTypes = extractClassLiteralVarargs (args , 1 );
375+ if (ownerType == null || name == null || paramTypes == null ) {
376+ return ;
377+ }
378+ removeReflectivelyAccessedMethod (name , ownerType , paramTypes );
379+ } else if (CLASS_GET_CONSTRUCTOR .matches (tree , state )) {
380+ // getConstructor/getDeclaredConstructor(Class<?>... parameterTypes)
381+ List <? extends ExpressionTree > args = tree .getArguments ();
382+ Type ownerType = extractClassOwnerFromReceiver (tree );
383+ ImmutableList <Type > paramTypes = extractClassLiteralVarargs (args , 0 );
384+ if (ownerType == null || paramTypes == null ) {
385+ return ;
386+ }
387+ removeReflectivelyAccessedMethod (null , ownerType , paramTypes );
388+ }
389+ }
390+
391+ private @ Nullable Type extractTypeFromClassLiteral (ExpressionTree expr ) {
392+ if (!(expr instanceof MemberSelectTree select )) {
393+ return null ;
394+ }
395+ if (!select .getIdentifier ().contentEquals ("class" )) {
396+ return null ;
397+ }
398+ return getType (select .getExpression ());
399+ }
400+
401+ private @ Nullable ImmutableList <Type > extractParamTypesFromMethodType (ExpressionTree expr ) {
402+ if (!(expr instanceof MethodInvocationTree call )) {
403+ return null ;
404+ }
405+ if (!METHOD_TYPE_FACTORY .matches (call , state )) {
406+ return null ;
407+ }
408+ return extractClassLiteralVarargs (call .getArguments (), 1 );
409+ }
410+
411+ private @ Nullable Type extractClassOwnerFromReceiver (MethodInvocationTree tree ) {
412+ ExpressionTree receiver = getReceiver (tree );
413+ if (receiver == null ) {
414+ return null ;
415+ }
416+ return extractTypeFromClassLiteral (receiver );
417+ }
418+
419+ private @ Nullable ImmutableList <Type > extractClassLiteralVarargs (
420+ List <? extends ExpressionTree > args , int startIndex ) {
421+ ImmutableList .Builder <Type > builder = ImmutableList .builder ();
422+ for (int i = startIndex ; i < args .size (); i ++) {
423+ Type t = extractTypeFromClassLiteral (args .get (i ));
424+ if (t == null ) {
425+ return null ;
426+ }
427+ builder .add (t );
428+ }
429+ return builder .build ();
430+ }
431+
432+ private void removeReflectivelyAccessedMethod (
433+ @ Nullable String name , Type ownerType , ImmutableList <Type > paramTypes ) {
434+ unusedMethods
435+ .keySet ()
436+ .removeIf (sym -> methodMatches ((MethodSymbol ) sym , name , ownerType , paramTypes ));
437+ }
438+
439+ private boolean methodMatches (
440+ MethodSymbol sym , @ Nullable String name , Type ownerType , ImmutableList <Type > paramTypes ) {
441+ if (name == null ) {
442+ if (!sym .isConstructor ()) {
443+ return false ;
444+ }
445+ } else {
446+ if (sym .isConstructor () || !sym .getSimpleName ().contentEquals (name )) {
447+ return false ;
448+ }
449+ }
450+ if (!isSameType (sym .owner .type , ownerType , state )) {
451+ return false ;
452+ }
453+ List <VarSymbol > params = sym .params ();
454+ if (params .size () != paramTypes .size ()) {
455+ return false ;
456+ }
457+ for (int i = 0 ; i < params .size (); i ++) {
458+ if (!isSameType (params .get (i ).type , paramTypes .get (i ), state )) {
459+ return false ;
460+ }
461+ }
462+ return true ;
463+ }
464+
315465 @ Override
316466 public Void visitMethod (MethodTree tree , Void unused ) {
317467 handleMethodSource (tree );
0 commit comments