GCC Code Coverage Report | |||||||||||||||||||||
|
|||||||||||||||||||||
Line | Branch | Exec | Source |
1 |
/* $OpenBSD: mdoc.c,v 1.157 2017/08/11 16:55:10 schwarze Exp $ */ |
||
2 |
/* |
||
3 |
* Copyright (c) 2008, 2009, 2010, 2011 Kristaps Dzonsons <kristaps@bsd.lv> |
||
4 |
* Copyright (c) 2010, 2012-2017 Ingo Schwarze <schwarze@openbsd.org> |
||
5 |
* |
||
6 |
* Permission to use, copy, modify, and distribute this software for any |
||
7 |
* purpose with or without fee is hereby granted, provided that the above |
||
8 |
* copyright notice and this permission notice appear in all copies. |
||
9 |
* |
||
10 |
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHORS DISCLAIM ALL WARRANTIES |
||
11 |
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF |
||
12 |
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR |
||
13 |
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES |
||
14 |
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN |
||
15 |
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF |
||
16 |
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. |
||
17 |
*/ |
||
18 |
#include <sys/types.h> |
||
19 |
|||
20 |
#include <assert.h> |
||
21 |
#include <ctype.h> |
||
22 |
#include <stdarg.h> |
||
23 |
#include <stdio.h> |
||
24 |
#include <stdlib.h> |
||
25 |
#include <string.h> |
||
26 |
#include <time.h> |
||
27 |
|||
28 |
#include "mandoc_aux.h" |
||
29 |
#include "mandoc.h" |
||
30 |
#include "roff.h" |
||
31 |
#include "mdoc.h" |
||
32 |
#include "libmandoc.h" |
||
33 |
#include "roff_int.h" |
||
34 |
#include "libmdoc.h" |
||
35 |
|||
36 |
const char *const __mdoc_argnames[MDOC_ARG_MAX] = { |
||
37 |
"split", "nosplit", "ragged", |
||
38 |
"unfilled", "literal", "file", |
||
39 |
"offset", "bullet", "dash", |
||
40 |
"hyphen", "item", "enum", |
||
41 |
"tag", "diag", "hang", |
||
42 |
"ohang", "inset", "column", |
||
43 |
"width", "compact", "std", |
||
44 |
"filled", "words", "emphasis", |
||
45 |
"symbolic", "nested", "centered" |
||
46 |
}; |
||
47 |
const char * const *mdoc_argnames = __mdoc_argnames; |
||
48 |
|||
49 |
static int mdoc_ptext(struct roff_man *, int, char *, int); |
||
50 |
static int mdoc_pmacro(struct roff_man *, int, char *, int); |
||
51 |
|||
52 |
|||
53 |
/* |
||
54 |
* Main parse routine. Parses a single line -- really just hands off to |
||
55 |
* the macro (mdoc_pmacro()) or text parser (mdoc_ptext()). |
||
56 |
*/ |
||
57 |
int |
||
58 |
mdoc_parseln(struct roff_man *mdoc, int ln, char *buf, int offs) |
||
59 |
{ |
||
60 |
|||
61 |
✓✓✓✓ |
530749 |
if (mdoc->last->type != ROFFT_EQN || ln > mdoc->last->line) |
62 |
530558 |
mdoc->flags |= MDOC_NEWLINE; |
|
63 |
|||
64 |
/* |
||
65 |
* Let the roff nS register switch SYNOPSIS mode early, |
||
66 |
* such that the parser knows at all times |
||
67 |
* whether this mode is on or off. |
||
68 |
* Note that this mode is also switched by the Sh macro. |
||
69 |
*/ |
||
70 |
1061128 |
if (roff_getreg(mdoc->roff, "nS")) |
|
71 |
530564 |
mdoc->flags |= MDOC_SYNOPSIS; |
|
72 |
else |
||
73 |
530564 |
mdoc->flags &= ~MDOC_SYNOPSIS; |
|
74 |
|||
75 |
✓✓ | 1591692 |
return roff_getcontrol(mdoc->roff, buf, &offs) ? |
76 |
301259 |
mdoc_pmacro(mdoc, ln, buf, offs) : |
|
77 |
229305 |
mdoc_ptext(mdoc, ln, buf, offs); |
|
78 |
} |
||
79 |
|||
80 |
void |
||
81 |
mdoc_macro(MACRO_PROT_ARGS) |
||
82 |
{ |
||
83 |
✗✓ | 733372 |
assert(tok >= MDOC_Dd && tok < MDOC_MAX); |
84 |
366686 |
(*mdoc_macros[tok].fp)(mdoc, tok, line, ppos, pos, buf); |
|
85 |
366686 |
} |
|
86 |
|||
87 |
void |
||
88 |
mdoc_tail_alloc(struct roff_man *mdoc, int line, int pos, enum roff_tok tok) |
||
89 |
{ |
||
90 |
struct roff_node *p; |
||
91 |
|||
92 |
326 |
p = roff_node_alloc(mdoc, line, pos, ROFFT_TAIL, tok); |
|
93 |
163 |
roff_node_append(mdoc, p); |
|
94 |
163 |
mdoc->next = ROFF_NEXT_CHILD; |
|
95 |
163 |
} |
|
96 |
|||
97 |
struct roff_node * |
||
98 |
mdoc_endbody_alloc(struct roff_man *mdoc, int line, int pos, |
||
99 |
enum roff_tok tok, struct roff_node *body) |
||
100 |
{ |
||
101 |
struct roff_node *p; |
||
102 |
|||
103 |
1098 |
body->flags |= NODE_ENDED; |
|
104 |
549 |
body->parent->flags |= NODE_ENDED; |
|
105 |
549 |
p = roff_node_alloc(mdoc, line, pos, ROFFT_BODY, tok); |
|
106 |
549 |
p->body = body; |
|
107 |
549 |
p->norm = body->norm; |
|
108 |
549 |
p->end = ENDBODY_SPACE; |
|
109 |
549 |
roff_node_append(mdoc, p); |
|
110 |
549 |
mdoc->next = ROFF_NEXT_SIBLING; |
|
111 |
549 |
return p; |
|
112 |
} |
||
113 |
|||
114 |
struct roff_node * |
||
115 |
mdoc_block_alloc(struct roff_man *mdoc, int line, int pos, |
||
116 |
enum roff_tok tok, struct mdoc_arg *args) |
||
117 |
{ |
||
118 |
struct roff_node *p; |
||
119 |
|||
120 |
209198 |
p = roff_node_alloc(mdoc, line, pos, ROFFT_BLOCK, tok); |
|
121 |
104599 |
p->args = args; |
|
122 |
✓✓ | 104599 |
if (p->args) |
123 |
10524 |
(args->refcnt)++; |
|
124 |
|||
125 |
✗✗✗✗ ✓✓ |
115606 |
switch (tok) { |
126 |
case MDOC_Bd: |
||
127 |
case MDOC_Bf: |
||
128 |
case MDOC_Bl: |
||
129 |
case MDOC_En: |
||
130 |
case MDOC_Rs: |
||
131 |
11007 |
p->norm = mandoc_calloc(1, sizeof(union mdoc_data)); |
|
132 |
11007 |
break; |
|
133 |
default: |
||
134 |
break; |
||
135 |
} |
||
136 |
104599 |
roff_node_append(mdoc, p); |
|
137 |
104599 |
mdoc->next = ROFF_NEXT_CHILD; |
|
138 |
104599 |
return p; |
|
139 |
} |
||
140 |
|||
141 |
void |
||
142 |
mdoc_elem_alloc(struct roff_man *mdoc, int line, int pos, |
||
143 |
enum roff_tok tok, struct mdoc_arg *args) |
||
144 |
{ |
||
145 |
struct roff_node *p; |
||
146 |
|||
147 |
469836 |
p = roff_node_alloc(mdoc, line, pos, ROFFT_ELEM, tok); |
|
148 |
234918 |
p->args = args; |
|
149 |
✓✓ | 234918 |
if (p->args) |
150 |
714 |
(args->refcnt)++; |
|
151 |
|||
152 |
✓✓ | 234918 |
switch (tok) { |
153 |
case MDOC_An: |
||
154 |
1944 |
p->norm = mandoc_calloc(1, sizeof(union mdoc_data)); |
|
155 |
1944 |
break; |
|
156 |
default: |
||
157 |
break; |
||
158 |
} |
||
159 |
234918 |
roff_node_append(mdoc, p); |
|
160 |
234918 |
mdoc->next = ROFF_NEXT_CHILD; |
|
161 |
234918 |
} |
|
162 |
|||
163 |
void |
||
164 |
mdoc_node_relink(struct roff_man *mdoc, struct roff_node *p) |
||
165 |
{ |
||
166 |
|||
167 |
1358 |
roff_node_unlink(mdoc, p); |
|
168 |
679 |
p->prev = p->next = NULL; |
|
169 |
679 |
roff_node_append(mdoc, p); |
|
170 |
679 |
} |
|
171 |
|||
172 |
/* |
||
173 |
* Parse free-form text, that is, a line that does not begin with the |
||
174 |
* control character. |
||
175 |
*/ |
||
176 |
static int |
||
177 |
mdoc_ptext(struct roff_man *mdoc, int line, char *buf, int offs) |
||
178 |
{ |
||
179 |
struct roff_node *n; |
||
180 |
const char *cp, *sp; |
||
181 |
char *c, *ws, *end; |
||
182 |
|||
183 |
229305 |
n = mdoc->last; |
|
184 |
|||
185 |
/* |
||
186 |
* If a column list contains plain text, assume an implicit item |
||
187 |
* macro. This can happen one or more times at the beginning |
||
188 |
* of such a list, intermixed with non-It mdoc macros and with |
||
189 |
* nodes generated on the roff level, for example by tbl. |
||
190 |
*/ |
||
191 |
|||
192 |
✓✓✓✓ ✓✓ |
230040 |
if ((n->tok == MDOC_Bl && n->type == ROFFT_BODY && |
193 |
✓✓✓✓ |
222 |
n->end == ENDBODY_NOT && n->norm->Bl.type == LIST_column) || |
194 |
✓✗✓✓ |
458598 |
(n->parent != NULL && n->parent->tok == MDOC_Bl && |
195 |
99 |
n->parent->norm->Bl.type == LIST_column)) { |
|
196 |
12 |
mdoc->flags |= MDOC_FREECOL; |
|
197 |
12 |
mdoc_macro(mdoc, MDOC_It, line, offs, &offs, buf); |
|
198 |
12 |
return 1; |
|
199 |
} |
||
200 |
|||
201 |
/* |
||
202 |
* Search for the beginning of unescaped trailing whitespace (ws) |
||
203 |
* and for the first character not to be output (end). |
||
204 |
*/ |
||
205 |
|||
206 |
/* FIXME: replace with strcspn(). */ |
||
207 |
ws = NULL; |
||
208 |
✓✓ | 15357022 |
for (c = end = buf + offs; *c; c++) { |
209 |
✓✓✓✓ |
7453722 |
switch (*c) { |
210 |
case ' ': |
||
211 |
✓✓ | 1112525 |
if (NULL == ws) |
212 |
1084453 |
ws = c; |
|
213 |
continue; |
||
214 |
case '\t': |
||
215 |
/* |
||
216 |
* Always warn about trailing tabs, |
||
217 |
* even outside literal context, |
||
218 |
* where they should be put on the next line. |
||
219 |
*/ |
||
220 |
✓✓ | 10343 |
if (NULL == ws) |
221 |
8080 |
ws = c; |
|
222 |
/* |
||
223 |
* Strip trailing tabs in literal context only; |
||
224 |
* outside, they affect the next line. |
||
225 |
*/ |
||
226 |
✓✓ | 10343 |
if (MDOC_LITERAL & mdoc->flags) |
227 |
continue; |
||
228 |
break; |
||
229 |
case '\\': |
||
230 |
/* Skip the escaped character, too, if any. */ |
||
231 |
✓✗ | 4504 |
if (c[1]) |
232 |
4504 |
c++; |
|
233 |
/* FALLTHROUGH */ |
||
234 |
default: |
||
235 |
ws = NULL; |
||
236 |
6326350 |
break; |
|
237 |
} |
||
238 |
6326656 |
end = c + 1; |
|
239 |
6326656 |
} |
|
240 |
229293 |
*end = '\0'; |
|
241 |
|||
242 |
✓✓ | 229293 |
if (ws) |
243 |
240 |
mandoc_msg(MANDOCERR_SPACE_EOL, mdoc->parse, |
|
244 |
120 |
line, (int)(ws-buf), NULL); |
|
245 |
|||
246 |
/* |
||
247 |
* Blank lines are allowed in no-fill mode |
||
248 |
* and cancel preceding \c, |
||
249 |
* but add a single vertical space elsewhere. |
||
250 |
*/ |
||
251 |
|||
252 |
✓✓✓✓ |
230644 |
if (buf[offs] == '\0' && ! (mdoc->flags & MDOC_LITERAL)) { |
253 |
✓✓ | 84 |
switch (mdoc->last->type) { |
254 |
case ROFFT_TEXT: |
||
255 |
48 |
sp = mdoc->last->string; |
|
256 |
48 |
cp = end = strchr(sp, '\0') - 2; |
|
257 |
✓✗✓✓ ✓✗ |
99 |
if (cp < sp || cp[0] != '\\' || cp[1] != 'c') |
258 |
break; |
||
259 |
✓✗✗✓ |
9 |
while (cp > sp && cp[-1] == '\\') |
260 |
cp--; |
||
261 |
✓✗ | 3 |
if ((end - cp) % 2) |
262 |
break; |
||
263 |
3 |
*end = '\0'; |
|
264 |
3 |
return 1; |
|
265 |
default: |
||
266 |
break; |
||
267 |
} |
||
268 |
162 |
mandoc_msg(MANDOCERR_FI_BLANK, mdoc->parse, |
|
269 |
81 |
line, (int)(c - buf), NULL); |
|
270 |
81 |
roff_elem_alloc(mdoc, line, offs, ROFF_sp); |
|
271 |
81 |
mdoc->last->flags |= NODE_VALID | NODE_ENDED; |
|
272 |
81 |
mdoc->next = ROFF_NEXT_SIBLING; |
|
273 |
81 |
return 1; |
|
274 |
} |
||
275 |
|||
276 |
229209 |
roff_word_alloc(mdoc, line, offs, buf+offs); |
|
277 |
|||
278 |
✓✓ | 229209 |
if (mdoc->flags & MDOC_LITERAL) |
279 |
17132 |
return 1; |
|
280 |
|||
281 |
/* |
||
282 |
* End-of-sentence check. If the last character is an unescaped |
||
283 |
* EOS character, then flag the node as being the end of a |
||
284 |
* sentence. The front-end will know how to interpret this. |
||
285 |
*/ |
||
286 |
|||
287 |
✗✓ | 212077 |
assert(buf < end); |
288 |
|||
289 |
✓✓ | 212077 |
if (mandoc_eos(buf+offs, (size_t)(end-buf-offs))) |
290 |
67536 |
mdoc->last->flags |= NODE_EOS; |
|
291 |
|||
292 |
✓✓ | 858110 |
for (c = buf + offs; c != NULL; c = strchr(c + 1, '.')) { |
293 |
✓✓ | 284857 |
if (c - buf < offs + 2) |
294 |
continue; |
||
295 |
✓✓ | 72615 |
if (end - c < 3) |
296 |
break; |
||
297 |
✓✓✗✓ |
4738 |
if (c[1] != ' ' || |
298 |
✓✓ | 503 |
isalnum((unsigned char)c[-2]) == 0 || |
299 |
✓✓ | 38 |
isalnum((unsigned char)c[-1]) == 0 || |
300 |
✓✓✗✓ |
41 |
(c[-2] == 'n' && c[-1] == 'c') || |
301 |
✓✓ | 29 |
(c[-2] == 'v' && c[-1] == 's')) |
302 |
continue; |
||
303 |
25 |
c += 2; |
|
304 |
✓✓ | 25 |
if (*c == ' ') |
305 |
1 |
c++; |
|
306 |
✗✓ | 25 |
if (*c == ' ') |
307 |
c++; |
||
308 |
✓✓ | 25 |
if (isupper((unsigned char)(*c))) |
309 |
4 |
mandoc_msg(MANDOCERR_EOS, mdoc->parse, |
|
310 |
2 |
line, (int)(c - buf), NULL); |
|
311 |
} |
||
312 |
|||
313 |
212077 |
return 1; |
|
314 |
229305 |
} |
|
315 |
|||
316 |
/* |
||
317 |
* Parse a macro line, that is, a line beginning with the control |
||
318 |
* character. |
||
319 |
*/ |
||
320 |
static int |
||
321 |
mdoc_pmacro(struct roff_man *mdoc, int ln, char *buf, int offs) |
||
322 |
{ |
||
323 |
struct roff_node *n; |
||
324 |
301259 |
const char *cp; |
|
325 |
size_t sz; |
||
326 |
enum roff_tok tok; |
||
327 |
301259 |
int sv; |
|
328 |
|||
329 |
/* Determine the line macro. */ |
||
330 |
|||
331 |
301259 |
sv = offs; |
|
332 |
tok = TOKEN_NONE; |
||
333 |
✓✓✓✓ |
2711997 |
for (sz = 0; sz < 4 && strchr(" \t\\", buf[offs]) == NULL; sz++) |
334 |
602745 |
offs++; |
|
335 |
✓✓ | 301259 |
if (sz == 2 || sz == 3) |
336 |
301244 |
tok = roffhash_find(mdoc->mdocmac, buf + sv, sz); |
|
337 |
✓✓ | 301259 |
if (tok == TOKEN_NONE) { |
338 |
30 |
mandoc_msg(MANDOCERR_MACRO, mdoc->parse, |
|
339 |
15 |
ln, sv, buf + sv - 1); |
|
340 |
15 |
return 1; |
|
341 |
} |
||
342 |
|||
343 |
/* Skip a leading escape sequence or tab. */ |
||
344 |
|||
345 |
✓✓✓ | 301256 |
switch (buf[offs]) { |
346 |
case '\\': |
||
347 |
6 |
cp = buf + offs + 1; |
|
348 |
6 |
mandoc_escape(&cp, NULL, NULL); |
|
349 |
6 |
offs = cp - buf; |
|
350 |
6 |
break; |
|
351 |
case '\t': |
||
352 |
6 |
offs++; |
|
353 |
6 |
break; |
|
354 |
default: |
||
355 |
break; |
||
356 |
} |
||
357 |
|||
358 |
/* Jump to the next non-whitespace word. */ |
||
359 |
|||
360 |
✓✓ | 1090060 |
while (buf[offs] == ' ') |
361 |
243786 |
offs++; |
|
362 |
|||
363 |
/* |
||
364 |
* Trailing whitespace. Note that tabs are allowed to be passed |
||
365 |
* into the parser as "text", so we only warn about spaces here. |
||
366 |
*/ |
||
367 |
|||
368 |
✓✓✓✓ |
359085 |
if ('\0' == buf[offs] && ' ' == buf[offs - 1]) |
369 |
12 |
mandoc_msg(MANDOCERR_SPACE_EOL, mdoc->parse, |
|
370 |
ln, offs - 1, NULL); |
||
371 |
|||
372 |
/* |
||
373 |
* If an initial macro or a list invocation, divert directly |
||
374 |
* into macro processing. |
||
375 |
*/ |
||
376 |
|||
377 |
301244 |
n = mdoc->last; |
|
378 |
✓✓ | 301244 |
if (n == NULL || tok == MDOC_It || tok == MDOC_El) { |
379 |
44067 |
mdoc_macro(mdoc, tok, ln, sv, &offs, buf); |
|
380 |
44067 |
return 1; |
|
381 |
} |
||
382 |
|||
383 |
/* |
||
384 |
* If a column list contains a non-It macro, assume an implicit |
||
385 |
* item macro. This can happen one or more times at the |
||
386 |
* beginning of such a list, intermixed with text lines and |
||
387 |
* with nodes generated on the roff level, for example by tbl. |
||
388 |
*/ |
||
389 |
|||
390 |
✓✓✓✓ ✓✓ |
263328 |
if ((n->tok == MDOC_Bl && n->type == ROFFT_BODY && |
391 |
✓✓✓✓ |
110 |
n->end == ENDBODY_NOT && n->norm->Bl.type == LIST_column) || |
392 |
✓✓✓✓ |
508575 |
(n->parent != NULL && n->parent->tok == MDOC_Bl && |
393 |
148 |
n->parent->norm->Bl.type == LIST_column)) { |
|
394 |
18 |
mdoc->flags |= MDOC_FREECOL; |
|
395 |
18 |
mdoc_macro(mdoc, MDOC_It, ln, sv, &sv, buf); |
|
396 |
18 |
return 1; |
|
397 |
} |
||
398 |
|||
399 |
/* Normal processing of a macro. */ |
||
400 |
|||
401 |
257159 |
mdoc_macro(mdoc, tok, ln, sv, &offs, buf); |
|
402 |
|||
403 |
/* In quick mode (for mandocdb), abort after the NAME section. */ |
||
404 |
|||
405 |
✗✓✗✗ |
257159 |
if (mdoc->quick && MDOC_Sh == tok && |
406 |
SEC_NAME != mdoc->last->sec) |
||
407 |
return 2; |
||
408 |
|||
409 |
257159 |
return 1; |
|
410 |
301259 |
} |
|
411 |
|||
412 |
enum mdelim |
||
413 |
mdoc_isdelim(const char *p) |
||
414 |
{ |
||
415 |
|||
416 |
✓✓ | 2070964 |
if ('\0' == p[0]) |
417 |
473 |
return DELIM_NONE; |
|
418 |
|||
419 |
✓✓ | 1035009 |
if ('\0' == p[1]) |
420 |
✗✓✓✗ ✗✗✗✗ ✗✗✓✓ |
427841 |
switch (p[0]) { |
421 |
case '(': |
||
422 |
case '[': |
||
423 |
2552 |
return DELIM_OPEN; |
|
424 |
case '|': |
||
425 |
3415 |
return DELIM_MIDDLE; |
|
426 |
case '.': |
||
427 |
case ',': |
||
428 |
case ';': |
||
429 |
case ':': |
||
430 |
case '?': |
||
431 |
case '!': |
||
432 |
case ')': |
||
433 |
case ']': |
||
434 |
106366 |
return DELIM_CLOSE; |
|
435 |
default: |
||
436 |
315508 |
return DELIM_NONE; |
|
437 |
} |
||
438 |
|||
439 |
✓✓ | 607168 |
if ('\\' != p[0]) |
440 |
595830 |
return DELIM_NONE; |
|
441 |
|||
442 |
✓✓ | 11338 |
if (0 == strcmp(p + 1, ".")) |
443 |
91 |
return DELIM_CLOSE; |
|
444 |
✓✓ | 11247 |
if (0 == strcmp(p + 1, "fR|\\fP")) |
445 |
62 |
return DELIM_MIDDLE; |
|
446 |
|||
447 |
11185 |
return DELIM_NONE; |
|
448 |
1035482 |
} |
|
449 |
|||
450 |
void |
||
451 |
mdoc_validate(struct roff_man *mdoc) |
||
452 |
{ |
||
453 |
|||
454 |
11522 |
mdoc->last = mdoc->first; |
|
455 |
5761 |
mdoc_node_validate(mdoc); |
|
456 |
5761 |
mdoc_state_reset(mdoc); |
|
457 |
5761 |
} |
Generated by: GCOVR (Version 3.3) |