Annotation of mandoc/man_validate.c, Revision 1.124
1.107 schwarze 1: /* $OpenBSD$ */
1.1 kristaps 2: /*
1.63 schwarze 3: * Copyright (c) 2008, 2009, 2010, 2011 Kristaps Dzonsons <kristaps@bsd.lv>
1.123 schwarze 4: * Copyright (c) 2010, 2012-2017 Ingo Schwarze <schwarze@openbsd.org>
1.1 kristaps 5: *
6: * Permission to use, copy, modify, and distribute this software for any
1.8 kristaps 7: * purpose with or without fee is hereby granted, provided that the above
8: * copyright notice and this permission notice appear in all copies.
1.1 kristaps 9: *
1.114 schwarze 10: * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHORS DISCLAIM ALL WARRANTIES
1.8 kristaps 11: * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
1.114 schwarze 12: * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR
1.8 kristaps 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.
1.1 kristaps 17: */
1.28 kristaps 18: #include "config.h"
19:
1.1 kristaps 20: #include <sys/types.h>
21:
22: #include <assert.h>
23: #include <ctype.h>
1.16 kristaps 24: #include <errno.h>
25: #include <limits.h>
1.1 kristaps 26: #include <stdarg.h>
27: #include <stdlib.h>
1.46 kristaps 28: #include <string.h>
1.50 kristaps 29: #include <time.h>
1.1 kristaps 30:
1.114 schwarze 31: #include "mandoc_aux.h"
32: #include "mandoc.h"
33: #include "roff.h"
1.66 kristaps 34: #include "man.h"
1.114 schwarze 35: #include "libmandoc.h"
1.118 schwarze 36: #include "roff_int.h"
1.1 kristaps 37: #include "libman.h"
38:
1.117 schwarze 39: #define CHKARGS struct roff_man *man, struct roff_node *n
1.1 kristaps 40:
1.107 schwarze 41: typedef void (*v_check)(CHKARGS);
1.1 kristaps 42:
1.107 schwarze 43: static void check_par(CHKARGS);
44: static void check_part(CHKARGS);
45: static void check_root(CHKARGS);
46: static void check_text(CHKARGS);
47:
48: static void post_AT(CHKARGS);
49: static void post_IP(CHKARGS);
50: static void post_vs(CHKARGS);
51: static void post_ft(CHKARGS);
1.113 schwarze 52: static void post_OP(CHKARGS);
1.107 schwarze 53: static void post_TH(CHKARGS);
54: static void post_UC(CHKARGS);
55: static void post_UR(CHKARGS);
1.51 kristaps 56:
1.123 schwarze 57: static const v_check __man_valids[MAN_MAX - MAN_TH] = {
1.104 schwarze 58: post_TH, /* TH */
59: NULL, /* SH */
60: NULL, /* SS */
61: NULL, /* TP */
62: check_par, /* LP */
63: check_par, /* PP */
64: check_par, /* P */
65: post_IP, /* IP */
66: NULL, /* HP */
67: NULL, /* SM */
68: NULL, /* SB */
69: NULL, /* BI */
70: NULL, /* IB */
71: NULL, /* BR */
72: NULL, /* RB */
73: NULL, /* R */
74: NULL, /* B */
75: NULL, /* I */
76: NULL, /* IR */
77: NULL, /* RI */
78: post_vs, /* sp */
1.121 schwarze 79: NULL, /* nf */
80: NULL, /* fi */
1.104 schwarze 81: NULL, /* RE */
82: check_part, /* RS */
83: NULL, /* DT */
84: post_UC, /* UC */
1.112 schwarze 85: NULL, /* PD */
1.104 schwarze 86: post_AT, /* AT */
87: NULL, /* in */
88: post_ft, /* ft */
1.113 schwarze 89: post_OP, /* OP */
1.121 schwarze 90: NULL, /* EX */
91: NULL, /* EE */
1.104 schwarze 92: post_UR, /* UR */
93: NULL, /* UE */
94: NULL, /* ll */
1.1 kristaps 95: };
1.123 schwarze 96: static const v_check *man_valids = __man_valids - MAN_TH;
1.1 kristaps 97:
98:
1.107 schwarze 99: void
1.121 schwarze 100: man_node_validate(struct roff_man *man)
1.1 kristaps 101: {
1.115 schwarze 102: struct roff_node *n;
1.123 schwarze 103: const v_check *cp;
1.1 kristaps 104:
1.104 schwarze 105: n = man->last;
1.121 schwarze 106: man->last = man->last->child;
107: while (man->last != NULL) {
108: man_node_validate(man);
109: if (man->last == n)
110: man->last = man->last->child;
111: else
112: man->last = man->last->next;
113: }
1.1 kristaps 114:
1.121 schwarze 115: man->last = n;
116: man->next = ROFF_NEXT_SIBLING;
1.104 schwarze 117: switch (n->type) {
1.114 schwarze 118: case ROFFT_TEXT:
1.107 schwarze 119: check_text(man, n);
120: break;
1.114 schwarze 121: case ROFFT_ROOT:
1.107 schwarze 122: check_root(man, n);
123: break;
1.114 schwarze 124: case ROFFT_EQN:
125: case ROFFT_TBL:
1.107 schwarze 126: break;
1.1 kristaps 127: default:
1.124 ! schwarze 128: if (n->tok < ROFF_MAX) {
! 129: switch (n->tok) {
! 130: case ROFF_br:
! 131: post_vs(man, n);
! 132: break;
! 133: default:
! 134: abort();
! 135: }
! 136: break;
! 137: }
! 138: assert(n->tok >= MAN_TH && n->tok < MAN_MAX);
1.104 schwarze 139: cp = man_valids + n->tok;
1.107 schwarze 140: if (*cp)
141: (*cp)(man, n);
1.121 schwarze 142: if (man->last == n)
143: man_state(man, n);
1.107 schwarze 144: break;
1.1 kristaps 145: }
146: }
147:
1.107 schwarze 148: static void
1.91 schwarze 149: check_root(CHKARGS)
1.14 kristaps 150: {
1.17 kristaps 151:
1.101 schwarze 152: assert((man->flags & (MAN_BLINE | MAN_ELINE)) == 0);
1.17 kristaps 153:
1.93 schwarze 154: if (NULL == man->first->child)
1.103 schwarze 155: mandoc_msg(MANDOCERR_DOC_EMPTY, man->parse,
156: n->line, n->pos, NULL);
1.93 schwarze 157: else
158: man->meta.hasbody = 1;
159:
160: if (NULL == man->meta.title) {
1.105 schwarze 161: mandoc_msg(MANDOCERR_TH_NOTITLE, man->parse,
1.103 schwarze 162: n->line, n->pos, NULL);
1.54 kristaps 163:
1.34 kristaps 164: /*
165: * If a title hasn't been set, do so now (by
166: * implication, date and section also aren't set).
167: */
1.54 kristaps 168:
1.105 schwarze 169: man->meta.title = mandoc_strdup("");
170: man->meta.msec = mandoc_strdup("");
1.88 schwarze 171: man->meta.date = man->quick ? mandoc_strdup("") :
172: mandoc_normdate(man->parse, NULL, n->line, n->pos);
1.34 kristaps 173: }
1.75 kristaps 174: }
175:
1.107 schwarze 176: static void
1.75 kristaps 177: check_text(CHKARGS)
178: {
179: char *cp, *p;
180:
1.85 schwarze 181: if (MAN_LITERAL & man->flags)
1.107 schwarze 182: return;
1.76 schwarze 183:
184: cp = n->string;
185: for (p = cp; NULL != (p = strchr(p, '\t')); p++)
1.99 schwarze 186: mandoc_msg(MANDOCERR_FI_TAB, man->parse,
187: n->line, n->pos + (p - cp), NULL);
1.1 kristaps 188: }
189:
1.113 schwarze 190: static void
191: post_OP(CHKARGS)
192: {
193:
1.122 schwarze 194: if (n->child == NULL)
1.113 schwarze 195: mandoc_msg(MANDOCERR_OP_EMPTY, man->parse,
196: n->line, n->pos, "OP");
1.122 schwarze 197: else if (n->child->next != NULL && n->child->next->next != NULL) {
1.113 schwarze 198: n = n->child->next->next;
199: mandoc_vmsg(MANDOCERR_ARG_EXCESS, man->parse,
200: n->line, n->pos, "OP ... %s", n->string);
201: }
1.1 kristaps 202: }
203:
1.107 schwarze 204: static void
1.104 schwarze 205: post_UR(CHKARGS)
1.86 schwarze 206: {
207:
1.114 schwarze 208: if (n->type == ROFFT_HEAD && n->child == NULL)
1.113 schwarze 209: mandoc_vmsg(MANDOCERR_UR_NOHEAD, man->parse,
210: n->line, n->pos, "UR");
1.107 schwarze 211: check_part(man, n);
1.86 schwarze 212: }
1.1 kristaps 213:
1.107 schwarze 214: static void
1.73 kristaps 215: post_ft(CHKARGS)
1.55 kristaps 216: {
217: char *cp;
218: int ok;
219:
1.122 schwarze 220: if (n->child == NULL)
1.107 schwarze 221: return;
1.55 kristaps 222:
223: ok = 0;
224: cp = n->child->string;
225: switch (*cp) {
1.91 schwarze 226: case '1':
227: case '2':
228: case '3':
229: case '4':
230: case 'I':
231: case 'P':
232: case 'R':
1.55 kristaps 233: if ('\0' == cp[1])
234: ok = 1;
235: break;
1.91 schwarze 236: case 'B':
1.55 kristaps 237: if ('\0' == cp[1] || ('I' == cp[1] && '\0' == cp[2]))
238: ok = 1;
239: break;
1.91 schwarze 240: case 'C':
1.55 kristaps 241: if ('W' == cp[1] && '\0' == cp[2])
242: ok = 1;
243: break;
244: default:
245: break;
246: }
247:
248: if (0 == ok) {
1.98 schwarze 249: mandoc_vmsg(MANDOCERR_FT_BAD, man->parse,
250: n->line, n->pos, "ft %s", cp);
1.55 kristaps 251: *cp = '\0';
252: }
253: }
1.16 kristaps 254:
1.107 schwarze 255: static void
1.23 kristaps 256: check_part(CHKARGS)
257: {
258:
1.114 schwarze 259: if (n->type == ROFFT_BODY && n->child == NULL)
1.110 schwarze 260: mandoc_msg(MANDOCERR_BLK_EMPTY, man->parse,
1.123 schwarze 261: n->line, n->pos, roff_name[n->tok]);
1.23 kristaps 262: }
263:
1.107 schwarze 264: static void
1.17 kristaps 265: check_par(CHKARGS)
266: {
267:
1.59 kristaps 268: switch (n->type) {
1.114 schwarze 269: case ROFFT_BLOCK:
1.122 schwarze 270: if (n->body->child == NULL)
1.118 schwarze 271: roff_node_delete(man, n);
1.59 kristaps 272: break;
1.114 schwarze 273: case ROFFT_BODY:
1.122 schwarze 274: if (n->child == NULL)
1.95 schwarze 275: mandoc_vmsg(MANDOCERR_PAR_SKIP,
276: man->parse, n->line, n->pos,
1.123 schwarze 277: "%s empty", roff_name[n->tok]);
1.59 kristaps 278: break;
1.114 schwarze 279: case ROFFT_HEAD:
1.122 schwarze 280: if (n->child != NULL)
1.97 schwarze 281: mandoc_vmsg(MANDOCERR_ARG_SKIP,
1.123 schwarze 282: man->parse, n->line, n->pos, "%s %s%s",
283: roff_name[n->tok], n->child->string,
1.122 schwarze 284: n->child->next != NULL ? " ..." : "");
1.59 kristaps 285: break;
286: default:
287: break;
288: }
1.17 kristaps 289: }
290:
1.107 schwarze 291: static void
1.83 schwarze 292: post_IP(CHKARGS)
293: {
294:
295: switch (n->type) {
1.114 schwarze 296: case ROFFT_BLOCK:
1.122 schwarze 297: if (n->head->child == NULL && n->body->child == NULL)
1.118 schwarze 298: roff_node_delete(man, n);
1.83 schwarze 299: break;
1.114 schwarze 300: case ROFFT_BODY:
1.122 schwarze 301: if (n->parent->head->child == NULL && n->child == NULL)
1.95 schwarze 302: mandoc_vmsg(MANDOCERR_PAR_SKIP,
303: man->parse, n->line, n->pos,
1.123 schwarze 304: "%s empty", roff_name[n->tok]);
1.83 schwarze 305: break;
306: default:
307: break;
308: }
309: }
1.17 kristaps 310:
1.107 schwarze 311: static void
1.51 kristaps 312: post_TH(CHKARGS)
313: {
1.115 schwarze 314: struct roff_node *nb;
1.60 schwarze 315: const char *p;
1.51 kristaps 316:
1.85 schwarze 317: free(man->meta.title);
318: free(man->meta.vol);
1.116 schwarze 319: free(man->meta.os);
1.85 schwarze 320: free(man->meta.msec);
321: free(man->meta.date);
1.51 kristaps 322:
1.85 schwarze 323: man->meta.title = man->meta.vol = man->meta.date =
1.116 schwarze 324: man->meta.msec = man->meta.os = NULL;
1.51 kristaps 325:
1.92 schwarze 326: nb = n;
327:
1.116 schwarze 328: /* ->TITLE<- MSEC DATE OS VOL */
1.51 kristaps 329:
330: n = n->child;
1.60 schwarze 331: if (n && n->string) {
332: for (p = n->string; '\0' != *p; p++) {
333: /* Only warn about this once... */
1.91 schwarze 334: if (isalpha((unsigned char)*p) &&
335: ! isupper((unsigned char)*p)) {
1.102 schwarze 336: mandoc_vmsg(MANDOCERR_TITLE_CASE,
1.94 schwarze 337: man->parse, n->line,
338: n->pos + (p - n->string),
1.102 schwarze 339: "TH %s", n->string);
1.60 schwarze 340: break;
341: }
342: }
1.85 schwarze 343: man->meta.title = mandoc_strdup(n->string);
1.105 schwarze 344: } else {
1.85 schwarze 345: man->meta.title = mandoc_strdup("");
1.105 schwarze 346: mandoc_msg(MANDOCERR_TH_NOTITLE, man->parse,
347: nb->line, nb->pos, "TH");
348: }
1.51 kristaps 349:
1.116 schwarze 350: /* TITLE ->MSEC<- DATE OS VOL */
1.51 kristaps 351:
1.60 schwarze 352: if (n)
353: n = n->next;
354: if (n && n->string)
1.85 schwarze 355: man->meta.msec = mandoc_strdup(n->string);
1.105 schwarze 356: else {
1.85 schwarze 357: man->meta.msec = mandoc_strdup("");
1.105 schwarze 358: mandoc_vmsg(MANDOCERR_MSEC_MISSING, man->parse,
359: nb->line, nb->pos, "TH %s", man->meta.title);
360: }
1.51 kristaps 361:
1.116 schwarze 362: /* TITLE MSEC ->DATE<- OS VOL */
1.51 kristaps 363:
1.60 schwarze 364: if (n)
365: n = n->next;
1.77 schwarze 366: if (n && n->string && '\0' != n->string[0]) {
1.88 schwarze 367: man->meta.date = man->quick ?
368: mandoc_strdup(n->string) :
369: mandoc_normdate(man->parse, n->string,
370: n->line, n->pos);
1.92 schwarze 371: } else {
1.85 schwarze 372: man->meta.date = mandoc_strdup("");
1.102 schwarze 373: mandoc_msg(MANDOCERR_DATE_MISSING, man->parse,
374: n ? n->line : nb->line,
375: n ? n->pos : nb->pos, "TH");
1.92 schwarze 376: }
1.51 kristaps 377:
1.116 schwarze 378: /* TITLE MSEC DATE ->OS<- VOL */
1.51 kristaps 379:
380: if (n && (n = n->next))
1.116 schwarze 381: man->meta.os = mandoc_strdup(n->string);
1.108 schwarze 382: else if (man->defos != NULL)
1.116 schwarze 383: man->meta.os = mandoc_strdup(man->defos);
1.51 kristaps 384:
1.116 schwarze 385: /* TITLE MSEC DATE OS ->VOL<- */
1.79 schwarze 386: /* If missing, use the default VOL name for MSEC. */
1.51 kristaps 387:
388: if (n && (n = n->next))
1.85 schwarze 389: man->meta.vol = mandoc_strdup(n->string);
390: else if ('\0' != man->meta.msec[0] &&
391: (NULL != (p = mandoc_a2msec(man->meta.msec))))
392: man->meta.vol = mandoc_strdup(p);
1.113 schwarze 393:
394: if (n != NULL && (n = n->next) != NULL)
395: mandoc_vmsg(MANDOCERR_ARG_EXCESS, man->parse,
396: n->line, n->pos, "TH ... %s", n->string);
1.51 kristaps 397:
398: /*
399: * Remove the `TH' node after we've processed it for our
400: * meta-data.
401: */
1.118 schwarze 402: roff_node_delete(man, man->last);
1.51 kristaps 403: }
404:
1.107 schwarze 405: static void
1.51 kristaps 406: post_UC(CHKARGS)
407: {
408: static const char * const bsd_versions[] = {
409: "3rd Berkeley Distribution",
410: "4th Berkeley Distribution",
411: "4.2 Berkeley Distribution",
412: "4.3 Berkeley Distribution",
413: "4.4 Berkeley Distribution",
414: };
415:
416: const char *p, *s;
417:
418: n = n->child;
419:
1.114 schwarze 420: if (n == NULL || n->type != ROFFT_TEXT)
1.51 kristaps 421: p = bsd_versions[0];
422: else {
423: s = n->string;
424: if (0 == strcmp(s, "3"))
425: p = bsd_versions[0];
426: else if (0 == strcmp(s, "4"))
427: p = bsd_versions[1];
428: else if (0 == strcmp(s, "5"))
429: p = bsd_versions[2];
430: else if (0 == strcmp(s, "6"))
431: p = bsd_versions[3];
432: else if (0 == strcmp(s, "7"))
433: p = bsd_versions[4];
434: else
435: p = bsd_versions[0];
436: }
437:
1.116 schwarze 438: free(man->meta.os);
439: man->meta.os = mandoc_strdup(p);
1.51 kristaps 440: }
441:
1.107 schwarze 442: static void
1.51 kristaps 443: post_AT(CHKARGS)
444: {
445: static const char * const unix_versions[] = {
446: "7th Edition",
447: "System III",
448: "System V",
449: "System V Release 2",
450: };
451:
1.115 schwarze 452: struct roff_node *nn;
1.51 kristaps 453: const char *p, *s;
454:
455: n = n->child;
456:
1.114 schwarze 457: if (n == NULL || n->type != ROFFT_TEXT)
1.51 kristaps 458: p = unix_versions[0];
459: else {
460: s = n->string;
461: if (0 == strcmp(s, "3"))
462: p = unix_versions[0];
463: else if (0 == strcmp(s, "4"))
464: p = unix_versions[1];
465: else if (0 == strcmp(s, "5")) {
466: nn = n->next;
1.114 schwarze 467: if (nn != NULL &&
468: nn->type == ROFFT_TEXT &&
469: nn->string[0] != '\0')
1.51 kristaps 470: p = unix_versions[3];
471: else
472: p = unix_versions[2];
473: } else
474: p = unix_versions[0];
475: }
476:
1.116 schwarze 477: free(man->meta.os);
478: man->meta.os = mandoc_strdup(p);
1.70 kristaps 479: }
480:
1.107 schwarze 481: static void
1.70 kristaps 482: post_vs(CHKARGS)
483: {
484:
1.82 schwarze 485: if (NULL != n->prev)
1.107 schwarze 486: return;
1.82 schwarze 487:
488: switch (n->parent->tok) {
1.91 schwarze 489: case MAN_SH:
490: case MAN_SS:
1.95 schwarze 491: mandoc_vmsg(MANDOCERR_PAR_SKIP, man->parse, n->line, n->pos,
1.123 schwarze 492: "%s after %s", roff_name[n->tok],
493: roff_name[n->parent->tok]);
1.82 schwarze 494: /* FALLTHROUGH */
1.119 schwarze 495: case TOKEN_NONE:
1.91 schwarze 496: /*
1.82 schwarze 497: * Don't warn about this because it occurs in pod2man
498: * and would cause considerable (unfixable) warnage.
499: */
1.118 schwarze 500: roff_node_delete(man, n);
1.82 schwarze 501: break;
502: default:
503: break;
504: }
1.51 kristaps 505: }
CVSweb