Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.

Commit 0ac9e97

Browse files
authored
Check return values for Sk[I]Rect::intersect (#54577)
The `SkRect::intersect` and `SkIRect::intersect` methods return values that must be handled or the calling code might end up with a non-empty answer for non-intersecting rectangles. There were 3 or 4 places in our code that we weren't doing this so now we check them all, in some cases even if we believe that the answer will not be empty. This is prep work so that Skia can add `[[nodiscard]]` directives to these 2 methods so that nobody else makes that mistake.
1 parent cf7399f commit 0ac9e97

File tree

6 files changed

+88
-9
lines changed

6 files changed

+88
-9
lines changed

display_list/display_list_unittests.cc

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4119,6 +4119,32 @@ TEST_F(DisplayListTest, SaveLayerBoundsClipDetectionSimpleClippedRect) {
41194119
EXPECT_TRUE(expector.all_bounds_checked());
41204120
}
41214121

4122+
TEST_F(DisplayListTest, DisjointSaveLayerBoundsProduceEmptySuppliedBounds) {
4123+
// This test was added when we fixed the Builder code to check the
4124+
// return value of the SkRect::intersect method, but it turns out
4125+
// that the indicated case never happens in practice due to the
4126+
// internal culling during the recording process. It actually passes
4127+
// both before and after the fix, but is here to ensure the right
4128+
// behavior does not regress.
4129+
4130+
SkRect layer_bounds = SkRect::MakeLTRB(10.0f, 10.0f, 20.0f, 20.0f);
4131+
SkRect draw_rect = SkRect::MakeLTRB(50.0f, 50.0f, 100.0f, 100.0f);
4132+
ASSERT_FALSE(layer_bounds.intersects(draw_rect));
4133+
ASSERT_FALSE(layer_bounds.isEmpty());
4134+
ASSERT_FALSE(draw_rect.isEmpty());
4135+
4136+
DisplayListBuilder builder;
4137+
builder.SaveLayer(&layer_bounds, nullptr);
4138+
builder.DrawRect(draw_rect, DlPaint());
4139+
builder.Restore();
4140+
auto display_list = builder.Build();
4141+
4142+
SaveLayerBoundsExpector expector;
4143+
expector.addSuppliedExpectation(SkRect::MakeEmpty(), false);
4144+
display_list->Dispatch(expector);
4145+
EXPECT_TRUE(expector.all_bounds_checked());
4146+
}
4147+
41224148
class DepthExpector : public virtual DlOpReceiver,
41234149
virtual IgnoreAttributeDispatchHelper,
41244150
virtual IgnoreTransformDispatchHelper,

display_list/dl_builder.cc

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -617,7 +617,12 @@ void DisplayListBuilder::RestoreLayer() {
617617
if (layer_op->options.bounds_from_caller()) {
618618
if (!content_bounds.isEmpty() && !layer_op->rect.contains(content_bounds)) {
619619
layer_op->options = layer_op->options.with_content_is_clipped();
620-
content_bounds.intersect(layer_op->rect);
620+
if (!content_bounds.intersect(layer_op->rect)) {
621+
// Should never happen because we prune ops that don't intersect the
622+
// supplied bounds so content_bounds would already be empty and we
623+
// wouldn't come into this control block due to the empty test above.
624+
content_bounds.setEmpty();
625+
}
621626
}
622627
}
623628
layer_op->rect = content_bounds;

display_list/geometry/dl_region.cc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -499,8 +499,8 @@ DlRegion DlRegion::MakeIntersection(const DlRegion& a, const DlRegion& b) {
499499
return DlRegion();
500500
} else if (a.isSimple() && b.isSimple()) {
501501
SkIRect r(a.bounds_);
502+
[[maybe_unused]]
502503
auto res = r.intersect(b.bounds_);
503-
(void)res; // Suppress unused variable warning in release builds.
504504
FML_DCHECK(res);
505505
return DlRegion(r);
506506
} else if (a.isSimple() && a.bounds_.contains(b.bounds_)) {

flow/diff_context.cc

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -139,8 +139,12 @@ Damage DiffContext::ComputeDamage(const SkIRect& accumulated_buffer_damage,
139139
frame_damage.roundOut(&res.frame_damage);
140140

141141
SkIRect frame_clip = SkIRect::MakeSize(frame_size_);
142-
res.buffer_damage.intersect(frame_clip);
143-
res.frame_damage.intersect(frame_clip);
142+
if (!res.buffer_damage.intersect(frame_clip)) {
143+
res.buffer_damage.setEmpty();
144+
}
145+
if (!res.frame_damage.intersect(frame_clip)) {
146+
res.frame_damage.setEmpty();
147+
}
144148

145149
if (horizontal_clip_alignment > 1 || vertical_clip_alignment > 1) {
146150
AlignRect(res.buffer_damage, horizontal_clip_alignment,

flow/diff_context_unittests.cc

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,5 +32,41 @@ TEST_F(DiffContextTest, ClipAlignment) {
3232
EXPECT_EQ(damage.buffer_damage, SkIRect::MakeLTRB(16, 16, 64, 64));
3333
}
3434

35+
TEST_F(DiffContextTest, DisjointDamage) {
36+
SkISize frame_size = SkISize::Make(90, 90);
37+
auto in_bounds_dl = CreateDisplayList(SkRect::MakeLTRB(30, 30, 50, 50));
38+
auto out_bounds_dl = CreateDisplayList(SkRect::MakeLTRB(100, 100, 120, 120));
39+
40+
// We need both DisplayLists to be non-empty.
41+
ASSERT_FALSE(in_bounds_dl->bounds().isEmpty());
42+
ASSERT_FALSE(out_bounds_dl->bounds().isEmpty());
43+
44+
// We need the in_bounds DisplayList to be inside the frame size.
45+
// We need the out_bounds DisplayList to be completely outside the frame.
46+
ASSERT_TRUE(SkRect::Make(frame_size).contains(in_bounds_dl->bounds()));
47+
ASSERT_FALSE(SkRect::Make(frame_size).intersects(out_bounds_dl->bounds()));
48+
49+
MockLayerTree t1(frame_size);
50+
t1.root()->Add(CreateDisplayListLayer(in_bounds_dl));
51+
52+
MockLayerTree t2(frame_size);
53+
// Include previous
54+
t2.root()->Add(CreateDisplayListLayer(in_bounds_dl));
55+
// Add a new layer that is out of frame bounds
56+
t2.root()->Add(CreateDisplayListLayer(out_bounds_dl));
57+
58+
// Cannot use DiffLayerTree because it implicitly adds a clip layer
59+
// around the tree, but we want the out of bounds dl to not be pruned
60+
// to test the intersection code inside layer::Diff/ComputeDamage
61+
// damage = DiffLayerTree(t2, t1, SkIRect::MakeEmpty(), 0, 0);
62+
63+
DiffContext dc(frame_size, t2.paint_region_map(), t1.paint_region_map(), true,
64+
false);
65+
t2.root()->Diff(&dc, t1.root());
66+
auto damage = dc.ComputeDamage(SkIRect::MakeEmpty(), 0, 0);
67+
EXPECT_EQ(damage.frame_damage, SkIRect::MakeEmpty());
68+
EXPECT_EQ(damage.buffer_damage, SkIRect::MakeEmpty());
69+
}
70+
3571
} // namespace testing
3672
} // namespace flutter

flow/view_slicer.cc

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -89,11 +89,19 @@ std::unordered_map<int64_t, SkRect> SliceViews(
8989
}
9090

9191
// Get the intersection rect with the `current_view_rect`,
92-
partial_joined_rect.intersect(SkRect::Make(current_view_rect.roundOut()));
93-
94-
// Join the `partial_joined_rect` into `full_joined_rect` to get the rect
95-
// above the current `slice`
96-
full_joined_rect.join(partial_joined_rect);
92+
if (partial_joined_rect.intersect(
93+
SkRect::Make(current_view_rect.roundOut()))) {
94+
// Join the `partial_joined_rect` into `full_joined_rect` to get the
95+
// rect above the current `slice`, only if it intersects the indicated
96+
// view. This should always be the case because we just deleted any
97+
// rects that don't intersect the "rounded-in" view, so they must
98+
// all intersect the "rounded-out" view (or the partial join could
99+
// be empty in which case this would be a NOP). Either way, the
100+
// penalty for not checking the return value of the intersect method
101+
// would be to join a non-overlapping rectangle into the overlay
102+
// bounds - if the above implementation ever changes - so we check it.
103+
full_joined_rect.join(partial_joined_rect);
104+
}
97105
}
98106

99107
if (!full_joined_rect.isEmpty()) {

0 commit comments

Comments
 (0)