summaryrefslogtreecommitdiffstats
path: root/gl/msvc-inval.h
blob: 42ed4a057ef3aa1384c9274348dd4defdd8d9718 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
/* Invalid parameter handler for MSVC runtime libraries.
   Copyright (C) 2011-2021 Free Software Foundation, Inc.

   This program is free software; you can redistribute it and/or modify
   it under the terms of the GNU Lesser General Public License as published by
   the Free Software Foundation; either version 2.1, or (at your option)
   any later version.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU Lesser General Public License for more details.

   You should have received a copy of the GNU Lesser General Public License along
   with this program; if not, see <https://www.gnu.org/licenses/>.  */

#ifndef _MSVC_INVAL_H
#define _MSVC_INVAL_H

/* With MSVC runtime libraries with the "invalid parameter handler" concept,
   functions like fprintf(), dup2(), or close() crash when the caller passes
   an invalid argument.  But POSIX wants error codes (such as EINVAL or EBADF)
   instead.
   This file defines macros that turn such an invalid parameter notification
   into a non-local exit.  An error code can then be produced at the target
   of this exit.  You can thus write code like

     TRY_MSVC_INVAL
       {
         <Code that can trigger an invalid parameter notification
          but does not do 'return', 'break', 'continue', nor 'goto'.>
       }
     CATCH_MSVC_INVAL
       {
         <Code that handles an invalid parameter notification
          but does not do 'return', 'break', 'continue', nor 'goto'.>
       }
     DONE_MSVC_INVAL;

   This entire block expands to a single statement.

   The handling of invalid parameters can be done in three ways:

     * The default way, which is reasonable for programs (not libraries):
       AC_DEFINE([MSVC_INVALID_PARAMETER_HANDLING], [DEFAULT_HANDLING])

     * The way for libraries that make "hairy" calls (like close(-1), or
       fclose(fp) where fileno(fp) is closed, or simply getdtablesize()):
       AC_DEFINE([MSVC_INVALID_PARAMETER_HANDLING], [HAIRY_LIBRARY_HANDLING])

     * The way for libraries that make no "hairy" calls:
       AC_DEFINE([MSVC_INVALID_PARAMETER_HANDLING], [SANE_LIBRARY_HANDLING])
 */

#define DEFAULT_HANDLING       0
#define HAIRY_LIBRARY_HANDLING 1
#define SANE_LIBRARY_HANDLING  2

#if HAVE_MSVC_INVALID_PARAMETER_HANDLER \
    && !(MSVC_INVALID_PARAMETER_HANDLING == SANE_LIBRARY_HANDLING)
/* A native Windows platform with the "invalid parameter handler" concept,
   and either DEFAULT_HANDLING or HAIRY_LIBRARY_HANDLING.  */

# if MSVC_INVALID_PARAMETER_HANDLING == DEFAULT_HANDLING
/* Default handling.  */

#  ifdef __cplusplus
extern "C" {
#  endif

/* Ensure that the invalid parameter handler in installed that just returns.
   Because we assume no other part of the program installs a different
   invalid parameter handler, this solution is multithread-safe.  */
extern void gl_msvc_inval_ensure_handler (void);

#  ifdef __cplusplus
}
#  endif

#  define TRY_MSVC_INVAL \
     do                                                                        \
       {                                                                       \
         gl_msvc_inval_ensure_handler ();                                      \
         if (1)
#  define CATCH_MSVC_INVAL \
         else
#  define DONE_MSVC_INVAL \
       }                                                                       \
     while (0)

# else
/* Handling for hairy libraries.  */

#  include <excpt.h>

/* Gnulib can define its own status codes, as described in the page
   "Raising Software Exceptions" on microsoft.com
   <https://docs.microsoft.com/en-us/cpp/cpp/raising-software-exceptions>.
   Our status codes are composed of
     - 0xE0000000, mandatory for all user-defined status codes,
     - 0x474E550, a API identifier ("GNU"),
     - 0, 1, 2, ..., used to distinguish different status codes from the
       same API.  */
#  define STATUS_GNULIB_INVALID_PARAMETER (0xE0000000 + 0x474E550 + 0)

#  if defined _MSC_VER
/* A compiler that supports __try/__except, as described in the page
   "try-except statement" on microsoft.com
   <https://docs.microsoft.com/en-us/cpp/cpp/try-except-statement>.
   With __try/__except, we can use the multithread-safe exception handling.  */

#   ifdef __cplusplus
extern "C" {
#   endif

/* Ensure that the invalid parameter handler in installed that raises a
   software exception with code STATUS_GNULIB_INVALID_PARAMETER.
   Because we assume no other part of the program installs a different
   invalid parameter handler, this solution is multithread-safe.  */
extern void gl_msvc_inval_ensure_handler (void);

#   ifdef __cplusplus
}
#   endif

#   define TRY_MSVC_INVAL \
      do                                                                       \
        {                                                                      \
          gl_msvc_inval_ensure_handler ();                                     \
          __try
#   define CATCH_MSVC_INVAL \
          __except (GetExceptionCode () == STATUS_GNULIB_INVALID_PARAMETER     \
                    ? EXCEPTION_EXECUTE_HANDLER                                \
                    : EXCEPTION_CONTINUE_SEARCH)
#   define DONE_MSVC_INVAL \
        }                                                                      \
      while (0)

#  else
/* Any compiler.
   We can only use setjmp/longjmp.  */

#   include <setjmp.h>

#   ifdef __cplusplus
extern "C" {
#   endif

struct gl_msvc_inval_per_thread
{
  /* The restart that will resume execution at the code between
     CATCH_MSVC_INVAL and DONE_MSVC_INVAL.  It is enabled only between
     TRY_MSVC_INVAL and CATCH_MSVC_INVAL.  */
  jmp_buf restart;

  /* Tells whether the contents of restart is valid.  */
  int restart_valid;
};

/* Ensure that the invalid parameter handler in installed that passes
   control to the gl_msvc_inval_restart if it is valid, or raises a
   software exception with code STATUS_GNULIB_INVALID_PARAMETER otherwise.
   Because we assume no other part of the program installs a different
   invalid parameter handler, this solution is multithread-safe.  */
extern void gl_msvc_inval_ensure_handler (void);

/* Return a pointer to the per-thread data for the current thread.  */
extern struct gl_msvc_inval_per_thread *gl_msvc_inval_current (void);

#   ifdef __cplusplus
}
#   endif

#   define TRY_MSVC_INVAL \
      do                                                                       \
        {                                                                      \
          struct gl_msvc_inval_per_thread *msvc_inval_current;                 \
          gl_msvc_inval_ensure_handler ();                                     \
          msvc_inval_current = gl_msvc_inval_current ();                       \
          /* First, initialize gl_msvc_inval_restart.  */                      \
          if (setjmp (msvc_inval_current->restart) == 0)                       \
            {                                                                  \
              /* Then, mark it as valid.  */                                   \
              msvc_inval_current->restart_valid = 1;
#   define CATCH_MSVC_INVAL \
              /* Execution completed.                                          \
                 Mark gl_msvc_inval_restart as invalid.  */                    \
              msvc_inval_current->restart_valid = 0;                           \
            }                                                                  \
          else                                                                 \
            {                                                                  \
              /* Execution triggered an invalid parameter notification.        \
                 Mark gl_msvc_inval_restart as invalid.  */                    \
              msvc_inval_current->restart_valid = 0;
#   define DONE_MSVC_INVAL \
            }                                                                  \
        }                                                                      \
      while (0)

#  endif

# endif

#else
/* A platform that does not need to the invalid parameter handler,
   or when SANE_LIBRARY_HANDLING is desired.  */

/* The braces here avoid GCC warnings like
   "warning: suggest explicit braces to avoid ambiguous 'else'".  */
# define TRY_MSVC_INVAL \
    do                                                                         \
      {                                                                        \
        if (1)
# define CATCH_MSVC_INVAL \
        else
# define DONE_MSVC_INVAL \
      }                                                                        \
    while (0)

#endif

#endif /* _MSVC_INVAL_H */