Annotation of mandoc/man_validate.c, Revision 1.144
1.144 ! schwarze 1: /* $Id: man_validate.c,v 1.143 2018/12/31 04:55:46 schwarze Exp $ */
1.1 kristaps 2: /*
1.63 schwarze 3: * Copyright (c) 2008, 2009, 2010, 2011 Kristaps Dzonsons <kristaps@bsd.lv>
1.134 schwarze 4: * Copyright (c) 2010, 2012-2018 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>
1.140 schwarze 27: #include <stdio.h>
1.1 kristaps 28: #include <stdlib.h>
1.46 kristaps 29: #include <string.h>
1.50 kristaps 30: #include <time.h>
1.1 kristaps 31:
1.114 schwarze 32: #include "mandoc_aux.h"
33: #include "mandoc.h"
34: #include "roff.h"
1.66 kristaps 35: #include "man.h"
1.114 schwarze 36: #include "libmandoc.h"
1.118 schwarze 37: #include "roff_int.h"
1.1 kristaps 38: #include "libman.h"
39:
1.117 schwarze 40: #define CHKARGS struct roff_man *man, struct roff_node *n
1.1 kristaps 41:
1.107 schwarze 42: typedef void (*v_check)(CHKARGS);
1.1 kristaps 43:
1.138 schwarze 44: static void check_abort(CHKARGS);
1.107 schwarze 45: static void check_par(CHKARGS);
46: static void check_part(CHKARGS);
47: static void check_root(CHKARGS);
48: static void check_text(CHKARGS);
49:
50: static void post_AT(CHKARGS);
51: static void post_IP(CHKARGS);
1.113 schwarze 52: static void post_OP(CHKARGS);
1.139 schwarze 53: static void post_SH(CHKARGS);
1.107 schwarze 54: static void post_TH(CHKARGS);
55: static void post_UC(CHKARGS);
56: static void post_UR(CHKARGS);
1.129 schwarze 57: static void post_in(CHKARGS);
1.51 kristaps 58:
1.135 schwarze 59: static const v_check man_valids[MAN_MAX - MAN_TH] = {
1.104 schwarze 60: post_TH, /* TH */
1.139 schwarze 61: post_SH, /* SH */
62: post_SH, /* SS */
1.104 schwarze 63: NULL, /* TP */
1.136 schwarze 64: NULL, /* TQ */
1.138 schwarze 65: check_abort,/* LP */
1.104 schwarze 66: check_par, /* PP */
1.138 schwarze 67: check_abort,/* P */
1.104 schwarze 68: post_IP, /* IP */
69: NULL, /* HP */
70: NULL, /* SM */
71: NULL, /* SB */
72: NULL, /* BI */
73: NULL, /* IB */
74: NULL, /* BR */
75: NULL, /* RB */
76: NULL, /* R */
77: NULL, /* B */
78: NULL, /* I */
79: NULL, /* IR */
80: NULL, /* RI */
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 */
1.129 schwarze 87: post_in, /* in */
1.137 schwarze 88: NULL, /* SY */
89: NULL, /* YS */
1.113 schwarze 90: post_OP, /* OP */
1.121 schwarze 91: NULL, /* EX */
92: NULL, /* EE */
1.104 schwarze 93: post_UR, /* UR */
94: NULL, /* UE */
1.132 schwarze 95: post_UR, /* MT */
96: NULL, /* ME */
1.1 kristaps 97: };
98:
99:
1.138 schwarze 100: /* Validate the subtree rooted at man->last. */
1.107 schwarze 101: void
1.143 schwarze 102: man_validate(struct roff_man *man)
1.1 kristaps 103: {
1.115 schwarze 104: struct roff_node *n;
1.123 schwarze 105: const v_check *cp;
1.1 kristaps 106:
1.138 schwarze 107: /*
108: * Translate obsolete macros such that later code
109: * does not need to look for them.
110: */
111:
1.104 schwarze 112: n = man->last;
1.138 schwarze 113: switch (n->tok) {
114: case MAN_LP:
115: case MAN_P:
116: n->tok = MAN_PP;
117: break;
118: default:
119: break;
120: }
121:
122: /*
123: * Iterate over all children, recursing into each one
124: * in turn, depth-first.
125: */
126:
1.121 schwarze 127: man->last = man->last->child;
128: while (man->last != NULL) {
1.143 schwarze 129: man_validate(man);
1.121 schwarze 130: if (man->last == n)
131: man->last = man->last->child;
132: else
133: man->last = man->last->next;
134: }
1.1 kristaps 135:
1.138 schwarze 136: /* Finally validate the macro itself. */
137:
1.121 schwarze 138: man->last = n;
139: man->next = ROFF_NEXT_SIBLING;
1.104 schwarze 140: switch (n->type) {
1.114 schwarze 141: case ROFFT_TEXT:
1.107 schwarze 142: check_text(man, n);
143: break;
1.114 schwarze 144: case ROFFT_ROOT:
1.107 schwarze 145: check_root(man, n);
146: break;
1.134 schwarze 147: case ROFFT_COMMENT:
1.114 schwarze 148: case ROFFT_EQN:
149: case ROFFT_TBL:
1.107 schwarze 150: break;
1.1 kristaps 151: default:
1.124 schwarze 152: if (n->tok < ROFF_MAX) {
1.139 schwarze 153: roff_validate(man);
1.144 ! schwarze 154: man_state(man, n);
1.124 schwarze 155: break;
156: }
157: assert(n->tok >= MAN_TH && n->tok < MAN_MAX);
1.135 schwarze 158: cp = man_valids + (n->tok - MAN_TH);
1.107 schwarze 159: if (*cp)
160: (*cp)(man, n);
1.121 schwarze 161: if (man->last == n)
162: man_state(man, n);
1.107 schwarze 163: break;
1.1 kristaps 164: }
165: }
166:
1.107 schwarze 167: static void
1.91 schwarze 168: check_root(CHKARGS)
1.14 kristaps 169: {
1.101 schwarze 170: assert((man->flags & (MAN_BLINE | MAN_ELINE)) == 0);
1.17 kristaps 171:
1.134 schwarze 172: if (n->last == NULL || n->last->type == ROFFT_COMMENT)
1.141 schwarze 173: mandoc_msg(MANDOCERR_DOC_EMPTY, n->line, n->pos, NULL);
1.93 schwarze 174: else
175: man->meta.hasbody = 1;
176:
177: if (NULL == man->meta.title) {
1.141 schwarze 178: mandoc_msg(MANDOCERR_TH_NOTITLE, n->line, n->pos, NULL);
1.54 kristaps 179:
1.34 kristaps 180: /*
181: * If a title hasn't been set, do so now (by
182: * implication, date and section also aren't set).
183: */
1.54 kristaps 184:
1.105 schwarze 185: man->meta.title = mandoc_strdup("");
186: man->meta.msec = mandoc_strdup("");
1.88 schwarze 187: man->meta.date = man->quick ? mandoc_strdup("") :
1.128 schwarze 188: mandoc_normdate(man, NULL, n->line, n->pos);
1.34 kristaps 189: }
1.130 schwarze 190:
191: if (man->meta.os_e &&
192: (man->meta.rcsids & (1 << man->meta.os_e)) == 0)
1.141 schwarze 193: mandoc_msg(MANDOCERR_RCS_MISSING, 0, 0,
1.131 schwarze 194: man->meta.os_e == MANDOC_OS_OPENBSD ?
195: "(OpenBSD)" : "(NetBSD)");
1.75 kristaps 196: }
197:
1.107 schwarze 198: static void
1.138 schwarze 199: check_abort(CHKARGS)
200: {
201: abort();
202: }
203:
204: static void
1.75 kristaps 205: check_text(CHKARGS)
206: {
207: char *cp, *p;
208:
1.144 ! schwarze 209: if (man->flags & ROFF_NOFILL)
1.107 schwarze 210: return;
1.76 schwarze 211:
212: cp = n->string;
213: for (p = cp; NULL != (p = strchr(p, '\t')); p++)
1.141 schwarze 214: mandoc_msg(MANDOCERR_FI_TAB,
215: n->line, n->pos + (int)(p - cp), NULL);
1.1 kristaps 216: }
217:
1.113 schwarze 218: static void
219: post_OP(CHKARGS)
220: {
221:
1.122 schwarze 222: if (n->child == NULL)
1.141 schwarze 223: mandoc_msg(MANDOCERR_OP_EMPTY, n->line, n->pos, "OP");
1.122 schwarze 224: else if (n->child->next != NULL && n->child->next->next != NULL) {
1.113 schwarze 225: n = n->child->next->next;
1.141 schwarze 226: mandoc_msg(MANDOCERR_ARG_EXCESS,
1.113 schwarze 227: n->line, n->pos, "OP ... %s", n->string);
228: }
1.1 kristaps 229: }
230:
1.107 schwarze 231: static void
1.139 schwarze 232: post_SH(CHKARGS)
233: {
234: struct roff_node *nc;
235:
236: if (n->type != ROFFT_BODY || (nc = n->child) == NULL)
237: return;
238:
239: if (nc->tok == MAN_PP && nc->body->child != NULL) {
240: while (nc->body->last != NULL) {
241: man->next = ROFF_NEXT_CHILD;
242: roff_node_relink(man, nc->body->last);
243: man->last = n;
244: }
245: }
246:
247: if (nc->tok == MAN_PP || nc->tok == ROFF_sp || nc->tok == ROFF_br) {
1.141 schwarze 248: mandoc_msg(MANDOCERR_PAR_SKIP, nc->line, nc->pos,
249: "%s after %s", roff_name[nc->tok], roff_name[n->tok]);
1.139 schwarze 250: roff_node_delete(man, nc);
251: }
252:
253: /*
254: * Trailing PP is empty, so it is deleted by check_par().
255: * Trailing sp is significant.
256: */
257:
258: if ((nc = n->last) != NULL && nc->tok == ROFF_br) {
1.141 schwarze 259: mandoc_msg(MANDOCERR_PAR_SKIP,
1.139 schwarze 260: nc->line, nc->pos, "%s at the end of %s",
261: roff_name[nc->tok], roff_name[n->tok]);
262: roff_node_delete(man, nc);
263: }
264: }
265:
266: static void
1.104 schwarze 267: post_UR(CHKARGS)
1.86 schwarze 268: {
1.114 schwarze 269: if (n->type == ROFFT_HEAD && n->child == NULL)
1.141 schwarze 270: mandoc_msg(MANDOCERR_UR_NOHEAD, n->line, n->pos,
271: "%s", roff_name[n->tok]);
1.107 schwarze 272: check_part(man, n);
1.55 kristaps 273: }
1.16 kristaps 274:
1.107 schwarze 275: static void
1.23 kristaps 276: check_part(CHKARGS)
277: {
278:
1.114 schwarze 279: if (n->type == ROFFT_BODY && n->child == NULL)
1.141 schwarze 280: mandoc_msg(MANDOCERR_BLK_EMPTY, n->line, n->pos,
281: "%s", roff_name[n->tok]);
1.23 kristaps 282: }
283:
1.107 schwarze 284: static void
1.17 kristaps 285: check_par(CHKARGS)
286: {
287:
1.59 kristaps 288: switch (n->type) {
1.114 schwarze 289: case ROFFT_BLOCK:
1.122 schwarze 290: if (n->body->child == NULL)
1.118 schwarze 291: roff_node_delete(man, n);
1.59 kristaps 292: break;
1.114 schwarze 293: case ROFFT_BODY:
1.139 schwarze 294: if (n->child != NULL &&
295: (n->child->tok == ROFF_sp || n->child->tok == ROFF_br)) {
1.141 schwarze 296: mandoc_msg(MANDOCERR_PAR_SKIP,
297: n->child->line, n->child->pos,
1.139 schwarze 298: "%s after %s", roff_name[n->child->tok],
299: roff_name[n->tok]);
300: roff_node_delete(man, n->child);
301: }
1.122 schwarze 302: if (n->child == NULL)
1.141 schwarze 303: mandoc_msg(MANDOCERR_PAR_SKIP, n->line, n->pos,
1.123 schwarze 304: "%s empty", roff_name[n->tok]);
1.59 kristaps 305: break;
1.114 schwarze 306: case ROFFT_HEAD:
1.122 schwarze 307: if (n->child != NULL)
1.141 schwarze 308: mandoc_msg(MANDOCERR_ARG_SKIP,
309: n->line, n->pos, "%s %s%s",
1.123 schwarze 310: roff_name[n->tok], n->child->string,
1.122 schwarze 311: n->child->next != NULL ? " ..." : "");
1.59 kristaps 312: break;
313: default:
314: break;
315: }
1.17 kristaps 316: }
317:
1.107 schwarze 318: static void
1.83 schwarze 319: post_IP(CHKARGS)
320: {
321:
322: switch (n->type) {
1.114 schwarze 323: case ROFFT_BLOCK:
1.122 schwarze 324: if (n->head->child == NULL && n->body->child == NULL)
1.118 schwarze 325: roff_node_delete(man, n);
1.83 schwarze 326: break;
1.114 schwarze 327: case ROFFT_BODY:
1.122 schwarze 328: if (n->parent->head->child == NULL && n->child == NULL)
1.141 schwarze 329: mandoc_msg(MANDOCERR_PAR_SKIP, n->line, n->pos,
1.123 schwarze 330: "%s empty", roff_name[n->tok]);
1.83 schwarze 331: break;
332: default:
333: break;
334: }
335: }
1.17 kristaps 336:
1.107 schwarze 337: static void
1.51 kristaps 338: post_TH(CHKARGS)
339: {
1.115 schwarze 340: struct roff_node *nb;
1.60 schwarze 341: const char *p;
1.51 kristaps 342:
1.85 schwarze 343: free(man->meta.title);
344: free(man->meta.vol);
1.116 schwarze 345: free(man->meta.os);
1.85 schwarze 346: free(man->meta.msec);
347: free(man->meta.date);
1.51 kristaps 348:
1.85 schwarze 349: man->meta.title = man->meta.vol = man->meta.date =
1.116 schwarze 350: man->meta.msec = man->meta.os = NULL;
1.51 kristaps 351:
1.92 schwarze 352: nb = n;
353:
1.116 schwarze 354: /* ->TITLE<- MSEC DATE OS VOL */
1.51 kristaps 355:
356: n = n->child;
1.60 schwarze 357: if (n && n->string) {
358: for (p = n->string; '\0' != *p; p++) {
359: /* Only warn about this once... */
1.91 schwarze 360: if (isalpha((unsigned char)*p) &&
361: ! isupper((unsigned char)*p)) {
1.141 schwarze 362: mandoc_msg(MANDOCERR_TITLE_CASE, n->line,
363: n->pos + (int)(p - n->string),
1.102 schwarze 364: "TH %s", n->string);
1.60 schwarze 365: break;
366: }
367: }
1.85 schwarze 368: man->meta.title = mandoc_strdup(n->string);
1.105 schwarze 369: } else {
1.85 schwarze 370: man->meta.title = mandoc_strdup("");
1.141 schwarze 371: mandoc_msg(MANDOCERR_TH_NOTITLE, nb->line, nb->pos, "TH");
1.105 schwarze 372: }
1.51 kristaps 373:
1.116 schwarze 374: /* TITLE ->MSEC<- DATE OS VOL */
1.51 kristaps 375:
1.60 schwarze 376: if (n)
377: n = n->next;
378: if (n && n->string)
1.85 schwarze 379: man->meta.msec = mandoc_strdup(n->string);
1.105 schwarze 380: else {
1.85 schwarze 381: man->meta.msec = mandoc_strdup("");
1.141 schwarze 382: mandoc_msg(MANDOCERR_MSEC_MISSING,
1.105 schwarze 383: nb->line, nb->pos, "TH %s", man->meta.title);
384: }
1.51 kristaps 385:
1.116 schwarze 386: /* TITLE MSEC ->DATE<- OS VOL */
1.51 kristaps 387:
1.60 schwarze 388: if (n)
389: n = n->next;
1.77 schwarze 390: if (n && n->string && '\0' != n->string[0]) {
1.88 schwarze 391: man->meta.date = man->quick ?
392: mandoc_strdup(n->string) :
1.128 schwarze 393: mandoc_normdate(man, n->string, n->line, n->pos);
1.92 schwarze 394: } else {
1.85 schwarze 395: man->meta.date = mandoc_strdup("");
1.141 schwarze 396: mandoc_msg(MANDOCERR_DATE_MISSING,
1.102 schwarze 397: n ? n->line : nb->line,
398: n ? n->pos : nb->pos, "TH");
1.92 schwarze 399: }
1.51 kristaps 400:
1.116 schwarze 401: /* TITLE MSEC DATE ->OS<- VOL */
1.51 kristaps 402:
403: if (n && (n = n->next))
1.116 schwarze 404: man->meta.os = mandoc_strdup(n->string);
1.131 schwarze 405: else if (man->os_s != NULL)
406: man->meta.os = mandoc_strdup(man->os_s);
407: if (man->meta.os_e == MANDOC_OS_OTHER && man->meta.os != NULL) {
408: if (strstr(man->meta.os, "OpenBSD") != NULL)
409: man->meta.os_e = MANDOC_OS_OPENBSD;
410: else if (strstr(man->meta.os, "NetBSD") != NULL)
411: man->meta.os_e = MANDOC_OS_NETBSD;
412: }
1.51 kristaps 413:
1.116 schwarze 414: /* TITLE MSEC DATE OS ->VOL<- */
1.79 schwarze 415: /* If missing, use the default VOL name for MSEC. */
1.51 kristaps 416:
417: if (n && (n = n->next))
1.85 schwarze 418: man->meta.vol = mandoc_strdup(n->string);
419: else if ('\0' != man->meta.msec[0] &&
420: (NULL != (p = mandoc_a2msec(man->meta.msec))))
421: man->meta.vol = mandoc_strdup(p);
1.113 schwarze 422:
423: if (n != NULL && (n = n->next) != NULL)
1.141 schwarze 424: mandoc_msg(MANDOCERR_ARG_EXCESS,
1.113 schwarze 425: n->line, n->pos, "TH ... %s", n->string);
1.51 kristaps 426:
427: /*
428: * Remove the `TH' node after we've processed it for our
429: * meta-data.
430: */
1.118 schwarze 431: roff_node_delete(man, man->last);
1.51 kristaps 432: }
433:
1.107 schwarze 434: static void
1.51 kristaps 435: post_UC(CHKARGS)
436: {
437: static const char * const bsd_versions[] = {
438: "3rd Berkeley Distribution",
439: "4th Berkeley Distribution",
440: "4.2 Berkeley Distribution",
441: "4.3 Berkeley Distribution",
442: "4.4 Berkeley Distribution",
443: };
444:
445: const char *p, *s;
446:
447: n = n->child;
448:
1.114 schwarze 449: if (n == NULL || n->type != ROFFT_TEXT)
1.51 kristaps 450: p = bsd_versions[0];
451: else {
452: s = n->string;
453: if (0 == strcmp(s, "3"))
454: p = bsd_versions[0];
455: else if (0 == strcmp(s, "4"))
456: p = bsd_versions[1];
457: else if (0 == strcmp(s, "5"))
458: p = bsd_versions[2];
459: else if (0 == strcmp(s, "6"))
460: p = bsd_versions[3];
461: else if (0 == strcmp(s, "7"))
462: p = bsd_versions[4];
463: else
464: p = bsd_versions[0];
465: }
466:
1.116 schwarze 467: free(man->meta.os);
468: man->meta.os = mandoc_strdup(p);
1.51 kristaps 469: }
470:
1.107 schwarze 471: static void
1.51 kristaps 472: post_AT(CHKARGS)
473: {
474: static const char * const unix_versions[] = {
475: "7th Edition",
476: "System III",
477: "System V",
478: "System V Release 2",
479: };
480:
1.115 schwarze 481: struct roff_node *nn;
1.51 kristaps 482: const char *p, *s;
483:
484: n = n->child;
485:
1.114 schwarze 486: if (n == NULL || n->type != ROFFT_TEXT)
1.51 kristaps 487: p = unix_versions[0];
488: else {
489: s = n->string;
490: if (0 == strcmp(s, "3"))
491: p = unix_versions[0];
492: else if (0 == strcmp(s, "4"))
493: p = unix_versions[1];
494: else if (0 == strcmp(s, "5")) {
495: nn = n->next;
1.114 schwarze 496: if (nn != NULL &&
497: nn->type == ROFFT_TEXT &&
498: nn->string[0] != '\0')
1.51 kristaps 499: p = unix_versions[3];
500: else
501: p = unix_versions[2];
502: } else
503: p = unix_versions[0];
504: }
505:
1.116 schwarze 506: free(man->meta.os);
507: man->meta.os = mandoc_strdup(p);
1.129 schwarze 508: }
509:
510: static void
511: post_in(CHKARGS)
512: {
513: char *s;
514:
515: if (n->parent->tok != MAN_TP ||
516: n->parent->type != ROFFT_HEAD ||
517: n->child == NULL ||
518: *n->child->string == '+' ||
519: *n->child->string == '-')
520: return;
521: mandoc_asprintf(&s, "+%s", n->child->string);
522: free(n->child->string);
523: n->child->string = s;
1.51 kristaps 524: }
CVSweb