From 8cc1360b740a63a675f6ec388d8142ceb92ac067 Mon Sep 17 00:00:00 2001 From: Eric Anderson Date: Mon, 16 May 2022 10:46:16 -0700 Subject: [PATCH] api: Ignore ClassCastExceptions for hard-coded providers This was observed in the Bazel/Blaze build where io.grpc.util is a separate target from the rest of core. During the build of a library SecretRoundRobinLoadBalancerProvider was not on the classpath, and the library was later included into a binary using grpc-core from Maven Central which includes SecretRoundRobinLoadBalancerProvider. ``` java.util.ServiceConfigurationError: Provider io.grpc.util.SecretRoundRobinLoadBalancerProvider$Provider could not be instantiated java.lang.ClassCastException: class io.grpc.util.SecretRoundRobinLoadBalancerProvider$Provider cannot be cast to some.app.aaa.aab ``` --- .../main/java/io/grpc/ServiceProviders.java | 17 +++++++++-- .../java/io/grpc/ServiceProvidersTest.java | 28 +++++++++++++++---- 2 files changed, 36 insertions(+), 9 deletions(-) diff --git a/api/src/main/java/io/grpc/ServiceProviders.java b/api/src/main/java/io/grpc/ServiceProviders.java index 08837bcf5cc..ac4b27d8783 100644 --- a/api/src/main/java/io/grpc/ServiceProviders.java +++ b/api/src/main/java/io/grpc/ServiceProviders.java @@ -122,15 +122,26 @@ public static Iterable getCandidatesViaServiceLoader(Class klass, Clas static Iterable getCandidatesViaHardCoded(Class klass, Iterable> hardcoded) { List list = new ArrayList<>(); for (Class candidate : hardcoded) { - list.add(create(klass, candidate)); + T t = createForHardCoded(klass, candidate); + if (t == null) { + continue; + } + list.add(t); } return list; } - @VisibleForTesting - static T create(Class klass, Class rawClass) { + private static T createForHardCoded(Class klass, Class rawClass) { try { return rawClass.asSubclass(klass).getConstructor().newInstance(); + } catch (ClassCastException ex) { + // Tools like Proguard that perform obfuscation rewrite strings only when the class they + // reference is known, as otherwise they wouldn't know its new name. This means some + // hard-coded Class.forNames() won't be rewritten. This can cause ClassCastException at + // runtime if the class ends up appearing on the classpath but that class is part of a + // separate copy of grpc. With tools like Maven Shade Plugin the class wouldn't be found at + // all and so would be skipped. We want to skip in this case as well. + return null; } catch (Throwable t) { throw new ServiceConfigurationError( String.format("Provider %s could not be instantiated %s", rawClass.getName(), t), t); diff --git a/api/src/test/java/io/grpc/ServiceProvidersTest.java b/api/src/test/java/io/grpc/ServiceProvidersTest.java index 4bbfe2117be..7d4388a5bb9 100644 --- a/api/src/test/java/io/grpc/ServiceProvidersTest.java +++ b/api/src/test/java/io/grpc/ServiceProvidersTest.java @@ -215,19 +215,35 @@ public void getCandidatesViaHardCoded_failAtInit_moreCandidates() throws Excepti } @Test - public void create_throwsErrorOnMisconfiguration() throws Exception { - class PrivateClass {} + public void getCandidatesViaHardCoded_throwsErrorOnMisconfiguration() throws Exception { + class PrivateClass extends BaseProvider { + private PrivateClass() { + super(true, 5); + } + } try { - ServiceProviders.create( - ServiceProvidersTestAbstractProvider.class, PrivateClass.class); + ServiceProviders.getCandidatesViaHardCoded( + ServiceProvidersTestAbstractProvider.class, + Collections.>singletonList(PrivateClass.class)); fail("Expected exception"); } catch (ServiceConfigurationError expected) { - assertTrue("Expected ClassCastException cause: " + expected.getCause(), - expected.getCause() instanceof ClassCastException); + assertTrue("Expected NoSuchMethodException cause: " + expected.getCause(), + expected.getCause() instanceof NoSuchMethodException); } } + @Test + public void getCandidatesViaHardCoded_skipsWrongClassType() throws Exception { + class RandomClass {} + + Iterable candidates = + ServiceProviders.getCandidatesViaHardCoded( + ServiceProvidersTestAbstractProvider.class, + Collections.>singletonList(RandomClass.class)); + assertFalse(candidates.iterator().hasNext()); + } + private static class BaseProvider extends ServiceProvidersTestAbstractProvider { private final boolean isAvailable; private final int priority;