Make the GIF dirty_rect be in the frame_rect

Updates https://bugs.chromium.org/p/oss-fuzz/issues/detail?id=11823
diff --git a/fuzz/c/std/gif_fuzzer.c b/fuzz/c/std/gif_fuzzer.c
index abb5cc0..58f911d 100644
--- a/fuzz/c/std/gif_fuzzer.c
+++ b/fuzz/c/std/gif_fuzzer.c
@@ -106,6 +106,15 @@
 
     bool seen_ok = false;
     while (true) {
+      wuffs_base__frame_config fc = ((wuffs_base__frame_config){});
+      status = wuffs_gif__decoder__decode_frame_config(&dec, &fc, src_reader);
+      if (status) {
+        if ((status != wuffs_base__warning__end_of_data) || !seen_ok) {
+          ret = status;
+        }
+        goto exit;
+      }
+
       status = wuffs_gif__decoder__decode_frame(&dec, &pb, src_reader, workbuf,
                                                 NULL);
       if (status) {
@@ -115,6 +124,15 @@
         goto exit;
       }
       seen_ok = true;
+
+      wuffs_base__rect_ie_u32 frame_rect =
+          wuffs_base__frame_config__bounds(&fc);
+      wuffs_base__rect_ie_u32 dirty_rect =
+          wuffs_gif__decoder__frame_dirty_rect(&dec);
+      if (!wuffs_base__rect_ie_u32__contains_rect(&frame_rect, dirty_rect)) {
+        ret = "internal error: frame_rect does not contain dirty_rect";
+        goto exit;
+      }
     }
   }
 
diff --git a/release/c/wuffs-unsupported-snapshot.c b/release/c/wuffs-unsupported-snapshot.c
index 764ec51..debb131 100644
--- a/release/c/wuffs-unsupported-snapshot.c
+++ b/release/c/wuffs-unsupported-snapshot.c
@@ -8639,9 +8639,6 @@
       status = wuffs_base__error__bad_call_sequence;
       goto exit;
     }
-    (memset(&self->private_impl.f_dirty_y, 0,
-            sizeof((wuffs_base__range_ie_u32){})),
-     wuffs_base__return_empty_struct());
     WUFFS_BASE__COROUTINE_SUSPENSION_POINT(1);
     status = wuffs_gif__decoder__decode_header(self, a_src);
     if (status) {
@@ -8747,9 +8744,11 @@
   }
 
   return wuffs_base__utility__make_rect_ie_u32(
-      self->private_impl.f_frame_rect_x0,
+      wuffs_base__u32__min(self->private_impl.f_frame_rect_x0,
+                           self->private_impl.f_width),
       wuffs_base__range_ie_u32__get_min_incl(&self->private_impl.f_dirty_y),
-      self->private_impl.f_frame_rect_x1,
+      wuffs_base__u32__min(self->private_impl.f_frame_rect_x1,
+                           self->private_impl.f_width),
       wuffs_base__range_ie_u32__get_max_excl(&self->private_impl.f_dirty_y));
 }
 
@@ -8826,6 +8825,9 @@
   switch (coro_susp_point) {
     WUFFS_BASE__COROUTINE_SUSPENSION_POINT_0;
 
+    (memset(&self->private_impl.f_dirty_y, 0,
+            sizeof((wuffs_base__range_ie_u32){})),
+     wuffs_base__return_empty_struct());
     if (!self->private_impl.f_end_of_data) {
       if (self->private_impl.f_call_sequence == 0) {
         WUFFS_BASE__COROUTINE_SUSPENSION_POINT(1);
diff --git a/std/gif/decode_gif.wuffs b/std/gif/decode_gif.wuffs
index 469e37f..6241fd0 100644
--- a/std/gif/decode_gif.wuffs
+++ b/std/gif/decode_gif.wuffs
@@ -132,7 +132,6 @@
 	if this.call_sequence >= 1 {
 		return base."?bad call sequence"
 	}
-	this.dirty_y.reset!()
 
 	this.decode_header?(src:args.src)
 	this.decode_lsd?(src:args.src)
@@ -177,10 +176,16 @@
 }
 
 pub func decoder.frame_dirty_rect() base.rect_ie_u32 {
+	// TODO: intersect this with the frame_rect? In theory, that should be
+	// unnecessary, and could hide bugs, but it'd be a cheap way to ensure that
+	// the dirty_rect is inside the frame_rect.
+	//
+	// Note that this method is pure, so it cannot set a sticky error bit if
+	// the dirty_rect is too big.
 	return this.util.make_rect_ie_u32(
-		min_incl_x:this.frame_rect_x0,
+		min_incl_x:this.frame_rect_x0.min(x:this.width),
 		min_incl_y:this.dirty_y.get_min_incl(),
-		max_excl_x:this.frame_rect_x1,
+		max_excl_x:this.frame_rect_x1.min(x:this.width),
 		max_excl_y:this.dirty_y.get_max_excl())
 }
 
@@ -206,6 +211,8 @@
 pub func decoder.decode_frame_config?(dst nptr base.frame_config, src base.io_reader) {
 	var blend base.u8
 
+	this.dirty_y.reset!()
+
 	if not this.end_of_data {
 		if this.call_sequence == 0 {
 			this.decode_image_config?(dst:nullptr, src:args.src)
diff --git a/test/c/std/gif.c b/test/c/std/gif.c
index 27d2699..158a10c 100644
--- a/test/c/std/gif.c
+++ b/test/c/std/gif.c
@@ -611,6 +611,13 @@
     if (status) {
       RETURN_FAIL("decode_frame #%" PRIu32 ": got \"%s\"", i, status);
     }
+
+    wuffs_base__rect_ie_u32 frame_rect = wuffs_base__frame_config__bounds(&fc);
+    wuffs_base__rect_ie_u32 dirty_rect =
+        wuffs_gif__decoder__frame_dirty_rect(&dec);
+    if (!wuffs_base__rect_ie_u32__contains_rect(&frame_rect, dirty_rect)) {
+      RETURN_FAIL("internal error: frame_rect does not contain dirty_rect");
+    }
   }
 
   // There should be no more frames, no matter how many times we call
@@ -730,8 +737,8 @@
 
   uint32_t i;
   for (i = 0; true; i++) {
+    wuffs_base__frame_config fc = ((wuffs_base__frame_config){});
     {
-      wuffs_base__frame_config fc = ((wuffs_base__frame_config){});
       status = wuffs_gif__decoder__decode_frame_config(&dec, &fc, src_reader);
       if (i == WUFFS_TESTLIB_ARRAY_SIZE(want_frame_config_bounds)) {
         if (status != wuffs_base__warning__end_of_data) {
@@ -771,6 +778,14 @@
         RETURN_FAIL("decode_frame #%" PRIu32 ": got \"%s\"", i, status);
       }
 
+      wuffs_base__rect_ie_u32 frame_rect =
+          wuffs_base__frame_config__bounds(&fc);
+      wuffs_base__rect_ie_u32 dirty_rect =
+          wuffs_gif__decoder__frame_dirty_rect(&dec);
+      if (!wuffs_base__rect_ie_u32__contains_rect(&frame_rect, dirty_rect)) {
+        RETURN_FAIL("internal error: frame_rect does not contain dirty_rect");
+      }
+
       char got[(width * height) + 1];
       for (y = 0; y < height; y++) {
         for (x = 0; x < width; x++) {