From 678c201dbc8106188f3b32c7484ba9e48093dba5 Mon Sep 17 00:00:00 2001 From: Scott Goodson Date: Sun, 10 Apr 2016 16:42:22 -0700 Subject: [PATCH] [ASLayout] Add additional validation step to intercept incorrect values in production and safely zero them out. These conditions already had assertions, but at runtime, an insufficiently tested and incorrect ASLayoutSpec could generate values that cause UIKit to enter an infinite loop (e.g. inside of UICollectionView layout validation). --- AsyncDisplayKit/ASMapNode.mm | 11 ++++++++++- AsyncDisplayKit/Layout/ASDimension.h | 3 +++ AsyncDisplayKit/Layout/ASLayout.h | 2 ++ AsyncDisplayKit/Layout/ASLayout.mm | 10 +++++++++- 4 files changed, 24 insertions(+), 2 deletions(-) diff --git a/AsyncDisplayKit/ASMapNode.mm b/AsyncDisplayKit/ASMapNode.mm index 96a4536a69..c44b0b0c78 100644 --- a/AsyncDisplayKit/ASMapNode.mm +++ b/AsyncDisplayKit/ASMapNode.mm @@ -293,9 +293,18 @@ CGSize size = self.preferredFrameSize; if (CGSizeEqualToSize(size, CGSizeZero)) { size = constrainedSize; + + // FIXME: Need a better way to allow maps to take up the right amount of space in a layout (sizeRange, etc) + // These fallbacks protect against inheriting a constrainedSize that contains a CGFLOAT_MAX value. + if (!isValidForLayout(size.width)) { + size.width = 100.0; + } + if (!isValidForLayout(size.height)) { + size.height = 100.0; + } } [self setSnapshotSizeWithReloadIfNeeded:size]; - return constrainedSize; + return size; } // -layout isn't usually needed over -layoutSpecThatFits, but this way we can avoid a needless node wrapper for MKMapView. diff --git a/AsyncDisplayKit/Layout/ASDimension.h b/AsyncDisplayKit/Layout/ASDimension.h index 3dba264468..8a09eb5f2e 100644 --- a/AsyncDisplayKit/Layout/ASDimension.h +++ b/AsyncDisplayKit/Layout/ASDimension.h @@ -8,6 +8,7 @@ * */ +#pragma once #import #import @@ -34,6 +35,8 @@ typedef struct { extern ASRelativeDimension const ASRelativeDimensionUnconstrained; +#define isValidForLayout(x) ((isnormal(x) || x == 0.0) && x >= 0.0 && x < (CGFLOAT_MAX / 2.0)) + ASDISPLAYNODE_EXTERN_C_BEGIN NS_ASSUME_NONNULL_BEGIN diff --git a/AsyncDisplayKit/Layout/ASLayout.h b/AsyncDisplayKit/Layout/ASLayout.h index 41b4878461..af7b99ad23 100644 --- a/AsyncDisplayKit/Layout/ASLayout.h +++ b/AsyncDisplayKit/Layout/ASLayout.h @@ -8,6 +8,8 @@ * */ +#pragma once + #import #import #import diff --git a/AsyncDisplayKit/Layout/ASLayout.mm b/AsyncDisplayKit/Layout/ASLayout.mm index d6af48bcba..3d710316ca 100644 --- a/AsyncDisplayKit/Layout/ASLayout.mm +++ b/AsyncDisplayKit/Layout/ASLayout.mm @@ -39,7 +39,15 @@ extern BOOL CGPointIsNull(CGPoint point) ASLayout *l = [super new]; if (l) { l->_layoutableObject = layoutableObject; - l->_size = CGSizeMake(ASCeilPixelValue(size.width), ASCeilPixelValue(size.height)); + + if (!isValidForLayout(size.width) || !isValidForLayout(size.height)) { + ASDisplayNodeAssert(NO, @"layoutSize is invalid and unsafe to provide to Core Animation! Production will force to 0, 0. Size = %@, node = %@", NSStringFromCGSize(size), layoutableObject); + size = CGSizeZero; + } else { + size = CGSizeMake(ASCeilPixelValue(size.width), ASCeilPixelValue(size.height)); + } + l->_size = size; + if (CGPointIsNull(position) == NO) { l->_position = CGPointMake(ASCeilPixelValue(position.x), ASCeilPixelValue(position.y)); } else {