Commit 243f8ae84b for aom

commit 243f8ae84bfbc495b3a3c12948abc4dff3af2f84
Author: Cheng Chen <chengchen@google.com>
Date:   Mon Apr 20 13:09:59 2026 -0700

    Handle buffer pointer in LAP mode to avoid overflow

    1. Always allocate at least MAX_GF_LENGTH_LAP + 1 stats buffers
    even though lag-in-frames < MAX_GF_LENGTH_LAP.

    2. Use compacting sliding window for stats buffers in LAP mode to avoid
    out of boundary problems.

    3. rest_frames in av1_get_second_pass_params is mistakenly calculated
    in LAP mode. Correct the off by 1 error.

    BUG=aomedia:504317456

    Change-Id: I23df3262e760f9ae501434f0f828c0e745df73f4

diff --git a/av1/encoder/encoder.h b/av1/encoder/encoder.h
index 61153b9fb3..0e38bb8a9b 100644
--- a/av1/encoder/encoder.h
+++ b/av1/encoder/encoder.h
@@ -4194,7 +4194,10 @@ static inline int allow_postencode_drop_rtc(const AV1_COMP *cpi) {
 // Function return size of frame stats buffer
 static inline int get_stats_buf_size(int num_lap_buffer, int num_lag_buffer) {
   /* if lookahead is enabled return num_lap_buffers else num_lag_buffers */
-  return (num_lap_buffer > 0 ? num_lap_buffer + 1 : num_lag_buffer);
+  if (num_lap_buffer > 0) {
+    return AOMMAX(num_lap_buffer + 1, MAX_GF_LENGTH_LAP + 1);
+  }
+  return num_lag_buffer;
 }

 // TODO(zoeliu): To set up cpi->oxcf.gf_cfg.enable_auto_brf
diff --git a/av1/encoder/firstpass.c b/av1/encoder/firstpass.c
index e8c15cb897..605e9687ad 100644
--- a/av1/encoder/firstpass.c
+++ b/av1/encoder/firstpass.c
@@ -1004,6 +1004,19 @@ static void update_firstpass_stats(AV1_COMP *cpi,
          twopass->stats_buf_ctx->stats_in_buf_end)) {
       twopass->stats_buf_ctx->stats_in_end =
           twopass->stats_buf_ctx->stats_in_start;
+    } else if (cpi->ppi->lap_enabled &&
+               (twopass->stats_buf_ctx->stats_in_end >=
+                twopass->stats_buf_ctx->stats_in_buf_end)) {
+      const int num_valid = (int)(twopass->stats_buf_ctx->stats_in_end -
+                                  cpi->twopass_frame.stats_in);
+      if (num_valid > 0) {
+        memmove(twopass->stats_buf_ctx->stats_in_start,
+                cpi->twopass_frame.stats_in,
+                num_valid * sizeof(FIRSTPASS_STATS));
+      }
+      cpi->twopass_frame.stats_in = twopass->stats_buf_ctx->stats_in_start;
+      twopass->stats_buf_ctx->stats_in_end =
+          twopass->stats_buf_ctx->stats_in_start + num_valid;
     }
   }
 }
diff --git a/av1/encoder/pass2_strategy.c b/av1/encoder/pass2_strategy.c
index 52b92b24b2..81adede24f 100644
--- a/av1/encoder/pass2_strategy.c
+++ b/av1/encoder/pass2_strategy.c
@@ -4100,10 +4100,12 @@ void av1_get_second_pass_params(AV1_COMP *cpi,
       // how many frames we can analyze from this frame
       int rest_frames =
           AOMMIN(rc->frames_to_key, MAX_FIRSTPASS_ANALYSIS_FRAMES);
-      rest_frames =
-          AOMMIN(rest_frames, (int)(twopass->stats_buf_ctx->stats_in_end -
-                                    cpi->twopass_frame.stats_in +
-                                    (rc->frames_since_key == 0)));
+      int available_frames = (int)(twopass->stats_buf_ctx->stats_in_end -
+                                   cpi->twopass_frame.stats_in);
+      if (!cpi->ppi->lap_enabled) {
+        available_frames += (rc->frames_since_key == 0);
+      }
+      rest_frames = AOMMIN(rest_frames, available_frames);
       p_rc->frames_till_regions_update = rest_frames;

       int ret;
@@ -4114,9 +4116,8 @@ void av1_get_second_pass_params(AV1_COMP *cpi,
                        twopass->stats_buf_ctx->stats_in_end, cpi->common.error);
         estimate_coeff(twopass->stats_buf_ctx->stats_in_start,
                        twopass->stats_buf_ctx->stats_in_end);
-        ret = identify_regions(cpi->twopass_frame.stats_in, rest_frames,
-                               (rc->frames_since_key == 0), p_rc->regions,
-                               &p_rc->num_regions);
+        ret = identify_regions(cpi->twopass_frame.stats_in, rest_frames, 0,
+                               p_rc->regions, &p_rc->num_regions);
       } else {
         ret = identify_regions(
             cpi->twopass_frame.stats_in - (rc->frames_since_key == 0),
diff --git a/test/encode_api_test.cc b/test/encode_api_test.cc
index 4b75ece6ff..70f7c4689c 100644
--- a/test/encode_api_test.cc
+++ b/test/encode_api_test.cc
@@ -2445,4 +2445,47 @@ TEST(EncodeAPI, Buganizer503197490) {
   ASSERT_EQ(aom_codec_destroy(&codec), AOM_CODEC_OK);
 }

+TEST(EncodeAPI, Buganizer504317456) {
+  aom_codec_iface_t *iface = aom_codec_av1_cx();
+  aom_codec_enc_cfg_t cfg;
+  ASSERT_EQ(aom_codec_enc_config_default(iface, &cfg, AOM_USAGE_GOOD_QUALITY),
+            AOM_CODEC_OK);
+
+  cfg.g_w = 640;
+  cfg.g_h = 360;
+  cfg.g_lag_in_frames = 1;
+  cfg.g_error_resilient = 0;
+
+  aom_codec_ctx_t enc;
+  ASSERT_EQ(aom_codec_enc_init(&enc, iface, &cfg, 0), AOM_CODEC_OK);
+
+  aom_image_t *img = aom_img_alloc(nullptr, AOM_IMG_FMT_I420, 640, 360, 1);
+  FillImage(img, 128);
+  ASSERT_NE(img, nullptr);
+
+  struct Frame {
+    int64_t pts;
+    unsigned long duration;
+    aom_enc_frame_flags_t flags;
+  } frames[] = { { 0, 2, 0 },
+                 { 1075, 9, AOM_EFLAG_NO_UPD_LAST | AOM_EFLAG_NO_UPD_GF },
+                 { 2099, 6, AOM_EFLAG_FORCE_KF },
+                 { 3035, 5, AOM_EFLAG_NO_UPD_LAST | AOM_EFLAG_FORCE_KF } };
+
+  for (const auto &f : frames) {
+    ASSERT_EQ(aom_codec_encode(&enc, img, f.pts, f.duration, f.flags),
+              AOM_CODEC_OK);
+  }
+
+  // Add 10+ additional frames to extend overflow and cause crash on destruction
+  for (int i = 0; i < 15; ++i) {
+    int64_t pts = 4000 + i * 100;
+    aom_enc_frame_flags_t flags = (i % 3 == 0) ? AOM_EFLAG_FORCE_KF : 0;
+    ASSERT_EQ(aom_codec_encode(&enc, img, pts, 5, flags), AOM_CODEC_OK);
+  }
+
+  aom_img_free(img);
+  ASSERT_EQ(aom_codec_destroy(&enc), AOM_CODEC_OK);
+}
+
 }  // namespace