Bug Summary

File:src/usr.bin/mandoc/roff_escape.c
Warning:line 535, column 7
2nd function call argument is an uninitialized value

Annotated Source Code

Press '?' to see keyboard shortcuts

clang -cc1 -cc1 -triple amd64-unknown-openbsd7.4 -analyze -disable-free -clear-ast-before-backend -disable-llvm-verifier -discard-value-names -main-file-name roff_escape.c -analyzer-checker=core -analyzer-checker=apiModeling -analyzer-checker=unix -analyzer-checker=deadcode -analyzer-checker=security.insecureAPI.UncheckedReturn -analyzer-checker=security.insecureAPI.getpw -analyzer-checker=security.insecureAPI.gets -analyzer-checker=security.insecureAPI.mktemp -analyzer-checker=security.insecureAPI.mkstemp -analyzer-checker=security.insecureAPI.vfork -analyzer-checker=nullability.NullPassedToNonnull -analyzer-checker=nullability.NullReturnedFromNonnull -analyzer-output plist -w -setup-static-analyzer -mrelocation-model pic -pic-level 1 -pic-is-pie -mframe-pointer=all -relaxed-aliasing -ffp-contract=on -fno-rounding-math -mconstructor-aliases -funwind-tables=2 -target-cpu x86-64 -target-feature +retpoline-indirect-calls -target-feature +retpoline-indirect-branches -tune-cpu generic -debugger-tuning=gdb -fcoverage-compilation-dir=/usr/src/usr.bin/mandoc/obj -resource-dir /usr/local/llvm16/lib/clang/16 -internal-isystem /usr/local/llvm16/lib/clang/16/include -internal-externc-isystem /usr/include -O2 -Wno-unused-parameter -fdebug-compilation-dir=/usr/src/usr.bin/mandoc/obj -ferror-limit 19 -fwrapv -D_RET_PROTECTOR -ret-protector -fcf-protection=branch -fno-jump-tables -fgnuc-version=4.2.1 -vectorize-loops -vectorize-slp -fno-builtin-malloc -fno-builtin-calloc -fno-builtin-realloc -fno-builtin-valloc -fno-builtin-free -fno-builtin-strdup -fno-builtin-strndup -analyzer-output=html -faddrsig -D__GCC_HAVE_DWARF2_CFI_ASM=1 -o /home/ben/Projects/scan/2024-01-11-140451-98009-1 -x c /usr/src/usr.bin/mandoc/roff_escape.c
1/* $OpenBSD: roff_escape.c,v 1.14 2022/06/08 13:08:00 schwarze Exp $ */
2/*
3 * Copyright (c) 2011, 2012, 2013, 2014, 2015, 2017, 2018, 2020, 2022
4 * Ingo Schwarze <schwarze@openbsd.org>
5 * Copyright (c) 2010, 2011 Kristaps Dzonsons <kristaps@bsd.lv>
6 *
7 * Permission to use, copy, modify, and distribute this software for any
8 * purpose with or without fee is hereby granted, provided that the above
9 * copyright notice and this permission notice appear in all copies.
10 *
11 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHORS DISCLAIM ALL WARRANTIES
12 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
13 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR
14 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
15 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
16 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
17 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
18 *
19 * Parser for roff(7) escape sequences.
20 * To be used by all mandoc(1) parsers and formatters.
21 */
22#include <assert.h>
23#include <ctype.h>
24#include <limits.h>
25#include <stdio.h>
26#include <string.h>
27
28#include "mandoc.h"
29#include "roff.h"
30#include "roff_int.h"
31
32/*
33 * Traditional escape sequence interpreter for general use
34 * including in high-level formatters. This function does not issue
35 * diagnostics and is not usable for expansion in the roff(7) parser.
36 * It is documented in the mandoc_escape(3) manual page.
37 */
38enum mandoc_esc
39mandoc_escape(const char **rendarg, const char **rarg, int *rargl)
40{
41 int iarg, iendarg, iend;
42 enum mandoc_esc rval;
43
44 rval = roff_escape(--*rendarg, 0, 0,
45 NULL((void *)0), NULL((void *)0), &iarg, &iendarg, &iend);
46 assert(rval != ESCAPE_EXPAND)((rval != ESCAPE_EXPAND) ? (void)0 : __assert2("/usr/src/usr.bin/mandoc/roff_escape.c"
, 46, __func__, "rval != ESCAPE_EXPAND"))
;
47 if (rarg != NULL((void *)0))
48 *rarg = *rendarg + iarg;
49 if (rargl != NULL((void *)0))
50 *rargl = iendarg - iarg;
51 *rendarg += iend;
52 return rval;
53}
54
55/*
56 * Full-featured escape sequence parser.
57 * If it encounters a nested escape sequence that requires expansion
58 * by the parser and re-parsing, the positions of that inner escape
59 * sequence are returned in *resc ... *rend.
60 * Otherwise, *resc is set to aesc and the positions of the escape
61 * sequence starting at aesc are returned.
62 * Diagnostic messages are generated if and only if ln != 0,
63 * that is, if and only if called by roff_expand().
64 */
65enum mandoc_esc
66roff_escape(const char *buf, const int ln, const int aesc,
67 int *resc, int *rnam, int *rarg, int *rendarg, int *rend)
68{
69 int iesc; /* index of leading escape char */
70 int inam; /* index of escape name */
71 int iarg; /* index beginning the argument */
72 int iendarg; /* index right after the argument */
73 int iend; /* index right after the sequence */
74 int sesc, snam, sarg, sendarg, send; /* for sub-escape */
75 int escterm; /* whether term is escaped */
76 int maxl; /* expected length of the argument */
77 int argl; /* actual length of the argument */
1
'argl' declared without an initial value
78 int c, i; /* for \[char...] parsing */
79 int valid_A; /* for \A parsing */
80 enum mandoc_esc rval; /* return value */
81 enum mandoc_esc stype; /* for sub-escape */
82 enum mandocerr err; /* diagnostic code */
83 char term; /* byte terminating the argument */
84
85 /*
86 * Treat "\E" just like "\";
87 * it only makes a difference in copy mode.
88 */
89
90 iesc = inam = aesc;
91 do {
2
Loop condition is false. Exiting loop
92 inam++;
93 } while (buf[inam] == 'E');
94
95 /*
96 * Sort the following cases first by syntax category,
97 * then by escape sequence type, and finally by ASCII code.
98 */
99
100 iarg = iendarg = iend = inam + 1;
101 maxl = INT_MAX0x7fffffff;
102 term = '\0';
103 err = MANDOCERR_OK;
104 switch (buf[inam]) {
3
Control jumps to 'case 67:' at line 210
105
106 /* Escape sequences taking no arguments at all. */
107
108 case '!':
109 case '?':
110 case 'r':
111 rval = ESCAPE_UNSUPP;
112 goto out;
113
114 case '%':
115 case '&':
116 case ')':
117 case ',':
118 case '/':
119 case '^':
120 case 'a':
121 case 'd':
122 case 't':
123 case 'u':
124 case '{':
125 case '|':
126 case '}':
127 rval = ESCAPE_IGNORE;
128 goto out;
129
130 case '\0':
131 iendarg = --iend;
132 /* FALLTHROUGH */
133 case '.':
134 case '\\':
135 default:
136 iarg--;
137 rval = ESCAPE_UNDEF;
138 goto out;
139
140 case ' ':
141 case '\'':
142 case '-':
143 case '0':
144 case ':':
145 case '_':
146 case '`':
147 case 'e':
148 case '~':
149 iarg--;
150 argl = 1;
151 rval = ESCAPE_SPECIAL;
152 goto out;
153 case 'p':
154 rval = ESCAPE_BREAK;
155 goto out;
156 case 'c':
157 rval = ESCAPE_NOSPACE;
158 goto out;
159 case 'z':
160 rval = ESCAPE_SKIPCHAR;
161 goto out;
162
163 /* Standard argument format. */
164
165 case '$':
166 case '*':
167 case 'V':
168 case 'g':
169 case 'n':
170 rval = ESCAPE_EXPAND;
171 break;
172 case 'F':
173 case 'M':
174 case 'O':
175 case 'Y':
176 case 'k':
177 case 'm':
178 rval = ESCAPE_IGNORE;
179 break;
180 case '(':
181 case '[':
182 rval = ESCAPE_SPECIAL;
183 iendarg = iend = --iarg;
184 break;
185 case 'f':
186 rval = ESCAPE_FONT;
187 break;
188
189 /* Quoted arguments */
190
191 case 'A':
192 case 'B':
193 case 'w':
194 rval = ESCAPE_EXPAND;
195 term = '\b';
196 break;
197 case 'D':
198 case 'H':
199 case 'L':
200 case 'R':
201 case 'S':
202 case 'X':
203 case 'Z':
204 case 'b':
205 case 'v':
206 case 'x':
207 rval = ESCAPE_IGNORE;
208 term = '\b';
209 break;
210 case 'C':
211 rval = ESCAPE_SPECIAL;
212 term = '\b';
213 break;
214 case 'N':
215 rval = ESCAPE_NUMBERED;
216 term = '\b';
217 break;
218 case 'h':
219 rval = ESCAPE_HORIZ;
220 term = '\b';
221 break;
222 case 'l':
223 rval = ESCAPE_HLINE;
224 term = '\b';
225 break;
226 case 'o':
227 rval = ESCAPE_OVERSTRIKE;
228 term = '\b';
229 break;
230
231 /* Sizes support both forms, with additional peculiarities. */
232
233 case 's':
234 rval = ESCAPE_IGNORE;
235 if (buf[iarg] == '+' || buf[iarg] == '-'||
236 buf[iarg] == ASCII_HYPH28)
237 iarg++;
238 switch (buf[iarg]) {
239 case '(':
240 maxl = 2;
241 iarg++;
242 break;
243 case '[':
244 term = ']';
245 iarg++;
246 break;
247 case '\'':
248 term = '\'';
249 iarg++;
250 break;
251 case '1':
252 case '2':
253 case '3':
254 if (buf[iarg - 1] == 's' &&
255 isdigit((unsigned char)buf[iarg + 1])) {
256 maxl = 2;
257 break;
258 }
259 /* FALLTHROUGH */
260 default:
261 maxl = 1;
262 break;
263 }
264 iendarg = iend = iarg;
265 }
266
267 /* Decide how to end the argument. */
268
269 escterm = 0;
4
Execution continues on line 269
270 stype = ESCAPE_EXPAND;
271 if ((term == '\b' || (term == '\0' && maxl == INT_MAX0x7fffffff)) &&
6
Taking true branch
272 buf[iarg] == buf[iesc]) {
5
Assuming the condition is true
273 stype = roff_escape(buf, ln, iendarg,
274 &sesc, &snam, &sarg, &sendarg, &send);
275 if (stype == ESCAPE_EXPAND)
7
Assuming 'stype' is not equal to ESCAPE_EXPAND
8
Taking false branch
276 goto out_sub;
277 }
278
279 if (term == '\b') {
9
Taking true branch
280 if (stype == ESCAPE_UNDEF)
10
Assuming 'stype' is not equal to ESCAPE_UNDEF
281 iarg++;
282 if (stype
10.1
'stype' is not equal to ESCAPE_EXPAND
!= ESCAPE_EXPAND && stype
10.2
'stype' is not equal to ESCAPE_UNDEF
!= ESCAPE_UNDEF) {
283 if (strchr("BHLRSNhlvx", buf[inam]) != NULL((void *)0) &&
11
Assuming the condition is true
13
Taking true branch
284 strchr(" ,.0DLOXYZ^abdhlortuvx|~",
12
Assuming the condition is true
285 buf[snam]) != NULL((void *)0)) {
286 err = MANDOCERR_ESC_DELIM;
287 iend = send;
288 iarg = iendarg = sesc;
289 goto out;
14
Control jumps to line 508
290 }
291 escterm = 1;
292 iarg = send;
293 term = buf[snam];
294 } else if (strchr("BDHLRSvxNhl", buf[inam]) != NULL((void *)0) &&
295 strchr(" %&()*+-./0123456789:<=>", buf[iarg]) != NULL((void *)0)) {
296 err = MANDOCERR_ESC_DELIM;
297 if (rval != ESCAPE_EXPAND)
298 rval = ESCAPE_ERROR;
299 if (buf[inam] != 'D') {
300 iendarg = iend = iarg + 1;
301 goto out;
302 }
303 }
304 if (term == '\b')
305 term = buf[iarg++];
306 } else if (term == '\0' && maxl == INT_MAX0x7fffffff) {
307 if (buf[inam] == 'n' && (buf[iarg] == '+' || buf[iarg] == '-'))
308 iarg++;
309 switch (buf[iarg]) {
310 case '(':
311 maxl = 2;
312 iarg++;
313 break;
314 case '[':
315 if (buf[++iarg] == ' ') {
316 iendarg = iend = iarg + 1;
317 err = MANDOCERR_ESC_ARG;
318 rval = ESCAPE_ERROR;
319 goto out;
320 }
321 term = ']';
322 break;
323 default:
324 maxl = 1;
325 break;
326 }
327 }
328
329 /* Advance to the end of the argument. */
330
331 valid_A = 1;
332 iendarg = iarg;
333 while (maxl > 0) {
334 if (buf[iendarg] == '\0') {
335 err = MANDOCERR_ESC_INCOMPLETE;
336 if (rval != ESCAPE_EXPAND &&
337 rval != ESCAPE_OVERSTRIKE)
338 rval = ESCAPE_ERROR;
339 /* Usually, ignore an incomplete argument. */
340 if (strchr("Aow", buf[inam]) == NULL((void *)0))
341 iendarg = iarg;
342 break;
343 }
344 if (escterm == 0 && buf[iendarg] == term) {
345 iend = iendarg + 1;
346 break;
347 }
348 if (buf[iendarg] == buf[iesc]) {
349 stype = roff_escape(buf, ln, iendarg,
350 &sesc, &snam, &sarg, &sendarg, &send);
351 if (stype == ESCAPE_EXPAND)
352 goto out_sub;
353 iend = send;
354 if (escterm == 1 &&
355 (buf[snam] == term || buf[inam] == 'N'))
356 break;
357 if (stype != ESCAPE_UNDEF)
358 valid_A = 0;
359 iendarg = send;
360 } else if (buf[inam] == 'N' &&
361 isdigit((unsigned char)buf[iendarg]) == 0) {
362 iend = iendarg + 1;
363 break;
364 } else {
365 if (buf[iendarg] == ' ' || buf[iendarg] == '\t')
366 valid_A = 0;
367 if (maxl != INT_MAX0x7fffffff)
368 maxl--;
369 iend = ++iendarg;
370 }
371 }
372
373 /* Post-process depending on the content of the argument. */
374
375 argl = iendarg - iarg;
376 switch (buf[inam]) {
377 case '*':
378 if (resc == NULL((void *)0) && argl == 2 &&
379 buf[iarg] == '.' && buf[iarg + 1] == 'T')
380 rval = ESCAPE_DEVICE;
381 break;
382 case 'A':
383 if (valid_A == 0)
384 iendarg = iarg;
385 break;
386 case 'O':
387 switch (buf[iarg]) {
388 case '0':
389 rval = ESCAPE_UNSUPP;
390 break;
391 case '1':
392 case '2':
393 case '3':
394 case '4':
395 if (argl == 1)
396 rval = ESCAPE_IGNORE;
397 else {
398 err = MANDOCERR_ESC_ARG;
399 rval = ESCAPE_ERROR;
400 }
401 break;
402 case '5':
403 if (buf[iarg - 1] == '[')
404 rval = ESCAPE_UNSUPP;
405 else {
406 err = MANDOCERR_ESC_ARG;
407 rval = ESCAPE_ERROR;
408 }
409 break;
410 default:
411 err = MANDOCERR_ESC_ARG;
412 rval = ESCAPE_ERROR;
413 break;
414 }
415 break;
416 default:
417 break;
418 }
419
420 switch (rval) {
421 case ESCAPE_FONT:
422 rval = mandoc_font(buf + iarg, argl);
423 if (rval == ESCAPE_ERROR)
424 err = MANDOCERR_ESC_ARG;
425 break;
426
427 case ESCAPE_SPECIAL:
428 if (argl == 0) {
429 err = MANDOCERR_ESC_BADCHAR;
430 rval = ESCAPE_ERROR;
431 break;
432 }
433
434 /*
435 * The file chars.c only provides one common list of
436 * character names, but \[-] == \- is the only one of
437 * the characters with one-byte names that allows
438 * enclosing the name in brackets.
439 */
440
441 if (term != '\0' && argl == 1 && buf[iarg] != '-') {
442 err = MANDOCERR_ESC_BADCHAR;
443 rval = ESCAPE_ERROR;
444 break;
445 }
446
447 /* Treat \[char...] as an alias for \N'...'. */
448
449 if (buf[iarg] == 'c') {
450 if (argl < 6 || argl > 7 ||
451 strncmp(buf + iarg, "char", 4) != 0 ||
452 (int)strspn(buf + iarg + 4, "0123456789")
453 + 4 < argl)
454 break;
455 c = 0;
456 for (i = iarg; i < iendarg; i++)
457 c = 10 * c + (buf[i] - '0');
458 if (c < 0x21 || (c > 0x7e && c < 0xa0) || c > 0xff) {
459 err = MANDOCERR_ESC_BADCHAR;
460 break;
461 }
462 iarg += 4;
463 rval = ESCAPE_NUMBERED;
464 break;
465 }
466
467 /*
468 * Unicode escapes are defined in groff as \[u0000]
469 * to \[u10FFFF], where the contained value must be
470 * a valid Unicode codepoint. Here, however, only
471 * check the length and range.
472 */
473
474 if (buf[iarg] != 'u' || argl < 5 || argl > 7)
475 break;
476 if (argl == 7 &&
477 (buf[iarg + 1] != '1' || buf[iarg + 2] != '0')) {
478 err = MANDOCERR_ESC_BADCHAR;
479 break;
480 }
481 if (argl == 6 && buf[iarg + 1] == '0') {
482 err = MANDOCERR_ESC_BADCHAR;
483 break;
484 }
485 if (argl == 5 && buf[iarg + 1] == 'D' &&
486 strchr("89ABCDEF", buf[iarg + 2]) != NULL((void *)0)) {
487 err = MANDOCERR_ESC_BADCHAR;
488 break;
489 }
490 if ((int)strspn(buf + iarg + 1, "0123456789ABCDEFabcdef")
491 + 1 == argl)
492 rval = ESCAPE_UNICODE;
493 break;
494 default:
495 break;
496 }
497 goto out;
498
499out_sub:
500 iesc = sesc;
501 inam = snam;
502 iarg = sarg;
503 iendarg = sendarg;
504 iend = send;
505 rval = ESCAPE_EXPAND;
506
507out:
508 if (resc != NULL((void *)0))
15
Assuming 'resc' is equal to NULL
16
Taking false branch
509 *resc = iesc;
510 if (rnam != NULL((void *)0))
17
Assuming 'rnam' is equal to NULL
18
Taking false branch
511 *rnam = inam;
512 if (rarg != NULL((void *)0))
19
Assuming 'rarg' is equal to NULL
20
Taking false branch
513 *rarg = iarg;
514 if (rendarg != NULL((void *)0))
21
Assuming 'rendarg' is equal to NULL
22
Taking false branch
515 *rendarg = iendarg;
516 if (rend != NULL((void *)0))
23
Assuming 'rend' is equal to NULL
24
Taking false branch
517 *rend = iend;
518 if (ln == 0)
25
Assuming 'ln' is not equal to 0
26
Taking false branch
519 return rval;
520
521 /*
522 * Diagnostic messages are only issued when called
523 * from the parser, not when called from the formatters.
524 */
525
526 switch (rval) {
27
Control jumps to 'case ESCAPE_SPECIAL:' at line 534
527 case ESCAPE_UNSUPP:
528 err = MANDOCERR_ESC_UNSUPP;
529 break;
530 case ESCAPE_UNDEF:
531 if (buf[inam] != '\\' && buf[inam] != '.')
532 err = MANDOCERR_ESC_UNDEF;
533 break;
534 case ESCAPE_SPECIAL:
535 if (mchars_spec2cp(buf + iarg, argl) >= 0)
28
2nd function call argument is an uninitialized value
536 err = MANDOCERR_OK;
537 else if (err == MANDOCERR_OK)
538 err = MANDOCERR_ESC_UNKCHAR;
539 break;
540 default:
541 break;
542 }
543 if (err != MANDOCERR_OK)
544 mandoc_msg(err, ln, iesc, "%.*s", iend - iesc, buf + iesc);
545 return rval;
546}