Commit 1a0484cc authored by Hixie's avatar Hixie

Improve exceptions and asserts for rendering lib.

* Use actual exceptions rather than assertions containing code
  containing strings when trying to give messages to authors.
* Introduce RenderingError which is an AssertionError that takes a
  string argument, to support the above.
* Provide a BoxDimensions.hasBoundedWidth/hasBoundedHeight API.
* Document BoxDimensions.isNormalized.
* Provide more useful information when we assert isNormalized and find
  that it is false.
* When finding the size is infinite, crawl the tree to figure out which
  render box is likely responsible for the infinite constraints.
* Provide more information when size doesn't match the constraints.
* Provide more information when intrinsic dimension methods violate the
  constraints.
* Only spam a huge amount of information for the first exception from
  the rendering library. I've noticed a lot of people looking at the
  last exception printed rather than the first and that's very
  misleading -- after the rendering library hits an exception, all bets
  are off regarding what'll happen in the future. All kinds of asserts
  might fire.
* Improve docs around the debug methods and flags for the above.
* Make Block default to have no children. Previously, giving no children
  crashed with a confusing message about a null deref in an assert.
parent 79828ef2
......@@ -79,16 +79,16 @@ abstract class RenderSector extends RenderObject {
}
SectorConstraints get constraints => super.constraints;
bool debugDoesMeetConstraints() {
void debugAssertDoesMeetConstraints() {
assert(constraints != null);
assert(deltaRadius != null);
assert(deltaRadius < double.INFINITY);
assert(deltaTheta != null);
assert(deltaTheta < double.INFINITY);
return constraints.minDeltaRadius <= deltaRadius &&
deltaRadius <= math.max(constraints.minDeltaRadius, constraints.maxDeltaRadius) &&
constraints.minDeltaTheta <= deltaTheta &&
deltaTheta <= math.max(constraints.minDeltaTheta, constraints.maxDeltaTheta);
assert(constraints.minDeltaRadius <= deltaRadius);
assert(deltaRadius <= math.max(constraints.minDeltaRadius, constraints.maxDeltaRadius));
assert(constraints.minDeltaTheta <= deltaTheta);
assert(deltaTheta <= math.max(constraints.minDeltaTheta, constraints.maxDeltaTheta));
}
void performResize() {
// default behaviour for subclasses that have sizedByParent = true
......
......@@ -350,8 +350,9 @@ class RenderBlockViewport extends RenderBlockBase {
double result;
if (intrinsicCallback == null) {
assert(() {
'RenderBlockViewport does not support returning intrinsic dimensions if the relevant callbacks have not been specified.';
return RenderObject.debugInDebugDoesMeetConstraints;
if (!RenderObject.debugCheckingIntrinsics)
throw new UnsupportedError('$runtimeType does not support returning intrinsic dimensions if the relevant callbacks have not been specified.');
return true;
});
return constrainer(0.0);
}
......
This diff is collapsed.
......@@ -724,11 +724,14 @@ abstract class RenderObject extends AbstractNode implements HitTestTarget {
void visitChildren(RenderObjectVisitor visitor) { }
dynamic debugOwner;
static int _debugPrintedExceptionCount = 0;
void _debugReportException(String method, dynamic exception, StackTrace stack) {
try {
if (debugRenderingExceptionHandler != null) {
debugRenderingExceptionHandler(this, method, exception, stack);
} else {
_debugPrintedExceptionCount += 1;
if (_debugPrintedExceptionCount == 1) {
debugPrint('-- EXCEPTION CAUGHT BY RENDERING LIBRARY -------------------------------');
debugPrint('The following exception was raised during $method():');
debugPrint('$exception');
......@@ -754,22 +757,12 @@ abstract class RenderObject extends AbstractNode implements HitTestTarget {
debugPrint('This RenderObject has no descendants.');
}
descendants.forEach(debugPrint);
assert(() {
if (debugInDebugDoesMeetConstraints) {
debugPrint('This exception was thrown while debugDoesMeetConstraints() was running.');
debugPrint('debugDoesMeetConstraints() verifies that some invariants are not being');
debugPrint('violated. For example, it verifies that RenderBox objects are sized in');
debugPrint('a manner consistent with the constraints provided, and, in addition, that');
debugPrint('the getMinIntrinsicWidth(), getMaxIntrinsicWidth(), etc, functions all');
debugPrint('return consistent values within the same constraints.');
debugPrint('If you are not writing your own RenderObject subclass, then this is not');
debugPrint('your fault. Contact support: https://github.com/flutter/flutter/issues/new');
}
return true;
});
debugPrint('Stack trace:');
debugPrint('$stack');
debugPrint('------------------------------------------------------------------------');
} else {
debugPrint('Another exception was raised: ${exception.toString().split("\n")[0]}');
}
}
} catch (exception) {
debugPrint('(exception during exception handler: $exception)');
......@@ -809,15 +802,23 @@ abstract class RenderObject extends AbstractNode implements HitTestTarget {
Constraints _constraints;
/// The layout constraints most recently supplied by the parent.
Constraints get constraints => _constraints;
/// Override this function in a subclass to verify that your state matches the constraints object.
bool debugDoesMeetConstraints();
/// When true, debugDoesMeetConstraints() is currently executing.
///
/// This should be set by implementations of debugDoesMeetConstraints() so that
/// tests can selectively ignore custom layout callbacks. It should not be set
/// outside of debugDoesMeetConstraints() implementations and should not be used
/// for purposes other than tests.
static bool debugInDebugDoesMeetConstraints = false;
/// Verify that the object's constraints are being met. Override
/// this function in a subclass to verify that your state matches
/// the constraints object. This function is only called in checked
/// mode. If the constraints are not met, it should assert or throw
/// an exception.
void debugAssertDoesMeetConstraints();
/// When true, debugAssertDoesMeetConstraints() is currently
/// executing asserts for verifying the consistent behaviour of
/// intrinsic dimensions methods.
///
/// This should only be set by debugAssertDoesMeetConstraints()
/// implementations. It is used by tests to selectively ignore
/// custom layout callbacks. It should not be set outside of
/// debugAssertDoesMeetConstraints(), and should not be checked in
/// release mode (where it will always be false).
static bool debugCheckingIntrinsics = false;
bool debugAncestorsAlreadyMarkedNeedsLayout() {
if (_relayoutSubtreeRoot == null)
return true; // we haven't yet done layout even once, so there's nothing for us to do
......@@ -1022,7 +1023,7 @@ abstract class RenderObject extends AbstractNode implements HitTestTarget {
assert(() { _debugDoingThisResize = true; return true; });
try {
performResize();
assert(debugDoesMeetConstraints());
assert(() { debugAssertDoesMeetConstraints(); return true; });
} catch (e, stack) {
_debugReportException('performResize', e, stack);
}
......@@ -1038,7 +1039,7 @@ abstract class RenderObject extends AbstractNode implements HitTestTarget {
try {
performLayout();
markNeedsSemanticsUpdate();
assert(debugDoesMeetConstraints());
assert(() { debugAssertDoesMeetConstraints(); return true; });
} catch (e, stack) {
_debugReportException('performLayout', e, stack);
}
......@@ -2023,3 +2024,10 @@ abstract class ContainerRenderObjectMixin<ChildType extends RenderObject, Parent
return result;
}
}
/// Error thrown when the rendering library encounters a contract violation.
class RenderingError extends AssertionError {
RenderingError(this.message);
final String message;
String toString() => message;
}
......@@ -78,7 +78,7 @@ class RenderView extends RenderObject with RenderObjectWithChildMixin<RenderBox>
// We never call layout() on this class, so this should never get
// checked. (This class is laid out using scheduleInitialLayout().)
bool debugDoesMeetConstraints() { assert(false); return false; }
void debugAssertDoesMeetConstraints() { assert(false); }
void performResize() {
assert(false);
......
......@@ -155,10 +155,14 @@ class _MixedViewportElement extends RenderObjectElement<MixedViewport> {
double _noIntrinsicExtent(BoxConstraints constraints) {
assert(() {
'MixedViewport does not support returning intrinsic dimensions. ' +
'Calculating the intrinsic dimensions would require walking the entire child list, ' +
'which defeats the entire point of having a lazily-built list of children.';
return RenderObject.debugInDebugDoesMeetConstraints;
if (!RenderObject.debugCheckingIntrinsics) {
throw new UnsupportedError(
'MixedViewport does not support returning intrinsic dimensions.\n'
'Calculating the intrinsic dimensions would require walking the entire child list,\n'
'which defeats the entire point of having a lazily-built list of children.'
);
}
return true;
});
return null;
}
......
......@@ -442,13 +442,14 @@ class ScrollableViewportState extends ScrollableState<ScrollableViewport> {
class Block extends StatelessComponent {
Block({
Key key,
this.children,
this.children: const <Widget>[],
this.padding,
this.initialScrollOffset,
this.scrollDirection: Axis.vertical,
this.onScroll,
this.scrollableKey
}) : super(key: key) {
assert(children != null);
assert(!children.any((Widget child) => child == null));
}
......
......@@ -11,7 +11,7 @@ class TestMultiChildLayoutDelegate extends MultiChildLayoutDelegate {
BoxConstraints getSizeConstraints;
Size getSize(BoxConstraints constraints) {
if (!RenderObject.debugInDebugDoesMeetConstraints)
if (!RenderObject.debugCheckingIntrinsics)
getSizeConstraints = constraints;
return new Size(200.0, 300.0);
}
......@@ -23,7 +23,7 @@ class TestMultiChildLayoutDelegate extends MultiChildLayoutDelegate {
bool performLayoutIsChild;
void performLayout(Size size, BoxConstraints constraints) {
assert(!RenderObject.debugInDebugDoesMeetConstraints);
assert(!RenderObject.debugCheckingIntrinsics);
expect(() {
performLayoutSize = size;
performLayoutConstraints = constraints;
......@@ -36,7 +36,7 @@ class TestMultiChildLayoutDelegate extends MultiChildLayoutDelegate {
bool shouldRelayoutCalled = false;
bool shouldRelayoutValue = false;
bool shouldRelayout(_) {
assert(!RenderObject.debugInDebugDoesMeetConstraints);
assert(!RenderObject.debugCheckingIntrinsics);
shouldRelayoutCalled = true;
return shouldRelayoutValue;
}
......
......@@ -14,13 +14,13 @@ class TestOneChildLayoutDelegate extends OneChildLayoutDelegate {
Size childSizeFromGetPositionForChild;
Size getSize(BoxConstraints constraints) {
if (!RenderObject.debugInDebugDoesMeetConstraints)
if (!RenderObject.debugCheckingIntrinsics)
constraintsFromGetSize = constraints;
return new Size(200.0, 300.0);
}
BoxConstraints getConstraintsForChild(BoxConstraints constraints) {
assert(!RenderObject.debugInDebugDoesMeetConstraints);
assert(!RenderObject.debugCheckingIntrinsics);
constraintsFromGetConstraintsForChild = constraints;
return new BoxConstraints(
minWidth: 100.0,
......@@ -31,7 +31,7 @@ class TestOneChildLayoutDelegate extends OneChildLayoutDelegate {
}
Offset getPositionForChild(Size size, Size childSize) {
assert(!RenderObject.debugInDebugDoesMeetConstraints);
assert(!RenderObject.debugCheckingIntrinsics);
sizeFromGetPositionForChild = size;
childSizeFromGetPositionForChild = childSize;
return Offset.zero;
......@@ -40,7 +40,7 @@ class TestOneChildLayoutDelegate extends OneChildLayoutDelegate {
bool shouldRelayoutCalled = false;
bool shouldRelayoutValue = false;
bool shouldRelayout(_) {
assert(!RenderObject.debugInDebugDoesMeetConstraints);
assert(!RenderObject.debugCheckingIntrinsics);
shouldRelayoutCalled = true;
return shouldRelayoutValue;
}
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment