Skip to content

Commit

Permalink
Merge pull request #32885 from Ladicek/arc-fixes
Browse files Browse the repository at this point in the history
ArC fixes for spec compatibility, round 7
  • Loading branch information
Ladicek authored Apr 27, 2023
2 parents 5104c58 + 6b04b3c commit eab2f0a
Show file tree
Hide file tree
Showing 36 changed files with 2,157 additions and 192 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -1534,10 +1534,36 @@ private void findNamespaces(BeanInfo bean, Set<String> namespaces) {
}
}

/**
* Returns the set of names of non-binding annotation members of given interceptor
* binding annotation that was registered through {@code InterceptorBindingRegistrar}.
* <p>
* Does <em>not</em> return non-binding members of interceptor bindings that were
* discovered based on the {@code @InterceptorBinding} annotation; in such case,
* one has to manually check presence of the {@code @NonBinding} annotation on
* the annotation member declaration.
*
* @param name name of the interceptor binding annotation that was registered through
* {@code InterceptorBindingRegistrar}
* @return set of non-binding annotation members of the interceptor binding annotation
*/
public Set<String> getInterceptorNonbindingMembers(DotName name) {
return interceptorNonbindingMembers.getOrDefault(name, Collections.emptySet());
}

/**
* Returns the set of names of non-binding annotation members of given qualifier
* annotation that was registered through {@code QualifierRegistrar}.
* <p>
* Does <em>not</em> return non-binding members of interceptor bindings that were
* discovered based on the {@code @Qualifier} annotation; in such case, one has to
* manually check presence of the {@code @NonBinding} annotation on the annotation member
* declaration.
*
* @param name name of the qualifier annotation that was registered through
* {@code QualifierRegistrar}
* @return set of non-binding annotation members of the qualifier annotation
*/
public Set<String> getQualifierNonbindingMembers(DotName name) {
return qualifierNonbindingMembers.getOrDefault(name, Collections.emptySet());
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -819,16 +819,43 @@ protected void implementDestroy(BeanInfo bean, ClassCreator beanCreator, Provide

if (bean.isClassBean()) {
if (!bean.isInterceptor()) {
// in case someone calls `Bean.destroy()` directly (i.e., they use the low-level CDI API),
// they may pass us a client proxy
ResultHandle instance = destroy.invokeStaticInterfaceMethod(MethodDescriptors.CLIENT_PROXY_UNWRAP,
destroy.getMethodParam(0));

// if there's no `@PreDestroy` interceptor, we'll generate code to invoke `@PreDestroy` callbacks
// directly into the `destroy` method:
//
// public void destroy(MyBean var1, CreationalContext var2) {
// var1.myPreDestroyCallback();
// var2.release();
// }
BytecodeCreator preDestroyBytecode = destroy;

// PreDestroy interceptors
if (!bean.getLifecycleInterceptors(InterceptionType.PRE_DESTROY).isEmpty()) {
// if there _is_ some `@PreDestroy` interceptor, however, we'll reify the chain of `@PreDestroy`
// callbacks into a `Runnable` that we pass into the interceptor chain to be called
// by the last `proceed()` call:
//
// public void destroy(MyBean var1, CreationalContext var2) {
// // this is a `Runnable` that calls `MyBean.myPreDestroyCallback()`
// MyBean_Bean$$function$$2 var3 = new MyBean_Bean$$function$$2(var1);
// ((MyBean_Subclass)var1).arc$destroy((Runnable)var3);
// var2.release();
// }
FunctionCreator preDestroyForwarder = destroy.createFunction(Runnable.class);
preDestroyBytecode = preDestroyForwarder.getBytecode();

destroy.invokeVirtualMethod(
MethodDescriptor.ofMethod(SubclassGenerator.generatedName(bean.getProviderType().name(), baseName),
SubclassGenerator.DESTROY_METHOD_NAME,
void.class),
destroy.getMethodParam(0));
SubclassGenerator.DESTROY_METHOD_NAME, void.class, Runnable.class),
instance, preDestroyForwarder.getInstance());
}

// PreDestroy callbacks
// possibly wrapped into Runnable so that PreDestroy interceptors can proceed() correctly
List<MethodInfo> preDestroyCallbacks = Beans.getCallbacks(bean.getTarget().get().asClass(),
DotNames.PRE_DESTROY,
bean.getDeployment().getBeanArchiveIndex());
Expand All @@ -839,16 +866,21 @@ protected void implementDestroy(BeanInfo bean, ClassCreator beanCreator, Provide
callback.declaringClass().name(), callback.name()));
}
reflectionRegistration.registerMethod(callback);
destroy.invokeStaticMethod(MethodDescriptors.REFLECTIONS_INVOKE_METHOD,
destroy.loadClass(callback.declaringClass().name().toString()),
destroy.load(callback.name()), destroy.newArray(Class.class, destroy.load(0)),
destroy.getMethodParam(0),
destroy.newArray(Object.class, destroy.load(0)));
preDestroyBytecode.invokeStaticMethod(MethodDescriptors.REFLECTIONS_INVOKE_METHOD,
preDestroyBytecode.loadClass(callback.declaringClass().name().toString()),
preDestroyBytecode.load(callback.name()),
preDestroyBytecode.newArray(Class.class, preDestroyBytecode.load(0)),
instance,
preDestroyBytecode.newArray(Object.class, preDestroyBytecode.load(0)));
} else {
// instance.superCoolDestroyCallback()
destroy.invokeVirtualMethod(MethodDescriptor.of(callback), destroy.getMethodParam(0));
preDestroyBytecode.invokeVirtualMethod(MethodDescriptor.of(callback), instance);
}
}
if (preDestroyBytecode != destroy) {
// only if we're generating a `Runnable`, see above
preDestroyBytecode.returnVoid();
}
}

// ctx.release()
Expand Down Expand Up @@ -1582,10 +1614,20 @@ void implementCreateForClassBean(ClassOutput classOutput, ClassCreator beanCreat
interceptorToWrap, transientReferences, injectableCtorParams, allOtherCtorParams);

// Forwarding function
// Supplier<Object> forward = () -> new SimpleBean_Subclass(ctx,lifecycleInterceptorProvider1)
FunctionCreator func = create.createFunction(Supplier.class);
// Function<Object[], Object> forward = (params) -> new SimpleBean_Subclass(params[0], ctx, lifecycleInterceptorProvider1)
FunctionCreator func = create.createFunction(Function.class);
BytecodeCreator funcBytecode = func.getBytecode();
List<ResultHandle> providerHandles = new ArrayList<>(injectableCtorParams);
List<ResultHandle> params = new ArrayList<>();
if (!injectableCtorParams.isEmpty()) {
// `injectableCtorParams` are passed to the first interceptor in the chain
// the `Function` generated here obtains the parameter array from `InvocationContext`
// these 2 arrays have the same shape (size and element types), but not necessarily the same content
ResultHandle paramsArray = funcBytecode.checkCast(funcBytecode.getMethodParam(0), Object[].class);
for (int i = 0; i < injectableCtorParams.size(); i++) {
params.add(funcBytecode.readArrayValue(paramsArray, i));
}
}
List<ResultHandle> providerHandles = new ArrayList<>(params);
providerHandles.addAll(allOtherCtorParams);
ResultHandle retHandle = newInstanceHandle(bean, beanCreator, funcBytecode, create, providerType.className(),
baseName,
Expand Down Expand Up @@ -1616,6 +1658,8 @@ void implementCreateForClassBean(ClassOutput classOutput, ClassCreator beanCreat
create.invokeStaticMethod(MethodDescriptors.SETS_OF, bindingsArray));
TryBlock tryCatch = create.tryBlock();
CatchBlockCreator exceptionCatch = tryCatch.addCatch(Exception.class);
exceptionCatch.ifFalse(exceptionCatch.instanceOf(exceptionCatch.getCaughtException(), RuntimeException.class))
.falseBranch().throwException(exceptionCatch.getCaughtException());
// throw new RuntimeException(e)
exceptionCatch.throwException(RuntimeException.class, "Error invoking aroundConstructs",
exceptionCatch.getCaughtException());
Expand Down Expand Up @@ -1750,9 +1794,35 @@ void implementCreateForClassBean(ClassOutput classOutput, ClassCreator beanCreat
SubclassGenerator.MARK_CONSTRUCTED_METHOD_NAME, void.class), instanceHandle);
}

// if there's no `@PostConstruct` interceptor, we'll generate code to invoke `@PostConstruct` callbacks
// directly into the `doCreate` method:
//
// private MyBean doCreate(CreationalContext var1) {
// MyBean var2 = new MyBean();
// var2.myPostConstructCallback();
// return var2;
// }
BytecodeCreator postConstructsBytecode = create;

// PostConstruct lifecycle callback interceptors
InterceptionInfo postConstructs = bean.getLifecycleInterceptors(InterceptionType.POST_CONSTRUCT);
if (!postConstructs.isEmpty()) {
// if there _is_ some `@PostConstruct` interceptor, however, we'll reify the chain of `@PostConstruct`
// callbacks into a `Runnable` that we pass into the interceptor chain to be called
// by the last `proceed()` call:
//
// private MyBean doCreate(CreationalContext var1) {
// ...
// MyBean var7 = new MyBean();
// // this is a `Runnable` that calls `MyBean.myPostConstructCallback()`
// MyBean_Bean$$function$$1 var11 = new MyBean_Bean$$function$$1(var7);
// ...
// InvocationContext var12 = InvocationContexts.postConstruct(var7, (List)var5, var10, (Runnable)var11);
// var12.proceed();
// return var7;
// }
FunctionCreator postConstructForwarder = create.createFunction(Runnable.class);
postConstructsBytecode = postConstructForwarder.getBytecode();

// Interceptor bindings
ResultHandle bindingsArray = create.newArray(Object.class, postConstructs.bindings.size());
Expand All @@ -1768,10 +1838,13 @@ void implementCreateForClassBean(ClassOutput classOutput, ClassCreator beanCreat
// InvocationContextImpl.postConstruct(instance,postConstructs).proceed()
ResultHandle invocationContextHandle = create.invokeStaticMethod(
MethodDescriptors.INVOCATION_CONTEXTS_POST_CONSTRUCT, instanceHandle,
postConstructsHandle, create.invokeStaticMethod(MethodDescriptors.SETS_OF, bindingsArray));
postConstructsHandle, create.invokeStaticMethod(MethodDescriptors.SETS_OF, bindingsArray),
postConstructForwarder.getInstance());

TryBlock tryCatch = create.tryBlock();
CatchBlockCreator exceptionCatch = tryCatch.addCatch(Exception.class);
exceptionCatch.ifFalse(exceptionCatch.instanceOf(exceptionCatch.getCaughtException(), RuntimeException.class))
.falseBranch().throwException(exceptionCatch.getCaughtException());
// throw new RuntimeException(e)
exceptionCatch.throwException(RuntimeException.class, "Error invoking postConstructs",
exceptionCatch.getCaughtException());
Expand All @@ -1780,10 +1853,11 @@ void implementCreateForClassBean(ClassOutput classOutput, ClassCreator beanCreat
}

// PostConstruct callbacks
// possibly wrapped into Runnable so that PostConstruct interceptors can proceed() correctly
if (!bean.isInterceptor()) {
List<MethodInfo> postConstructCallbacks = Beans.getCallbacks(bean.getTarget().get().asClass(),
DotNames.POST_CONSTRUCT,
bean.getDeployment().getBeanArchiveIndex());
DotNames.POST_CONSTRUCT, bean.getDeployment().getBeanArchiveIndex());

for (MethodInfo callback : postConstructCallbacks) {
if (isReflectionFallbackNeeded(callback, targetPackage)) {
if (Modifier.isPrivate(callback.flags())) {
Expand All @@ -1792,15 +1866,20 @@ void implementCreateForClassBean(ClassOutput classOutput, ClassCreator beanCreat
callback.name()));
}
reflectionRegistration.registerMethod(callback);
create.invokeStaticMethod(MethodDescriptors.REFLECTIONS_INVOKE_METHOD,
create.loadClass(callback.declaringClass().name().toString()),
create.load(callback.name()), create.newArray(Class.class, create.load(0)), instanceHandle,
create.newArray(Object.class, create.load(0)));
postConstructsBytecode.invokeStaticMethod(MethodDescriptors.REFLECTIONS_INVOKE_METHOD,
postConstructsBytecode.loadClass(callback.declaringClass().name().toString()),
postConstructsBytecode.load(callback.name()),
postConstructsBytecode.newArray(Class.class, postConstructsBytecode.load(0)), instanceHandle,
postConstructsBytecode.newArray(Object.class, postConstructsBytecode.load(0)));
} else {
create.invokeVirtualMethod(MethodDescriptor.of(callback), instanceHandle);
postConstructsBytecode.invokeVirtualMethod(MethodDescriptor.of(callback), instanceHandle);
}
}
}
if (postConstructsBytecode != create) {
// only if we're generating a `Runnable`, see above
postConstructsBytecode.returnVoid();
}

create.returnValue(instanceHandle);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -611,13 +611,10 @@ private Map<MethodInfo, InterceptionInfo> initInterceptedMethods(List<Throwable>
Map<MethodInfo, InterceptionInfo> interceptedMethods = new HashMap<>();
Map<MethodKey, Set<AnnotationInstance>> candidates = new HashMap<>();

ClassInfo targetClass = target.get().asClass();
List<AnnotationInstance> classLevelBindings = new ArrayList<>();
addClassLevelBindings(target.get().asClass(), classLevelBindings);
if (!stereotypes.isEmpty()) {
for (StereotypeInfo stereotype : stereotypes) {
addClassLevelBindings(stereotype.getTarget(), classLevelBindings);
}
}
addClassLevelBindings(targetClass, classLevelBindings);
Interceptors.checkClassLevelInterceptorBindings(classLevelBindings, targetClass, beanDeployment);

Set<MethodInfo> finalMethods = Methods.addInterceptedMethodCandidates(this, candidates, classLevelBindings,
bytecodeTransformerConsumer, transformUnproxyableClasses);
Expand Down Expand Up @@ -754,7 +751,8 @@ private Map<InterceptionType, InterceptionInfo> initLifecycleInterceptors() {
putLifecycleInterceptors(lifecycleInterceptors, classLevelBindings, InterceptionType.PRE_DESTROY);
MethodInfo interceptedConstructor = findInterceptedConstructor(target.get().asClass());
if (beanDeployment.getAnnotation(interceptedConstructor, DotNames.NO_CLASS_INTERCEPTORS) == null) {
constructorLevelBindings.addAll(classLevelBindings);
constructorLevelBindings = Methods.mergeMethodAndClassLevelBindings(constructorLevelBindings,
classLevelBindings);
}
putLifecycleInterceptors(lifecycleInterceptors, constructorLevelBindings, InterceptionType.AROUND_CONSTRUCT);
return lifecycleInterceptors;
Expand All @@ -773,15 +771,35 @@ private void putLifecycleInterceptors(Map<InterceptionType, InterceptionInfo> li
}
}

private void addClassLevelBindings(ClassInfo classInfo, Collection<AnnotationInstance> bindings) {
private void addClassLevelBindings(ClassInfo targetClass, Collection<AnnotationInstance> bindings) {
List<AnnotationInstance> classLevelBindings = new ArrayList<>();
doAddClassLevelBindings(targetClass, classLevelBindings, Set.of());
bindings.addAll(classLevelBindings);
if (!stereotypes.isEmpty()) {
// interceptor binding declared on a bean class replaces an interceptor binding of the same type
// declared by a stereotype that is applied to the bean class
Set<DotName> skip = new HashSet<>();
for (AnnotationInstance classLevelBinding : classLevelBindings) {
skip.add(classLevelBinding.name());
}
for (StereotypeInfo stereotype : Beans.stereotypesWithTransitive(stereotypes,
beanDeployment.getStereotypesMap())) {
doAddClassLevelBindings(stereotype.getTarget(), bindings, skip);
}
}
}

// bindings whose class name is present in `skip` are ignored (this is used to ignore bindings on stereotypes
// when the original class has a binding of the same type)
private void doAddClassLevelBindings(ClassInfo classInfo, Collection<AnnotationInstance> bindings, Set<DotName> skip) {
beanDeployment.getAnnotations(classInfo).stream()
.filter(a -> beanDeployment.getInterceptorBinding(a.name()) != null
&& bindings.stream().noneMatch(e -> e.name().equals(a.name())))
.filter(a -> beanDeployment.getInterceptorBinding(a.name()) != null)
.filter(a -> !skip.contains(a.name()))
.forEach(bindings::add);
if (classInfo.superClassType() != null && !classInfo.superClassType().name().equals(DotNames.OBJECT)) {
ClassInfo superClass = getClassByName(beanDeployment.getBeanArchiveIndex(), classInfo.superName());
if (superClass != null) {
addClassLevelBindings(superClass, bindings);
doAddClassLevelBindings(superClass, bindings, skip);
}
}
}
Expand Down
Loading

0 comments on commit eab2f0a

Please sign in to comment.