std/*io_transformer*: add "#truncated input"

name                                              old speed      new speed       delta

wuffs_bzip2_decode_10k/clang11                    58.5MB/s ± 0%   59.9MB/s ± 0%   +2.50%  (p=0.008 n=5+5)
wuffs_bzip2_decode_100k/clang11                   45.7MB/s ± 0%   50.5MB/s ± 0%  +10.66%  (p=0.008 n=5+5)

wuffs_bzip2_decode_10k/gcc10                      56.2MB/s ± 0%   61.9MB/s ± 0%  +10.01%  (p=0.008 n=5+5)
wuffs_bzip2_decode_100k/gcc10                     46.5MB/s ± 0%   50.4MB/s ± 0%   +8.41%  (p=0.016 n=4+5)

wuffs_deflate_decode_1k_full_init/clang11          195MB/s ± 0%    191MB/s ± 0%   -1.71%  (p=0.008 n=5+5)
wuffs_deflate_decode_1k_part_init/clang11          224MB/s ± 0%    225MB/s ± 0%   +0.87%  (p=0.008 n=5+5)
wuffs_deflate_decode_10k_full_init/clang11         421MB/s ± 0%    401MB/s ± 0%   -4.59%  (p=0.008 n=5+5)
wuffs_deflate_decode_10k_part_init/clang11         432MB/s ± 0%    413MB/s ± 0%   -4.49%  (p=0.008 n=5+5)
wuffs_deflate_decode_100k_just_one_read/clang11    542MB/s ± 0%    518MB/s ± 0%   -4.43%  (p=0.008 n=5+5)
wuffs_deflate_decode_100k_many_big_reads/clang11   338MB/s ± 0%    325MB/s ± 0%   -3.81%  (p=0.008 n=5+5)

wuffs_deflate_decode_1k_full_init/gcc10            181MB/s ± 0%    185MB/s ± 0%   +1.76%  (p=0.008 n=5+5)
wuffs_deflate_decode_1k_part_init/gcc10            209MB/s ± 0%    212MB/s ± 0%   +1.79%  (p=0.008 n=5+5)
wuffs_deflate_decode_10k_full_init/gcc10           410MB/s ± 0%    407MB/s ± 0%   -0.93%  (p=0.008 n=5+5)
wuffs_deflate_decode_10k_part_init/gcc10           421MB/s ± 0%    418MB/s ± 0%   -0.87%  (p=0.008 n=5+5)
wuffs_deflate_decode_100k_just_one_read/gcc10      533MB/s ± 0%    533MB/s ± 0%     ~     (p=0.548 n=5+5)
wuffs_deflate_decode_100k_many_big_reads/gcc10     336MB/s ± 0%    334MB/s ± 1%   -0.67%  (p=0.008 n=5+5)

wuffs_gzip_decode_10k/clang11                      401MB/s ± 0%    398MB/s ± 0%   -0.77%  (p=0.008 n=5+5)
wuffs_gzip_decode_100k/clang11                     505MB/s ± 0%    503MB/s ± 0%   -0.46%  (p=0.008 n=5+5)

wuffs_gzip_decode_10k/gcc10                        403MB/s ± 0%    410MB/s ± 0%   +1.90%  (p=0.008 n=5+5)
wuffs_gzip_decode_100k/gcc10                       522MB/s ± 0%    523MB/s ± 0%   +0.10%  (p=0.032 n=5+5)

wuffs_zlib_decode_10k/clang11                      418MB/s ± 0%    417MB/s ± 1%     ~     (p=0.310 n=5+5)
wuffs_zlib_decode_100k/clang11                     530MB/s ± 0%    529MB/s ± 0%     ~     (p=0.421 n=5+5)

wuffs_zlib_decode_10k/gcc10                        428MB/s ± 0%    434MB/s ± 0%   +1.50%  (p=0.008 n=5+5)
wuffs_zlib_decode_100k/gcc10                       551MB/s ± 0%    551MB/s ± 0%     ~     (p=0.690 n=5+5)

Fixes #96
diff --git a/release/c/wuffs-unsupported-snapshot.c b/release/c/wuffs-unsupported-snapshot.c
index d00acc1..58c7c14 100644
--- a/release/c/wuffs-unsupported-snapshot.c
+++ b/release/c/wuffs-unsupported-snapshot.c
@@ -6893,6 +6893,7 @@
 extern const char wuffs_bzip2__error__bad_checksum[];
 extern const char wuffs_bzip2__error__bad_header[];
 extern const char wuffs_bzip2__error__bad_number_of_sections[];
+extern const char wuffs_bzip2__error__truncated_input[];
 extern const char wuffs_bzip2__error__unsupported_block_randomization[];
 
 // ---------------- Public Consts
@@ -7018,6 +7019,7 @@
     uint32_t f_code_lengths_bitmask;
 
     uint32_t p_transform_io[1];
+    uint32_t p_do_transform_io[1];
     uint32_t p_prepare_block[1];
     uint32_t p_read_code_lengths[1];
     uint32_t p_flush_slow[1];
@@ -7038,7 +7040,7 @@
       uint32_t v_i;
       uint64_t v_tag;
       uint32_t v_final_checksum_want;
-    } s_transform_io[1];
+    } s_do_transform_io[1];
     struct {
       uint32_t v_i;
       uint32_t v_selector;
@@ -7581,6 +7583,7 @@
 extern const char wuffs_deflate__error__inconsistent_stored_block_length[];
 extern const char wuffs_deflate__error__missing_end_of_block_code[];
 extern const char wuffs_deflate__error__no_huffman_codes[];
+extern const char wuffs_deflate__error__truncated_input[];
 
 // ---------------- Public Consts
 
@@ -7695,6 +7698,7 @@
     bool f_end_of_block;
 
     uint32_t p_transform_io[1];
+    uint32_t p_do_transform_io[1];
     uint32_t p_decode_blocks[1];
     uint32_t p_decode_uncompressed[1];
     uint32_t p_init_dynamic_huffman[1];
@@ -8512,6 +8516,7 @@
 extern const char wuffs_gzip__error__bad_compression_method[];
 extern const char wuffs_gzip__error__bad_encoding_flags[];
 extern const char wuffs_gzip__error__bad_header[];
+extern const char wuffs_gzip__error__truncated_input[];
 
 // ---------------- Public Consts
 
@@ -8616,6 +8621,7 @@
     bool f_ignore_checksum;
 
     uint32_t p_transform_io[1];
+    uint32_t p_do_transform_io[1];
   } private_impl;
 
   struct {
@@ -8628,7 +8634,7 @@
       uint32_t v_decoded_length_got;
       uint32_t v_checksum_want;
       uint64_t scratch;
-    } s_transform_io[1];
+    } s_do_transform_io[1];
   } private_data;
 
 #ifdef __cplusplus
@@ -9324,6 +9330,7 @@
 extern const char wuffs_zlib__error__bad_compression_window_size[];
 extern const char wuffs_zlib__error__bad_parity_check[];
 extern const char wuffs_zlib__error__incorrect_dictionary[];
+extern const char wuffs_zlib__error__truncated_input[];
 
 // ---------------- Public Consts
 
@@ -9446,6 +9453,7 @@
     uint32_t f_dict_id_want;
 
     uint32_t p_transform_io[1];
+    uint32_t p_do_transform_io[1];
   } private_impl;
 
   struct {
@@ -9456,7 +9464,7 @@
     struct {
       uint32_t v_checksum_got;
       uint64_t scratch;
-    } s_transform_io[1];
+    } s_do_transform_io[1];
   } private_data;
 
 #ifdef __cplusplus
@@ -24981,6 +24989,7 @@
 const char wuffs_bzip2__error__bad_checksum[] = "#bzip2: bad checksum";
 const char wuffs_bzip2__error__bad_header[] = "#bzip2: bad header";
 const char wuffs_bzip2__error__bad_number_of_sections[] = "#bzip2: bad number of sections";
+const char wuffs_bzip2__error__truncated_input[] = "#bzip2: truncated input";
 const char wuffs_bzip2__error__unsupported_block_randomization[] = "#bzip2: unsupported block randomization";
 const char wuffs_bzip2__error__internal_error_inconsistent_huffman_decoder_state[] = "#bzip2: internal error: inconsistent Huffman decoder state";
 
@@ -25032,6 +25041,13 @@
 // ---------------- Private Function Prototypes
 
 static wuffs_base__status
+wuffs_bzip2__decoder__do_transform_io(
+    wuffs_bzip2__decoder* self,
+    wuffs_base__io_buffer* a_dst,
+    wuffs_base__io_buffer* a_src,
+    wuffs_base__slice_u8 a_workbuf);
+
+static wuffs_base__status
 wuffs_bzip2__decoder__prepare_block(
     wuffs_bzip2__decoder* self,
     wuffs_base__io_buffer* a_src);
@@ -25225,6 +25241,74 @@
   self->private_impl.active_coroutine = 0;
   wuffs_base__status status = wuffs_base__make_status(NULL);
 
+  wuffs_base__status v_status = wuffs_base__make_status(NULL);
+
+  const uint8_t* iop_a_src = NULL;
+  const uint8_t* io0_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL;
+  const uint8_t* io1_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL;
+  const uint8_t* io2_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL;
+  if (a_src && a_src->data.ptr) {
+    io0_a_src = a_src->data.ptr;
+    io1_a_src = io0_a_src + a_src->meta.ri;
+    iop_a_src = io1_a_src;
+    io2_a_src = io0_a_src + a_src->meta.wi;
+  }
+
+  uint32_t coro_susp_point = self->private_impl.p_transform_io[0];
+  switch (coro_susp_point) {
+    WUFFS_BASE__COROUTINE_SUSPENSION_POINT_0;
+
+    while (true) {
+      {
+        if (a_src) {
+          a_src->meta.ri = ((size_t)(iop_a_src - a_src->data.ptr));
+        }
+        wuffs_base__status t_0 = wuffs_bzip2__decoder__do_transform_io(self, a_dst, a_src, a_workbuf);
+        v_status = t_0;
+        if (a_src) {
+          iop_a_src = a_src->data.ptr + a_src->meta.ri;
+        }
+      }
+      if ((v_status.repr == wuffs_base__suspension__short_read) && (a_src && a_src->meta.closed)) {
+        status = wuffs_base__make_status(wuffs_bzip2__error__truncated_input);
+        goto exit;
+      }
+      status = v_status;
+      WUFFS_BASE__COROUTINE_SUSPENSION_POINT_MAYBE_SUSPEND(1);
+    }
+
+    ok:
+    self->private_impl.p_transform_io[0] = 0;
+    goto exit;
+  }
+
+  goto suspend;
+  suspend:
+  self->private_impl.p_transform_io[0] = wuffs_base__status__is_suspension(&status) ? coro_susp_point : 0;
+  self->private_impl.active_coroutine = wuffs_base__status__is_suspension(&status) ? 1 : 0;
+
+  goto exit;
+  exit:
+  if (a_src && a_src->data.ptr) {
+    a_src->meta.ri = ((size_t)(iop_a_src - a_src->data.ptr));
+  }
+
+  if (wuffs_base__status__is_error(&status)) {
+    self->private_impl.magic = WUFFS_BASE__DISABLED;
+  }
+  return status;
+}
+
+// -------- func bzip2.decoder.do_transform_io
+
+static wuffs_base__status
+wuffs_bzip2__decoder__do_transform_io(
+    wuffs_bzip2__decoder* self,
+    wuffs_base__io_buffer* a_dst,
+    wuffs_base__io_buffer* a_src,
+    wuffs_base__slice_u8 a_workbuf) {
+  wuffs_base__status status = wuffs_base__make_status(NULL);
+
   uint8_t v_c = 0;
   uint32_t v_i = 0;
   uint64_t v_tag = 0;
@@ -25242,11 +25326,11 @@
     io2_a_src = io0_a_src + a_src->meta.wi;
   }
 
-  uint32_t coro_susp_point = self->private_impl.p_transform_io[0];
+  uint32_t coro_susp_point = self->private_impl.p_do_transform_io[0];
   if (coro_susp_point) {
-    v_i = self->private_data.s_transform_io[0].v_i;
-    v_tag = self->private_data.s_transform_io[0].v_tag;
-    v_final_checksum_want = self->private_data.s_transform_io[0].v_final_checksum_want;
+    v_i = self->private_data.s_do_transform_io[0].v_i;
+    v_tag = self->private_data.s_do_transform_io[0].v_tag;
+    v_final_checksum_want = self->private_data.s_do_transform_io[0].v_final_checksum_want;
   }
   switch (coro_susp_point) {
     WUFFS_BASE__COROUTINE_SUSPENSION_POINT_0;
@@ -25435,17 +25519,16 @@
 
     goto ok;
     ok:
-    self->private_impl.p_transform_io[0] = 0;
+    self->private_impl.p_do_transform_io[0] = 0;
     goto exit;
   }
 
   goto suspend;
   suspend:
-  self->private_impl.p_transform_io[0] = wuffs_base__status__is_suspension(&status) ? coro_susp_point : 0;
-  self->private_impl.active_coroutine = wuffs_base__status__is_suspension(&status) ? 1 : 0;
-  self->private_data.s_transform_io[0].v_i = v_i;
-  self->private_data.s_transform_io[0].v_tag = v_tag;
-  self->private_data.s_transform_io[0].v_final_checksum_want = v_final_checksum_want;
+  self->private_impl.p_do_transform_io[0] = wuffs_base__status__is_suspension(&status) ? coro_susp_point : 0;
+  self->private_data.s_do_transform_io[0].v_i = v_i;
+  self->private_data.s_do_transform_io[0].v_tag = v_tag;
+  self->private_data.s_do_transform_io[0].v_final_checksum_want = v_final_checksum_want;
 
   goto exit;
   exit:
@@ -25453,9 +25536,6 @@
     a_src->meta.ri = ((size_t)(iop_a_src - a_src->data.ptr));
   }
 
-  if (wuffs_base__status__is_error(&status)) {
-    self->private_impl.magic = WUFFS_BASE__DISABLED;
-  }
   return status;
 }
 
@@ -28418,6 +28498,7 @@
 const char wuffs_deflate__error__inconsistent_stored_block_length[] = "#deflate: inconsistent stored block length";
 const char wuffs_deflate__error__missing_end_of_block_code[] = "#deflate: missing end-of-block code";
 const char wuffs_deflate__error__no_huffman_codes[] = "#deflate: no Huffman codes";
+const char wuffs_deflate__error__truncated_input[] = "#deflate: truncated input";
 const char wuffs_deflate__error__internal_error_inconsistent_huffman_decoder_state[] = "#deflate: internal error: inconsistent Huffman decoder state";
 const char wuffs_deflate__error__internal_error_inconsistent_i_o[] = "#deflate: internal error: inconsistent I/O";
 const char wuffs_deflate__error__internal_error_inconsistent_distance[] = "#deflate: internal error: inconsistent distance";
@@ -28493,6 +28574,13 @@
 // ---------------- Private Function Prototypes
 
 static wuffs_base__status
+wuffs_deflate__decoder__do_transform_io(
+    wuffs_deflate__decoder* self,
+    wuffs_base__io_buffer* a_dst,
+    wuffs_base__io_buffer* a_src,
+    wuffs_base__slice_u8 a_workbuf);
+
+static wuffs_base__status
 wuffs_deflate__decoder__decode_blocks(
     wuffs_deflate__decoder* self,
     wuffs_base__io_buffer* a_dst,
@@ -28735,6 +28823,74 @@
   self->private_impl.active_coroutine = 0;
   wuffs_base__status status = wuffs_base__make_status(NULL);
 
+  wuffs_base__status v_status = wuffs_base__make_status(NULL);
+
+  const uint8_t* iop_a_src = NULL;
+  const uint8_t* io0_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL;
+  const uint8_t* io1_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL;
+  const uint8_t* io2_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL;
+  if (a_src && a_src->data.ptr) {
+    io0_a_src = a_src->data.ptr;
+    io1_a_src = io0_a_src + a_src->meta.ri;
+    iop_a_src = io1_a_src;
+    io2_a_src = io0_a_src + a_src->meta.wi;
+  }
+
+  uint32_t coro_susp_point = self->private_impl.p_transform_io[0];
+  switch (coro_susp_point) {
+    WUFFS_BASE__COROUTINE_SUSPENSION_POINT_0;
+
+    while (true) {
+      {
+        if (a_src) {
+          a_src->meta.ri = ((size_t)(iop_a_src - a_src->data.ptr));
+        }
+        wuffs_base__status t_0 = wuffs_deflate__decoder__do_transform_io(self, a_dst, a_src, a_workbuf);
+        v_status = t_0;
+        if (a_src) {
+          iop_a_src = a_src->data.ptr + a_src->meta.ri;
+        }
+      }
+      if ((v_status.repr == wuffs_base__suspension__short_read) && (a_src && a_src->meta.closed)) {
+        status = wuffs_base__make_status(wuffs_deflate__error__truncated_input);
+        goto exit;
+      }
+      status = v_status;
+      WUFFS_BASE__COROUTINE_SUSPENSION_POINT_MAYBE_SUSPEND(1);
+    }
+
+    ok:
+    self->private_impl.p_transform_io[0] = 0;
+    goto exit;
+  }
+
+  goto suspend;
+  suspend:
+  self->private_impl.p_transform_io[0] = wuffs_base__status__is_suspension(&status) ? coro_susp_point : 0;
+  self->private_impl.active_coroutine = wuffs_base__status__is_suspension(&status) ? 1 : 0;
+
+  goto exit;
+  exit:
+  if (a_src && a_src->data.ptr) {
+    a_src->meta.ri = ((size_t)(iop_a_src - a_src->data.ptr));
+  }
+
+  if (wuffs_base__status__is_error(&status)) {
+    self->private_impl.magic = WUFFS_BASE__DISABLED;
+  }
+  return status;
+}
+
+// -------- func deflate.decoder.do_transform_io
+
+static wuffs_base__status
+wuffs_deflate__decoder__do_transform_io(
+    wuffs_deflate__decoder* self,
+    wuffs_base__io_buffer* a_dst,
+    wuffs_base__io_buffer* a_src,
+    wuffs_base__slice_u8 a_workbuf) {
+  wuffs_base__status status = wuffs_base__make_status(NULL);
+
   uint64_t v_mark = 0;
   wuffs_base__status v_status = wuffs_base__make_status(NULL);
 
@@ -28752,7 +28908,7 @@
     }
   }
 
-  uint32_t coro_susp_point = self->private_impl.p_transform_io[0];
+  uint32_t coro_susp_point = self->private_impl.p_do_transform_io[0];
   switch (coro_susp_point) {
     WUFFS_BASE__COROUTINE_SUSPENSION_POINT_0;
 
@@ -28790,14 +28946,13 @@
     }
 
     ok:
-    self->private_impl.p_transform_io[0] = 0;
+    self->private_impl.p_do_transform_io[0] = 0;
     goto exit;
   }
 
   goto suspend;
   suspend:
-  self->private_impl.p_transform_io[0] = wuffs_base__status__is_suspension(&status) ? coro_susp_point : 0;
-  self->private_impl.active_coroutine = wuffs_base__status__is_suspension(&status) ? 1 : 0;
+  self->private_impl.p_do_transform_io[0] = wuffs_base__status__is_suspension(&status) ? coro_susp_point : 0;
 
   goto exit;
   exit:
@@ -28805,9 +28960,6 @@
     a_dst->meta.wi = ((size_t)(iop_a_dst - a_dst->data.ptr));
   }
 
-  if (wuffs_base__status__is_error(&status)) {
-    self->private_impl.magic = WUFFS_BASE__DISABLED;
-  }
   return status;
 }
 
@@ -33808,6 +33960,7 @@
 const char wuffs_gzip__error__bad_compression_method[] = "#gzip: bad compression method";
 const char wuffs_gzip__error__bad_encoding_flags[] = "#gzip: bad encoding flags";
 const char wuffs_gzip__error__bad_header[] = "#gzip: bad header";
+const char wuffs_gzip__error__truncated_input[] = "#gzip: truncated input";
 
 // ---------------- Private Consts
 
@@ -33815,6 +33968,13 @@
 
 // ---------------- Private Function Prototypes
 
+static wuffs_base__status
+wuffs_gzip__decoder__do_transform_io(
+    wuffs_gzip__decoder* self,
+    wuffs_base__io_buffer* a_dst,
+    wuffs_base__io_buffer* a_src,
+    wuffs_base__slice_u8 a_workbuf);
+
 // ---------------- VTables
 
 const wuffs_base__io_transformer__func_ptrs
@@ -33979,6 +34139,74 @@
   self->private_impl.active_coroutine = 0;
   wuffs_base__status status = wuffs_base__make_status(NULL);
 
+  wuffs_base__status v_status = wuffs_base__make_status(NULL);
+
+  const uint8_t* iop_a_src = NULL;
+  const uint8_t* io0_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL;
+  const uint8_t* io1_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL;
+  const uint8_t* io2_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL;
+  if (a_src && a_src->data.ptr) {
+    io0_a_src = a_src->data.ptr;
+    io1_a_src = io0_a_src + a_src->meta.ri;
+    iop_a_src = io1_a_src;
+    io2_a_src = io0_a_src + a_src->meta.wi;
+  }
+
+  uint32_t coro_susp_point = self->private_impl.p_transform_io[0];
+  switch (coro_susp_point) {
+    WUFFS_BASE__COROUTINE_SUSPENSION_POINT_0;
+
+    while (true) {
+      {
+        if (a_src) {
+          a_src->meta.ri = ((size_t)(iop_a_src - a_src->data.ptr));
+        }
+        wuffs_base__status t_0 = wuffs_gzip__decoder__do_transform_io(self, a_dst, a_src, a_workbuf);
+        v_status = t_0;
+        if (a_src) {
+          iop_a_src = a_src->data.ptr + a_src->meta.ri;
+        }
+      }
+      if ((v_status.repr == wuffs_base__suspension__short_read) && (a_src && a_src->meta.closed)) {
+        status = wuffs_base__make_status(wuffs_gzip__error__truncated_input);
+        goto exit;
+      }
+      status = v_status;
+      WUFFS_BASE__COROUTINE_SUSPENSION_POINT_MAYBE_SUSPEND(1);
+    }
+
+    ok:
+    self->private_impl.p_transform_io[0] = 0;
+    goto exit;
+  }
+
+  goto suspend;
+  suspend:
+  self->private_impl.p_transform_io[0] = wuffs_base__status__is_suspension(&status) ? coro_susp_point : 0;
+  self->private_impl.active_coroutine = wuffs_base__status__is_suspension(&status) ? 1 : 0;
+
+  goto exit;
+  exit:
+  if (a_src && a_src->data.ptr) {
+    a_src->meta.ri = ((size_t)(iop_a_src - a_src->data.ptr));
+  }
+
+  if (wuffs_base__status__is_error(&status)) {
+    self->private_impl.magic = WUFFS_BASE__DISABLED;
+  }
+  return status;
+}
+
+// -------- func gzip.decoder.do_transform_io
+
+static wuffs_base__status
+wuffs_gzip__decoder__do_transform_io(
+    wuffs_gzip__decoder* self,
+    wuffs_base__io_buffer* a_dst,
+    wuffs_base__io_buffer* a_src,
+    wuffs_base__slice_u8 a_workbuf) {
+  wuffs_base__status status = wuffs_base__make_status(NULL);
+
   uint8_t v_c = 0;
   uint8_t v_flags = 0;
   uint16_t v_xlen = 0;
@@ -34013,12 +34241,12 @@
     io2_a_src = io0_a_src + a_src->meta.wi;
   }
 
-  uint32_t coro_susp_point = self->private_impl.p_transform_io[0];
+  uint32_t coro_susp_point = self->private_impl.p_do_transform_io[0];
   if (coro_susp_point) {
-    v_flags = self->private_data.s_transform_io[0].v_flags;
-    v_checksum_got = self->private_data.s_transform_io[0].v_checksum_got;
-    v_decoded_length_got = self->private_data.s_transform_io[0].v_decoded_length_got;
-    v_checksum_want = self->private_data.s_transform_io[0].v_checksum_want;
+    v_flags = self->private_data.s_do_transform_io[0].v_flags;
+    v_checksum_got = self->private_data.s_do_transform_io[0].v_checksum_got;
+    v_decoded_length_got = self->private_data.s_do_transform_io[0].v_decoded_length_got;
+    v_checksum_want = self->private_data.s_do_transform_io[0].v_checksum_want;
   }
   switch (coro_susp_point) {
     WUFFS_BASE__COROUTINE_SUSPENSION_POINT_0;
@@ -34071,15 +34299,15 @@
       uint8_t t_3 = *iop_a_src++;
       v_flags = t_3;
     }
-    self->private_data.s_transform_io[0].scratch = 6;
+    self->private_data.s_do_transform_io[0].scratch = 6;
     WUFFS_BASE__COROUTINE_SUSPENSION_POINT(5);
-    if (self->private_data.s_transform_io[0].scratch > ((uint64_t)(io2_a_src - iop_a_src))) {
-      self->private_data.s_transform_io[0].scratch -= ((uint64_t)(io2_a_src - iop_a_src));
+    if (self->private_data.s_do_transform_io[0].scratch > ((uint64_t)(io2_a_src - iop_a_src))) {
+      self->private_data.s_do_transform_io[0].scratch -= ((uint64_t)(io2_a_src - iop_a_src));
       iop_a_src = io2_a_src;
       status = wuffs_base__make_status(wuffs_base__suspension__short_read);
       goto suspend;
     }
-    iop_a_src += self->private_data.s_transform_io[0].scratch;
+    iop_a_src += self->private_data.s_do_transform_io[0].scratch;
     if ((v_flags & 4) != 0) {
       {
         WUFFS_BASE__COROUTINE_SUSPENSION_POINT(6);
@@ -34088,14 +34316,14 @@
           t_4 = wuffs_base__peek_u16le__no_bounds_check(iop_a_src);
           iop_a_src += 2;
         } else {
-          self->private_data.s_transform_io[0].scratch = 0;
+          self->private_data.s_do_transform_io[0].scratch = 0;
           WUFFS_BASE__COROUTINE_SUSPENSION_POINT(7);
           while (true) {
             if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) {
               status = wuffs_base__make_status(wuffs_base__suspension__short_read);
               goto suspend;
             }
-            uint64_t* scratch = &self->private_data.s_transform_io[0].scratch;
+            uint64_t* scratch = &self->private_data.s_do_transform_io[0].scratch;
             uint32_t num_bits_4 = ((uint32_t)(*scratch >> 56));
             *scratch <<= 8;
             *scratch >>= 8;
@@ -34110,15 +34338,15 @@
         }
         v_xlen = t_4;
       }
-      self->private_data.s_transform_io[0].scratch = ((uint32_t)(v_xlen));
+      self->private_data.s_do_transform_io[0].scratch = ((uint32_t)(v_xlen));
       WUFFS_BASE__COROUTINE_SUSPENSION_POINT(8);
-      if (self->private_data.s_transform_io[0].scratch > ((uint64_t)(io2_a_src - iop_a_src))) {
-        self->private_data.s_transform_io[0].scratch -= ((uint64_t)(io2_a_src - iop_a_src));
+      if (self->private_data.s_do_transform_io[0].scratch > ((uint64_t)(io2_a_src - iop_a_src))) {
+        self->private_data.s_do_transform_io[0].scratch -= ((uint64_t)(io2_a_src - iop_a_src));
         iop_a_src = io2_a_src;
         status = wuffs_base__make_status(wuffs_base__suspension__short_read);
         goto suspend;
       }
-      iop_a_src += self->private_data.s_transform_io[0].scratch;
+      iop_a_src += self->private_data.s_do_transform_io[0].scratch;
     }
     if ((v_flags & 8) != 0) {
       while (true) {
@@ -34155,15 +34383,15 @@
       label__1__break:;
     }
     if ((v_flags & 2) != 0) {
-      self->private_data.s_transform_io[0].scratch = 2;
+      self->private_data.s_do_transform_io[0].scratch = 2;
       WUFFS_BASE__COROUTINE_SUSPENSION_POINT(11);
-      if (self->private_data.s_transform_io[0].scratch > ((uint64_t)(io2_a_src - iop_a_src))) {
-        self->private_data.s_transform_io[0].scratch -= ((uint64_t)(io2_a_src - iop_a_src));
+      if (self->private_data.s_do_transform_io[0].scratch > ((uint64_t)(io2_a_src - iop_a_src))) {
+        self->private_data.s_do_transform_io[0].scratch -= ((uint64_t)(io2_a_src - iop_a_src));
         iop_a_src = io2_a_src;
         status = wuffs_base__make_status(wuffs_base__suspension__short_read);
         goto suspend;
       }
-      iop_a_src += self->private_data.s_transform_io[0].scratch;
+      iop_a_src += self->private_data.s_do_transform_io[0].scratch;
     }
     if ((v_flags & 224) != 0) {
       status = wuffs_base__make_status(wuffs_gzip__error__bad_encoding_flags);
@@ -34205,14 +34433,14 @@
         t_8 = wuffs_base__peek_u32le__no_bounds_check(iop_a_src);
         iop_a_src += 4;
       } else {
-        self->private_data.s_transform_io[0].scratch = 0;
+        self->private_data.s_do_transform_io[0].scratch = 0;
         WUFFS_BASE__COROUTINE_SUSPENSION_POINT(14);
         while (true) {
           if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) {
             status = wuffs_base__make_status(wuffs_base__suspension__short_read);
             goto suspend;
           }
-          uint64_t* scratch = &self->private_data.s_transform_io[0].scratch;
+          uint64_t* scratch = &self->private_data.s_do_transform_io[0].scratch;
           uint32_t num_bits_8 = ((uint32_t)(*scratch >> 56));
           *scratch <<= 8;
           *scratch >>= 8;
@@ -34234,14 +34462,14 @@
         t_9 = wuffs_base__peek_u32le__no_bounds_check(iop_a_src);
         iop_a_src += 4;
       } else {
-        self->private_data.s_transform_io[0].scratch = 0;
+        self->private_data.s_do_transform_io[0].scratch = 0;
         WUFFS_BASE__COROUTINE_SUSPENSION_POINT(16);
         while (true) {
           if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) {
             status = wuffs_base__make_status(wuffs_base__suspension__short_read);
             goto suspend;
           }
-          uint64_t* scratch = &self->private_data.s_transform_io[0].scratch;
+          uint64_t* scratch = &self->private_data.s_do_transform_io[0].scratch;
           uint32_t num_bits_9 = ((uint32_t)(*scratch >> 56));
           *scratch <<= 8;
           *scratch >>= 8;
@@ -34262,18 +34490,17 @@
     }
 
     ok:
-    self->private_impl.p_transform_io[0] = 0;
+    self->private_impl.p_do_transform_io[0] = 0;
     goto exit;
   }
 
   goto suspend;
   suspend:
-  self->private_impl.p_transform_io[0] = wuffs_base__status__is_suspension(&status) ? coro_susp_point : 0;
-  self->private_impl.active_coroutine = wuffs_base__status__is_suspension(&status) ? 1 : 0;
-  self->private_data.s_transform_io[0].v_flags = v_flags;
-  self->private_data.s_transform_io[0].v_checksum_got = v_checksum_got;
-  self->private_data.s_transform_io[0].v_decoded_length_got = v_decoded_length_got;
-  self->private_data.s_transform_io[0].v_checksum_want = v_checksum_want;
+  self->private_impl.p_do_transform_io[0] = wuffs_base__status__is_suspension(&status) ? coro_susp_point : 0;
+  self->private_data.s_do_transform_io[0].v_flags = v_flags;
+  self->private_data.s_do_transform_io[0].v_checksum_got = v_checksum_got;
+  self->private_data.s_do_transform_io[0].v_decoded_length_got = v_decoded_length_got;
+  self->private_data.s_do_transform_io[0].v_checksum_want = v_checksum_want;
 
   goto exit;
   exit:
@@ -34284,9 +34511,6 @@
     a_src->meta.ri = ((size_t)(iop_a_src - a_src->data.ptr));
   }
 
-  if (wuffs_base__status__is_error(&status)) {
-    self->private_impl.magic = WUFFS_BASE__DISABLED;
-  }
   return status;
 }
 
@@ -37339,6 +37563,7 @@
 const char wuffs_zlib__error__bad_compression_window_size[] = "#zlib: bad compression window size";
 const char wuffs_zlib__error__bad_parity_check[] = "#zlib: bad parity check";
 const char wuffs_zlib__error__incorrect_dictionary[] = "#zlib: incorrect dictionary";
+const char wuffs_zlib__error__truncated_input[] = "#zlib: truncated input";
 
 // ---------------- Private Consts
 
@@ -37350,6 +37575,13 @@
 
 // ---------------- Private Function Prototypes
 
+static wuffs_base__status
+wuffs_zlib__decoder__do_transform_io(
+    wuffs_zlib__decoder* self,
+    wuffs_base__io_buffer* a_dst,
+    wuffs_base__io_buffer* a_src,
+    wuffs_base__slice_u8 a_workbuf);
+
 // ---------------- VTables
 
 const wuffs_base__io_transformer__func_ptrs
@@ -37567,6 +37799,74 @@
   self->private_impl.active_coroutine = 0;
   wuffs_base__status status = wuffs_base__make_status(NULL);
 
+  wuffs_base__status v_status = wuffs_base__make_status(NULL);
+
+  const uint8_t* iop_a_src = NULL;
+  const uint8_t* io0_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL;
+  const uint8_t* io1_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL;
+  const uint8_t* io2_a_src WUFFS_BASE__POTENTIALLY_UNUSED = NULL;
+  if (a_src && a_src->data.ptr) {
+    io0_a_src = a_src->data.ptr;
+    io1_a_src = io0_a_src + a_src->meta.ri;
+    iop_a_src = io1_a_src;
+    io2_a_src = io0_a_src + a_src->meta.wi;
+  }
+
+  uint32_t coro_susp_point = self->private_impl.p_transform_io[0];
+  switch (coro_susp_point) {
+    WUFFS_BASE__COROUTINE_SUSPENSION_POINT_0;
+
+    while (true) {
+      {
+        if (a_src) {
+          a_src->meta.ri = ((size_t)(iop_a_src - a_src->data.ptr));
+        }
+        wuffs_base__status t_0 = wuffs_zlib__decoder__do_transform_io(self, a_dst, a_src, a_workbuf);
+        v_status = t_0;
+        if (a_src) {
+          iop_a_src = a_src->data.ptr + a_src->meta.ri;
+        }
+      }
+      if ((v_status.repr == wuffs_base__suspension__short_read) && (a_src && a_src->meta.closed)) {
+        status = wuffs_base__make_status(wuffs_zlib__error__truncated_input);
+        goto exit;
+      }
+      status = v_status;
+      WUFFS_BASE__COROUTINE_SUSPENSION_POINT_MAYBE_SUSPEND(1);
+    }
+
+    ok:
+    self->private_impl.p_transform_io[0] = 0;
+    goto exit;
+  }
+
+  goto suspend;
+  suspend:
+  self->private_impl.p_transform_io[0] = wuffs_base__status__is_suspension(&status) ? coro_susp_point : 0;
+  self->private_impl.active_coroutine = wuffs_base__status__is_suspension(&status) ? 1 : 0;
+
+  goto exit;
+  exit:
+  if (a_src && a_src->data.ptr) {
+    a_src->meta.ri = ((size_t)(iop_a_src - a_src->data.ptr));
+  }
+
+  if (wuffs_base__status__is_error(&status)) {
+    self->private_impl.magic = WUFFS_BASE__DISABLED;
+  }
+  return status;
+}
+
+// -------- func zlib.decoder.do_transform_io
+
+static wuffs_base__status
+wuffs_zlib__decoder__do_transform_io(
+    wuffs_zlib__decoder* self,
+    wuffs_base__io_buffer* a_dst,
+    wuffs_base__io_buffer* a_src,
+    wuffs_base__slice_u8 a_workbuf) {
+  wuffs_base__status status = wuffs_base__make_status(NULL);
+
   uint16_t v_x = 0;
   uint32_t v_checksum_got = 0;
   wuffs_base__status v_status = wuffs_base__make_status(NULL);
@@ -37597,9 +37897,9 @@
     io2_a_src = io0_a_src + a_src->meta.wi;
   }
 
-  uint32_t coro_susp_point = self->private_impl.p_transform_io[0];
+  uint32_t coro_susp_point = self->private_impl.p_do_transform_io[0];
   if (coro_susp_point) {
-    v_checksum_got = self->private_data.s_transform_io[0].v_checksum_got;
+    v_checksum_got = self->private_data.s_do_transform_io[0].v_checksum_got;
   }
   switch (coro_susp_point) {
     WUFFS_BASE__COROUTINE_SUSPENSION_POINT_0;
@@ -37616,14 +37916,14 @@
           t_0 = wuffs_base__peek_u16be__no_bounds_check(iop_a_src);
           iop_a_src += 2;
         } else {
-          self->private_data.s_transform_io[0].scratch = 0;
+          self->private_data.s_do_transform_io[0].scratch = 0;
           WUFFS_BASE__COROUTINE_SUSPENSION_POINT(2);
           while (true) {
             if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) {
               status = wuffs_base__make_status(wuffs_base__suspension__short_read);
               goto suspend;
             }
-            uint64_t* scratch = &self->private_data.s_transform_io[0].scratch;
+            uint64_t* scratch = &self->private_data.s_do_transform_io[0].scratch;
             uint32_t num_bits_0 = ((uint32_t)(*scratch & 0xFF));
             *scratch >>= 8;
             *scratch <<= 8;
@@ -37660,14 +37960,14 @@
             t_1 = wuffs_base__peek_u32be__no_bounds_check(iop_a_src);
             iop_a_src += 4;
           } else {
-            self->private_data.s_transform_io[0].scratch = 0;
+            self->private_data.s_do_transform_io[0].scratch = 0;
             WUFFS_BASE__COROUTINE_SUSPENSION_POINT(4);
             while (true) {
               if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) {
                 status = wuffs_base__make_status(wuffs_base__suspension__short_read);
                 goto suspend;
               }
-              uint64_t* scratch = &self->private_data.s_transform_io[0].scratch;
+              uint64_t* scratch = &self->private_data.s_do_transform_io[0].scratch;
               uint32_t num_bits_1 = ((uint32_t)(*scratch & 0xFF));
               *scratch >>= 8;
               *scratch <<= 8;
@@ -37733,14 +38033,14 @@
           t_3 = wuffs_base__peek_u32be__no_bounds_check(iop_a_src);
           iop_a_src += 4;
         } else {
-          self->private_data.s_transform_io[0].scratch = 0;
+          self->private_data.s_do_transform_io[0].scratch = 0;
           WUFFS_BASE__COROUTINE_SUSPENSION_POINT(7);
           while (true) {
             if (WUFFS_BASE__UNLIKELY(iop_a_src == io2_a_src)) {
               status = wuffs_base__make_status(wuffs_base__suspension__short_read);
               goto suspend;
             }
-            uint64_t* scratch = &self->private_data.s_transform_io[0].scratch;
+            uint64_t* scratch = &self->private_data.s_do_transform_io[0].scratch;
             uint32_t num_bits_3 = ((uint32_t)(*scratch & 0xFF));
             *scratch >>= 8;
             *scratch <<= 8;
@@ -37762,15 +38062,14 @@
     }
 
     ok:
-    self->private_impl.p_transform_io[0] = 0;
+    self->private_impl.p_do_transform_io[0] = 0;
     goto exit;
   }
 
   goto suspend;
   suspend:
-  self->private_impl.p_transform_io[0] = wuffs_base__status__is_suspension(&status) ? coro_susp_point : 0;
-  self->private_impl.active_coroutine = wuffs_base__status__is_suspension(&status) ? 1 : 0;
-  self->private_data.s_transform_io[0].v_checksum_got = v_checksum_got;
+  self->private_impl.p_do_transform_io[0] = wuffs_base__status__is_suspension(&status) ? coro_susp_point : 0;
+  self->private_data.s_do_transform_io[0].v_checksum_got = v_checksum_got;
 
   goto exit;
   exit:
@@ -37781,9 +38080,6 @@
     a_src->meta.ri = ((size_t)(iop_a_src - a_src->data.ptr));
   }
 
-  if (wuffs_base__status__is_error(&status)) {
-    self->private_impl.magic = WUFFS_BASE__DISABLED;
-  }
   return status;
 }
 
diff --git a/std/bzip2/decode_bzip2.wuffs b/std/bzip2/decode_bzip2.wuffs
index 19f670f..4bd3e92 100644
--- a/std/bzip2/decode_bzip2.wuffs
+++ b/std/bzip2/decode_bzip2.wuffs
@@ -19,6 +19,7 @@
 pub status "#bad checksum"
 pub status "#bad header"
 pub status "#bad number of sections"
+pub status "#truncated input"
 pub status "#unsupported block randomization"
 
 pri status "#internal error: inconsistent Huffman decoder state"
@@ -121,6 +122,18 @@
 }
 
 pub func decoder.transform_io?(dst: base.io_writer, src: base.io_reader, workbuf: slice base.u8) {
+	var status : base.status
+
+	while true {
+		status =? this.do_transform_io?(dst: args.dst, src: args.src, workbuf: args.workbuf)
+		if (status == base."$short read") and args.src.is_closed() {
+			return "#truncated input"
+		}
+		yield? status
+	} endwhile
+}
+
+pri func decoder.do_transform_io?(dst: base.io_writer, src: base.io_reader, workbuf: slice base.u8) {
 	var c                   : base.u8
 	var i                   : base.u32
 	var tag                 : base.u64
diff --git a/std/deflate/decode_deflate.wuffs b/std/deflate/decode_deflate.wuffs
index 947f4f2..188b0ce 100644
--- a/std/deflate/decode_deflate.wuffs
+++ b/std/deflate/decode_deflate.wuffs
@@ -25,6 +25,7 @@
 pub status "#inconsistent stored block length"
 pub status "#missing end-of-block code"
 pub status "#no Huffman codes"
+pub status "#truncated input"
 
 pri status "#internal error: inconsistent Huffman decoder state"
 pri status "#internal error: inconsistent I/O"
@@ -208,6 +209,18 @@
 }
 
 pub func decoder.transform_io?(dst: base.io_writer, src: base.io_reader, workbuf: slice base.u8) {
+	var status : base.status
+
+	while true {
+		status =? this.do_transform_io?(dst: args.dst, src: args.src, workbuf: args.workbuf)
+		if (status == base."$short read") and args.src.is_closed() {
+			return "#truncated input"
+		}
+		yield? status
+	} endwhile
+}
+
+pri func decoder.do_transform_io?(dst: base.io_writer, src: base.io_reader, workbuf: slice base.u8) {
 	var mark   : base.u64
 	var status : base.status
 
diff --git a/std/gzip/decode_gzip.wuffs b/std/gzip/decode_gzip.wuffs
index f66e63d..699975b 100644
--- a/std/gzip/decode_gzip.wuffs
+++ b/std/gzip/decode_gzip.wuffs
@@ -19,6 +19,7 @@
 pub status "#bad compression method"
 pub status "#bad encoding flags"
 pub status "#bad header"
+pub status "#truncated input"
 
 // TODO: reference deflate.DECODER_WORKBUF_LEN_MAX_INCL_WORST_CASE.
 pub const DECODER_WORKBUF_LEN_MAX_INCL_WORST_CASE : base.u64 = 1
@@ -45,6 +46,18 @@
 }
 
 pub func decoder.transform_io?(dst: base.io_writer, src: base.io_reader, workbuf: slice base.u8) {
+	var status : base.status
+
+	while true {
+		status =? this.do_transform_io?(dst: args.dst, src: args.src, workbuf: args.workbuf)
+		if (status == base."$short read") and args.src.is_closed() {
+			return "#truncated input"
+		}
+		yield? status
+	} endwhile
+}
+
+pri func decoder.do_transform_io?(dst: base.io_writer, src: base.io_reader, workbuf: slice base.u8) {
 	var c                   : base.u8
 	var flags               : base.u8
 	var xlen                : base.u16
diff --git a/std/zlib/decode_zlib.wuffs b/std/zlib/decode_zlib.wuffs
index b328588..cf06113 100644
--- a/std/zlib/decode_zlib.wuffs
+++ b/std/zlib/decode_zlib.wuffs
@@ -22,6 +22,7 @@
 pub status "#bad compression window size"
 pub status "#bad parity check"
 pub status "#incorrect dictionary"
+pub status "#truncated input"
 
 // TODO: reference deflate.DECODER_WORKBUF_LEN_MAX_INCL_WORST_CASE.
 pub const DECODER_WORKBUF_LEN_MAX_INCL_WORST_CASE : base.u64 = 1
@@ -81,6 +82,18 @@
 }
 
 pub func decoder.transform_io?(dst: base.io_writer, src: base.io_reader, workbuf: slice base.u8) {
+	var status : base.status
+
+	while true {
+		status =? this.do_transform_io?(dst: args.dst, src: args.src, workbuf: args.workbuf)
+		if (status == base."$short read") and args.src.is_closed() {
+			return "#truncated input"
+		}
+		yield? status
+	} endwhile
+}
+
+pri func decoder.do_transform_io?(dst: base.io_writer, src: base.io_reader, workbuf: slice base.u8) {
 	var x             : base.u16
 	var checksum_got  : base.u32
 	var status        : base.status
diff --git a/test/c/std/bzip2.c b/test/c/std/bzip2.c
index 8fe938a..380496b 100644
--- a/test/c/std/bzip2.c
+++ b/test/c/std/bzip2.c
@@ -110,6 +110,36 @@
 }
 
 const char*  //
+test_wuffs_bzip2_decode_truncated_input() {
+  CHECK_FOCUS(__func__);
+
+  wuffs_base__io_buffer have = wuffs_base__ptr_u8__writer(g_have_array_u8, 1);
+  wuffs_base__io_buffer src =
+      wuffs_base__ptr_u8__reader(g_src_array_u8, 0, false);
+  wuffs_bzip2__decoder dec;
+  CHECK_STATUS("initialize",
+               wuffs_bzip2__decoder__initialize(
+                   &dec, sizeof dec, WUFFS_VERSION,
+                   WUFFS_INITIALIZE__LEAVE_INTERNAL_BUFFERS_UNINITIALIZED));
+
+  wuffs_base__status status =
+      wuffs_bzip2__decoder__transform_io(&dec, &have, &src, g_work_slice_u8);
+  if (status.repr != wuffs_base__suspension__short_read) {
+    RETURN_FAIL("closed=false: have \"%s\", want \"%s\"", status.repr,
+                wuffs_base__suspension__short_read);
+  }
+
+  src.meta.closed = true;
+  status =
+      wuffs_bzip2__decoder__transform_io(&dec, &have, &src, g_work_slice_u8);
+  if (status.repr != wuffs_bzip2__error__truncated_input) {
+    RETURN_FAIL("closed=true: have \"%s\", want \"%s\"", status.repr,
+                wuffs_bzip2__error__truncated_input);
+  }
+  return NULL;
+}
+
+const char*  //
 wuffs_bzip2_decode(wuffs_base__io_buffer* dst,
                    wuffs_base__io_buffer* src,
                    uint32_t wuffs_initialize_flags,
@@ -262,6 +292,7 @@
     test_wuffs_bzip2_decode_interface,
     test_wuffs_bzip2_decode_midsummer,
     test_wuffs_bzip2_decode_pi,
+    test_wuffs_bzip2_decode_truncated_input,
 
 #ifdef WUFFS_MIMIC
 
diff --git a/test/c/std/deflate.c b/test/c/std/deflate.c
index a6d381a..de2dff6 100644
--- a/test/c/std/deflate.c
+++ b/test/c/std/deflate.c
@@ -167,6 +167,36 @@
 }
 
 const char*  //
+test_wuffs_deflate_decode_truncated_input() {
+  CHECK_FOCUS(__func__);
+
+  wuffs_base__io_buffer have = wuffs_base__ptr_u8__writer(g_have_array_u8, 1);
+  wuffs_base__io_buffer src =
+      wuffs_base__ptr_u8__reader(g_src_array_u8, 0, false);
+  wuffs_deflate__decoder dec;
+  CHECK_STATUS("initialize",
+               wuffs_deflate__decoder__initialize(
+                   &dec, sizeof dec, WUFFS_VERSION,
+                   WUFFS_INITIALIZE__LEAVE_INTERNAL_BUFFERS_UNINITIALIZED));
+
+  wuffs_base__status status =
+      wuffs_deflate__decoder__transform_io(&dec, &have, &src, g_work_slice_u8);
+  if (status.repr != wuffs_base__suspension__short_read) {
+    RETURN_FAIL("closed=false: have \"%s\", want \"%s\"", status.repr,
+                wuffs_base__suspension__short_read);
+  }
+
+  src.meta.closed = true;
+  status =
+      wuffs_deflate__decoder__transform_io(&dec, &have, &src, g_work_slice_u8);
+  if (status.repr != wuffs_deflate__error__truncated_input) {
+    RETURN_FAIL("closed=true: have \"%s\", want \"%s\"", status.repr,
+                wuffs_deflate__error__truncated_input);
+  }
+  return NULL;
+}
+
+const char*  //
 wuffs_deflate_decode(wuffs_base__io_buffer* dst,
                      wuffs_base__io_buffer* src,
                      uint32_t wuffs_initialize_flags,
@@ -903,6 +933,7 @@
     test_wuffs_deflate_decode_romeo,
     test_wuffs_deflate_decode_romeo_fixed,
     test_wuffs_deflate_decode_split_src,
+    test_wuffs_deflate_decode_truncated_input,
     test_wuffs_deflate_history_full,
     test_wuffs_deflate_history_partial,
     test_wuffs_deflate_table_redirect,
diff --git a/test/c/std/gzip.c b/test/c/std/gzip.c
index a7072ab..f3620d2 100644
--- a/test/c/std/gzip.c
+++ b/test/c/std/gzip.c
@@ -97,6 +97,36 @@
 }
 
 const char*  //
+test_wuffs_gzip_decode_truncated_input() {
+  CHECK_FOCUS(__func__);
+
+  wuffs_base__io_buffer have = wuffs_base__ptr_u8__writer(g_have_array_u8, 1);
+  wuffs_base__io_buffer src =
+      wuffs_base__ptr_u8__reader(g_src_array_u8, 0, false);
+  wuffs_gzip__decoder dec;
+  CHECK_STATUS("initialize",
+               wuffs_gzip__decoder__initialize(
+                   &dec, sizeof dec, WUFFS_VERSION,
+                   WUFFS_INITIALIZE__LEAVE_INTERNAL_BUFFERS_UNINITIALIZED));
+
+  wuffs_base__status status =
+      wuffs_gzip__decoder__transform_io(&dec, &have, &src, g_work_slice_u8);
+  if (status.repr != wuffs_base__suspension__short_read) {
+    RETURN_FAIL("closed=false: have \"%s\", want \"%s\"", status.repr,
+                wuffs_base__suspension__short_read);
+  }
+
+  src.meta.closed = true;
+  status =
+      wuffs_gzip__decoder__transform_io(&dec, &have, &src, g_work_slice_u8);
+  if (status.repr != wuffs_gzip__error__truncated_input) {
+    RETURN_FAIL("closed=true: have \"%s\", want \"%s\"", status.repr,
+                wuffs_gzip__error__truncated_input);
+  }
+  return NULL;
+}
+
+const char*  //
 wuffs_gzip_decode(wuffs_base__io_buffer* dst,
                   wuffs_base__io_buffer* src,
                   uint32_t wuffs_initialize_flags,
@@ -255,6 +285,7 @@
   // Decode 5 source bytes at a time. Compact every 15 source bytes.
   for (size_t i = 0; i < src.data.len; i += 5) {
     src.meta.wi = i;
+    src.meta.closed = false;
     wuffs_base__status status =
         wuffs_gzip__decoder__transform_io(&dec, &dst, &src, g_work_slice_u8);
     if (status.repr != wuffs_base__suspension__short_read) {
@@ -361,6 +392,7 @@
     test_wuffs_gzip_decode_interface,
     test_wuffs_gzip_decode_midsummer,
     test_wuffs_gzip_decode_pi,
+    test_wuffs_gzip_decode_truncated_input,
 
 #ifdef WUFFS_MIMIC
 
diff --git a/test/c/std/zlib.c b/test/c/std/zlib.c
index d7fd607..4c6b775 100644
--- a/test/c/std/zlib.c
+++ b/test/c/std/zlib.c
@@ -113,6 +113,36 @@
 }
 
 const char*  //
+test_wuffs_zlib_decode_truncated_input() {
+  CHECK_FOCUS(__func__);
+
+  wuffs_base__io_buffer have = wuffs_base__ptr_u8__writer(g_have_array_u8, 1);
+  wuffs_base__io_buffer src =
+      wuffs_base__ptr_u8__reader(g_src_array_u8, 0, false);
+  wuffs_zlib__decoder dec;
+  CHECK_STATUS("initialize",
+               wuffs_zlib__decoder__initialize(
+                   &dec, sizeof dec, WUFFS_VERSION,
+                   WUFFS_INITIALIZE__LEAVE_INTERNAL_BUFFERS_UNINITIALIZED));
+
+  wuffs_base__status status =
+      wuffs_zlib__decoder__transform_io(&dec, &have, &src, g_work_slice_u8);
+  if (status.repr != wuffs_base__suspension__short_read) {
+    RETURN_FAIL("closed=false: have \"%s\", want \"%s\"", status.repr,
+                wuffs_base__suspension__short_read);
+  }
+
+  src.meta.closed = true;
+  status =
+      wuffs_zlib__decoder__transform_io(&dec, &have, &src, g_work_slice_u8);
+  if (status.repr != wuffs_zlib__error__truncated_input) {
+    RETURN_FAIL("closed=true: have \"%s\", want \"%s\"", status.repr,
+                wuffs_zlib__error__truncated_input);
+  }
+  return NULL;
+}
+
+const char*  //
 wuffs_raw_deflate_decode(wuffs_base__io_buffer* dst,
                          wuffs_base__io_buffer* src,
                          uint32_t wuffs_initialize_flags,
@@ -426,6 +456,7 @@
     test_wuffs_zlib_decode_pi,
     test_wuffs_zlib_decode_raw_deflate_romeo,
     test_wuffs_zlib_decode_sheep,
+    test_wuffs_zlib_decode_truncated_input,
 
 #ifdef WUFFS_MIMIC