Skip to content

Commit

Permalink
ArC: fix generating Bean.create() to wrap checked exceptions in Creat…
Browse files Browse the repository at this point in the history
…ionException
  • Loading branch information
Ladicek committed Jan 30, 2023
1 parent ce6860b commit a5e4cae
Show file tree
Hide file tree
Showing 3 changed files with 126 additions and 11 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -940,44 +940,61 @@ protected void implementCreate(ClassOutput classOutput, ClassCreator beanCreator
Map<DecoratorInfo, String> decoratorToProviderSupplierField,
String targetPackage, boolean isApplicationClass) {

MethodCreator create = beanCreator.getMethodCreator("create", providerType.descriptorName(), CreationalContext.class)
.setModifiers(ACC_PUBLIC);
MethodCreator doCreate = beanCreator
.getMethodCreator("doCreate", providerType.descriptorName(), CreationalContext.class)
.setModifiers(ACC_PRIVATE);

if (bean.isClassBean()) {
implementCreateForClassBean(classOutput, beanCreator, bean, providerType, baseName,
injectionPointToProviderSupplierField, interceptorToProviderSupplierField, decoratorToProviderSupplierField,
reflectionRegistration,
targetPackage, isApplicationClass, create);
targetPackage, isApplicationClass, doCreate);
} else if (bean.isProducerMethod()) {
implementCreateForProducerMethod(classOutput, beanCreator, bean, providerType, baseName,
injectionPointToProviderSupplierField, reflectionRegistration,
targetPackage, isApplicationClass, create);
targetPackage, isApplicationClass, doCreate);
} else if (bean.isProducerField()) {
implementCreateForProducerField(classOutput, beanCreator, bean, providerType, baseName,
injectionPointToProviderSupplierField, reflectionRegistration,
targetPackage, isApplicationClass, create);
targetPackage, isApplicationClass, doCreate);
} else if (bean.isSynthetic()) {
if (bean.getScope().isNormal()) {
// Normal scoped synthetic beans should never return null
MethodCreator createSynthetic = beanCreator
.getMethodCreator("createSynthetic", providerType.descriptorName(), CreationalContext.class)
.setModifiers(ACC_PRIVATE);
bean.getCreatorConsumer().accept(createSynthetic);
ResultHandle ret = create.invokeVirtualMethod(createSynthetic.getMethodDescriptor(), create.getThis(),
create.getMethodParam(0));
BytecodeCreator nullBeanInstance = create.ifNull(ret).trueBranch();
ResultHandle ret = doCreate.invokeVirtualMethod(createSynthetic.getMethodDescriptor(), doCreate.getThis(),
doCreate.getMethodParam(0));
BytecodeCreator nullBeanInstance = doCreate.ifNull(ret).trueBranch();
StringBuilderGenerator message = Gizmo.newStringBuilder(nullBeanInstance);
message.append("Null contextual instance was produced by a normal scoped synthetic bean: ");
message.append(Gizmo.toString(nullBeanInstance, nullBeanInstance.getThis()));
ResultHandle e = nullBeanInstance.newInstance(
MethodDescriptor.ofConstructor(CreationException.class, String.class), message.callToString());
nullBeanInstance.throwException(e);
create.returnValue(ret);
doCreate.returnValue(ret);
} else {
bean.getCreatorConsumer().accept(create);
bean.getCreatorConsumer().accept(doCreate);
}
}

MethodCreator create = beanCreator.getMethodCreator("create", providerType.descriptorName(), CreationalContext.class)
.setModifiers(ACC_PUBLIC);
TryBlock tryBlock = create.tryBlock();
tryBlock.returnValue(
tryBlock.invokeSpecialMethod(doCreate.getMethodDescriptor(), tryBlock.getThis(), tryBlock.getMethodParam(0)));
// `Reflections.newInstance()` throws `CreationException` on its own,
// but that's handled like all other `RuntimeException`s
// also ignore custom Throwables, they are virtually never used in practice
CatchBlockCreator catchBlock = tryBlock.addCatch(Exception.class);
catchBlock.ifFalse(catchBlock.instanceOf(catchBlock.getCaughtException(), RuntimeException.class))
.falseBranch().throwException(catchBlock.getCaughtException());
ResultHandle creationException = catchBlock.newInstance(
MethodDescriptor.ofConstructor(CreationException.class, Throwable.class),
catchBlock.getCaughtException());
catchBlock.throwException(creationException);

// Bridge method needed
MethodCreator bridgeCreate = beanCreator.getMethodCreator("create", Object.class, CreationalContext.class)
.setModifiers(ACC_PUBLIC | ACC_BRIDGE);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@
import java.util.Set;
import java.util.function.Function;

import javax.enterprise.inject.CreationException;

/**
* Neither the class nor its methods are considered a public API and should only be used internally.
*/
Expand Down Expand Up @@ -129,7 +131,17 @@ public static Object newInstance(Class<?> clazz, Class<?>[] parameterTypes, Obje
}
try {
return constructor.newInstance(args);
} catch (InstantiationException | IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
} catch (InvocationTargetException e) {
Throwable cause = e.getCause();
if (cause instanceof RuntimeException) {
throw (RuntimeException) cause;
}
if (cause instanceof Error) {
throw (Error) cause;
}
// this method is only used to instantiate beans, so throwing `CreationException` is fine
throw new CreationException(cause);
} catch (InstantiationException | IllegalAccessException | IllegalArgumentException e) {
throw new RuntimeException("Cannot invoke constructor: " + clazz.getName(), e);
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
package io.quarkus.arc.test.bean.create;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;

import java.math.BigDecimal;
import java.math.BigInteger;

import javax.enterprise.context.Dependent;
import javax.enterprise.inject.CreationException;
import javax.enterprise.inject.Produces;
import javax.inject.Inject;

import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;

import io.quarkus.arc.Arc;
import io.quarkus.arc.test.ArcTestContainer;

public class BeanCreateErrorTest {
@RegisterExtension
ArcTestContainer container = new ArcTestContainer(CheckedExceptionBean.class, CheckedExceptionProducerBean.class,
UncheckedExceptionBean.class, UncheckedExceptionProducerBean.class);

@Test
public void checkedExceptionWrapped() {
// Test that the checked exception is wrapped

CreationException exception = assertThrows(CreationException.class, () -> {
Arc.container().instance(CheckedExceptionBean.class);
});
assertEquals("foo", exception.getCause().getMessage());

exception = assertThrows(CreationException.class, () -> {
Arc.container().instance(BigInteger.class);
});
assertEquals("bar", exception.getCause().getMessage());
}

@Test
public void uncheckedExceptionNotWrapped() {
// Test that the unchecked exception is _not_ wrapped

assertThrows(IllegalArgumentException.class, () -> {
Arc.container().instance(UncheckedExceptionBean.class);
});

assertThrows(IllegalStateException.class, () -> {
Arc.container().instance(BigDecimal.class);
});
}

@Dependent
static class CheckedExceptionBean {
@Inject
public CheckedExceptionBean() throws Exception {
throw new Exception("foo");
}
}

@Dependent
static class CheckedExceptionProducerBean {
@Produces
@Dependent
BigInteger produce() throws Exception {
throw new Exception("bar");
}
}

@Dependent
static class UncheckedExceptionBean {
@Inject
public UncheckedExceptionBean() {
throw new IllegalArgumentException();
}
}

@Dependent
static class UncheckedExceptionProducerBean {
@Produces
@Dependent
BigDecimal produce() {
throw new IllegalStateException();
}
}
}

0 comments on commit a5e4cae

Please sign in to comment.