Fix bug #9221 with memory leak in bidi display.
[bpt/emacs.git] / src / bidi.c
index a2ba60a..00a6aee 100644 (file)
@@ -299,11 +299,11 @@ bidi_copy_it (struct bidi_it *to, struct bidi_it *from)
 
 #define BIDI_CACHE_CHUNK 200
 static struct bidi_it *bidi_cache;
-static size_t bidi_cache_size = 0;
-static size_t elsz = sizeof (struct bidi_it);
-static EMACS_INT bidi_cache_idx;       /* next unused cache slot */
-static EMACS_INT bidi_cache_last_idx;  /* slot of last cache hit */
-static EMACS_INT bidi_cache_start = 0; /* start of cache for this
+static ptrdiff_t bidi_cache_size = 0;
+enum { elsz = sizeof (struct bidi_it) };
+static ptrdiff_t bidi_cache_idx;       /* next unused cache slot */
+static ptrdiff_t bidi_cache_last_idx;  /* slot of last cache hit */
+static ptrdiff_t bidi_cache_start = 0; /* start of cache for this
                                           "stack" level */
 
 /* Reset the cache state to the empty state.  We only reset the part
@@ -336,7 +336,7 @@ bidi_cache_shrink (void)
 }
 
 static inline void
-bidi_cache_fetch_state (EMACS_INT idx, struct bidi_it *bidi_it)
+bidi_cache_fetch_state (ptrdiff_t idx, struct bidi_it *bidi_it)
 {
   int current_scan_dir = bidi_it->scan_dir;
 
@@ -352,10 +352,10 @@ bidi_cache_fetch_state (EMACS_INT idx, struct bidi_it *bidi_it)
    level less or equal to LEVEL.  if LEVEL is -1, disregard the
    resolved levels in cached states.  DIR, if non-zero, means search
    in that direction from the last cache hit.  */
-static inline EMACS_INT
+static inline ptrdiff_t
 bidi_cache_search (EMACS_INT charpos, int level, int dir)
 {
-  EMACS_INT i, i_start;
+  ptrdiff_t i, i_start;
 
   if (bidi_cache_idx > bidi_cache_start)
     {
@@ -417,12 +417,12 @@ bidi_cache_search (EMACS_INT charpos, int level, int dir)
    C, searching backwards (DIR = -1) for LEVEL = 2 will return the
    index of slot B or A, depending whether BEFORE is, respectively,
    non-zero or zero.  */
-static EMACS_INT
+static ptrdiff_t
 bidi_cache_find_level_change (int level, int dir, int before)
 {
   if (bidi_cache_idx)
     {
-      EMACS_INT i = dir ? bidi_cache_last_idx : bidi_cache_idx - 1;
+      ptrdiff_t i = dir ? bidi_cache_last_idx : bidi_cache_idx - 1;
       int incr = before ? 1 : 0;
 
       xassert (!dir || bidi_cache_last_idx >= 0);
@@ -458,22 +458,33 @@ bidi_cache_find_level_change (int level, int dir, int before)
 }
 
 static inline void
-bidi_cache_ensure_space (EMACS_INT idx)
+bidi_cache_ensure_space (ptrdiff_t idx)
 {
   /* Enlarge the cache as needed.  */
   if (idx >= bidi_cache_size)
     {
-      while (idx >= bidi_cache_size)
-       bidi_cache_size += BIDI_CACHE_CHUNK;
-      bidi_cache =
-       (struct bidi_it *) xrealloc (bidi_cache, bidi_cache_size * elsz);
+      ptrdiff_t new_size;
+
+      /* The bidi cache cannot be larger than the largest Lisp string
+        or buffer.  */
+      ptrdiff_t string_or_buffer_bound =
+       max (BUF_BYTES_MAX, STRING_BYTES_BOUND);
+
+      /* Also, it cannot be larger than what C can represent.  */
+      ptrdiff_t c_bound = min (PTRDIFF_MAX, SIZE_MAX) / elsz;
+
+      if (min (string_or_buffer_bound, c_bound) <= idx)
+       memory_full (SIZE_MAX);
+      new_size = idx - idx % BIDI_CACHE_CHUNK + BIDI_CACHE_CHUNK;
+      bidi_cache = (struct bidi_it *) xrealloc (bidi_cache, new_size * elsz);
+      bidi_cache_size = new_size;
     }
 }
 
 static inline void
 bidi_cache_iterator_state (struct bidi_it *bidi_it, int resolved)
 {
-  EMACS_INT idx;
+  ptrdiff_t idx;
 
   /* We should never cache on backward scans.  */
   if (bidi_it->scan_dir == -1)
@@ -528,7 +539,7 @@ bidi_cache_iterator_state (struct bidi_it *bidi_it, int resolved)
 static inline bidi_type_t
 bidi_cache_find (EMACS_INT charpos, int level, struct bidi_it *bidi_it)
 {
-  EMACS_INT i = bidi_cache_search (charpos, level, bidi_it->scan_dir);
+  ptrdiff_t i = bidi_cache_search (charpos, level, bidi_it->scan_dir);
 
   if (i >= bidi_cache_start)
     {
@@ -560,7 +571,7 @@ bidi_peek_at_next_level (struct bidi_it *bidi_it)
 /* 5-slot stack for saving the start of the previous level of the
    cache.  xdisp.c maintains a 5-slot stack for its iterator state,
    and we need the same size of our stack.  */
-static EMACS_INT bidi_cache_start_stack[IT_STACK_SIZE];
+static ptrdiff_t bidi_cache_start_stack[IT_STACK_SIZE];
 static int bidi_cache_sp;
 
 /* Push the bidi iterator state in preparation for reordering a
@@ -807,6 +818,7 @@ bidi_init_it (EMACS_INT charpos, EMACS_INT bytepos, int frame_window_p,
     bidi_it->prev_for_neutral.orig_type = UNKNOWN_BT;
   bidi_it->sor = L2R;   /* FIXME: should it be user-selectable? */
   bidi_it->disp_pos = -1;      /* invalid/unknown */
+  bidi_it->disp_prop_p = 0;
   /* We can only shrink the cache if we are at the bottom level of its
      "stack".  */
   if (bidi_cache_start == 0)
@@ -889,14 +901,16 @@ bidi_char_at_pos (EMACS_INT bytepos, const unsigned char *s, int unibyte)
    covered characters as a single character u+FFFC, and return their
    combined length in CH_LEN and NCHARS.  DISP_POS specifies the
    character position of the next display string, or -1 if not yet
-   computed.  When the next character is at or beyond that position,
-   the function updates DISP_POS with the position of the next display
-   string.  STRING->s is the C string to iterate, or NULL if iterating
-   over a buffer or a Lisp string; in the latter case, STRING->lstring
-   is the Lisp string.  */
+   computed.  DISP_PROP_P non-zero means that there's really a display
+   string at DISP_POS, as opposed to when we searched till DISP_POS
+   without findingone.  When the next character is at or beyond that
+   position, the function updates DISP_POS with the position of the
+   next display string.  STRING->s is the C string to iterate, or NULL
+   if iterating over a buffer or a Lisp string; in the latter case,
+   STRING->lstring is the Lisp string.  */
 static inline int
 bidi_fetch_char (EMACS_INT bytepos, EMACS_INT charpos, EMACS_INT *disp_pos,
-                struct bidi_string_data *string,
+                int *disp_prop_p, struct bidi_string_data *string,
                 int frame_window_p, EMACS_INT *ch_len, EMACS_INT *nchars)
 {
   int ch;
@@ -909,7 +923,8 @@ bidi_fetch_char (EMACS_INT bytepos, EMACS_INT charpos, EMACS_INT *disp_pos,
   if (charpos < endpos && charpos > *disp_pos)
     {
       SET_TEXT_POS (pos, charpos, bytepos);
-      *disp_pos = compute_display_string_pos (&pos, string, frame_window_p);
+      *disp_pos = compute_display_string_pos (&pos, string, frame_window_p,
+                                             disp_prop_p);
     }
 
   /* Fetch the character at BYTEPOS.  */
@@ -919,8 +934,9 @@ bidi_fetch_char (EMACS_INT bytepos, EMACS_INT charpos, EMACS_INT *disp_pos,
       *ch_len = 1;
       *nchars = 1;
       *disp_pos = endpos;
+      *disp_prop_p = 0;
     }
-  else if (charpos >= *disp_pos)
+  else if (charpos >= *disp_pos && *disp_prop_p)
     {
       EMACS_INT disp_end_pos;
 
@@ -987,10 +1003,12 @@ bidi_fetch_char (EMACS_INT bytepos, EMACS_INT charpos, EMACS_INT *disp_pos,
 
   /* If we just entered a run of characters covered by a display
      string, compute the position of the next display string.  */
-  if (charpos + *nchars <= endpos && charpos + *nchars > *disp_pos)
+  if (charpos + *nchars <= endpos && charpos + *nchars > *disp_pos
+      && *disp_prop_p)
     {
       SET_TEXT_POS (pos, charpos + *nchars, bytepos + *ch_len);
-      *disp_pos = compute_display_string_pos (&pos, string, frame_window_p);
+      *disp_pos = compute_display_string_pos (&pos, string, frame_window_p,
+                                             disp_prop_p);
     }
 
   return ch;
@@ -1098,6 +1116,7 @@ bidi_paragraph_init (bidi_dir_t dir, struct bidi_it *bidi_it, int no_default_p)
       int ch;
       EMACS_INT ch_len, nchars;
       EMACS_INT pos, disp_pos = -1;
+      int disp_prop_p = 0;
       bidi_type_t type;
       const unsigned char *s;
 
@@ -1145,7 +1164,8 @@ bidi_paragraph_init (bidi_dir_t dir, struct bidi_it *bidi_it, int no_default_p)
        bytepos = pstartbyte;
        if (!string_p)
          pos = BYTE_TO_CHAR (bytepos);
-       ch = bidi_fetch_char (bytepos, pos, &disp_pos, &bidi_it->string,
+       ch = bidi_fetch_char (bytepos, pos, &disp_pos, &disp_prop_p,
+                             &bidi_it->string,
                              bidi_it->frame_window_p, &ch_len, &nchars);
        type = bidi_get_type (ch, NEUTRAL_DIR);
 
@@ -1172,7 +1192,8 @@ bidi_paragraph_init (bidi_dir_t dir, struct bidi_it *bidi_it, int no_default_p)
                && bidi_at_paragraph_end (pos, bytepos) >= -1)
              break;
            /* Fetch next character and advance to get past it.  */
-           ch = bidi_fetch_char (bytepos, pos, &disp_pos, &bidi_it->string,
+           ch = bidi_fetch_char (bytepos, pos, &disp_pos,
+                                 &disp_prop_p, &bidi_it->string,
                                  bidi_it->frame_window_p, &ch_len, &nchars);
            pos += nchars;
            bytepos += ch_len;
@@ -1305,6 +1326,7 @@ bidi_resolve_explicit_1 (struct bidi_it *bidi_it)
       bidi_it->ch_len = 1;
       bidi_it->nchars = 1;
       bidi_it->disp_pos = (string_p ? bidi_it->string.schars : ZV);
+      bidi_it->disp_prop_p = 0;
     }
   else
     {
@@ -1312,8 +1334,8 @@ bidi_resolve_explicit_1 (struct bidi_it *bidi_it)
         display string, treat the entire run of covered characters as
         a single character u+FFFC.  */
       curchar = bidi_fetch_char (bidi_it->bytepos, bidi_it->charpos,
-                                &bidi_it->disp_pos, &bidi_it->string,
-                                bidi_it->frame_window_p,
+                                &bidi_it->disp_pos, &bidi_it->disp_prop_p,
+                                &bidi_it->string, bidi_it->frame_window_p,
                                 &bidi_it->ch_len, &bidi_it->nchars);
     }
   bidi_it->ch = curchar;
@@ -2047,12 +2069,13 @@ bidi_level_of_next_char (struct bidi_it *bidi_it)
       struct bidi_string_data bs = bidi_it->string;
       bidi_type_t chtype;
       int fwp = bidi_it->frame_window_p;
+      int dpp = bidi_it->disp_prop_p;
 
       if (bidi_it->nchars <= 0)
        abort ();
       do {
-       ch = bidi_fetch_char (bpos += clen, cpos += nc, &disp_pos, &bs, fwp,
-                             &clen, &nc);
+       ch = bidi_fetch_char (bpos += clen, cpos += nc, &disp_pos, &dpp, &bs,
+                             fwp, &clen, &nc);
        if (ch == '\n' || ch == BIDI_EOB /* || ch == LINESEP_CHAR */)
          chtype = NEUTRAL_B;
        else
@@ -2149,7 +2172,7 @@ static void
 bidi_find_other_level_edge (struct bidi_it *bidi_it, int level, int end_flag)
 {
   int dir = end_flag ? -bidi_it->scan_dir : bidi_it->scan_dir;
-  EMACS_INT idx;
+  ptrdiff_t idx;
 
   /* Try the cache first.  */
   if ((idx = bidi_cache_find_level_change (level, dir, end_flag))
@@ -2326,7 +2349,7 @@ void bidi_dump_cached_states (void) EXTERNALLY_VISIBLE;
 void
 bidi_dump_cached_states (void)
 {
-  int i;
+  ptrdiff_t i;
   int ndigits = 1;
 
   if (bidi_cache_idx == 0)
@@ -2334,7 +2357,7 @@ bidi_dump_cached_states (void)
       fprintf (stderr, "The cache is empty.\n");
       return;
     }
-  fprintf (stderr, "Total of %d state%s in cache:\n",
+  fprintf (stderr, "Total of  %"pD"d state%s in cache:\n",
           bidi_cache_idx, bidi_cache_idx == 1 ? "" : "s");
 
   for (i = bidi_cache[bidi_cache_idx - 1].charpos; i > 0; i /= 10)