Coverage Report

Created: 2026-02-24 07:11

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/gstreamer/subprojects/glib-2.86.3/glib/gprint.c
Line
Count
Source
1
/*
2
 * Copyright © 2025 Luca Bacci
3
 *
4
 * SPDX-License-Identifier: LGPL-2.1-or-later
5
 *
6
 * This library is free software; you can redistribute it and/or
7
 * modify it under the terms of the GNU Lesser General Public
8
 * License as published by the Free Software Foundation; either
9
 * version 2.1 of the License, or (at your option) any later version.
10
 *
11
 * This library is distributed in the hope that it will be useful,
12
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14
 * Lesser General Public License for more details.
15
 *
16
 * You should have received a copy of the GNU Lesser General Public
17
 * License along with this library; if not, see <http://www.gnu.org/licenses/>.
18
 *
19
 * Author: Luca Bacci <luca.bacci@outlook.com>
20
 */
21
22
#include "config.h"
23
24
#include <stdlib.h>
25
#include <stdio.h>
26
27
#include "gtypes.h"
28
#include "gunicodeprivate.h"
29
#include "gprintprivate.h"
30
#include "gprintfint.h"
31
32
#ifndef _WIN32
33
#include <unistd.h>
34
#else
35
#include "gwin32private.h"
36
#include <io.h>
37
#endif
38
39
0
#define CHAR_IS_SAFE(wc) (!((wc < 0x20 && wc != '\t' && wc != '\n' && wc != '\r') || \
40
0
                            (wc == 0x7f) || \
41
0
                            (wc >= 0x80 && wc < 0xa0)))
42
43
char *
44
g_print_convert (const char *string,
45
                 const char *charset)
46
0
{
47
0
  if (!g_utf8_validate (string, -1, NULL))
48
0
    {
49
0
      GString *gstring = g_string_new ("[Invalid UTF-8] ");
50
0
      guchar *p;
51
52
0
      for (p = (guchar *)string; *p; p++)
53
0
        {
54
0
          if (CHAR_IS_SAFE(*p) &&
55
0
              !(*p == '\r' && *(p + 1) != '\n') &&
56
0
              *p < 0x80)
57
0
            g_string_append_c (gstring, *p);
58
0
          else
59
0
            g_string_append_printf (gstring, "\\x%02x", (guint)(guchar)*p);
60
0
        }
61
62
0
      return g_string_free (gstring, FALSE);
63
0
    }
64
0
  else
65
0
    {
66
0
      GError *err = NULL;
67
68
0
      gchar *result = g_convert_with_fallback (string, -1, charset, "UTF-8", "?", NULL, NULL, &err);
69
0
      if (result)
70
0
        return result;
71
0
      else
72
0
        {
73
          /* Not thread-safe, but doesn't matter if we print the warning twice
74
           */
75
0
          static gboolean warned = FALSE;
76
0
          if (!warned)
77
0
            {
78
0
              warned = TRUE;
79
0
              _g_fprintf (stderr, "GLib: Cannot convert message: %s\n", err->message);
80
0
            }
81
0
          g_error_free (err);
82
83
0
          return g_strdup (string);
84
0
        }
85
0
    }
86
0
}
87
88
#ifdef _WIN32
89
90
static int
91
print_console_nolock (const char *string,
92
                      FILE       *stream)
93
{
94
  HANDLE handle = (HANDLE) _get_osfhandle (_fileno (stream));
95
  size_t size = strlen (string);
96
  DWORD written = 0;
97
98
  if (size > INT_MAX)
99
    return 0;
100
101
  /* WriteFile are WriteConsole are limited to DWORD lengths,
102
   * but int and DWORD should are of the same size, so we don't
103
   * care.
104
   */
105
  G_STATIC_ASSERT (INT_MAX <= MAXDWORD);
106
107
  /* We might also check if the source string is ASCII */
108
  if (GetConsoleOutputCP () == CP_UTF8)
109
    {
110
      /* If the output codepage is UTF-8, we can just call WriteFile,
111
       * avoiding a conversion to UTF-16 (which probably will be done
112
       * by ConDrv).
113
       */
114
      /* Note: we cannot use fputs() here. When outputting to the
115
       * console, the UCRT converts the passed string to the console
116
       * charset, which is UTF-8, but interprets the string in the
117
       * LC_CTYPE charset, which can be anything.
118
       */
119
120
      if (!WriteFile (handle, string, size, &written, NULL))
121
        WIN32_API_FAILED ("WriteFile");
122
    }
123
  else
124
    {
125
      /* Convert to UTF-16 and output using WriteConsole */
126
127
      /* Note: we can't use fputws() with mode _O_U16TEXT because:
128
       *
129
       * - file descriptors cannot be locked, unlike FILE streams, so
130
       *   we cannot set a custom mode on the file descriptor.
131
       * - the fputws() implementation is not very good: it outputs codeunit
132
       *   by codeunit in a loop, so it's slow [1] and breaks UTF-16 surrogate
133
       *   pairs [2].
134
       *
135
       * [1] https://github.com/microsoft/terminal/issues/18124#issuecomment-2451987873
136
       * [2] https://developercommunity.visualstudio.com/t/wprintf-with-_setmode-_O_U16TEXT-or-_O_U/10447076
137
       */
138
139
      wchar_t buffer[1024];
140
      wchar_t *utf16 = NULL;
141
      size_t utf16_len = 0;
142
      DWORD utf16_written = 0;
143
144
      g_utf8_to_utf16_make_valid (string,
145
                                  buffer, G_N_ELEMENTS (buffer),
146
                                  &utf16, &utf16_len);
147
148
      /* The length of the UTF-16 string (in count of gunichar2) cannot be
149
       * greater than the length of the UTF-8 string (in count of bytes).
150
       * So utf16_len <= size <= INT_MAX <= MAXDWORD.
151
       */
152
      g_assert (utf16_len <= size);
153
154
      if (!WriteConsole (handle, utf16, utf16_len, &utf16_written, NULL))
155
        WIN32_API_FAILED ("WriteConsole");
156
157
      if (utf16_written < utf16_len)
158
        {
159
          written = g_utf8_to_utf16_make_valid_backtrack (string, utf16_written);
160
        }
161
      else
162
        {
163
          written = size;
164
        }
165
166
      if (utf16 != buffer)
167
        g_free (utf16);
168
    }
169
170
  if (written > INT_MAX)
171
    written = INT_MAX;
172
173
  return (int) written;
174
}
175
176
static int
177
print_console (const char *string,
178
               FILE       *stream)
179
{
180
  int ret;
181
182
  /* Locking the stream is not important, but leads
183
   * to nicer output in case of concurrent writes.
184
   */
185
  _lock_file (stream);
186
187
#if defined (_MSC_VER) || defined (_UCRT)
188
  _fflush_nolock (stream);
189
#else
190
  fflush (stream);
191
#endif
192
193
  ret = print_console_nolock (string, stream);
194
195
  _unlock_file (stream);
196
197
  return ret;
198
}
199
200
#endif /* _WIN32 */
201
202
static int
203
print_string (char const *string,
204
              FILE       *stream)
205
2
{
206
2
  size_t written = fwrite (string, 1, strlen (string), stream);
207
208
2
  return MIN (written, INT_MAX);
209
2
}
210
211
int
212
g_fputs (char const *string,
213
         FILE       *stream)
214
2
{
215
2
  int ret;
216
217
#ifdef _WIN32
218
  if (g_win32_file_stream_is_console_output (stream))
219
    {
220
      ret = print_console (string, stream);
221
222
      if (string[ret] != '\0')
223
        ret += print_string (&string[ret], stream);
224
    }
225
  else
226
    {
227
      ret = print_string (string, stream);
228
    }
229
#else
230
2
  const char *charset;
231
232
2
  if (isatty (fileno (stream)) &&
233
0
      !g_get_charset (&charset))
234
0
    {
235
0
      char *converted = g_print_convert (string, charset);
236
0
      ret = print_string (converted, stream);
237
0
      g_free (converted);
238
0
    }
239
2
  else
240
2
    {
241
2
      ret = print_string (string, stream);
242
2
    }
243
2
#endif
244
245
2
  return ret;
246
2
}