Skip to content

Commit 9c4d044

Browse files
committed
ArC: beans injected into All List injection points should be unremovable
1 parent f3bbc25 commit 9c4d044

File tree

6 files changed

+103
-115
lines changed

6 files changed

+103
-115
lines changed

extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/ArcProcessor.java

Lines changed: 0 additions & 95 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
package io.quarkus.arc.deployment;
22

3-
import static io.quarkus.arc.processor.KotlinUtils.isKotlinClass;
43
import static io.quarkus.deployment.annotations.ExecutionTime.RUNTIME_INIT;
54
import static io.quarkus.deployment.annotations.ExecutionTime.STATIC_INIT;
65

@@ -9,7 +8,6 @@
98
import java.util.Collection;
109
import java.util.HashMap;
1110
import java.util.HashSet;
12-
import java.util.Iterator;
1311
import java.util.List;
1412
import java.util.Map;
1513
import java.util.Optional;
@@ -23,11 +21,9 @@
2321
import jakarta.enterprise.context.ApplicationScoped;
2422
import jakarta.enterprise.inject.AmbiguousResolutionException;
2523
import jakarta.enterprise.inject.UnsatisfiedResolutionException;
26-
import jakarta.enterprise.inject.spi.DefinitionException;
2724

2825
import org.jboss.jandex.AnnotationInstance;
2926
import org.jboss.jandex.AnnotationTarget;
30-
import org.jboss.jandex.AnnotationValue;
3127
import org.jboss.jandex.ClassInfo;
3228
import org.jboss.jandex.DotName;
3329
import org.jboss.jandex.FieldInfo;
@@ -46,7 +42,6 @@
4642
import io.quarkus.arc.deployment.UnremovableBeanBuildItem.BeanTypeExclusion;
4743
import io.quarkus.arc.deployment.ValidationPhaseBuildItem.ValidationErrorBuildItem;
4844
import io.quarkus.arc.processor.AlternativePriorities;
49-
import io.quarkus.arc.processor.Annotations;
5045
import io.quarkus.arc.processor.AnnotationsTransformer;
5146
import io.quarkus.arc.processor.BeanConfigurator;
5247
import io.quarkus.arc.processor.BeanDefiningAnnotation;
@@ -56,13 +51,10 @@
5651
import io.quarkus.arc.processor.BeanProcessor;
5752
import io.quarkus.arc.processor.BeanRegistrar;
5853
import io.quarkus.arc.processor.BeanResolver;
59-
import io.quarkus.arc.processor.Beans;
6054
import io.quarkus.arc.processor.BytecodeTransformer;
6155
import io.quarkus.arc.processor.ContextConfigurator;
6256
import io.quarkus.arc.processor.ContextRegistrar;
6357
import io.quarkus.arc.processor.DotNames;
64-
import io.quarkus.arc.processor.InjectionPointInfo;
65-
import io.quarkus.arc.processor.InjectionPointInfo.TypeAndQualifiers;
6658
import io.quarkus.arc.processor.ObserverConfigurator;
6759
import io.quarkus.arc.processor.ObserverRegistrar;
6860
import io.quarkus.arc.processor.ReflectionRegistration;
@@ -466,39 +458,6 @@ public ObserverRegistrationPhaseBuildItem registerSyntheticObservers(BeanRegistr
466458
// Initialize the type -> bean map
467459
beanRegistrationPhase.getBeanProcessor().getBeanDeployment().initBeanByTypeMap();
468460

469-
// Register a synthetic bean for each List<?> with qualifier @All
470-
List<InjectionPointInfo> listAll = beanRegistrationPhase.getInjectionPoints().stream()
471-
.filter(this::isListAllInjectionPoint).collect(Collectors.toList());
472-
for (InjectionPointInfo injectionPoint : listAll) {
473-
// Note that at this point we can be sure that the required type is List<>
474-
Type typeParam = injectionPoint.getType().asParameterizedType().arguments().get(0);
475-
if (typeParam.kind() == Type.Kind.WILDCARD_TYPE) {
476-
ClassInfo declaringClass;
477-
if (injectionPoint.isField()) {
478-
declaringClass = injectionPoint.getTarget().asField().declaringClass();
479-
} else {
480-
declaringClass = injectionPoint.getTarget().asMethod().declaringClass();
481-
}
482-
if (isKotlinClass(declaringClass)) {
483-
validationErrors.produce(new ValidationErrorBuildItem(
484-
new DefinitionException(
485-
"kotlin.collections.List cannot be used together with the @All qualifier, please use MutableList or java.util.List instead: "
486-
+ injectionPoint.getTargetInfo())));
487-
} else {
488-
validationErrors.produce(new ValidationErrorBuildItem(
489-
new DefinitionException(
490-
"Wildcard is not a legal type argument for " + injectionPoint.getTargetInfo())));
491-
}
492-
} else if (typeParam.kind() == Type.Kind.TYPE_VARIABLE) {
493-
validationErrors.produce(new ValidationErrorBuildItem(new DefinitionException(
494-
"Type variable is not a legal type argument for " + injectionPoint.getTargetInfo())));
495-
}
496-
}
497-
if (!listAll.isEmpty()) {
498-
registerUnremovableListBeans(beanRegistrationPhase, listAll, reflectiveMethods, reflectiveFields,
499-
unremovableBeans);
500-
}
501-
502461
BeanProcessor beanProcessor = beanRegistrationPhase.getBeanProcessor();
503462
ObserverRegistrar.RegistrationContext registrationContext = beanProcessor.registerSyntheticObservers();
504463

@@ -793,60 +752,6 @@ void registerContextPropagation(ArcConfig config, BuildProducer<ThreadContextPro
793752
}
794753
}
795754

796-
private void registerUnremovableListBeans(BeanRegistrationPhaseBuildItem beanRegistrationPhase,
797-
List<InjectionPointInfo> injectionPoints, BuildProducer<ReflectiveMethodBuildItem> reflectiveMethods,
798-
BuildProducer<ReflectiveFieldBuildItem> reflectiveFields,
799-
BuildProducer<UnremovableBeanBuildItem> unremovableBeans) {
800-
BeanDeployment beanDeployment = beanRegistrationPhase.getBeanProcessor().getBeanDeployment();
801-
List<TypeAndQualifiers> unremovables = new ArrayList<>();
802-
803-
for (InjectionPointInfo injectionPoint : injectionPoints) {
804-
// All qualifiers but @All
805-
Set<AnnotationInstance> qualifiers = new HashSet<>(injectionPoint.getRequiredQualifiers());
806-
for (Iterator<AnnotationInstance> it = qualifiers.iterator(); it.hasNext();) {
807-
AnnotationInstance qualifier = it.next();
808-
if (DotNames.ALL.equals(qualifier.name())) {
809-
it.remove();
810-
}
811-
}
812-
if (qualifiers.isEmpty()) {
813-
// If no other qualifier is used then add @Any
814-
qualifiers.add(AnnotationInstance.create(DotNames.ANY, null, new AnnotationValue[] {}));
815-
}
816-
817-
Type elementType = injectionPoint.getType().asParameterizedType().arguments().get(0);
818-
819-
// make note of all types inside @All List<X> to make sure they are unremovable
820-
unremovables.add(new TypeAndQualifiers(
821-
elementType.name().equals(DotNames.INSTANCE_HANDLE)
822-
? elementType.asParameterizedType().arguments().get(0)
823-
: elementType,
824-
qualifiers));
825-
}
826-
if (!unremovables.isEmpty()) {
827-
// New beans were registered - we need to re-init the type -> bean map
828-
// Also make all beans that match the List<> injection points unremovable
829-
beanDeployment.initBeanByTypeMap();
830-
// And make all the matching beans unremovable
831-
unremovableBeans.produce(new UnremovableBeanBuildItem(new Predicate<BeanInfo>() {
832-
@Override
833-
public boolean test(BeanInfo bean) {
834-
for (TypeAndQualifiers tq : unremovables) {
835-
if (Beans.matches(bean, tq)) {
836-
return true;
837-
}
838-
}
839-
return false;
840-
}
841-
}));
842-
}
843-
}
844-
845-
private boolean isListAllInjectionPoint(InjectionPointInfo injectionPoint) {
846-
return DotNames.LIST.equals(injectionPoint.getRequiredType().name())
847-
&& Annotations.contains(injectionPoint.getRequiredQualifiers(), DotNames.ALL);
848-
}
849-
850755
private abstract static class AbstractCompositeApplicationClassesPredicate<T> implements Predicate<T> {
851756

852757
private final IndexView applicationClassesIndex;

independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanDeployment.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -455,7 +455,8 @@ private Set<DecoratorInfo> removeUnusedDecorators(Set<DecoratorInfo> removedDeco
455455
}
456456

457457
private Set<BeanInfo> removeUnusedBeans(Set<BeanInfo> declaresObserver, List<Predicate<BeanInfo>> allUnusedExclusions) {
458-
Set<BeanInfo> removableBeans = UnusedBeans.findRemovableBeans(this.beans, this.injectionPoints, declaresObserver,
458+
Set<BeanInfo> removableBeans = UnusedBeans.findRemovableBeans(beanResolver, this.beans, this.injectionPoints,
459+
declaresObserver,
459460
allUnusedExclusions);
460461
if (!removableBeans.isEmpty()) {
461462
this.beans.removeAll(removableBeans);

independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BuiltinBean.java

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package io.quarkus.arc.processor;
22

33
import static io.quarkus.arc.processor.IndexClassLookupUtils.getClassByName;
4+
import static io.quarkus.arc.processor.KotlinUtils.isKotlinClass;
45

56
import java.lang.reflect.Member;
67
import java.util.HashSet;
@@ -64,7 +65,7 @@ public enum BuiltinBean {
6465
BuiltinBean::validateEventMetadata, DotNames.EVENT_METADATA),
6566
LIST(BuiltinBean::generateListBytecode,
6667
(ip, names) -> cdiAndRawTypeMatches(ip, DotNames.LIST) && ip.getRequiredQualifier(DotNames.ALL) != null,
67-
DotNames.LIST),
68+
BuiltinBean::validateList, DotNames.LIST),
6869
;
6970

7071
private final DotName[] rawTypeDotNames;
@@ -443,6 +444,38 @@ private static void validateInstance(InjectionTargetInfo injectionTarget, Inject
443444
}
444445
}
445446

447+
private static void validateList(InjectionTargetInfo injectionTarget, InjectionPointInfo injectionPoint,
448+
Consumer<Throwable> errors) {
449+
if (injectionPoint.getType().kind() != Kind.PARAMETERIZED_TYPE) {
450+
errors.accept(
451+
new DefinitionException("An injection point of raw type is defined: " + injectionPoint.getTargetInfo()));
452+
} else {
453+
// Note that at this point we can be sure that the required type is List<>
454+
Type typeParam = injectionPoint.getType().asParameterizedType().arguments().get(0);
455+
if (typeParam.kind() == Type.Kind.WILDCARD_TYPE) {
456+
ClassInfo declaringClass;
457+
if (injectionPoint.isField()) {
458+
declaringClass = injectionPoint.getTarget().asField().declaringClass();
459+
} else {
460+
declaringClass = injectionPoint.getTarget().asMethod().declaringClass();
461+
}
462+
if (isKotlinClass(declaringClass)) {
463+
errors.accept(
464+
new DefinitionException(
465+
"kotlin.collections.List cannot be used together with the @All qualifier, please use MutableList or java.util.List instead: "
466+
+ injectionPoint.getTargetInfo()));
467+
} else {
468+
errors.accept(
469+
new DefinitionException(
470+
"Wildcard is not a legal type argument for: " + injectionPoint.getTargetInfo()));
471+
}
472+
} else if (typeParam.kind() == Type.Kind.TYPE_VARIABLE) {
473+
errors.accept(new DefinitionException(
474+
"Type variable is not a legal type argument for: " + injectionPoint.getTargetInfo()));
475+
}
476+
}
477+
}
478+
446479
private static void validateInjectionPoint(InjectionTargetInfo injectionTarget, InjectionPointInfo injectionPoint,
447480
Consumer<Throwable> errors) {
448481
if (injectionTarget.kind() != TargetKind.BEAN || !BuiltinScope.DEPENDENT.is(injectionTarget.asBean().getScope())) {

independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/UnusedBeans.java

Lines changed: 52 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,41 +1,72 @@
11
package io.quarkus.arc.processor;
22

3-
import static java.util.function.Predicate.not;
4-
3+
import java.util.ArrayList;
54
import java.util.Collection;
65
import java.util.HashSet;
6+
import java.util.Iterator;
77
import java.util.List;
88
import java.util.Map;
99
import java.util.Map.Entry;
1010
import java.util.Set;
1111
import java.util.function.Predicate;
1212
import java.util.stream.Collectors;
1313

14+
import org.jboss.jandex.AnnotationInstance;
15+
import org.jboss.jandex.Type;
1416
import org.jboss.logging.Logger;
1517

18+
import io.quarkus.arc.processor.InjectionPointInfo.TypeAndQualifiers;
19+
1620
final class UnusedBeans {
1721

1822
private static final Logger LOG = Logger.getLogger(UnusedBeans.class);
1923

2024
private UnusedBeans() {
2125
}
2226

23-
static Set<BeanInfo> findRemovableBeans(Collection<BeanInfo> beans, Collection<InjectionPointInfo> injectionPoints,
24-
Set<BeanInfo> declaresObserver, List<Predicate<BeanInfo>> allUnusedExclusions) {
27+
static Set<BeanInfo> findRemovableBeans(BeanResolver beanResolver, Collection<BeanInfo> beans,
28+
Iterable<InjectionPointInfo> injectionPoints, Set<BeanInfo> declaresObserver,
29+
List<Predicate<BeanInfo>> allUnusedExclusions) {
2530
Set<BeanInfo> removableBeans = new HashSet<>();
2631

2732
Set<BeanInfo> unusedProducers = new HashSet<>();
2833
Set<BeanInfo> unusedButDeclaresProducer = new HashSet<>();
2934
List<BeanInfo> producers = beans.stream().filter(BeanInfo::isProducer)
3035
.collect(Collectors.toList());
31-
List<InjectionPointInfo> instanceInjectionPoints = injectionPoints.stream()
32-
.filter(InjectionPointInfo::isProgrammaticLookup)
33-
.collect(Collectors.toList());
34-
// Collect all injected beans; skip delegate injection points and injection points that resolve to a built-in bean
35-
Set<BeanInfo> injected = injectionPoints.stream()
36-
.filter(not(InjectionPointInfo::isDelegate).and(InjectionPointInfo::hasResolvedBean))
37-
.map(InjectionPointInfo::getResolvedBean)
38-
.collect(Collectors.toSet());
36+
// Collect all:
37+
// - injected beans; skip delegate injection points and injection points that resolve to a built-in bean
38+
// - Instance<> injection points
39+
// - @All List<> injection points
40+
Set<BeanInfo> injected = new HashSet<>();
41+
List<InjectionPointInfo> instanceInjectionPoints = new ArrayList<>();
42+
List<TypeAndQualifiers> listAllInjectionPoints = new ArrayList<>();
43+
44+
for (InjectionPointInfo injectionPoint : injectionPoints) {
45+
if (injectionPoint.isProgrammaticLookup()) {
46+
instanceInjectionPoints.add(injectionPoint);
47+
} else if (!injectionPoint.isDelegate()) {
48+
BeanInfo resolved = injectionPoint.getResolvedBean();
49+
if (resolved != null) {
50+
injected.add(resolved);
51+
} else {
52+
BuiltinBean builtin = BuiltinBean.resolve(injectionPoint);
53+
if (builtin == BuiltinBean.LIST) {
54+
Type requiredType = injectionPoint.getType().asParameterizedType().arguments().get(0);
55+
if (requiredType.name().equals(DotNames.INSTANCE_HANDLE)) {
56+
requiredType = requiredType.asParameterizedType().arguments().get(0);
57+
}
58+
Set<AnnotationInstance> qualifiers = new HashSet<>(injectionPoint.getRequiredQualifiers());
59+
for (Iterator<AnnotationInstance> it = qualifiers.iterator(); it.hasNext();) {
60+
AnnotationInstance qualifier = it.next();
61+
if (qualifier.name().equals(DotNames.ALL)) {
62+
it.remove();
63+
}
64+
}
65+
listAllInjectionPoints.add(new TypeAndQualifiers(requiredType, qualifiers));
66+
}
67+
}
68+
}
69+
}
3970
Set<BeanInfo> declaresProducer = producers.stream().map(BeanInfo::getDeclaringBean).collect(Collectors.toSet());
4071

4172
// Beans - first pass to find unused beans that do not declare a producer
@@ -70,12 +101,20 @@ static Set<BeanInfo> findRemovableBeans(Collection<BeanInfo> beans, Collection<I
70101
// Instance<Foo>
71102
for (InjectionPointInfo injectionPoint : instanceInjectionPoints) {
72103
if (Beans.hasQualifiers(bean, injectionPoint.getRequiredQualifiers())
73-
&& bean.getDeployment().getBeanResolver().matchesType(bean,
104+
&& beanResolver.matchesType(bean,
74105
injectionPoint.getType().asParameterizedType().arguments().get(0))) {
75106
LOG.debugf("Unremovable - programmatic lookup: %s", bean);
76107
continue test;
77108
}
78109
}
110+
// @All List<Foo>
111+
for (TypeAndQualifiers tq : listAllInjectionPoints) {
112+
if (Beans.hasQualifiers(bean, tq.qualifiers)
113+
&& beanResolver.matchesType(bean, tq.type)) {
114+
LOG.debugf("Unremovable - @All List: %s", bean);
115+
continue test;
116+
}
117+
}
79118
// Declares a producer - see also second pass
80119
if (declaresProducer.contains(bean)) {
81120
unusedButDeclaresProducer.add(bean);

independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/all/ListAllTest.java

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,6 @@ public class ListAllTest {
3131
public ArcTestContainer container = new ArcTestContainer(Service.class, ServiceAlpha.class, ServiceBravo.class,
3232
MyQualifier.class, Foo.class);
3333

34-
@SuppressWarnings("serial")
3534
@Test
3635
public void testSelectAll() {
3736
verifyHandleInjection(Arc.container().listAll(Service.class), Object.class);

independent-projects/arc/tests/src/test/java/io/quarkus/arc/test/unused/RemoveUnusedBeansTest.java

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
import java.math.BigDecimal;
88
import java.math.BigInteger;
9+
import java.util.List;
910

1011
import jakarta.annotation.PostConstruct;
1112
import jakarta.annotation.Priority;
@@ -22,6 +23,7 @@
2223
import org.junit.jupiter.api.Test;
2324
import org.junit.jupiter.api.extension.RegisterExtension;
2425

26+
import io.quarkus.arc.All;
2527
import io.quarkus.arc.Arc;
2628
import io.quarkus.arc.ArcContainer;
2729
import io.quarkus.arc.impl.ArcContainerImpl;
@@ -32,10 +34,9 @@ public class RemoveUnusedBeansTest extends RemoveUnusedComponentsTest {
3234
@RegisterExtension
3335
public ArcTestContainer container = ArcTestContainer.builder()
3436
.beanClasses(HasObserver.class, Foo.class, FooAlternative.class, HasName.class, UnusedProducers.class,
35-
InjectedViaInstance.class, InjectedViaInstanceWithWildcard.class,
36-
InjectedViaProvider.class, Excluded.class,
37-
UsedProducers.class,
38-
UnusedProducerButInjected.class, UsedViaInstanceWithUnusedProducer.class, UsesBeanViaInstance.class)
37+
InjectedViaInstance.class, InjectedViaInstanceWithWildcard.class, InjectedViaProvider.class, Excluded.class,
38+
UsedProducers.class, UnusedProducerButInjected.class, UsedViaInstanceWithUnusedProducer.class,
39+
UsesBeanViaInstance.class, UsedViaAllList.class)
3940
.removeUnusedBeans(true)
4041
.addRemovalExclusion(b -> b.getBeanClass().toString().equals(Excluded.class.getName()))
4142
.build();
@@ -67,6 +68,7 @@ public void testRemoval() {
6768
assertFalse(ArcContainerImpl.instance().getRemovedBeans().isEmpty());
6869
assertNotPresent(UnusedBean.class);
6970
assertNotPresent(OnlyInjectedInUnusedBean.class);
71+
assertPresent(UsedViaAllList.class);
7072
}
7173

7274
@Dependent
@@ -197,6 +199,10 @@ static class UsesBeanViaInstance {
197199

198200
@Inject
199201
Instance<UsedViaInstanceWithUnusedProducer> instance;
202+
203+
@Inject
204+
@All
205+
List<UsedViaAllList> list;
200206
}
201207

202208
@Singleton
@@ -212,4 +218,9 @@ static class OnlyInjectedInUnusedBean {
212218

213219
}
214220

221+
@Singleton
222+
static class UsedViaAllList {
223+
224+
}
225+
215226
}

0 commit comments

Comments
 (0)