From c442157e07706ff17f655b4c216722a510cae195 Mon Sep 17 00:00:00 2001 From: Georgios Andrianakis Date: Fri, 1 Nov 2019 00:57:10 +0200 Subject: [PATCH] Provide the ability to remove final flag from methods of CDI beans We need security annotations to result in the creation of an interceptor which is not possible when a method is final. The solution we follow is to remove the final modifier from methods that need intercepting. This is now configurable with the default value true, meaning that Arc will remove the final flag. If the value is set to false Arc throws an exception at build time. Fixes: #5051 --- .../io/quarkus/arc/deployment/ArcConfig.java | 10 +++ .../quarkus/arc/deployment/ArcProcessor.java | 16 +++- .../test/cdi/BeanWithSecuredFinalMethod.java | 19 ++++ .../SecurityAnnotationOnFinalMethodTest.java | 45 ++++++++++ ...inalMethodWithDisableFinalRemovalTest.java | 39 +++++++++ .../quarkus/arc/processor/BeanDeployment.java | 13 +-- .../io/quarkus/arc/processor/BeanInfo.java | 10 ++- .../quarkus/arc/processor/BeanProcessor.java | 28 ++++-- .../arc/processor/BytecodeTransformer.java | 24 ++++++ .../io/quarkus/arc/processor/Methods.java | 86 +++++++++++++++++-- .../io/quarkus/arc/processor/TypesTest.java | 8 +- 11 files changed, 268 insertions(+), 30 deletions(-) create mode 100644 extensions/security/deployment/src/test/java/io/quarkus/security/test/cdi/BeanWithSecuredFinalMethod.java create mode 100644 extensions/security/deployment/src/test/java/io/quarkus/security/test/cdi/SecurityAnnotationOnFinalMethodTest.java create mode 100644 extensions/security/deployment/src/test/java/io/quarkus/security/test/cdi/SecurityAnnotationOnFinalMethodWithDisableFinalRemovalTest.java create mode 100644 independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BytecodeTransformer.java diff --git a/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/ArcConfig.java b/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/ArcConfig.java index a4329145212ab..72fd88468b7e4 100644 --- a/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/ArcConfig.java +++ b/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/ArcConfig.java @@ -50,6 +50,16 @@ public class ArcConfig { @ConfigItem(defaultValue = "true") public boolean autoInjectFields; + /** + * If set to true, Arc will transform the bytecode of beans containing methods that need to be proxyable + * but have been declared as final. The transformation is simply a matter of removing final. + * This ensures that a proxy can be created properly. + * If the value is set to false, then an exception is thrown at build time indicating + * that a proxy could not be created because a method was final. + */ + @ConfigItem(defaultValue = "true") + public boolean removeFinalForProxyableMethods; + public final boolean isRemoveUnusedBeansFieldValid() { return ALLOWED_REMOVE_UNUSED_BEANS_VALUES.contains(removeUnusedBeans.toLowerCase()); } diff --git a/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/ArcProcessor.java b/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/ArcProcessor.java index 483890b4c70c9..04426b90b3819 100644 --- a/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/ArcProcessor.java +++ b/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/ArcProcessor.java @@ -9,6 +9,7 @@ import java.util.Map; import java.util.Optional; import java.util.Set; +import java.util.function.Consumer; import java.util.function.Predicate; import java.util.stream.Collectors; @@ -33,6 +34,7 @@ import io.quarkus.arc.processor.BeanInfo; import io.quarkus.arc.processor.BeanProcessor; import io.quarkus.arc.processor.BuiltinScope; +import io.quarkus.arc.processor.BytecodeTransformer; import io.quarkus.arc.processor.ContextConfigurator; import io.quarkus.arc.processor.ContextRegistrar; import io.quarkus.arc.processor.ReflectionRegistration; @@ -47,6 +49,7 @@ import io.quarkus.deployment.annotations.Record; import io.quarkus.deployment.builditem.ApplicationArchivesBuildItem; import io.quarkus.deployment.builditem.ApplicationClassPredicateBuildItem; +import io.quarkus.deployment.builditem.BytecodeTransformerBuildItem; import io.quarkus.deployment.builditem.ExecutorBuildItem; import io.quarkus.deployment.builditem.FeatureBuildItem; import io.quarkus.deployment.builditem.GeneratedClassBuildItem; @@ -214,6 +217,7 @@ public boolean test(BeanInfo bean) { } }); } + builder.setRemoveFinalFromProxyableMethods(arcConfig.removeFinalForProxyableMethods); BeanProcessor beanProcessor = builder.build(); ContextRegistrar.RegistrationContext context = beanProcessor.registerCustomContexts(); @@ -239,7 +243,8 @@ public BeanRegistrationPhaseBuildItem registerBeans(ContextRegistrationPhaseBuil // PHASE 3 - initialize and validate the bean deployment @BuildStep public ValidationPhaseBuildItem validate(BeanRegistrationPhaseBuildItem beanRegistrationPhase, - List beanConfigurators) { + List beanConfigurators, + BuildProducer bytecodeTransformer) { for (BeanConfiguratorBuildItem beanConfigurator : beanConfigurators) { for (BeanConfigurator value : beanConfigurator.getValues()) { @@ -248,7 +253,12 @@ public ValidationPhaseBuildItem validate(BeanRegistrationPhaseBuildItem beanRegi } } - beanRegistrationPhase.getBeanProcessor().initialize(); + beanRegistrationPhase.getBeanProcessor().initialize(new Consumer() { + @Override + public void accept(BytecodeTransformer t) { + bytecodeTransformer.produce(new BytecodeTransformerBuildItem(t.getClassToTransform(), t.getVisitorFunction())); + } + }); return new ValidationPhaseBuildItem(beanRegistrationPhase.getBeanProcessor().validate(), beanRegistrationPhase.getBeanProcessor()); } @@ -364,4 +374,4 @@ public boolean test(T t) { return false; } } -} \ No newline at end of file +} diff --git a/extensions/security/deployment/src/test/java/io/quarkus/security/test/cdi/BeanWithSecuredFinalMethod.java b/extensions/security/deployment/src/test/java/io/quarkus/security/test/cdi/BeanWithSecuredFinalMethod.java new file mode 100644 index 0000000000000..bee22955535f4 --- /dev/null +++ b/extensions/security/deployment/src/test/java/io/quarkus/security/test/cdi/BeanWithSecuredFinalMethod.java @@ -0,0 +1,19 @@ +package io.quarkus.security.test.cdi; + +import javax.annotation.security.DenyAll; +import javax.annotation.security.RolesAllowed; +import javax.inject.Singleton; + +@Singleton +public class BeanWithSecuredFinalMethod { + + @RolesAllowed("admin") + public final String securedMethod() { + return "accessibleForAdminOnly"; + } + + @DenyAll + public final String otherSecuredMethod(String input) { + return "denied"; + } +} diff --git a/extensions/security/deployment/src/test/java/io/quarkus/security/test/cdi/SecurityAnnotationOnFinalMethodTest.java b/extensions/security/deployment/src/test/java/io/quarkus/security/test/cdi/SecurityAnnotationOnFinalMethodTest.java new file mode 100644 index 0000000000000..c528784863279 --- /dev/null +++ b/extensions/security/deployment/src/test/java/io/quarkus/security/test/cdi/SecurityAnnotationOnFinalMethodTest.java @@ -0,0 +1,45 @@ +package io.quarkus.security.test.cdi; + +import static io.quarkus.security.test.cdi.SecurityTestUtils.assertFailureFor; +import static io.quarkus.security.test.cdi.SecurityTestUtils.assertSuccess; +import static io.quarkus.security.test.utils.IdentityMock.ADMIN; +import static io.quarkus.security.test.utils.IdentityMock.ANONYMOUS; +import static io.quarkus.security.test.utils.IdentityMock.USER; + +import javax.inject.Inject; + +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.security.ForbiddenException; +import io.quarkus.security.UnauthorizedException; +import io.quarkus.security.test.utils.AuthData; +import io.quarkus.security.test.utils.IdentityMock; +import io.quarkus.test.QuarkusUnitTest; + +public class SecurityAnnotationOnFinalMethodTest { + + @RegisterExtension + static final QuarkusUnitTest config = new QuarkusUnitTest() + .setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class) + .addClasses(BeanWithSecuredFinalMethod.class, IdentityMock.class, + AuthData.class, SecurityTestUtils.class)); + + @Inject + BeanWithSecuredFinalMethod bean; + + @Test + public void shouldRestrictAccessToSpecificRole() { + assertFailureFor(() -> bean.securedMethod(), UnauthorizedException.class, ANONYMOUS); + assertSuccess(() -> bean.securedMethod(), "accessibleForAdminOnly", ADMIN); + } + + @Test + public void shouldFailToAccessCompletely() { + assertFailureFor(() -> bean.otherSecuredMethod("whatever"), UnauthorizedException.class, ANONYMOUS); + assertFailureFor(() -> bean.otherSecuredMethod("whatever"), ForbiddenException.class, USER, ADMIN); + } + +} diff --git a/extensions/security/deployment/src/test/java/io/quarkus/security/test/cdi/SecurityAnnotationOnFinalMethodWithDisableFinalRemovalTest.java b/extensions/security/deployment/src/test/java/io/quarkus/security/test/cdi/SecurityAnnotationOnFinalMethodWithDisableFinalRemovalTest.java new file mode 100644 index 0000000000000..aafe5e031393c --- /dev/null +++ b/extensions/security/deployment/src/test/java/io/quarkus/security/test/cdi/SecurityAnnotationOnFinalMethodWithDisableFinalRemovalTest.java @@ -0,0 +1,39 @@ +package io.quarkus.security.test.cdi; + +import static org.junit.jupiter.api.Assertions.fail; + +import javax.enterprise.inject.spi.DeploymentException; +import javax.inject.Inject; + +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.asset.StringAsset; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.security.test.utils.AuthData; +import io.quarkus.security.test.utils.IdentityMock; +import io.quarkus.test.QuarkusUnitTest; + +public class SecurityAnnotationOnFinalMethodWithDisableFinalRemovalTest { + + @RegisterExtension + static final QuarkusUnitTest config = new QuarkusUnitTest() + .setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class) + .addClasses(BeanWithSecuredFinalMethod.class, IdentityMock.class, + AuthData.class, SecurityTestUtils.class) + .addAsResource(new StringAsset( + "quarkus.arc.remove-final-for-proxyable-methods=false"), + "application.properties")) + .setExpectedException(DeploymentException.class); + + @Inject + BeanWithSecuredFinalMethod bean; + + @Test + public void test() { + // should never be executed since the application should not be built + fail(); + } + +} diff --git a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanDeployment.java b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanDeployment.java index 11148f0bb3c11..af993182d193a 100644 --- a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanDeployment.java +++ b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanDeployment.java @@ -25,6 +25,7 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.CopyOnWriteArraySet; +import java.util.function.Consumer; import java.util.function.Function; import java.util.function.Predicate; import java.util.stream.Collectors; @@ -88,11 +89,12 @@ public class BeanDeployment { private final Map> customContexts; private final Collection beanDefiningAnnotations; + private final boolean removeFinalForProxyableMethods; BeanDeployment(IndexView index, Collection additionalBeanDefiningAnnotations, List annotationTransformers) { this(index, additionalBeanDefiningAnnotations, annotationTransformers, Collections.emptyList(), Collections.emptyList(), - null, false, null, Collections.emptyMap(), Collections.emptyList()); + null, false, null, Collections.emptyMap(), Collections.emptyList(), false); } BeanDeployment(IndexView index, Collection additionalBeanDefiningAnnotations, @@ -101,7 +103,7 @@ public class BeanDeployment { Collection resourceAnnotations, BuildContextImpl buildContext, boolean removeUnusedBeans, List> unusedExclusions, Map> additionalStereotypes, - List bindingRegistrars) { + List bindingRegistrars, boolean removeFinalForProxyableMethods) { this.buildContext = buildContext; Set beanDefiningAnnotations = new HashSet<>(); if (additionalBeanDefiningAnnotations != null) { @@ -154,6 +156,7 @@ public class BeanDeployment { this.beanResolver = new BeanResolver(this); this.interceptorResolver = new InterceptorResolver(this); + this.removeFinalForProxyableMethods = removeFinalForProxyableMethods; } ContextRegistrar.RegistrationContext registerCustomContexts(List contextRegistrars) { @@ -203,19 +206,19 @@ BeanRegistrar.RegistrationContext registerBeans(List beanRegistra return registerSyntheticBeans(beanRegistrars, buildContext); } - void init() { + void init(Consumer bytecodeTransformerConsumer) { long start = System.currentTimeMillis(); // Collect dependency resolution errors List errors = new ArrayList<>(); for (BeanInfo bean : beans) { - bean.init(errors); + bean.init(errors, bytecodeTransformerConsumer, removeFinalForProxyableMethods); } for (ObserverInfo observer : observers) { observer.init(errors); } for (InterceptorInfo interceptor : interceptors) { - interceptor.init(errors); + interceptor.init(errors, bytecodeTransformerConsumer, removeFinalForProxyableMethods); } processErrors(errors); diff --git a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanInfo.java b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanInfo.java index 91098f1ba09ea..09d26ee8bb389 100644 --- a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanInfo.java +++ b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanInfo.java @@ -398,7 +398,8 @@ void validate(List errors, List validators) } } - void init(List errors) { + void init(List errors, Consumer bytecodeTransformerConsumer, + boolean removeFinalForProxyableMethods) { for (Injection injection : injections) { for (InjectionPointInfo injectionPoint : injection.injectionPoints) { Beans.resolveInjectionPoint(beanDeployment, this, injectionPoint, errors); @@ -407,7 +408,7 @@ void init(List errors) { if (disposer != null) { disposer.init(errors); } - interceptedMethods.putAll(initInterceptedMethods(errors)); + interceptedMethods.putAll(initInterceptedMethods(errors, bytecodeTransformerConsumer, removeFinalForProxyableMethods)); if (errors.isEmpty()) { lifecycleInterceptors.putAll(initLifecycleInterceptors()); } @@ -425,7 +426,8 @@ protected String getType() { } } - private Map initInterceptedMethods(List errors) { + private Map initInterceptedMethods(List errors, + Consumer bytecodeTransformerConsumer, boolean removeFinalForProxyableMethods) { if (!isInterceptor() && isClassBean()) { Map interceptedMethods = new HashMap<>(); Map> candidates = new HashMap<>(); @@ -439,7 +441,7 @@ private Map initInterceptedMethods(List } Set finalMethods = Methods.addInterceptedMethodCandidates(beanDeployment, target.get().asClass(), - candidates, classLevelBindings); + candidates, classLevelBindings, bytecodeTransformerConsumer, removeFinalForProxyableMethods); if (!finalMethods.isEmpty()) { errors.add(new DeploymentException(String.format( "Bean %s has a bound interceptor and must not declare final methods:\n\t- %s", getBeanClass(), diff --git a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanProcessor.java b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanProcessor.java index 206d0a1e1406f..66358dfe3635b 100644 --- a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanProcessor.java +++ b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanProcessor.java @@ -15,6 +15,7 @@ import java.util.Map; import java.util.Objects; import java.util.concurrent.ConcurrentHashMap; +import java.util.function.Consumer; import java.util.function.Predicate; import java.util.stream.Collectors; import org.jboss.jandex.AnnotationInstance; @@ -29,7 +30,7 @@ *
    *
  1. {@link #registerCustomContexts()}
  2. *
  3. {@link #registerBeans()}
  4. - *
  5. {@link #initialize()}
  6. + *
  7. {@link #initialize(Consumer)}
  8. *
  9. {@link #validate()}
  10. *
  11. {@link #processValidationErrors(io.quarkus.arc.processor.BeanDeploymentValidator.ValidationContext)}
  12. *
  13. {@link #generateResources(ReflectionRegistration)}
  14. @@ -73,7 +74,8 @@ private BeanProcessor(String name, IndexView index, Collection beanDeploymentValidators, Predicate applicationClassPredicate, boolean unusedBeansRemovalEnabled, List> unusedExclusions, Map> additionalStereotypes, - List interceptorBindingRegistrars) { + List interceptorBindingRegistrars, + boolean removeFinalForProxyableMethods) { this.reflectionRegistration = reflectionRegistration; this.applicationClassPredicate = applicationClassPredicate; this.name = name; @@ -91,7 +93,7 @@ private BeanProcessor(String name, IndexView index, Collection bytecodeTransformerConsumer) { + beanDeployment.init(bytecodeTransformerConsumer); } public BeanDeploymentValidator.ValidationContext validate() { @@ -201,7 +203,12 @@ public BeanDeployment getBeanDeployment() { public BeanDeployment process() throws IOException { registerCustomContexts(); registerBeans(); - initialize(); + initialize(new Consumer() { + @Override + public void accept(BytecodeTransformer transformer) { + + } + }); ValidationContext validationContext = validate(); processValidationErrors(validationContext); generateResources(null); @@ -242,6 +249,8 @@ public boolean test(DotName dotName) { } }; + private boolean removeFinalForProxyableMethods; + public Builder setName(String name) { this.name = name; return this; @@ -353,12 +362,17 @@ public Builder addRemovalExclusion(Predicate exclusion) { return this; } + public Builder setRemoveFinalFromProxyableMethods(boolean removeFinalForProxyableMethods) { + this.removeFinalForProxyableMethods = removeFinalForProxyableMethods; + return this; + } + public BeanProcessor build() { return new BeanProcessor(name, index, additionalBeanDefiningAnnotations, output, sharedAnnotationLiterals, reflectionRegistration, annotationTransformers, injectionPointTransformers, resourceAnnotations, beanRegistrars, contextRegistrars, beanDeploymentValidators, applicationClassPredicate, removeUnusedBeans, removalExclusions, additionalStereotypes, - additionalInterceptorBindingRegistrars); + additionalInterceptorBindingRegistrars, removeFinalForProxyableMethods); } } diff --git a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BytecodeTransformer.java b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BytecodeTransformer.java new file mode 100644 index 0000000000000..ed939481d353a --- /dev/null +++ b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BytecodeTransformer.java @@ -0,0 +1,24 @@ +package io.quarkus.arc.processor; + +import java.util.function.BiFunction; +import org.objectweb.asm.ClassVisitor; + +public class BytecodeTransformer { + + final String classToTransform; + final BiFunction visitorFunction; + + public BytecodeTransformer(String classToTransform, + BiFunction visitorFunction) { + this.classToTransform = classToTransform; + this.visitorFunction = visitorFunction; + } + + public String getClassToTransform() { + return classToTransform; + } + + public BiFunction getVisitorFunction() { + return visitorFunction; + } +} diff --git a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/Methods.java b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/Methods.java index 5a1e23d383703..7167cd2610f24 100644 --- a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/Methods.java +++ b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/Methods.java @@ -2,13 +2,17 @@ import static io.quarkus.arc.processor.IndexClassLookupUtils.getClassByName; +import io.quarkus.gizmo.DescriptorUtils; import java.lang.reflect.Modifier; import java.util.ArrayList; import java.util.Collection; import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.Set; +import java.util.function.BiFunction; +import java.util.function.Consumer; import java.util.stream.Collectors; import org.jboss.jandex.AnnotationInstance; import org.jboss.jandex.ClassInfo; @@ -18,6 +22,9 @@ import org.jboss.jandex.Type; import org.jboss.jandex.TypeVariable; import org.jboss.logging.Logger; +import org.objectweb.asm.ClassVisitor; +import org.objectweb.asm.MethodVisitor; +import org.objectweb.asm.Opcodes; /** * @@ -115,8 +122,11 @@ static boolean isObjectToString(MethodInfo method) { static Set addInterceptedMethodCandidates(BeanDeployment beanDeployment, ClassInfo classInfo, Map> candidates, - List classLevelBindings) { - Set finalMethods = new HashSet<>(); + List classLevelBindings, Consumer bytecodeTransformerConsumer, + boolean removeFinalForProxyableMethods) { + + Set methodsFromWhichToRemoveFinal = new HashSet<>(); + Set finalMethodsFoundAndNotChanged = new HashSet<>(); for (MethodInfo method : classInfo.methods()) { if (skipForSubclass(method)) { continue; @@ -134,21 +144,83 @@ static Set addInterceptedMethodCandidates(BeanDeployment beanDeploym } } if (!merged.isEmpty()) { + boolean addToCandidates = true; if (Modifier.isFinal(method.flags())) { - finalMethods.add(method); - } else { + if (removeFinalForProxyableMethods) { + methodsFromWhichToRemoveFinal.add(NameAndDescriptor.fromMethodInfo(method)); + } else { + addToCandidates = false; + finalMethodsFoundAndNotChanged.add(method); + } + } + if (addToCandidates) { candidates.computeIfAbsent(new Methods.MethodKey(method), key -> merged); } } } + if (!methodsFromWhichToRemoveFinal.isEmpty()) { + bytecodeTransformerConsumer.accept( + new BytecodeTransformer(classInfo.name().toString(), new BiFunction() { + @Override + public ClassVisitor apply(String s, ClassVisitor classVisitor) { + return new ClassVisitor(Opcodes.ASM7, classVisitor) { + @Override + public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, + String[] exceptions) { + if (methodsFromWhichToRemoveFinal.contains(new NameAndDescriptor(name, descriptor))) { + access = access & (~Opcodes.ACC_FINAL); + } + return super.visitMethod(access, name, descriptor, signature, exceptions); + } + }; + } + })); + } if (classInfo.superClassType() != null) { ClassInfo superClassInfo = getClassByName(beanDeployment.getIndex(), classInfo.superName()); if (superClassInfo != null) { - finalMethods - .addAll(addInterceptedMethodCandidates(beanDeployment, superClassInfo, candidates, classLevelBindings)); + finalMethodsFoundAndNotChanged.addAll(addInterceptedMethodCandidates(beanDeployment, superClassInfo, candidates, + classLevelBindings, bytecodeTransformerConsumer, removeFinalForProxyableMethods)); + } + } + return finalMethodsFoundAndNotChanged; + } + + private static class NameAndDescriptor { + private final String name; + private final String descriptor; + + public NameAndDescriptor(String name, String descriptor) { + this.name = name; + this.descriptor = descriptor; + } + + public static NameAndDescriptor fromMethodInfo(MethodInfo method) { + String returnTypeDesc = DescriptorUtils.objectToDescriptor(method.returnType().name().toString()); + String[] paramTypesDesc = new String[(method.parameters().size())]; + for (int i = 0; i < method.parameters().size(); i++) { + paramTypesDesc[i] = DescriptorUtils.objectToDescriptor(method.parameters().get(i).name().toString()); } + + return new NameAndDescriptor(method.name(), + DescriptorUtils.methodSignatureToDescriptor(returnTypeDesc, paramTypesDesc)); + } + + @Override + public boolean equals(Object o) { + if (this == o) + return true; + if (o == null || getClass() != o.getClass()) + return false; + NameAndDescriptor that = (NameAndDescriptor) o; + return name.equals(that.name) && + descriptor.equals(that.descriptor); + } + + @Override + public int hashCode() { + return Objects.hash(name, descriptor); } - return finalMethods; } private static boolean skipForSubclass(MethodInfo method) { diff --git a/independent-projects/arc/processor/src/test/java/io/quarkus/arc/processor/TypesTest.java b/independent-projects/arc/processor/src/test/java/io/quarkus/arc/processor/TypesTest.java index b47ed0a32a6b6..ddcbd139cdecf 100644 --- a/independent-projects/arc/processor/src/test/java/io/quarkus/arc/processor/TypesTest.java +++ b/independent-projects/arc/processor/src/test/java/io/quarkus/arc/processor/TypesTest.java @@ -38,7 +38,7 @@ public void testGetTypeClosure() throws IOException { Collections.emptyMap(), new BeanDeployment(index, Collections.emptyList(), Collections.emptyList(), Collections.emptyList(), Collections.emptyList(), null, - false, Collections.emptyList(), Collections.emptyMap(), Collections.emptyList()), + false, Collections.emptyList(), Collections.emptyMap(), Collections.emptyList(), false), resolvedTypeVariables::put); assertEquals(3, bazTypes.size()); assertTrue(bazTypes.contains(Type.create(bazName, Kind.CLASS))); @@ -55,7 +55,7 @@ public void testGetTypeClosure() throws IOException { Set fooTypes = Types.getClassBeanTypeClosure(fooClass, new BeanDeployment(index, Collections.emptyList(), Collections.emptyList(), Collections.emptyList(), Collections.emptyList(), null, - false, Collections.emptyList(), Collections.emptyMap(), Collections.emptyList())); + false, Collections.emptyList(), Collections.emptyMap(), Collections.emptyList(), false)); assertEquals(2, fooTypes.size()); for (Type t : fooTypes) { if (t.kind().equals(Kind.PARAMETERIZED_TYPE)) { @@ -71,7 +71,7 @@ public void testGetTypeClosure() throws IOException { Set producerMethodTypes = Types.getProducerMethodTypeClosure(producerMethod, new BeanDeployment(index, Collections.emptyList(), Collections.emptyList(), Collections.emptyList(), Collections.emptyList(), null, - false, Collections.emptyList(), Collections.emptyMap(), Collections.emptyList())); + false, Collections.emptyList(), Collections.emptyMap(), Collections.emptyList(), false)); assertEquals(1, producerMethodTypes.size()); // Object is the sole type @@ -79,7 +79,7 @@ public void testGetTypeClosure() throws IOException { Set producerFieldTypes = Types.getProducerFieldTypeClosure(producerField, new BeanDeployment(index, Collections.emptyList(), Collections.emptyList(), Collections.emptyList(), Collections.emptyList(), null, - false, Collections.emptyList(), Collections.emptyMap(), Collections.emptyList())); + false, Collections.emptyList(), Collections.emptyMap(), Collections.emptyList(), false)); assertEquals(1, producerFieldTypes.size()); }