A better answer is to actually get rid of strcpy() / strcat() / sprintf()
because there will always be compilers complaining about them.

Here's a patch that does that for freetype2-demos, please take a look.

Le jeu. 23 juil. 2020 à 13:16, Werner LEMBERG <[email protected]> a écrit :

> > There seems to be a new warning in 2.10.2 (compared to 2.10.1) when
> > compiling for 32-bit: [...]
>
> Fixed in git, thanks.
>
> > Also have had a bunch of strncat related warning (probably gcc 1
> specific, or some compiler switch specific) in ft2-demos, for a while:
> >
> > ===
> >     inlined from 'RunIns' at src/ttdebug.c:2105:11:
> > /usr/include/bits/string_fortified.h:136:10: warning:
> > '__builtin___strncat_chk' output may be truncated copying 31 bytes
> > from a string of length 31 [-Wstringop-truncation]
>
> (The problematic calls of strncat are in function `Cur_U_Line'.)
>
> Hmm.  The answer to
>
>
> https://stackoverflow.com/questions/50198319/gcc-8-wstringop-truncation-what-is-the-good-practice
>
> recommends to switch off the warning if the code does exactly ...
>
>
>     Werner
>
>
From dc5dae42212453f9a7850f201ad82c39e041e698 Mon Sep 17 00:00:00 2001
From: David Turner <[email protected]>
Date: Sun, 17 May 2020 21:37:09 +0200
Subject: [build] Remove strncat / strcpy() / sprintf() from sources.

This removes all uses of these potentially unsafe functions from
the sources, mostly to avoid dealing with overzealous
compiler warnings, and preventing buffer overflow issues.

This is done through several steps:

+ common.h: Add ft_strdup() to implement a proper strdup()
  function that also works on Windows.

+ strbuf.h: New header and associated source file, providing
  a trivial data type and functions to safely perform formatted
  string concatenation on a fixed-size buffer, while ensuring
  the result is always properly zero-terminated.

  Usage is pretty simple, and documented in the header.

+ Update the other sources to use ft_debug(), stdbuf_xxx()
  and snprintf() instead of sprintf() in order to get rid of
  any potential for buffer overflow / unterminated strings.
---
 Makefile       |   2 +
 src/common.c   |  22 +++++++-
 src/common.h   |   9 +++-
 src/ftbench.c  |  32 ++++++------
 src/ftcommon.c | 133 +++++++++++++++++++++++++++-------------------
 src/ftcommon.h |  34 ++++++++++++
 src/ftdiff.c   |  22 ++++----
 src/ftdump.c   |   4 +-
 src/ftgamma.c  |   4 +-
 src/ftgrid.c   |  39 ++++++++------
 src/ftmulti.c  |  68 ++++++++++++------------
 src/ftsbit.c   |   2 +-
 src/ftstring.c |   9 ++--
 src/ftview.c   |  88 +++++++++++++++----------------
 src/strbuf.c   | 139 +++++++++++++++++++++++++++++++++++++++++++++++++
 src/strbuf.h   | 117 +++++++++++++++++++++++++++++++++++++++++
 src/ttdebug.c  | 131 ++++++++++++++++++++++------------------------
 17 files changed, 600 insertions(+), 255 deletions(-)
 create mode 100644 src/strbuf.c
 create mode 100644 src/strbuf.h

diff --git a/Makefile b/Makefile
index e6ad0af..851c4f7 100644
--- a/Makefile
+++ b/Makefile
@@ -333,9 +333,11 @@ else
   # Rules for compiling object files for text-only demos.
   #
   $(OBJ_DIR_2)/common.$(SO): $(SRC_DIR)/common.c
+  $(OBJ_DIR_2)/strbuf.$(SO): $(SRC_DIR)/strbuf.c
   $(OBJ_DIR_2)/output.$(SO): $(SRC_DIR)/output.c
   $(OBJ_DIR_2)/mlgetopt.$(SO): $(SRC_DIR)/mlgetopt.c
   COMMON_OBJ := $(OBJ_DIR_2)/common.$(SO) \
+                $(OBJ_DIR_2)/strbuf.$(SO) \
                 $(OBJ_DIR_2)/output.$(SO) \
                 $(OBJ_DIR_2)/mlgetopt.$(SO)
 
diff --git a/src/common.c b/src/common.c
index 1960165..3eed8ef 100644
--- a/src/common.c
+++ b/src/common.c
@@ -5,7 +5,7 @@
 #include <stdio.h>
 #include <stdarg.h>
 #include <stdlib.h>
-
+#include <string.h>
 
   char*
   ft_basename( const char*  name )
@@ -36,6 +36,25 @@
   }
 
 
+  char*
+  ft_strdup( const char* str )
+  {
+    char*   result;
+    size_t  len;
+
+
+    if ( !str )
+      return NULL;
+
+    len    = strlen( str );
+    result = (char*)malloc( len + 1 );
+    if (result)
+      memcpy( result, str, len + 1);
+
+    return result;
+  }
+
+
   void
   Panic( const char*  fmt,
          ... )
@@ -105,5 +124,4 @@
     return -1;
   }
 
-
 /* End */
diff --git a/src/common.h b/src/common.h
index afa5f70..0cf4fea 100644
--- a/src/common.h
+++ b/src/common.h
@@ -1,7 +1,6 @@
 #ifndef COMMON_H_
 #define COMMON_H_
 
-
 #ifdef __cplusplus
   extern "C" {
 #endif
@@ -26,6 +25,14 @@
   utf8_next( const char**  pcursor,
              const char*   end );
 
+  /*
+   * Implement strdup() which is POSIX, but not C89 or even C11, and
+   * Microsoft insists on renaming it _strdup() instead. Platform
+   * auto-detection is complicated, so just provide a re-implementation.
+   */
+  extern char*
+  ft_strdup( const char* name );
+
 #ifdef __cplusplus
   }
 #endif
diff --git a/src/ftbench.c b/src/ftbench.c
index 58a7741..28b3d70 100644
--- a/src/ftbench.c
+++ b/src/ftbench.c
@@ -835,27 +835,27 @@
 
     /* we expect that at least one interpreter version is available */
     if ( num_tt_interpreter_versions == 2 )
-      sprintf(interpreter_versions,
-              "%d and %d",
-              tt_interpreter_versions[0],
-              tt_interpreter_versions[1] );
+      snprintf(interpreter_versions, sizeof( interpreter_versions ),
+               "%d and %d",
+               tt_interpreter_versions[0],
+               tt_interpreter_versions[1] );
     else
-      sprintf(interpreter_versions,
-              "%d, %d, and %d",
-              tt_interpreter_versions[0],
-              tt_interpreter_versions[1],
-              tt_interpreter_versions[2] );
+      snprintf(interpreter_versions, sizeof( interpreter_versions ),
+               "%d, %d, and %d",
+               tt_interpreter_versions[0],
+               tt_interpreter_versions[1],
+               tt_interpreter_versions[2] );
 
     /* we expect that at least one hinting engine is available */
     if ( num_ps_hinting_engines == 1 )
-      sprintf(hinting_engines,
-              "`%s'",
-              ps_hinting_engine_names[ps_hinting_engines[0]] );
+      snprintf(hinting_engines, sizeof( hinting_engines ),
+               "`%s'",
+               ps_hinting_engine_names[ps_hinting_engines[0]] );
     else
-      sprintf(hinting_engines,
-              "`%s' and `%s'",
-              ps_hinting_engine_names[ps_hinting_engines[0]],
-              ps_hinting_engine_names[ps_hinting_engines[1]] );
+      snprintf(hinting_engines, sizeof( hinting_engines ),
+               "`%s' and `%s'",
+               ps_hinting_engine_names[ps_hinting_engines[0]],
+               ps_hinting_engine_names[ps_hinting_engines[1]] );
 
 
     fprintf( stderr,
diff --git a/src/ftcommon.c b/src/ftcommon.c
index 14a3aaa..7191fd7 100644
--- a/src/ftcommon.c
+++ b/src/ftcommon.c
@@ -35,6 +35,7 @@
 #define FT_ERROR_END_LIST       default: str = "unknown error"; }
 
 #include "common.h"
+#include "strbuf.h"
 #include "ftcommon.h"
 
 #include <stdio.h>
@@ -404,30 +405,41 @@
 
       if ( !strcmp( format, "Type 1" ) )
       {
-        char   orig[5];
-        char*  suffix        = (char*)strrchr( font->filepathname, '.' );
-        int    has_extension = suffix                                &&
-                               ( strcasecmp( suffix, ".pfa" ) == 0 ||
-                                 strcasecmp( suffix, ".pfb" ) == 0 );
-
-
+        /* Build the extension file name from the main font file name:
+         * The rules to follow are:
+         *   - If a .pfa or .pfb extension is used, remove/ignore them.
+         *   - Add .afm and call FT_Attach_File(), if this fails, try with
+         *     .pfm extension instead and call FT_Attach_File() again.
+         */
+        size_t  path_len      = strlen( font->filepathname );
+        char*   suffix        = (char*)strrchr( font->filepathname, '.' );
+        int     has_extension = suffix                                &&
+                                ( strcasecmp( suffix, ".pfa" ) == 0 ||
+                                  strcasecmp( suffix, ".pfb" ) == 0 );
         if ( has_extension )
-          memcpy( orig, suffix, 5 );
-        else
-          /* we have already allocated four more bytes */
-          suffix = (char*)font->filepathname + strlen( font->filepathname );
-
-        memcpy( suffix, ".afm", 5 );
-        if ( FT_Attach_File( *aface, font->filepathname ) )
         {
-          memcpy( suffix, ".pfm", 5 );
-          FT_Attach_File( *aface, font->filepathname );
+          /* Ignore the .pfa or .pfb extension in the original font path. */
+          path_len -= 4;
         }
 
-        if ( has_extension )
-          memcpy( suffix, orig, 5 );
-        else
-          *suffix = '\0';
+        size_t  ext_path_len = path_len + 5;  // 4 char extension + 1 zero byte.
+        char*   ext_path     = (char *)malloc( ext_path_len );
+
+        if ( ext_path != NULL )
+        {
+          snprintf( ext_path, ext_path_len, "%.*s.afm", (int)path_len,
+                    font->filepathname );
+
+          if ( FT_Attach_File( *aface, ext_path ) != FT_Err_Ok )
+          {
+            snprintf( ext_path, ext_path_len, "%.*s.pfm", (int)path_len,
+                      font->filepathname );
+
+            FT_Attach_File( *aface, ext_path );
+          }
+
+          free( ext_path );
+        }
       }
 
       if ( (*aface)->charmaps && font->cmap_index < (*aface)->num_charmaps )
@@ -539,6 +551,7 @@
   {
     FT_Int     major, minor, patch;
     FT_String  format[] = "%d.%d.%d";
+    StrBuf     sb;
 
 
     FT_Library_Version( handle->library, &major, &minor, &patch );
@@ -547,7 +560,8 @@
       format[5] = '\0';   /* terminate early */
 
     /* append the version string */
-    sprintf( str + strlen( str ), format, major, minor, patch );
+    strbuf_init( &sb, str, strlen( str ) );
+    strbuf_format( &sb, format, major, minor, patch );
   }
 
 
@@ -605,11 +619,9 @@
 
         font = (PFont)malloc( sizeof ( *font ) );
 
-        /* We allocate four more bytes since we want to attach an AFM */
-        /* or PFM file for Type 1 fonts (if available).  Such fonts   */
-        /* always have the extension `.afm' or `.pfm'.                */
-        font->filepathname = (char*)malloc( strlen( filepath ) + 4 + 1 );
-        strcpy( (char*)font->filepathname, filepath );
+        font->filepathname = ft_strdup( filepath );
+        if ( !font->filepathname )
+          return FT_Err_Out_Of_Memory;
 
         font->face_index = ( j << 16 ) + i;
 
@@ -946,7 +958,8 @@
                       int              error_code )
   {
     FT_Face      face;
-    char         buf[256];
+    char         buffer[256];
+    StrBuf       buf[1];
     const char*  basename;
     int          ppem;
 
@@ -965,9 +978,11 @@
 
 
     /* font and file name */
-    x = sprintf( buf, "%.50s %.50s", face->family_name, face->style_name );
+    strbuf_init( buf, buffer, sizeof( buffer ) );
+    x = strbuf_format( buf, "%.50s %.50s", face->family_name,
+                       face->style_name );
     grWriteCellString( display->bitmap, 0, line * HEADER_HEIGHT,
-                       buf, display->fore_color );
+                       strbuf_value( buf ), display->fore_color );
 
     basename = ft_basename( handle->current_font->filepathname );
     x = display->bitmap->width - 8 * (int)strlen( basename ) > 8 * x + 8 ?
@@ -980,49 +995,53 @@
                                                face->size->metrics.y_scale )
                                   : face->size->metrics.y_ppem * 64;
 
+    strbuf_reset( buf );
     if ( res == 72 )
-      x  = sprintf( buf, "%.4g ppem", ppem / 64.0 );
+      strbuf_format( buf, "%.4g ppem", ppem / 64.0 );
     else
-      x  = sprintf( buf, "%g pt at %d dpi, %.4g ppem",
-                         ptsize / 64.0, res, ppem / 64.0 );
+      strbuf_format( buf, "%g pt at %d dpi, %.4g ppem",
+                     ptsize / 64.0, res, ppem / 64.0 );
 
     if ( face->face_index >> 16 )
-      x += sprintf( buf + x, ", instance %ld/%ld",
-                             face->face_index >> 16,
-                             face->style_flags >> 16 );
+      strbuf_format( buf, ", instance %ld/%ld",
+                     face->face_index >> 16,
+                     face->style_flags >> 16 );
 
     grWriteCellString( display->bitmap, 0, line * HEADER_HEIGHT,
-                       buf, display->fore_color );
+                       strbuf_value( buf ), display->fore_color );
 
     if ( abs( ptsize * res / 64 - face->size->metrics.y_ppem * 72 ) > 36 ||
          error_code                                                      )
     {
+      strbuf_reset( buf );
+
       switch ( error_code )
       {
       case FT_Err_Ok:
-        sprintf( buf, "Available size shown" );
+        strbuf_add( buf, "Available size shown" );
         break;
       case FT_Err_Invalid_Pixel_Size:
-        sprintf( buf, "Invalid pixel size" );
+        strbuf_add( buf, "Invalid pixel size" );
         break;
       case FT_Err_Invalid_PPem:
-        sprintf( buf, "Invalid ppem value" );
+        strbuf_add( buf, "Invalid ppem value" );
         break;
       default:
-        sprintf( buf, "Error 0x%04x", (FT_UShort)error_code );
+        strbuf_format( buf, "Error 0x%04x", (FT_UShort)error_code );
       }
       grWriteCellString( display->bitmap, 8 * x + 16, line * HEADER_HEIGHT,
-                         buf, display->warn_color );
+                         strbuf_value( buf ), display->warn_color );
     }
 
     /* gamma */
+    strbuf_reset( buf );
     if ( display->gamma == 0.0 )
-      sprintf( buf, "gamma: sRGB" );
+      strbuf_add( buf, "gamma: sRGB" );
     else
-      sprintf( buf, "gamma = %.1f", display->gamma );
+      strbuf_format( buf, "gamma = %.1f", display->gamma );
     grWriteCellString( display->bitmap,
                        display->bitmap->width - 8 * 11, line * HEADER_HEIGHT,
-                       buf, display->fore_color );
+                       strbuf_value( buf ), display->fore_color );
 
     line++;
 
@@ -1081,25 +1100,33 @@
         encoding = "Other";
       }
 
+      strbuf_reset( buf );
       if ( handle->encoding == FT_ENCODING_ORDER )
-        x = sprintf( buf, "%s idx: %d",
-                          encoding, idx );
+        x = strbuf_format( buf, "%s idx: %d", encoding, idx );
       else if ( handle->encoding == FT_ENCODING_UNICODE )
-        x = sprintf( buf, "%s charcode: U+%04X (glyph idx %d)",
-                          encoding, idx, glyph_idx );
+      {
+        x = strbuf_format( buf, "%s charcode: U+%04X (glyph idx %d)",
+                      encoding, idx, glyph_idx );
+      }
       else
-        x = sprintf( buf, "%s charcode: 0x%X (glyph idx %d)",
-                          encoding, idx, glyph_idx );
+      {
+        x = strbuf_format( buf, "%s charcode: 0x%X (glyph idx %d)",
+                           encoding, idx, glyph_idx );
+      }
 
       if ( FT_HAS_GLYPH_NAMES( face ) )
       {
-        x += sprintf( buf + x, ", name: " );
+        x += strbuf_add( buf, ", name: " );
 
-        FT_Get_Glyph_Name( face, glyph_idx, buf + x, (FT_UInt)( 256 - x ) );
+        /* NOTE: This relies on the fact that FT_Get_Glyph_Name will
+         * always append a terminating zero to the input. */
+        FT_Get_Glyph_Name( face, glyph_idx,
+                           strbuf_end( buf ),
+                           (FT_UInt)(strbuf_available( buf ) + 1) );
       }
 
       grWriteCellString( display->bitmap, 0, (line++) * HEADER_HEIGHT,
-                         buf, display->fore_color );
+                         strbuf_value( buf ), display->fore_color );
     }
 
   }
diff --git a/src/ftcommon.h b/src/ftcommon.h
index 3645a95..d9c3d8f 100644
--- a/src/ftcommon.h
+++ b/src/ftcommon.h
@@ -74,6 +74,40 @@
 #include "grobjs.h"
 #include "grfont.h"
 
+  typedef struct
+  {
+    grBitmap* bitmap;
+    grColor   fore_color;
+    grColor   back_color;
+    int       line_pos;
+  } GrTextWriter;
+
+  extern void
+  grTextWriter_init( GrTextWriter* writer,
+                     grBitmap*     bitmap,
+                     grColor       fore_color,
+                     grColor       back_color );
+
+  extern void
+  grTextWriter_print( GrTextWriter* writer,
+                      const char*   fmt,
+                      ... );
+
+  extern void
+  grTextWriter_vprint( GrTextWriter* writer,
+                       const char*   fmt,
+                       va_list       fmt_args );
+
+  extern void
+  grTextWriter_print_( GrTextWriter* writer,
+                       const char*   fmt,
+                       ... );
+
+  extern void
+  grTextWriter_vprint_( GrTextWriter* writer,
+                        const char*   fmt,
+                        va_list       fmt_args );
+
   typedef struct
   {
     grSurface*  surface;
diff --git a/src/ftdiff.c b/src/ftdiff.c
index 69ae197..95442cf 100644
--- a/src/ftdiff.c
+++ b/src/ftdiff.c
@@ -460,19 +460,17 @@
           fn = face->family_name;
         else
           fn = (char*)"(unknown family)";
-        family_name = (char*)malloc( strlen( fn ) + 1 );
+        family_name = ft_strdup( fn );
         if ( family_name == NULL )
           panic( "ftdiff: not enough memory\n" );
-        strcpy( family_name, fn );
 
         if ( face->style_name )
           sn = face->style_name;
         else
           sn = (char*)"(unknown style)";
-        style_name = (char*)malloc( strlen( sn ) + 1 );
+        style_name = ft_strdup( sn );
         if ( style_name == NULL )
           panic( "ftdiff: not enough memory\n" );
-        strcpy( style_name, sn );
 
         faces[num_faces].filepath    = files[0];
         faces[num_faces].index       = count;
@@ -876,7 +874,7 @@
       else if ( rmode == HINT_MODE_AUTOHINT )
         extra = warping ? " (+warp)" : " (-warp)";
 
-      sprintf( temp, "%s%s",
+      snprintf( temp, sizeof( temp ), "%s%s",
                render_mode_names[column->hint_mode], extra );
       state->display.disp_text( disp, left,
                                 bottom + 5, temp );
@@ -896,7 +894,7 @@
           unsigned char*  fw  = column->filter_weights;
 
 
-          sprintf( temp,
+          snprintf( temp, sizeof( temp ),
                    "%s0x%02X%s0x%02X%s0x%02X%s0x%02X%s0x%02X%s",
                    fwi == 0 ? "[" : " ",
                      fw[0],
@@ -939,7 +937,7 @@
                                   bottom + 2 * HEADER_HEIGHT + 5, msg );
       }
 
-      sprintf( temp, "%s %s %s",
+      snprintf( temp, sizeof( temp ), "%s %s %s",
                column->use_kerning ? "+kern"
                                    : "-kern",
                column->use_deltas ? "+delta"
@@ -1143,7 +1141,7 @@
     FT_Library_Version( state->library, &major, &minor, &patch );
 
     format = patch ? "%d.%d.%d" : "%d.%d";
-    sprintf( version, format, major, minor, patch );
+    snprintf( version, sizeof( version ), format, major, minor, patch );
 
     adisplay_clear( display );
     grSetLineHeight( 10 );
@@ -1151,7 +1149,7 @@
     grSetMargin( 2, 1 );
     grGotobitmap( display->bitmap );
 
-    sprintf( buf,
+    snprintf( buf, sizeof( buf ),
              "FreeType Hinting Mode Comparator - part of the FreeType %s test suite",
              version );
 
@@ -1479,7 +1477,7 @@
 
 
     basename = ft_basename( state->filename );
-    sprintf( buf, "%.50s %.50s (file `%.100s')",
+    snprintf( buf, sizeof( buf ), "%.50s %.50s (file `%.100s')",
                   face->family_name,
                   face->style_name,
                   basename );
@@ -1487,8 +1485,8 @@
                        buf, adisplay->fore_color );
 
     if ( adisplay->gamma != 0.0 )
-      sprintf( gamma, "%.1f", adisplay->gamma );
-    sprintf( buf, "%.1fpt (%dppem) at %ddpi, gamma: %s",
+      snprintf( gamma, sizeof( gamma ), "%.1f", adisplay->gamma );
+    snprintf( buf, sizeof( buf ), "%.1fpt (%dppem) at %ddpi, gamma: %s",
                   state->char_size,
                   (int)(state->char_size * state->resolution / 72 + 0.5),
                   state->resolution,
diff --git a/src/ftdump.c b/src/ftdump.c
index b87955b..ede314e 100644
--- a/src/ftdump.c
+++ b/src/ftdump.c
@@ -118,7 +118,7 @@
     if ( left <= 0 )
       left = 1;
 
-    sprintf( result, "   %s:%*s", name, left, " " );
+    snprintf( result, sizeof( result ), "   %s:%*s", name, left, " " );
 
     return result;
   }
@@ -865,7 +865,7 @@
         continue;
       }
 
-      sprintf( tag, "%04hx", i );
+      snprintf( tag, sizeof( tag ), "%04hx", i );
       printf( "\nglyf program %hd (%.4s)", i, tag );
       Print_Bytecode( buffer + loc, len, tag );
 
diff --git a/src/ftgamma.c b/src/ftgamma.c
index 8392e32..abfd4d3 100644
--- a/src/ftgamma.c
+++ b/src/ftgamma.c
@@ -241,7 +241,7 @@
       int     nx;
 
 
-      sprintf( temp, "%.1f", ggamma );
+      snprintf( temp, sizeof( temp ), "%.1f", ggamma );
       grWriteCellString( display->bitmap, x_0 - 32, y + ( yside - 6 ) / 2,
                          temp, display->fore_color );
 
@@ -420,7 +420,7 @@
 
       for ( i = 0; i <= 10; i++ )
       {
-        sprintf( buf, "%.1f", 1. + .2 * i );
+        snprintf( buf, sizeof( buf ), "%.1f", 1. + .2 * i );
         grWriteCellString( display->bitmap, x - 311 + i * 60, y + 155,
                            buf, display->fore_color );
       }
diff --git a/src/ftgrid.c b/src/ftgrid.c
index 3313e79..7cfac4a 100644
--- a/src/ftgrid.c
+++ b/src/ftgrid.c
@@ -887,7 +887,7 @@
     grSetMargin( 2, 1 );
     grGotobitmap( display->bitmap );
 
-    sprintf( buf,
+    snprintf( buf, sizeof( buf ),
             "FreeType Glyph Grid Viewer - part of the FreeType %s test suite",
              version );
 
@@ -1114,7 +1114,8 @@
       event_font_change( 0 );
     }
 
-    sprintf( status.header_buffer,
+    snprintf( status.header_buffer,
+              sizeof( status.header_buffer ),
              "TrueType engine changed to version %d",
              status.tt_interpreter_versions[
                status.tt_interpreter_version_idx]);
@@ -1185,7 +1186,8 @@
     if ( status.scale == scale_old && zoom > 1.0 )
       status.scale++;
 
-    sprintf( status.header_buffer, "zoom scale %d:1", status.scale );
+    snprintf( status.header_buffer, sizeof( status.header_buffer ),
+              "zoom scale %d:1", status.scale );
 
     status.header = (const char *)status.header_buffer;
   }
@@ -1235,8 +1237,9 @@
       event_font_change( 0 );
     }
 
-    sprintf( status.header_buffer, "rendering mode changed to %s",
-             lcd_mode );
+    snprintf( status.header_buffer, sizeof( status.header_buffer ),
+              "rendering mode changed to %s",
+              lcd_mode );
 
     status.header = (const char *)status.header_buffer;
 
@@ -1286,8 +1289,9 @@
         break;
       }
 
-      sprintf( status.header_buffer, "LCD filter changed to %s",
-               lcd_filter );
+      snprintf( status.header_buffer, sizeof( status.header_buffer ),
+                "LCD filter changed to %s",
+                lcd_filter );
 
       status.header = (const char *)status.header_buffer;
 
@@ -1548,9 +1552,10 @@
               event_font_change( 0 );
             }
 
-            sprintf( status.header_buffer, "CFF engine changed to %s",
-                     status.cff_hinting_engine == FT_HINTING_FREETYPE
-                       ? "FreeType" : "Adobe" );
+            snprintf( status.header_buffer, sizeof( status.header_buffer ),
+                      "CFF engine changed to %s",
+                      status.cff_hinting_engine == FT_HINTING_FREETYPE
+                        ? "FreeType" : "Adobe" );
 
             status.header = (const char *)status.header_buffer;
           }
@@ -1568,9 +1573,10 @@
               event_font_change( 0 );
             }
 
-            sprintf( status.header_buffer, "Type 1 engine changed to %s",
-                     status.type1_hinting_engine == FT_HINTING_FREETYPE
-                       ? "FreeType" : "Adobe" );
+            snprintf( status.header_buffer, sizeof( status.header_buffer ),
+                      "Type 1 engine changed to %s",
+                      status.type1_hinting_engine == FT_HINTING_FREETYPE
+                        ? "FreeType" : "Adobe" );
 
             status.header = (const char *)status.header_buffer;
           }
@@ -1588,9 +1594,10 @@
               event_font_change( 0 );
             }
 
-            sprintf( status.header_buffer, "CID engine changed to %s",
-                     status.t1cid_hinting_engine == FT_HINTING_FREETYPE
-                       ? "FreeType" : "Adobe" );
+            snprintf( status.header_buffer, sizeof( status.header_buffer ),
+                      "CID engine changed to %s",
+                      status.t1cid_hinting_engine == FT_HINTING_FREETYPE
+                        ? "FreeType" : "Adobe" );
 
             status.header = (const char *)status.header_buffer;
           }
diff --git a/src/ftmulti.c b/src/ftmulti.c
index 1562645..187ce1d 100644
--- a/src/ftmulti.c
+++ b/src/ftmulti.c
@@ -21,6 +21,7 @@
 
 #include "common.h"
 #include "mlgetopt.h"
+#include "strbuf.h"
 
 #include <stdio.h>
 #include <stdlib.h>
@@ -525,9 +526,9 @@
     FT_Library_Version( library, &major, &minor, &patch );
 
     if ( patch )
-      sprintf( version, "%d.%d.%d", major, minor, patch );
+      snprintf( version, sizeof( version ), "%d.%d.%d", major, minor, patch );
     else
-      sprintf( version, "%d.%d", major, minor );
+      snprintf( version, sizeof( version ), "%d.%d", major, minor );
 
     Clear_Display();
     grSetLineHeight( 10 );
@@ -535,9 +536,9 @@
     grSetMargin( 2, 1 );
     grGotobitmap( bit );
 
-    sprintf( buf,
-             "FreeType MM Glyph Viewer - part of the FreeType %s test suite",
-             version );
+    snprintf( buf, sizeof( buf ),
+              "FreeType MM Glyph Viewer - part of the FreeType %s test suite",
+              version );
 
     grWriteln( buf );
     grLn();
@@ -1144,7 +1145,8 @@
 
     for ( ;; )
     {
-      int  key;
+      int     key;
+      StrBuf  header[1];
 
 
       Clear_Display();
@@ -1161,10 +1163,11 @@
           Render_All( (unsigned int)Num, ptsize );
         }
 
-        sprintf( Header, "%.50s %.50s (file %.100s)",
-                         face->family_name,
-                         face->style_name,
-                         ft_basename( argv[file] ) );
+        strbuf_init( header, Header, sizeof( Header ) );
+        strbuf_format( header, "%.50s %.50s (file %.100s)",
+                       face->family_name,
+                       face->style_name,
+                       ft_basename( argv[file] ) );
 
         if ( !new_header )
           new_header = Header;
@@ -1172,11 +1175,13 @@
         grWriteCellString( bit, 0, 0, new_header, fore_color );
         new_header = NULL;
 
-        sprintf( Header, "PS name: %s",
-                         FT_Get_Postscript_Name( face ) );
+        strbuf_reset( header );
+        strbuf_format( header, "PS name: %s", FT_Get_Postscript_Name( face ) );
         grWriteCellString( bit, 0, 16, Header, fore_color );
 
-        sprintf( Header, "axes:" );
+        strbuf_reset( header );
+        strbuf_add( header, "axes:" );
+
         {
           unsigned int  limit = num_shown_axes > MAX_MM_AXES / 2
                                   ? MAX_MM_AXES / 2
@@ -1185,18 +1190,13 @@
 
           for ( n = 0; n < limit; n++ )
           {
-            char  temp[100];
-            int   axis;
-
+            int  axis = shown_axes[n];
 
-            axis = shown_axes[n];
 
-            sprintf( temp, "  %.50s%s: %.02f",
+            strbuf_format( header, "  %.50s%s: %.02f",
                            multimaster->axis[axis].name,
                            hidden[axis] ? "*" : "",
                            design_pos[axis] / 65536.0 );
-            strncat( Header, temp,
-                     sizeof ( Header ) - strlen( Header ) - 1 );
           }
         }
         grWriteCellString( bit, 0, 24, Header, fore_color );
@@ -1206,22 +1206,18 @@
           unsigned int  limit = num_shown_axes;
 
 
-          sprintf( Header, "     " );
+          strbuf_reset( header );
+          strbuf_add( header, "     " );
 
           for ( n = MAX_MM_AXES / 2; n < limit; n++ )
           {
-            char  temp[100];
-            int   axis;
-
+            int  axis = shown_axes[n];
 
-            axis = shown_axes[n];
 
-            sprintf( temp, "  %.50s%s: %.02f",
+            strbuf_format( header, "  %.50s%s: %.02f",
                            multimaster->axis[axis].name,
                            hidden[axis] ? "*" : "",
                            design_pos[axis] / 65536.0 );
-            strncat( Header, temp,
-                     sizeof ( Header ) - strlen( Header ) - 1 );
           }
 
           grWriteCellString( bit, 0, 32, Header, fore_color );
@@ -1252,15 +1248,19 @@
                                        ? "TrueType (v38)"
                                        : "TrueType (v40)" ) );
 
-          sprintf( Header, "at %d points, first glyph = %d, format = %s",
-                           ptsize,
-                           Num,
-                           format_str );
+          strbuf_reset( header );
+          strbuf_format( header, "at %d points, first glyph = %d, format = %s",
+                         ptsize,
+                         Num,
+                         format_str );
         }
       }
       else
-        sprintf( Header, "%.100s: not an MM font file, or could not be opened",
-                         ft_basename( argv[file] ) );
+      {
+        strbuf_format( header, 
+                       "%.100s: not an MM font file, or could not be opened",
+                       ft_basename( argv[file] ) );
+      }
 
       grWriteCellString( bit, 0, 8, Header, fore_color );
       grRefreshSurface( surface );
diff --git a/src/ftsbit.c b/src/ftsbit.c
index 0f71ca5..5bf8689 100644
--- a/src/ftsbit.c
+++ b/src/ftsbit.c
@@ -40,7 +40,7 @@
   {
     static char  temp[32];
 
-    sprintf( temp, "0x%04lx", error );
+    snprintf( temp, sizeof( temp ), "0x%04lx", error );
     return temp;
   }
 
diff --git a/src/ftstring.c b/src/ftstring.c
index 9e42e67..2ef5f96 100644
--- a/src/ftstring.c
+++ b/src/ftstring.c
@@ -249,9 +249,9 @@
     grSetMargin( 2, 1 );
     grGotobitmap( display->bitmap );
 
-    sprintf( buf,
-             "FreeType String Viewer - part of the FreeType %s test suite",
-             version );
+    snprintf( buf, sizeof( buf ),
+              "FreeType String Viewer - part of the FreeType %s test suite",
+              version );
 
     grWriteln( buf );
     grLn();
@@ -402,7 +402,8 @@
       lcd_mode = " monochrome";
     }
 
-    sprintf( status.header_buffer, "mode changed to %s", lcd_mode );
+    snprintf( status.header_buffer, sizeof( status.header_buffer ),
+              "mode changed to %s", lcd_mode );
     status.header = status.header_buffer;
   }
 
diff --git a/src/ftview.c b/src/ftview.c
index 0d5e249..957ee23 100644
--- a/src/ftview.c
+++ b/src/ftview.c
@@ -806,9 +806,9 @@
     grSetMargin( 2, 1 );
     grGotobitmap( display->bitmap );
 
-    sprintf( buf,
-             "FreeType Glyph Viewer - part of the FreeType %s test suite",
-             version );
+    snprintf( buf, sizeof( buf ),
+              "FreeType Glyph Viewer - part of the FreeType %s test suite",
+              version );
 
     grWriteln( buf );
     grLn();
@@ -1525,9 +1525,9 @@
         render_mode = "waterfall";
         break;
       }
-      sprintf( buf, "%d: %s",
-                    status.render_mode + 1,
-                    render_mode );
+      snprintf( buf, sizeof( buf ), "%d: %s",
+                status.render_mode + 1,
+                render_mode );
       grWriteCellString( display->bitmap, 0, (line++) * HEADER_HEIGHT,
                          buf, display->fore_color );
     }
@@ -1535,20 +1535,20 @@
     if ( status.render_mode == RENDER_MODE_FANCY )
     {
       /* x emboldening */
-      sprintf( buf, " x: % .3f",
-                    status.xbold_factor );
+      snprintf( buf, sizeof( buf ), " x: % .3f",
+                status.xbold_factor );
       grWriteCellString( display->bitmap, 0, (line++) * HEADER_HEIGHT,
                          buf, display->fore_color );
 
       /* y emboldening */
-      sprintf( buf, " y: % .3f",
-                    status.ybold_factor );
+      snprintf( buf, sizeof( buf ), " y: % .3f",
+                status.ybold_factor );
       grWriteCellString( display->bitmap, 0, (line++) * HEADER_HEIGHT,
                          buf, display->fore_color );
 
       /* slanting */
-      sprintf( buf, " s: % .3f",
-                    status.slant );
+      snprintf( buf, sizeof( buf ), " s: % .3f",
+                status.slant );
       grWriteCellString( display->bitmap, 0, (line++) * HEADER_HEIGHT,
                          buf, display->fore_color );
     }
@@ -1556,8 +1556,8 @@
     if ( status.render_mode == RENDER_MODE_STROKE )
     {
       /* stroking radius */
-      sprintf( buf, " radius: %.3f",
-                    status.radius );
+      snprintf( buf, sizeof( buf ), " radius: %.3f",
+                status.radius );
       grWriteCellString( display->bitmap, 0, (line++) * HEADER_HEIGHT,
                          buf, display->fore_color );
     }
@@ -1598,17 +1598,17 @@
     }
 
     /* hinting */
-    sprintf( buf, "hinting: %s",
-                  handle->hinted ? "on" : "off" );
+    snprintf( buf, sizeof( buf ), "hinting: %s",
+              handle->hinted ? "on" : "off" );
     grWriteCellString( display->bitmap, 0, (line++) * HEADER_HEIGHT,
                        buf, display->fore_color );
 
     if ( handle->hinted )
     {
       /* auto-hinting */
-      sprintf( buf, " forced auto: %s",
-                    ( handle->autohint                   ||
-                      handle->lcd_mode == LCD_MODE_LIGHT ) ? "on" : "off" );
+      snprintf( buf, sizeof( buf ), " forced auto: %s",
+                ( handle->autohint                   ||
+                  handle->lcd_mode == LCD_MODE_LIGHT ) ? "on" : "off" );
       grWriteCellString( display->bitmap, 0, (line++) * HEADER_HEIGHT,
                          buf, display->fore_color );
     }
@@ -1681,8 +1681,8 @@
 
       if ( hinting_engine )
       {
-        sprintf( buf, "engine: %s",
-                      hinting_engine );
+        snprintf( buf, sizeof( buf ), "engine: %s",
+                  hinting_engine );
         grWriteCellString( display->bitmap, 0, (line++) * HEADER_HEIGHT,
                            buf, display->fore_color );
       }
@@ -1690,8 +1690,8 @@
 
     if ( handle->lcd_mode == LCD_MODE_AA && handle->autohint )
     {
-      sprintf( buf, "warping: %s",
-                    status.warping ? "on" : "off" );
+      snprintf( buf, sizeof( buf ), "warping: %s",
+                status.warping ? "on" : "off" );
       grWriteCellString( display->bitmap, 0, (line++) * HEADER_HEIGHT,
                          buf, display->fore_color );
     }
@@ -1699,39 +1699,39 @@
     line++;
 
     /* embedded bitmaps */
-    sprintf( buf, "bitmaps: %s",
-                  handle->use_sbits ? "on" : "off" );
+    snprintf( buf, sizeof( buf ), "bitmaps: %s",
+              handle->use_sbits ? "on" : "off" );
     grWriteCellString( display->bitmap, 0, (line++) * HEADER_HEIGHT,
                        buf, display->fore_color );
 
     if ( FT_HAS_COLOR( face ) )
     {
-      sprintf( buf, "color:" );
+      snprintf( buf, sizeof( buf ), "color:" );
       grWriteCellString( display->bitmap, 0, (line++) * HEADER_HEIGHT,
                          buf, display->fore_color );
 
       /* color bitmaps */
-      sprintf( buf, "  bitmaps: %s",
-                    handle->use_color ? "on" : "off" );
+      snprintf( buf, sizeof( buf ), "  bitmaps: %s",
+                handle->use_color ? "on" : "off" );
       grWriteCellString( display->bitmap, 0, (line++) * HEADER_HEIGHT,
                          buf, display->fore_color );
 
       /* color-layered glyphs */
-      sprintf( buf, "  outlines: %s",
-                    handle->use_layers ? "on" : "off" );
+      snprintf( buf, sizeof( buf ), "  outlines: %s",
+                handle->use_layers ? "on" : "off" );
       grWriteCellString( display->bitmap, 0, (line++) * HEADER_HEIGHT,
                          buf, display->fore_color );
 
       /* color palette */
-      sprintf( buf, "  palette idx: %d",
-                    handle->current_font->palette_index );
+      snprintf( buf, sizeof( buf ), "  palette idx: %d",
+                handle->current_font->palette_index );
       grWriteCellString( display->bitmap, 0, (line++) * HEADER_HEIGHT,
                          buf, display->fore_color );
     }
 
     /* cache */
-    sprintf( buf, "cache: %s",
-                  handle->use_sbits_cache ? "on" : "off" );
+    snprintf( buf, sizeof( buf ), "cache: %s",
+              handle->use_sbits_cache ? "on" : "off" );
     grWriteCellString( display->bitmap, 0, (line++) * HEADER_HEIGHT,
                        buf, display->fore_color );
 
@@ -1742,11 +1742,11 @@
                          FT_Err_Unimplemented_Feature    &&
          handle->lcd_mode >= LCD_MODE_RGB                )
     {
-      sprintf( buf, "filter: %s",
-                    status.lcd_filter == 0 ? "none" :
-                    status.lcd_filter == 1 ? "default" :
-                    status.lcd_filter == 2 ? "light" :
-                    status.lcd_filter == 3 ? "legacy" : "custom" );
+      snprintf( buf, sizeof( buf ), "filter: %s",
+                status.lcd_filter == 0 ? "none" :
+                status.lcd_filter == 1 ? "default" :
+                status.lcd_filter == 2 ? "light" :
+                status.lcd_filter == 3 ? "legacy" : "custom" );
       grWriteCellString( display->bitmap, 0, (line++) * HEADER_HEIGHT,
                          buf, display->fore_color );
 
@@ -1760,11 +1760,11 @@
 
         for ( i = 0; i < 5; i++ )
         {
-          sprintf( buf,
-                   " %s0x%02X%s",
-                   fwi == i ? "[" : " ",
-                   fw[i],
-                   fwi == i ? "]" : " " );
+          snprintf( buf, sizeof( buf ),
+                    " %s0x%02X%s",
+                    fwi == i ? "[" : " ",
+                    fw[i],
+                    fwi == i ? "]" : " " );
           grWriteCellString( display->bitmap, 0, (line++) * HEADER_HEIGHT,
                              buf, display->fore_color );
         }
diff --git a/src/strbuf.c b/src/strbuf.c
new file mode 100644
index 0000000..5e7ae53
--- /dev/null
+++ b/src/strbuf.c
@@ -0,0 +1,139 @@
+/****************************************************************************/
+/*                                                                          */
+/*  The FreeType project -- a free and portable quality TrueType renderer.  */
+/*                                                                          */
+/*  Copyright (C) 2020 by                                                   */
+/*  D. Turner, R.Wilhelm, and W. Lemberg                                    */
+/*                                                                          */
+/*                                                                          */
+/*  strbuf.c - routines to safely append strings to fixed-size buffers.     */
+/*                                                                          */
+/****************************************************************************/
+
+#include "strbuf.h"
+
+#include <assert.h>
+#include <stdio.h>
+#include <string.h>
+
+  void
+  strbuf_init( StrBuf*  sb, char*  buffer, size_t  buffer_len )
+  {
+    assert( buffer_len > 0 );
+    sb->pos    = 0;
+    sb->limit  = buffer_len - 1;  /* Reserve one char for the terminating \0 */
+    sb->buffer = buffer;
+    buffer[0]  = '\0';
+  }
+
+  const char*
+  strbuf_value( const StrBuf*  sb )
+  {
+    assert( sb->pos < sb->limit );
+    assert( sb->buffer[ sb->pos ] == '\0' );
+    return sb->buffer;
+  }
+
+  size_t
+  strbuf_len( const StrBuf*  sb )
+  {
+    return sb->pos;
+  }
+
+  char*
+  strbuf_back( const StrBuf*  sb )
+  {
+    if ( sb->pos == 0 )
+      return NULL;
+
+    return &sb->buffer[ sb->pos - 1 ];
+  }
+
+  char*
+  strbuf_end( const StrBuf* sb )
+  {
+    return sb->buffer + sb->pos;
+  }
+
+  size_t
+  strbuf_available( const StrBuf* sb )
+  {
+    return sb->limit - sb->pos;
+  }
+
+  void
+  strbuf_skip_over( StrBuf*  sb, size_t  len )
+  {
+    assert(len <= strbuf_available( sb ));
+    sb->pos += len;
+    sb->buffer[sb->pos] = '\0';
+  }
+
+  void
+  strbuf_reset( StrBuf*  sb )
+  {
+    sb->pos       = 0;
+    sb->buffer[0] = '\0';
+  }
+
+  int
+  strbuf_add( StrBuf*  sb, const char*  str )
+  {
+    return strbuf_addn( sb, str, strlen(str) );
+  }
+
+  int
+  strbuf_addn( StrBuf*  sb, const char*  str, size_t  len )
+  {
+    size_t  available = sb->limit - sb->pos;
+
+
+    if ( len > available )
+      len = available;
+
+    memcpy( sb->buffer + sb->pos, str, len );
+    sb->pos += len;
+
+    sb->buffer[sb->pos] = '\0';
+
+    return (int) len;
+  }
+
+  int
+  strbuf_addc( StrBuf*  sb, char  ch )
+  {
+    if ( sb->pos >= sb->limit )
+      return 0;
+
+    sb->buffer[sb->pos++] = ch;
+    sb->buffer[sb->pos]   = '\0';
+    return 1;
+  }
+
+  extern int
+  strbuf_format( StrBuf*  sb, const char*  fmt, ... )
+  {
+    int      result;
+    va_list  args;
+
+
+    va_start( args, fmt );
+    result = strbuf_vformat( sb, fmt, args );
+    va_end( args );
+    return result;
+  }
+
+  extern int
+  strbuf_vformat( StrBuf*  sb, const char*  fmt, va_list  args )
+  {
+    size_t available = sb->limit - sb->pos;
+    int    ret       = vsnprintf( sb->buffer + sb->pos, available, fmt, args );
+
+    /* NOTE: On Windows, vsnprintf() can return -1 in case of truncation! */
+    if ( ret < 0 || ret > available )
+      return (int)available;
+
+    return (int)ret;
+  }
+
+/* END */
diff --git a/src/strbuf.h b/src/strbuf.h
new file mode 100644
index 0000000..62b3591
--- /dev/null
+++ b/src/strbuf.h
@@ -0,0 +1,117 @@
+#ifndef STRBUF_H
+#define STRBUF_H
+
+#include <stdarg.h>
+#include <stddef.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+  /*
+   * Helper struct to append strings to a fixed-size char buffer safely.
+   * Usage is the following:
+   *   1) Initialize instance with strbuf_init().
+   *   2) Use strbuff_add() to append a string to the target buffer,
+   *      strbuff_addc() to append a single char, and strbuff_format()
+   *      to append a formatted string.
+   *   3) Call strbuf_value() to retrieve the zero-terminated resulting
+   *      string.
+   */
+  typedef struct {
+    /* Private fields, do not access directly */
+    unsigned pos;
+    unsigned limit;
+    char*    buffer;
+  } StrBuf;
+
+  /* Initialize an StrBuf instance that will allow appending strings to
+   * |buffer|. Note that |buffer_len| *must* be > 0, or behaviour is undefined.
+   */
+  extern void
+  strbuf_init( StrBuf* sb, char* buffer, size_t buffer_len );
+
+  /* Convenience macro to call strbuf_init() from a char array. */
+#define STRBUF_INIT_FROM_ARRAY(sb, array) \
+  strbuf_init((sb), (array), sizeof((array)));
+
+  /* Return the zero-terminated value held by a StrBuf instance. */
+  extern const char*
+  strbuf_value( const StrBuf* sb );
+
+  /* Return the current length, in chars, of the StrBuf's content.
+   * Does not include the terminating zero. */
+  extern size_t
+  strbuf_len( const StrBuf* sb );
+
+  /* Return pointer to last character in StrBuf content, or NULL if it
+   * is empty. */
+  extern char*
+  strbuf_back( const StrBuf* sb );
+
+  /* Return a pointer to the first char after the StrBuf content. Useful
+   * if one needs to append stuff manually to the content, In this case
+   * use strbuf_available() to see how many chars are available in the
+   * rest of the storage buffer, excluding the terminating zero, then
+   * call strbuf_skip_over() to increment the internal cursor inside
+   * the StrBuf instance and ensure the storage is properly zero-terminated.
+   */
+  extern char*
+  strbuf_end( const StrBuf* sb );
+
+  /* Return the remaining number of chars available in the storage buffer
+   * for a given StrBuf instance. Does not include the terminating zero.
+   * NOTE: There is always one byte available after the last available char
+   * reserved for the terminating zero.
+   */
+  extern size_t
+  strbuf_available( const StrBuf* sb );
+
+  /* Skip over |len| characters in the storage buffer. This is only useful
+   * is strbuf_end() and strbuf_available() were previously called to let
+   * the caller append stuff to the buffer manually. It is an error to use
+   * a value of |len| that is larger than strbuf_available().
+   */
+  extern void
+  strbuf_skip_over( StrBuf* sb, size_t len );
+
+  /* Reset an StrBuf instance, i.e. clear its current string value. */
+  extern void
+  strbuf_reset( StrBuf* sb );
+
+  /* Append a string to an StrBuf instance. Return the number of chars that
+   * were really added, which will be smaller than the input string's length
+   * in case of truncation. Note that this is different from functions like
+   * snprintf() which return the number of characters in the formatted input,
+   * even if truncation occurs.
+   */
+  extern int
+  strbuf_add( StrBuf* sb, const char* str );
+
+  /* Append |len| bytes from |str| to an StrBuf instance. Return the number
+   * of chars that were really added. Note that the input can contain NUL
+   * chars. */
+  extern int
+  strbuf_addn( StrBuf* sb, const char* str, size_t len );
+
+  /* Append a single char to an StrBuf instance. Return 1, or 0 in the case
+   * where the buffer is already full.
+   */
+  extern int
+  strbuf_addc( StrBuf* sb, char ch );
+
+  /* Append a formatted string to an StrBuf instance. Return the number of
+   * chars that were really added. */
+  extern int
+  strbuf_format( StrBuf* sb, const char* fmt, ... );
+
+  /* A variant of strbuf_format() that takes a va_list argument for
+   * formatting arguments instead. */
+  extern int
+  strbuf_vformat( StrBuf* sb, const char* fmt, va_list args );
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif  /* STRBUF_H */
diff --git a/src/ttdebug.c b/src/ttdebug.c
index 7af1bfe..8705a37 100644
--- a/src/ttdebug.c
+++ b/src/ttdebug.c
@@ -55,6 +55,7 @@
 #include FT_FREETYPE_H
 #include FT_MULTIPLE_MASTERS_H
 #include "common.h"
+#include "strbuf.h"
 #include "mlgetopt.h"
 
 #include FT_DRIVER_H
@@ -96,9 +97,8 @@
   typedef char  ByteStr[2];
   typedef char  WordStr[4];
   typedef char  LongStr[8];
-  typedef char  DebugStr[128];
 
-  static DebugStr  tempStr;
+  static char   tempStr[256];
 
 
   typedef struct  Storage_
@@ -1656,19 +1656,19 @@
   static const FT_String*
   Cur_U_Line( TT_ExecContext  exc )
   {
-    FT_String  s[32];
-    FT_Int     op, i, n;
+    FT_Int  op, i, n;
+    StrBuf  bs[1];
 
 
     op = CUR.code[CUR.IP];
 
-    sprintf( tempStr, "%s", OpStr[op] );
+    strbuf_init( bs, tempStr, sizeof(tempStr) );
+    strbuf_add( bs, OpStr[op] );
 
     if ( op == 0x40 )  /* NPUSHB */
     {
       n = CUR.code[CUR.IP + 1];
-      sprintf( s, "(%d)", n );
-      strncat( tempStr, s, 8 );
+      strbuf_format( bs, "(%d)", n );
 
       /* limit output */
       if ( n > 20 )
@@ -1676,19 +1676,14 @@
 
       for ( i = 0; i < n; i++ )
       {
-        const FT_String*  temp;
-
-
-        temp = use_hex ? " $%02x" : " %d";
-        sprintf( s, temp, CUR.code[CUR.IP + i + 2] );
-        strncat( tempStr, s, 8 );
+        strbuf_format( bs, (use_hex ? " $%02x" : " %d"),
+                       CUR.code[CUR.IP + i + 2] );
       }
     }
     else if ( op == 0x41 )  /* NPUSHW */
     {
       n = CUR.code[CUR.IP + 1];
-      sprintf( s, "(%d)", n );
-      strncat( tempStr, s, 8 );
+      strbuf_format( bs, "(%d)", n );
 
       /* limit output */
       if ( n > 20 )
@@ -1697,9 +1692,11 @@
       for ( i = 0; i < n; i++ )
       {
         if ( use_hex )
-          sprintf( s, " $%02x%02x",
-                      CUR.code[CUR.IP + i * 2 + 2],
-                      CUR.code[CUR.IP + i * 2 + 3] );
+        {
+          strbuf_format( bs, " $%02x%02x",
+                            CUR.code[CUR.IP + i * 2 + 2],
+                            CUR.code[CUR.IP + i * 2 + 3] );
+        }
         else
         {
           unsigned short  temp;
@@ -1707,10 +1704,8 @@
 
           temp = (unsigned short)( ( CUR.code[CUR.IP + i * 2 + 2] << 8 ) +
                                      CUR.code[CUR.IP + i * 2 + 3]        );
-          sprintf( s, " %d",
-                      (signed short)temp );
+          strbuf_format( bs, " %u", temp);
         }
-        strncat( tempStr, s, 8 );
       }
     }
     else if ( ( op & 0xF8 ) == 0xB0 )  /* PUSHB */
@@ -1719,12 +1714,8 @@
 
       for ( i = 0; i <= n; i++ )
       {
-        const FT_String*  temp;
-
-
-        temp = use_hex ? " $%02x" : " %d";
-        sprintf( s, temp, CUR.code[CUR.IP + i + 1] );
-        strncat( tempStr, s, 8 );
+        strbuf_format( bs, (use_hex ? " $%02x" : " %d"),
+                          CUR.code[CUR.IP + i + 1] );
       }
     }
     else if ( ( op & 0xF8 ) == 0xB8 )  /* PUSHW */
@@ -1734,9 +1725,11 @@
       for ( i = 0; i <= n; i++ )
       {
         if ( use_hex )
-          sprintf( s, " $%02x%02x",
-                      CUR.code[CUR.IP + i * 2 + 1],
-                      CUR.code[CUR.IP + i * 2 + 2] );
+        {
+          strbuf_format( bs, " $%02x%02x",
+                            CUR.code[CUR.IP + i * 2 + 1],
+                            CUR.code[CUR.IP + i * 2 + 2] );
+        }
         else
         {
           unsigned short  temp;
@@ -1744,19 +1737,16 @@
 
           temp = (unsigned short)( ( CUR.code[CUR.IP + i * 2 + 1] << 8 ) +
                                      CUR.code[CUR.IP + i * 2 + 2]        );
-          sprintf( s, " %d",
-                      (signed short)temp );
+          strbuf_format( bs, " %d", (signed short)temp );
         }
-        strncat( tempStr, s, 8 );
       }
     }
     else if ( op == 0x39 )  /* IP */
     {
-      sprintf( s, " rp1=%d, rp2=%d", CUR.GS.rp1, CUR.GS.rp2 );
-      strncat( tempStr, s, 31 );
+      strbuf_format( bs, " rp1=%d, rp2=%d", CUR.GS.rp1, CUR.GS.rp2 );
     }
 
-    return (FT_String*)tempStr;
+    return (FT_String*)strbuf_value( bs );
   }
 
 
@@ -1999,7 +1989,7 @@
 
     const FT_String*  code_range;
 
-    const FT_String*  round_str[8] =
+    static const FT_String*  round_str[8] =
     {
       "to half-grid",
       "to grid",
@@ -2079,35 +2069,35 @@
         /* [loc]:[addr] [opcode]  [disassembly]         [a][b]|[c][d]      */
 
         {
-          char  temp[90];
-          int   n, col, pop;
-          int   args;
+          StrBuf  temp[1];
+          int     n, col, pop;
+          int     args;
 
 
-          sprintf( temp, "%78c\n", ' ' );
+          strbuf_init( temp, tempStr, sizeof( tempStr ) );
 
           /* first letter of location */
           switch ( CUR.curRange )
           {
           case tt_coderange_glyph:
-            temp[0] = 'g';
+            strbuf_addc( temp, 'g' );
             break;
 
           case tt_coderange_cvt:
-            temp[0] = 'c';
+            strbuf_addc( temp, 'c' );
             break;
 
           default:
-            temp[0] = 'f';
+            strbuf_addc( temp, 'f' );
           }
 
           /* current IP */
-          sprintf( temp + 1, "%04lx: %02x  %-36.36s",
-                             CUR.IP,
-                             CUR.opcode,
-                             Cur_U_Line( &CUR ) );
+          strbuf_format( temp, "%04lx: %02x  %-36.36s",
+                         CUR.IP,
+                         CUR.opcode,
+                         Cur_U_Line( &CUR ) );
 
-          strncpy( temp + 46, " (", 3 );
+          strbuf_add( temp, " (" );
 
           args = CUR.top - 1;
           pop  = Pop_Push_Count[CUR.opcode] >> 4;
@@ -2123,7 +2113,10 @@
 
 
             if ( pop == 0 )
-              temp[col - 1] = temp[col - 1] == '(' ? ' ' : ')';
+            {
+              char* last = strbuf_back( temp );
+              *last = (*last == '(') ? ' ' : ')';
+            }
 
             if ( args >= 0 )
             {
@@ -2134,13 +2127,14 @@
               {
                 /* we display signed hexadecimal numbers, which */
                 /* is easier to read and needs less space       */
-                num_chars = sprintf( temp + col, "%s%04lx",
-                                                 val < 0 ? "-" : "",
-                                                 val < 0 ? -val : val );
+                num_chars = strbuf_format( temp, "%s%04lx",
+                                              val < 0 ? "-" : "",
+                                              val < 0 ? -val : val );
               }
               else
-                num_chars = sprintf( temp + col, "%ld",
-                                                 val );
+              {
+                num_chars = strbuf_format( temp, "%ld", val );
+              }
 
               if ( col + num_chars >= 78 )
                 break;
@@ -2148,19 +2142,18 @@
             else
               num_chars = 0;
 
-            temp[col + num_chars] = ' ';
-            col                  += num_chars + 1;
+            strbuf_addc( temp, ' ' );
+            col += num_chars + 1;
 
             pop--;
             args--;
           }
 
           for ( n = col; n < 78; n++ )
-            temp[n] = ' ';
+            strbuf_addc( temp, ' ' );
 
-          temp[78] = '\n';
-          temp[79] = '\0';
-          printf( "%s", temp );
+          strbuf_addc( temp, '\n' );
+          printf( "%s", strbuf_value( temp ) );
         }
 
         /* First, check for empty stack and overflow */
@@ -2817,14 +2810,16 @@
 
     /* we expect that at least one interpreter version is available */
     if ( num_tt_interpreter_versions == 2 )
-      sprintf(versions, "%d and %d",
-                        tt_interpreter_versions[0],
-                        tt_interpreter_versions[1] );
+      snprintf(versions, sizeof(versions),
+               "%d and %d",
+               tt_interpreter_versions[0],
+               tt_interpreter_versions[1] );
     else
-      sprintf(versions, "%d, %d, and %d",
-                        tt_interpreter_versions[0],
-                        tt_interpreter_versions[1],
-                        tt_interpreter_versions[2] );
+      snprintf(versions, sizeof(versions),
+               "%d, %d, and %d",
+               tt_interpreter_versions[0],
+               tt_interpreter_versions[1],
+               tt_interpreter_versions[2] );
 
     fprintf( stderr,
       "\n"
-- 
2.20.1

Reply via email to