Fix for https://github.com/google/guice/issues/884 -- don't let
half-initialized objects leak out to ProvisionListeners. Throw
ProvisionException if there's any new errors during provision.
-------------
Created by MOE: http://code.google.com/p/moe-java
MOE_MIGRATED_REVID=81455693
diff --git a/core/src/com/google/inject/internal/ProvisionListenerStackCallback.java b/core/src/com/google/inject/internal/ProvisionListenerStackCallback.java
index ad292a6..f72da25 100644
--- a/core/src/com/google/inject/internal/ProvisionListenerStackCallback.java
+++ b/core/src/com/google/inject/internal/ProvisionListenerStackCallback.java
@@ -92,6 +92,7 @@
private class Provision extends ProvisionListener.ProvisionInvocation<T> {
final Errors errors;
+ final int numErrorsBefore;
final InternalContext context;
final ProvisionCallback<T> callable;
int index = -1;
@@ -103,6 +104,7 @@
this.callable = callable;
this.context = context;
this.errors = errors;
+ this.numErrorsBefore = errors.size();
}
@Override
@@ -111,6 +113,9 @@
if (index == listeners.length) {
try {
result = callable.call();
+ // Make sure we don't return the provisioned object if there were any errors
+ // injecting its field/method dependencies.
+ errors.throwIfNewErrors(numErrorsBefore);
} catch(ErrorsException ee) {
exceptionDuringProvision = ee;
throw new ProvisionException(errors.merge(ee.getErrors()).getMessages());
@@ -118,7 +123,6 @@
} else if (index < listeners.length) {
int currentIdx = index;
try {
-
listeners[index].onProvision(this);
} catch(RuntimeException re) {
erredListener = listeners[currentIdx];
diff --git a/core/test/com/google/inject/ProvisionListenerTest.java b/core/test/com/google/inject/ProvisionListenerTest.java
index f70ada5..6e1be84 100644
--- a/core/test/com/google/inject/ProvisionListenerTest.java
+++ b/core/test/com/google/inject/ProvisionListenerTest.java
@@ -38,6 +38,7 @@
import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicReference;
/**
* Tests for {@link Binder#bindListener(Matcher, ProvisionListener...)}
@@ -129,6 +130,66 @@
}
}
+ public void testExceptionInFieldProvision() throws Exception {
+ final CountAndCaptureExceptionListener listener = new CountAndCaptureExceptionListener();
+ Injector injector = Guice.createInjector(new AbstractModule() {
+ @Override protected void configure() {
+ bindListener(new AbstractMatcher<Binding<?>>() {
+ @Override public boolean matches(Binding<?> binding) {
+ return binding.getKey().getRawType().equals(DependsOnFooBombInField.class);
+ }
+ }, listener);
+ }
+ });
+ assertEquals(0, listener.beforeProvision);
+ String expectedMsg = null;
+ try {
+ injector.getInstance(DependsOnFooBombInField.class);
+ fail();
+ } catch (ProvisionException expected) {
+ assertEquals(1, expected.getErrorMessages().size());
+ expectedMsg = expected.getMessage();
+ assertContains(listener.capture.get().getMessage(),
+ "1) Error injecting constructor, java.lang.RuntimeException: Retry, Abort, Fail",
+ " at " + FooBomb.class.getName(),
+ " while locating " + FooBomb.class.getName(),
+ " while locating " + DependsOnFooBombInField.class.getName());
+ }
+ assertEquals(1, listener.beforeProvision);
+ assertEquals(expectedMsg, listener.capture.get().getMessage());
+ assertEquals(0, listener.afterProvision);
+ }
+
+ public void testExceptionInCxtorProvision() throws Exception {
+ final CountAndCaptureExceptionListener listener = new CountAndCaptureExceptionListener();
+ Injector injector = Guice.createInjector(new AbstractModule() {
+ @Override protected void configure() {
+ bindListener(new AbstractMatcher<Binding<?>>() {
+ @Override public boolean matches(Binding<?> binding) {
+ return binding.getKey().getRawType().equals(DependsOnFooBombInCxtor.class);
+ }
+ }, listener);
+ }
+ });
+ assertEquals(0, listener.beforeProvision);
+ String expectedMsg = null;
+ try {
+ injector.getInstance(DependsOnFooBombInCxtor.class);
+ fail();
+ } catch (ProvisionException expected) {
+ assertEquals(1, expected.getErrorMessages().size());
+ expectedMsg = expected.getMessage();
+ assertContains(listener.capture.get().getMessage(),
+ "1) Error injecting constructor, java.lang.RuntimeException: Retry, Abort, Fail",
+ " at " + FooBomb.class.getName(),
+ " while locating " + FooBomb.class.getName(),
+ " while locating " + DependsOnFooBombInCxtor.class.getName());
+ }
+ assertEquals(1, listener.beforeProvision);
+ assertEquals(expectedMsg, listener.capture.get().getMessage());
+ assertEquals(0, listener.afterProvision);
+ }
+
public void testListenerCallsProvisionTwice() {
Injector injector = Guice.createInjector(new AbstractModule() {
@Override
@@ -374,6 +435,14 @@
}
}
+ static class DependsOnFooBombInField {
+ @Inject FooBomb fooBomb;
+ }
+
+ static class DependsOnFooBombInCxtor {
+ @Inject DependsOnFooBombInCxtor(FooBomb fooBomb) {}
+ }
+
private static class Counter implements ProvisionListener {
int count = 0;
public <T> void onProvision(ProvisionInvocation<T> provision) {
@@ -381,6 +450,22 @@
}
}
+ private static class CountAndCaptureExceptionListener implements ProvisionListener {
+ int beforeProvision = 0;
+ int afterProvision = 0;
+ AtomicReference<RuntimeException> capture = new AtomicReference<RuntimeException>();
+ public <T> void onProvision(ProvisionInvocation<T> provision) {
+ beforeProvision++;
+ try {
+ provision.provision();
+ } catch (RuntimeException re) {
+ capture.set(re);
+ throw re;
+ }
+ afterProvision++;
+ }
+ }
+
private static class Capturer implements ProvisionListener {
List<Key> keys = Lists.newArrayList();
public <T> void onProvision(ProvisionInvocation<T> provision) {