diff options
author | Ingo Schwarze <schwarze@cvs.openbsd.org> | 2018-08-23 14:16:13 +0000 |
---|---|---|
committer | Ingo Schwarze <schwarze@cvs.openbsd.org> | 2018-08-23 14:16:13 +0000 |
commit | fc469ea850621ad85ab19d7482c03478fc263bcb (patch) | |
tree | 29ee64e3e0a1e94868f5a145d1dd55610ad040db | |
parent | 19b02c78f13b5e52f321e82720b4b70346dbb581 (diff) |
Implement the roff(7) .shift and .return requests,
for example used by groff_hdtbl(7) and groff_mom(7).
Also correctly interpolate arguments during nested macro execution
even after .shift and .return, implemented using a stack of argument
arrays.
Note that only read.c, but not roff.c can detect the end of a macro
execution, and the existence of .shift implies that arguments cannot
be interpolated up front, so unfortunately, this includes a partial
revert of roff.c rev. 1.209, moving argument interpolation back into
the function roff_res().
20 files changed, 457 insertions, 203 deletions
diff --git a/regress/usr.bin/mandoc/roff/Makefile b/regress/usr.bin/mandoc/roff/Makefile index 7afa56d9b53..7c6f5e16dda 100644 --- a/regress/usr.bin/mandoc/roff/Makefile +++ b/regress/usr.bin/mandoc/roff/Makefile @@ -1,7 +1,7 @@ -# $OpenBSD: Makefile,v 1.24 2017/06/18 17:35:40 schwarze Exp $ +# $OpenBSD: Makefile,v 1.25 2018/08/23 14:16:12 schwarze Exp $ SUBDIR = args cond esc scale string -SUBDIR += br cc de ds ft ig it ll na nr po ps rm rn sp ta ti tr +SUBDIR += br cc de ds ft ig it ll na nr po ps return rm rn shift sp ta ti tr .include "../Makefile.sub" .include <bsd.subdir.mk> diff --git a/regress/usr.bin/mandoc/roff/de/infinite.in b/regress/usr.bin/mandoc/roff/de/infinite.in index 00931a06c18..dd17885f608 100644 --- a/regress/usr.bin/mandoc/roff/de/infinite.in +++ b/regress/usr.bin/mandoc/roff/de/infinite.in @@ -1,5 +1,5 @@ -.\" $OpenBSD: infinite.in,v 1.3 2017/07/04 14:53:27 schwarze Exp $ -.Dd $Mdocdate: July 4 2017 $ +.\" $OpenBSD: infinite.in,v 1.4 2018/08/23 14:16:12 schwarze Exp $ +.Dd $Mdocdate: August 23 2018 $ .Dt DE-INFINITE 1 .Os .Sh NAME @@ -10,8 +10,8 @@ initial text .de mym .Op \\$1 \\$2 .. -.mym $1 \$1 -.mym \$1 nothing +.mym $1 \$1 end +.mym \$1 middle end middle text .de mym .mym diff --git a/regress/usr.bin/mandoc/roff/de/infinite.out_ascii b/regress/usr.bin/mandoc/roff/de/infinite.out_ascii index 452af775982..074362da409 100644 --- a/regress/usr.bin/mandoc/roff/de/infinite.out_ascii +++ b/regress/usr.bin/mandoc/roff/de/infinite.out_ascii @@ -4,6 +4,6 @@ NNAAMMEE ddee--iinnffiinniittee - inifinte recursion in a user-defined macro DDEESSCCRRIIPPTTIIOONN - initial text [$1 $1] middle text final text + initial text [$1 end] [middle end] middle text final text -OpenBSD July 4, 2017 OpenBSD +OpenBSD August 23, 2018 OpenBSD diff --git a/regress/usr.bin/mandoc/roff/de/infinite.out_lint b/regress/usr.bin/mandoc/roff/de/infinite.out_lint index e7beb090aa5..06254be506e 100644 --- a/regress/usr.bin/mandoc/roff/de/infinite.out_lint +++ b/regress/usr.bin/mandoc/roff/de/infinite.out_lint @@ -1,2 +1,3 @@ -mandoc: infinite.in:14:5: ERROR: input stack limit exceeded, infinite loop? +mandoc: infinite.in:13:9: ERROR: using macro argument outside macro: \$1 +mandoc: infinite.in:14:6: ERROR: using macro argument outside macro: \$1 mandoc: infinite.in:20:5: ERROR: input stack limit exceeded, infinite loop? diff --git a/regress/usr.bin/mandoc/roff/return/Makefile b/regress/usr.bin/mandoc/roff/return/Makefile new file mode 100644 index 00000000000..f7d510f7885 --- /dev/null +++ b/regress/usr.bin/mandoc/roff/return/Makefile @@ -0,0 +1,6 @@ +# $OpenBSD: Makefile,v 1.1 2018/08/23 14:16:12 schwarze Exp $ + +REGRESS_TARGETS = basic +LINT_TARGETS = basic + +.include <bsd.regress.mk> diff --git a/regress/usr.bin/mandoc/roff/return/basic.in b/regress/usr.bin/mandoc/roff/return/basic.in new file mode 100644 index 00000000000..f0a777c15f1 --- /dev/null +++ b/regress/usr.bin/mandoc/roff/return/basic.in @@ -0,0 +1,23 @@ +.\" $OpenBSD: basic.in,v 1.1 2018/08/23 14:16:12 schwarze Exp $ +.Dd $Mdocdate: August 23 2018 $ +.Dt RETURN-BASIC 1 +.Os +.Sh NAME +.Nm return-basic +.Nd the return request +.Sh DESCRIPTION +return before macro +.return +.Pp +.de mymacro +text from macro (\\n(.$ argument: "\\$1"), +.return +not printed, +.. +.mymacro myarg +\n(.$ arguments after return: "\$1", +.Pp +return after macro +.return +.Pp +final text diff --git a/regress/usr.bin/mandoc/roff/return/basic.out_ascii b/regress/usr.bin/mandoc/roff/return/basic.out_ascii new file mode 100644 index 00000000000..25f667442fc --- /dev/null +++ b/regress/usr.bin/mandoc/roff/return/basic.out_ascii @@ -0,0 +1,15 @@ +RETURN-BASIC(1) General Commands Manual RETURN-BASIC(1) + +NNAAMMEE + rreettuurrnn--bbaassiicc - the return request + +DDEESSCCRRIIPPTTIIOONN + return before macro + + text from macro (1 argument: "myarg"), 0 arguments after return: "", + + return after macro + + final text + +OpenBSD August 23, 2018 OpenBSD diff --git a/regress/usr.bin/mandoc/roff/return/basic.out_lint b/regress/usr.bin/mandoc/roff/return/basic.out_lint new file mode 100644 index 00000000000..f4b15bac840 --- /dev/null +++ b/regress/usr.bin/mandoc/roff/return/basic.out_lint @@ -0,0 +1,3 @@ +mandoc: basic.in:10:2: ERROR: ignoring request outside macro: return +mandoc: basic.in:18:32: ERROR: using macro argument outside macro: \$1 +mandoc: basic.in:21:2: ERROR: ignoring request outside macro: return diff --git a/regress/usr.bin/mandoc/roff/shift/Makefile b/regress/usr.bin/mandoc/roff/shift/Makefile new file mode 100644 index 00000000000..d356b963c85 --- /dev/null +++ b/regress/usr.bin/mandoc/roff/shift/Makefile @@ -0,0 +1,6 @@ +# $OpenBSD: Makefile,v 1.1 2018/08/23 14:16:12 schwarze Exp $ + +REGRESS_TARGETS = basic bad +LINT_TARGETS = bad + +.include <bsd.regress.mk> diff --git a/regress/usr.bin/mandoc/roff/shift/bad.in b/regress/usr.bin/mandoc/roff/shift/bad.in new file mode 100644 index 00000000000..809832defd8 --- /dev/null +++ b/regress/usr.bin/mandoc/roff/shift/bad.in @@ -0,0 +1,30 @@ +.\" $OpenBSD: bad.in,v 1.1 2018/08/23 14:16:12 schwarze Exp $ +.TH SHIFT_BAD 1 "August 23, 2018" +.SH NAME +.B shift-bad +\(en wrong usage of macro arguments +.SH DESCRIPTION +initial text +.de mym +in macro: "\\$1" +.PP +invalid argument number 'x': "\\$x" +.. +.PP +argument used before call: "\$1" +.shift +.PP +.mym argument +.PP +argument used after call: "\$1" +.shift 2 +.PP +.de mym +.shift badarg +after shift badarg: "\\$1" +.shift 2 +after excessive shift: \\n(.$ "\\$1" +.. +.mym arg1 arg2 +.PP +final text diff --git a/regress/usr.bin/mandoc/roff/shift/bad.out_ascii b/regress/usr.bin/mandoc/roff/shift/bad.out_ascii new file mode 100644 index 00000000000..0b21f574a48 --- /dev/null +++ b/regress/usr.bin/mandoc/roff/shift/bad.out_ascii @@ -0,0 +1,25 @@ +SHIFT_BAD(1) General Commands Manual SHIFT_BAD(1) + + + +NNAAMMEE + sshhiifftt--bbaadd - wrong usage of macro arguments + +DDEESSCCRRIIPPTTIIOONN + initial text + + argument used before call: "" + + in macro: "argument" + + invalid argument number 'x': "" + + argument used after call: "" + + after shift badarg: "arg2" after excessive shift: 0 "" + + final text + + + +OpenBSD August 23, 2018 SHIFT_BAD(1) diff --git a/regress/usr.bin/mandoc/roff/shift/bad.out_lint b/regress/usr.bin/mandoc/roff/shift/bad.out_lint new file mode 100644 index 00000000000..1f696fc8681 --- /dev/null +++ b/regress/usr.bin/mandoc/roff/shift/bad.out_lint @@ -0,0 +1,7 @@ +mandoc: bad.in:14:29: ERROR: using macro argument outside macro: \$1 +mandoc: bad.in:15:2: ERROR: ignoring request outside macro: shift +mandoc: bad.in:17:31: ERROR: argument number is not numeric: \$x +mandoc: bad.in:19:28: ERROR: using macro argument outside macro: \$1 +mandoc: bad.in:20:2: ERROR: ignoring request outside macro: shift +mandoc: bad.in:28:8: ERROR: argument is not numeric, using 1: shift badarg +mandoc: bad.in:28:9: ERROR: excessive shift: 2, but max is 1 diff --git a/regress/usr.bin/mandoc/roff/shift/basic.in b/regress/usr.bin/mandoc/roff/shift/basic.in new file mode 100644 index 00000000000..982042db278 --- /dev/null +++ b/regress/usr.bin/mandoc/roff/shift/basic.in @@ -0,0 +1,35 @@ +.\" $OpenBSD: basic.in,v 1.1 2018/08/23 14:16:12 schwarze Exp $ +.TH SHIFT_BASIC 1 "August 23, 2018" +.SH NAME +.B shift-basic +\(en the shift request +.SH DESCRIPTION +.de showargs +original arguments: +.BI \\$@ +.PP +.shift 2 +after shift 2: +.BI \\$@ +.PP +.shift +after shift without argument: +.BI \\$@ +.PP +.shift 0 +after shift 0: +.BI \\$@ +.. +.de useargs +<\\$*> +.. +.showargs one two three four five +.PP +expand to less than three bytes: +.useargs 1 +.PP +expand to exactly three bytes: +.useargs x y +.PP +expand to more than three bytes: +.useargs "a longer argument..." "and another" diff --git a/regress/usr.bin/mandoc/roff/shift/basic.out_ascii b/regress/usr.bin/mandoc/roff/shift/basic.out_ascii new file mode 100644 index 00000000000..40675c67543 --- /dev/null +++ b/regress/usr.bin/mandoc/roff/shift/basic.out_ascii @@ -0,0 +1,25 @@ +SHIFT_BASIC(1) General Commands Manual SHIFT_BASIC(1) + + + +NNAAMMEE + sshhiifftt--bbaassiicc - the shift request + +DDEESSCCRRIIPPTTIIOONN + original arguments: oonnee_t_w_otthhrreeee_f_o_u_rffiivvee + + after shift 2: tthhrreeee_f_o_u_rffiivvee + + after shift without argument: ffoouurr_f_i_v_e + + after shift 0: ffoouurr_f_i_v_e + + expand to less than three bytes: <1> + + expand to exactly three bytes: <x y> + + expand to more than three bytes: <a longer argument... and another> + + + +OpenBSD August 23, 2018 SHIFT_BASIC(1) diff --git a/share/man/man7/roff.7 b/share/man/man7/roff.7 index a48c10ffb76..2914ce486e3 100644 --- a/share/man/man7/roff.7 +++ b/share/man/man7/roff.7 @@ -1,4 +1,4 @@ -.\" $OpenBSD: roff.7,v 1.82 2018/08/21 18:15:17 schwarze Exp $ +.\" $OpenBSD: roff.7,v 1.83 2018/08/23 14:16:11 schwarze Exp $ .\" .\" Copyright (c) 2010, 2011, 2012 Kristaps Dzonsons <kristaps@bsd.lv> .\" Copyright (c) 2010-2018 Ingo Schwarze <schwarze@openbsd.org> @@ -15,7 +15,7 @@ .\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF .\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. .\" -.Dd $Mdocdate: August 21 2018 $ +.Dd $Mdocdate: August 23 2018 $ .Dt ROFF 7 .Os .Sh NAME @@ -1470,8 +1470,8 @@ Currently ignored. Set the maximum stack depth for recursive macros. This is a Heirloom extension and currently ignored. .It Ic \&return Op Ar twice -Exit a macro and return to the caller. -Currently unsupported. +Exit the presently executed macro and return to the caller. +The argument is currently ignored. .It Ic \&rfschar Ar font glyph ... Remove font-specific fallback glyph definitions. Currently unsupported. @@ -1520,8 +1520,11 @@ This is a Heirloom extension and currently ignored. Change the soft hyphen character. Currently ignored. .It Ic \&shift Op Ar number -Shift macro arguments. -Currently unsupported. +Shift macro arguments +.Ar number +times, by default once: \e\e$i becomes what \e\e$i+number was. +Also decrement \en(.$ by +.Ar number . .It Ic \&sizes Ar size ... Define permissible point sizes. This is a groff extension and currently ignored. diff --git a/usr.bin/mandoc/libmandoc.h b/usr.bin/mandoc/libmandoc.h index bee6afdeb8a..ba69e4d38be 100644 --- a/usr.bin/mandoc/libmandoc.h +++ b/usr.bin/mandoc/libmandoc.h @@ -1,7 +1,7 @@ -/* $OpenBSD: libmandoc.h,v 1.56 2018/04/09 22:26:25 schwarze Exp $ */ +/* $OpenBSD: libmandoc.h,v 1.57 2018/08/23 14:16:12 schwarze Exp $ */ /* * Copyright (c) 2009, 2010, 2011, 2012 Kristaps Dzonsons <kristaps@bsd.lv> - * Copyright (c) 2013, 2014, 2015, 2017 Ingo Schwarze <schwarze@openbsd.org> + * Copyright (c) 2013,2014,2015,2017,2018 Ingo Schwarze <schwarze@openbsd.org> * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -21,6 +21,8 @@ enum rofferr { ROFF_RERUN, /* re-run roff interpreter with offset */ ROFF_APPEND, /* re-run main parser, appending next line */ ROFF_REPARSE, /* re-run main parser on the result */ + ROFF_USERCALL, /* dto., calling a user-defined macro */ + ROFF_USERRET, /* abort parsing of user-defined macro */ ROFF_SO, /* include another file */ ROFF_IGN, /* ignore current line */ }; @@ -64,6 +66,7 @@ struct roff_man *roff_man_alloc(struct roff *, struct mparse *, const char *, int); void roff_man_reset(struct roff_man *); enum rofferr roff_parseln(struct roff *, int, struct buf *, int *); +void roff_userret(struct roff *); void roff_endparse(struct roff *); void roff_setreg(struct roff *, const char *, int, char sign); int roff_getreg(struct roff *, const char *); diff --git a/usr.bin/mandoc/mandoc.1 b/usr.bin/mandoc/mandoc.1 index 7873440fbca..cc55b3ceac0 100644 --- a/usr.bin/mandoc/mandoc.1 +++ b/usr.bin/mandoc/mandoc.1 @@ -1,4 +1,4 @@ -.\" $OpenBSD: mandoc.1,v 1.150 2018/07/28 18:32:30 schwarze Exp $ +.\" $OpenBSD: mandoc.1,v 1.151 2018/08/23 14:16:12 schwarze Exp $ .\" .\" Copyright (c) 2009, 2010, 2011 Kristaps Dzonsons <kristaps@bsd.lv> .\" Copyright (c) 2012, 2014-2018 Ingo Schwarze <schwarze@openbsd.org> @@ -15,7 +15,7 @@ .\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF .\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. .\" -.Dd $Mdocdate: July 28 2018 $ +.Dd $Mdocdate: August 23 2018 $ .Dt MANDOC 1 .Os .Sh NAME @@ -1807,6 +1807,13 @@ or macro. It may be mistyped or unsupported. The request or macro is discarded including its arguments. +.It Sy "skipping request outside macro" +.Pq roff +A +.Ic shift +or +.Ic return +request occurs outside any macro definition and has no effect. .It Sy "skipping insecure request" .Pq roff An input file attempted to run a shell command @@ -1916,6 +1923,14 @@ When parsing for a request or a user-defined macro name to be called, only the escape sequence is discarded. The characters preceding it are used as the request or macro name, the characters following it are used as the arguments to the request or macro. +.It Sy "using macro argument outside macro" +.Pq roff +The escape sequence \e$ occurs outside any macro definition +and expands to the empty string. +.It Sy "argument number is not numeric" +.Pq roff +The argument of the escape sequence \e$ is not a digit; +the escape sequence expands to the empty string. .It Sy "NOT IMPLEMENTED: Bd -file" .Pq mdoc For security reasons, the @@ -1978,6 +1993,13 @@ or .Ic \&gsize statement has a non-numeric or negative argument or no argument at all. The invalid request or statement is ignored. +.It Sy "excessive shift" +.Pq roff +The argument of a +.Ic shift +request is larger than the number of arguments of the macro that is +currently being executed. +All macro arguments are deleted and \en(.$ is set to zero. .It Sy "NOT IMPLEMENTED: .so with absolute path or \(dq..\(dq" .Pq roff For security reasons, diff --git a/usr.bin/mandoc/mandoc.h b/usr.bin/mandoc/mandoc.h index 79ddd10c9a2..c57a60f84fc 100644 --- a/usr.bin/mandoc/mandoc.h +++ b/usr.bin/mandoc/mandoc.h @@ -1,4 +1,4 @@ -/* $OpenBSD: mandoc.h,v 1.191 2018/08/16 13:49:40 schwarze Exp $ */ +/* $OpenBSD: mandoc.h,v 1.192 2018/08/23 14:16:12 schwarze Exp $ */ /* * Copyright (c) 2010, 2011, 2014 Kristaps Dzonsons <kristaps@bsd.lv> * Copyright (c) 2010-2018 Ingo Schwarze <schwarze@openbsd.org> @@ -195,6 +195,7 @@ enum mandocerr { MANDOCERR_ROFFLOOP, /* input stack limit exceeded, infinite loop? */ MANDOCERR_CHAR_BAD, /* skipping bad character: number */ MANDOCERR_MACRO, /* skipping unknown macro: macro */ + MANDOCERR_REQ_NOMAC, /* skipping request outside macro: ... */ MANDOCERR_REQ_INSEC, /* skipping insecure request: request */ MANDOCERR_IT_STRAY, /* skipping item outside list: It ... */ MANDOCERR_TA_STRAY, /* skipping column outside column list: Ta */ @@ -205,6 +206,8 @@ enum mandocerr { /* related to request and macro arguments */ MANDOCERR_NAMESC, /* escaped character not allowed in a name: name */ + MANDOCERR_ARG_UNDEF, /* using macro argument outside macro */ + MANDOCERR_ARG_NONUM, /* argument number is not numeric */ MANDOCERR_BD_FILE, /* NOT IMPLEMENTED: Bd -file */ MANDOCERR_BD_NOARG, /* skipping display without arguments: Bd */ MANDOCERR_BL_NOTYPE, /* missing list type, using -item: Bl */ @@ -213,6 +216,7 @@ enum mandocerr { MANDOCERR_OS_UNAME, /* uname(3) system call failed, using UNKNOWN */ MANDOCERR_ST_BAD, /* unknown standard specifier: St standard */ MANDOCERR_IT_NONUM, /* skipping request without numeric argument */ + MANDOCERR_SHIFT, /* excessive shift: ..., but max is ... */ MANDOCERR_SO_PATH, /* NOT IMPLEMENTED: .so with absolute path or ".." */ MANDOCERR_SO_FAIL, /* .so request failed */ MANDOCERR_ARG_SKIP, /* skipping all arguments: macro args */ diff --git a/usr.bin/mandoc/read.c b/usr.bin/mandoc/read.c index 0d119023ce5..079bce1982e 100644 --- a/usr.bin/mandoc/read.c +++ b/usr.bin/mandoc/read.c @@ -1,4 +1,4 @@ -/* $OpenBSD: read.c,v 1.168 2018/07/28 18:32:30 schwarze Exp $ */ +/* $OpenBSD: read.c,v 1.169 2018/08/23 14:16:12 schwarze Exp $ */ /* * Copyright (c) 2008, 2009, 2010, 2011 Kristaps Dzonsons <kristaps@bsd.lv> * Copyright (c) 2010-2018 Ingo Schwarze <schwarze@openbsd.org> @@ -60,7 +60,7 @@ struct mparse { static void choose_parser(struct mparse *); static void resize_buf(struct buf *, size_t); -static int mparse_buf_r(struct mparse *, struct buf, size_t, int); +static enum rofferr mparse_buf_r(struct mparse *, struct buf, size_t, int); static int read_whole_file(struct mparse *, const char *, int, struct buf *, int *); static void mparse_end(struct mparse *); @@ -231,6 +231,7 @@ static const char * const mandocerrs[MANDOCERR_MAX] = { "input stack limit exceeded, infinite loop?", "skipping bad character", "skipping unknown macro", + "ignoring request outside macro", "skipping insecure request", "skipping item outside list", "skipping column outside column list", @@ -241,6 +242,8 @@ static const char * const mandocerrs[MANDOCERR_MAX] = { /* related to request and macro arguments */ "escaped character not allowed in a name", + "using macro argument outside macro", + "argument number is not numeric", "NOT IMPLEMENTED: Bd -file", "skipping display without arguments", "missing list type, using -item", @@ -249,6 +252,7 @@ static const char * const mandocerrs[MANDOCERR_MAX] = { "uname(3) system call failed, using UNKNOWN", "unknown standard specifier", "skipping request without numeric argument", + "excessive shift", "NOT IMPLEMENTED: .so with absolute path or \"..\"", ".so request failed", "skipping all arguments", @@ -336,14 +340,14 @@ choose_parser(struct mparse *curp) * macros, inline equations, and input line traps) * and indirectly (for .so file inclusion). */ -static int +static enum rofferr mparse_buf_r(struct mparse *curp, struct buf blk, size_t i, int start) { struct buf ln; const char *save_file; char *cp; size_t pos; /* byte number in the ln buffer */ - enum rofferr rr; + enum rofferr line_result, sub_result; int of; int lnn; /* line number in the real file */ int fd; @@ -466,20 +470,36 @@ mparse_buf_r(struct mparse *curp, struct buf blk, size_t i, int start) [curp->secondary->sz] = '\0'; } rerun: - rr = roff_parseln(curp->roff, curp->line, &ln, &of); + line_result = roff_parseln(curp->roff, curp->line, &ln, &of); - switch (rr) { + switch (line_result) { case ROFF_REPARSE: - if (++curp->reparse_count > REPARSE_LIMIT) + case ROFF_USERCALL: + if (++curp->reparse_count > REPARSE_LIMIT) { + sub_result = ROFF_IGN; mandoc_msg(MANDOCERR_ROFFLOOP, curp, curp->line, pos, NULL); - else if (mparse_buf_r(curp, ln, of, 0) == 1 || - start == 1) { + } else { + sub_result = mparse_buf_r(curp, ln, of, 0); + if (line_result == ROFF_USERCALL) { + if (sub_result == ROFF_USERRET) + sub_result = ROFF_CONT; + roff_userret(curp->roff); + } + if (start || sub_result == ROFF_CONT) { + pos = 0; + continue; + } + } + free(ln.buf); + return sub_result; + case ROFF_USERRET: + if (start) { pos = 0; continue; } free(ln.buf); - return 0; + return ROFF_USERRET; case ROFF_APPEND: pos = strlen(ln.buf); continue; @@ -493,7 +513,7 @@ rerun: (i >= blk.sz || blk.buf[i] == '\0')) { curp->sodest = mandoc_strdup(ln.buf + of); free(ln.buf); - return 1; + return ROFF_CONT; } /* * We remove `so' clauses from our lookaside @@ -545,7 +565,7 @@ rerun: } free(ln.buf); - return 1; + return ROFF_CONT; } static int diff --git a/usr.bin/mandoc/roff.c b/usr.bin/mandoc/roff.c index b6731170b7d..442196e892f 100644 --- a/usr.bin/mandoc/roff.c +++ b/usr.bin/mandoc/roff.c @@ -1,4 +1,4 @@ -/* $OpenBSD: roff.c,v 1.210 2018/08/21 18:15:16 schwarze Exp $ */ +/* $OpenBSD: roff.c,v 1.211 2018/08/23 14:16:12 schwarze Exp $ */ /* * Copyright (c) 2008-2012, 2014 Kristaps Dzonsons <kristaps@bsd.lv> * Copyright (c) 2010-2015, 2017, 2018 Ingo Schwarze <schwarze@openbsd.org> @@ -83,10 +83,21 @@ struct roffreq { char name[]; }; +/* + * A macro processing context. + * More than one is needed when macro calls are nested. + */ +struct mctx { + char **argv; + int argc; + int argsz; +}; + struct roff { struct mparse *parse; /* parse point */ struct roff_man *man; /* mdoc or man parser */ struct roffnode *last; /* leaf of stack */ + struct mctx *mstack; /* stack of macro contexts */ int *rstack; /* stack of inverted `ie' values */ struct ohash *reqtab; /* request lookup table */ struct roffreg *regtab; /* number registers */ @@ -102,6 +113,8 @@ struct roff { struct eqn_node *eqn; /* active equation parser */ int eqn_inline; /* current equation is inline */ int options; /* parse options */ + int mstacksz; /* current size of mstack */ + int mstackpos; /* position in mstack */ int rstacksz; /* current size limit of rstack */ int rstackpos; /* position in rstack */ int format; /* current file in mdoc or man format */ @@ -203,6 +216,7 @@ static enum rofferr roff_parsetext(struct roff *, struct buf *, int, int *); static enum rofferr roff_renamed(ROFF_ARGS); static enum rofferr roff_res(struct roff *, struct buf *, int, int); +static enum rofferr roff_return(ROFF_ARGS); static enum rofferr roff_rm(ROFF_ARGS); static enum rofferr roff_rn(ROFF_ARGS); static enum rofferr roff_rr(ROFF_ARGS); @@ -212,6 +226,7 @@ static void roff_setstr(struct roff *, const char *, const char *, int); static void roff_setstrn(struct roffkv **, const char *, size_t, const char *, size_t, int); +static enum rofferr roff_shift(ROFF_ARGS); static enum rofferr roff_so(ROFF_ARGS); static enum rofferr roff_tr(ROFF_ARGS); static enum rofferr roff_Dd(ROFF_ARGS); @@ -519,7 +534,7 @@ static struct roffmac roffs[TOKEN_NONE] = { { roff_unsupp, NULL, NULL, 0 }, /* rchar */ { roff_line_ignore, NULL, NULL, 0 }, /* rd */ { roff_line_ignore, NULL, NULL, 0 }, /* recursionlimit */ - { roff_unsupp, NULL, NULL, 0 }, /* return */ + { roff_return, NULL, NULL, 0 }, /* return */ { roff_unsupp, NULL, NULL, 0 }, /* rfschar */ { roff_line_ignore, NULL, NULL, 0 }, /* rhang */ { roff_rm, NULL, NULL, 0 }, /* rm */ @@ -531,7 +546,7 @@ static struct roffmac roffs[TOKEN_NONE] = { { roff_unsupp, NULL, NULL, 0 }, /* schar */ { roff_line_ignore, NULL, NULL, 0 }, /* sentchar */ { roff_line_ignore, NULL, NULL, 0 }, /* shc */ - { roff_unsupp, NULL, NULL, 0 }, /* shift */ + { roff_shift, NULL, NULL, 0 }, /* shift */ { roff_line_ignore, NULL, NULL, 0 }, /* sizes */ { roff_so, NULL, NULL, 0 }, /* so */ { roff_line_ignore, NULL, NULL, 0 }, /* spacewidth */ @@ -711,6 +726,9 @@ roff_free1(struct roff *r) eqn_free(r->last_eqn); r->last_eqn = r->eqn = NULL; + while (r->mstackpos >= 0) + roff_userret(r); + while (r->last) roffnode_pop(r); @@ -750,7 +768,12 @@ roff_reset(struct roff *r) void roff_free(struct roff *r) { + int i; + roff_free1(r); + for (i = 0; i < r->mstacksz; i++) + free(r->mstack[i].argv); + free(r->mstack); roffhash_free(r->reqtab); free(r); } @@ -765,6 +788,7 @@ roff_alloc(struct mparse *parse, int options) r->reqtab = roffhash_alloc(0, ROFF_RENAMED); r->options = options; r->format = options & (MPARSE_MDOC | MPARSE_MAN); + r->mstackpos = -1; r->rstackpos = -1; r->escape = '\\'; return r; @@ -1121,6 +1145,7 @@ deroff(char **dest, const struct roff_node *n) static enum rofferr roff_res(struct roff *r, struct buf *buf, int ln, int pos) { + struct mctx *ctx; /* current macro call context */ char ubuf[24]; /* buffer to print the number */ struct roff_node *n; /* used for header comments */ const char *start; /* start of the string to process */ @@ -1132,11 +1157,14 @@ roff_res(struct roff *r, struct buf *buf, int ln, int pos) char *nbuf; /* new buffer to copy buf->buf to */ size_t maxl; /* expected length of the escape name */ size_t naml; /* actual length of the escape name */ + size_t asz; /* length of the replacement */ + size_t rsz; /* length of the rest of the string */ enum mandoc_esc esc; /* type of the escape sequence */ int inaml; /* length returned from mandoc_escape() */ int expand_count; /* to avoid infinite loops */ int npos; /* position in numeric expression */ int arg_complete; /* argument not interrupted by eol */ + int quote_args; /* true for \\$@, false for \\$* */ int done; /* no more input available */ int deftype; /* type of definition to paste */ int rcsid; /* kind of RCS id seen */ @@ -1273,6 +1301,7 @@ roff_res(struct roff *r, struct buf *buf, int ln, int pos) cp = stesc + 1; switch (*cp) { case '*': + case '$': res = NULL; break; case 'B': @@ -1389,6 +1418,62 @@ roff_res(struct roff *r, struct buf *buf, int ln, int pos) } } break; + case '$': + if (r->mstackpos < 0) { + mandoc_vmsg(MANDOCERR_ARG_UNDEF, + r->parse, ln, (int)(stesc - buf->buf), + "%.3s", stesc); + break; + } + ctx = r->mstack + r->mstackpos; + npos = stesc[2] - '1'; + if (npos >= 0 && npos <= 8) { + res = npos < ctx->argc ? + ctx->argv[npos] : ""; + break; + } + if (stesc[2] == '*') + quote_args = 0; + else if (stesc[2] == '@') + quote_args = 1; + else { + mandoc_vmsg(MANDOCERR_ARG_NONUM, + r->parse, ln, (int)(stesc - buf->buf), + "%.3s", stesc); + break; + } + asz = 0; + for (npos = 0; npos < ctx->argc; npos++) { + if (npos) + asz++; /* blank */ + if (quote_args) + asz += 2; /* quotes */ + asz += strlen(ctx->argv[npos]); + } + if (asz != 3) { + rsz = buf->sz - (stesc - buf->buf) - 3; + if (asz < 3) + memmove(stesc + asz, stesc + 3, rsz); + buf->sz += asz - 3; + nbuf = mandoc_realloc(buf->buf, buf->sz); + start = nbuf + pos; + stesc = nbuf + (stesc - buf->buf); + buf->buf = nbuf; + if (asz > 3) + memmove(stesc + asz, stesc + 3, rsz); + } + for (npos = 0; npos < ctx->argc; npos++) { + if (npos) + *stesc++ = ' '; + if (quote_args) + *stesc++ = '"'; + cp = ctx->argv[npos]; + while (*cp != '\0') + *stesc++ = *cp++; + if (quote_args) + *stesc++ = '"'; + } + continue; case 'B': npos = 0; ubuf[0] = arg_complete && @@ -1412,9 +1497,10 @@ roff_res(struct roff *r, struct buf *buf, int ln, int pos) } if (res == NULL) { - mandoc_vmsg(MANDOCERR_STR_UNDEF, - r->parse, ln, (int)(stesc - buf->buf), - "%.*s", (int)naml, stnam); + if (stesc[1] == '*') + mandoc_vmsg(MANDOCERR_STR_UNDEF, + r->parse, ln, (int)(stesc - buf->buf), + "%.*s", (int)naml, stnam); res = ""; } else if (buf->sz + strlen(res) > SHRT_MAX) { mandoc_msg(MANDOCERR_ROFFLOOP, r->parse, @@ -1632,6 +1718,25 @@ roff_parseln(struct roff *r, int ln, struct buf *buf, int *offs) return (*roffs[t].proc)(r, t, buf, ln, spos, pos, offs); } +/* + * Internal interface function to tell the roff parser that execution + * of the current macro ended. This is required because macro + * definitions usually do not end with a .return request. + */ +void +roff_userret(struct roff *r) +{ + struct mctx *ctx; + int i; + + assert(r->mstackpos >= 0); + ctx = r->mstack + r->mstackpos; + for (i = 0; i < ctx->argc; i++) + free(ctx->argv[i]); + ctx->argc = 0; + r->mstackpos--; +} + void roff_endparse(struct roff *r) { @@ -2660,7 +2765,7 @@ roff_getregro(const struct roff *r, const char *name) switch (*name) { case '$': /* Number of arguments of the last macro evaluated. */ - return 0; + return r->mstackpos < 0 ? 0 : r->mstack[r->mstackpos].argc; case 'A': /* ASCII approximation mode is always off. */ return 0; case 'g': /* Groff compatibility mode is always on. */ @@ -3291,6 +3396,22 @@ roff_tr(ROFF_ARGS) return ROFF_IGN; } +/* + * Implementation of the .return request. + * There is no need to call roff_userret() from here. + * The read module will call that after rewinding the reader stack + * to the place from where the current macro was called. + */ +static enum rofferr +roff_return(ROFF_ARGS) +{ + if (r->mstackpos >= 0) + return ROFF_USERRET; + + mandoc_msg(MANDOCERR_REQ_NOMAC, r->parse, ln, ppos, "return"); + return ROFF_IGN; +} + static enum rofferr roff_rn(ROFF_ARGS) { @@ -3342,6 +3463,39 @@ roff_rn(ROFF_ARGS) } static enum rofferr +roff_shift(ROFF_ARGS) +{ + struct mctx *ctx; + int levels, i; + + levels = 1; + if (buf->buf[pos] != '\0' && + roff_evalnum(r, ln, buf->buf, &pos, &levels, 0) == 0) { + mandoc_vmsg(MANDOCERR_CE_NONUM, r->parse, + ln, pos, "shift %s", buf->buf + pos); + levels = 1; + } + if (r->mstackpos < 0) { + mandoc_msg(MANDOCERR_REQ_NOMAC, r->parse, ln, ppos, "shift"); + return ROFF_IGN; + } + ctx = r->mstack + r->mstackpos; + if (levels > ctx->argc) { + mandoc_vmsg(MANDOCERR_SHIFT, r->parse, + ln, pos, "%d, but max is %d", levels, ctx->argc); + levels = ctx->argc; + } + if (levels == 0) + return ROFF_IGN; + for (i = 0; i < levels; i++) + free(ctx->argv[i]); + ctx->argc -= levels; + for (i = 0; i < ctx->argc; i++) + ctx->argv[i] = ctx->argv[i + levels]; + return ROFF_IGN; +} + +static enum rofferr roff_so(ROFF_ARGS) { char *name, *cp; @@ -3376,186 +3530,58 @@ roff_so(ROFF_ARGS) static enum rofferr roff_userdef(ROFF_ARGS) { - const char *arg[16], *ap; - char *cp, *n1, *n2; - int argc, expand_count, i, ib, ie, quote_args; - size_t asz, esz, rsz; + struct mctx *ctx; + char *arg, *ap, *dst, *src; + size_t sz; - /* - * Collect pointers to macro argument strings - * and NUL-terminate them. - */ + /* Initialize a new macro stack context. */ - argc = 0; - cp = buf->buf + pos; - for (i = 0; i < 16; i++) { - if (*cp == '\0') - arg[i] = ""; - else { - arg[i] = mandoc_getarg(r->parse, &cp, ln, &pos); - argc = i + 1; - } + if (++r->mstackpos == r->mstacksz) { + r->mstack = mandoc_recallocarray(r->mstack, + r->mstacksz, r->mstacksz + 8, sizeof(*r->mstack)); + r->mstacksz += 8; } + ctx = r->mstack + r->mstackpos; + ctx->argsz = 0; + ctx->argc = 0; + ctx->argv = NULL; /* - * Expand macro arguments. + * Collect pointers to macro argument strings, + * NUL-terminating them and escaping quotes. */ - buf->sz = strlen(r->current_string) + 1; - n1 = n2 = cp = mandoc_malloc(buf->sz); - memcpy(n1, r->current_string, buf->sz); - expand_count = 0; - while (*cp != '\0') { - - /* Scan ahead for the next argument invocation. */ - - if (*cp++ != '\\') - continue; - if (*cp++ != '$') - continue; - - quote_args = 0; - switch (*cp) { - case '@': /* \\$@ inserts all arguments, quoted */ - quote_args = 1; - /* FALLTHROUGH */ - case '*': /* \\$* inserts all arguments, unquoted */ - ib = 0; - ie = argc - 1; - break; - default: /* \\$1 .. \\$9 insert one argument */ - ib = ie = *cp - '1'; - if (ib < 0 || ib > 8) - continue; - break; - } - cp -= 2; - - /* - * Prevent infinite recursion. - */ - - if (cp >= n2) - expand_count = 1; - else if (++expand_count > EXPAND_LIMIT) { - mandoc_msg(MANDOCERR_ROFFLOOP, r->parse, - ln, (int)(cp - n1), NULL); - free(buf->buf); - buf->buf = n1; - *offs = 0; - return ROFF_IGN; - } - - /* - * Determine the size of the expanded argument, - * taking escaping of quotes into account. - */ - - asz = ie > ib ? ie - ib : 0; /* for blanks */ - for (i = ib; i <= ie; i++) { - if (quote_args) - asz += 2; - for (ap = arg[i]; *ap != '\0'; ap++) { - asz++; - if (*ap == '"') - asz += 3; - } - } - if (asz != 3) { - - /* - * Determine the size of the rest of the - * unexpanded macro, including the NUL. - */ - - rsz = buf->sz - (cp - n1) - 3; - - /* - * When shrinking, move before - * releasing the storage. - */ - - if (asz < 3) - memmove(cp + asz, cp + 3, rsz); - - /* - * Resize the storage for the macro - * and readjust the parse pointer. - */ - - buf->sz += asz - 3; - n2 = mandoc_realloc(n1, buf->sz); - cp = n2 + (cp - n1); - n1 = n2; - - /* - * When growing, make room - * for the expanded argument. - */ - - if (asz > 3) - memmove(cp + asz, cp + 3, rsz); + src = buf->buf + pos; + while (*src != '\0') { + if (ctx->argc == ctx->argsz) { + ctx->argsz += 8; + ctx->argv = mandoc_reallocarray(ctx->argv, + ctx->argsz, sizeof(*ctx->argv)); } - - /* Copy the expanded argument, escaping quotes. */ - - n2 = cp; - for (i = ib; i <= ie; i++) { - if (quote_args) - *n2++ = '"'; - for (ap = arg[i]; *ap != '\0'; ap++) { - if (*ap == '"') { - memcpy(n2, "\\(dq", 4); - n2 += 4; - } else - *n2++ = *ap; - } - if (quote_args) - *n2++ = '"'; - if (i < ie) - *n2++ = ' '; - } - } - - /* - * Expand the number of arguments, if it is used. - * This never makes the expanded macro longer. - */ - - for (cp = n1; *cp != '\0'; cp++) { - if (cp[0] != '\\') - continue; - if (cp[1] == '\\') { - cp++; - continue; + arg = mandoc_getarg(r->parse, &src, ln, &pos); + sz = 1; /* For the terminating NUL. */ + for (ap = arg; *ap != '\0'; ap++) + sz += *ap == '"' ? 4 : 1; + ctx->argv[ctx->argc++] = dst = mandoc_malloc(sz); + for (ap = arg; *ap != '\0'; ap++) { + if (*ap == '"') { + memcpy(dst, "\\(dq", 4); + dst += 4; + } else + *dst++ = *ap; } - if (strncmp(cp + 1, "n(.$", 4) == 0) - esz = 5; - else if (strncmp(cp + 1, "n[.$]", 5) == 0) - esz = 6; - else - continue; - asz = snprintf(cp, esz, "%d", argc); - assert(asz < esz); - rsz = buf->sz - (cp - n1) - esz; - memmove(cp + asz, cp + esz, rsz); - buf->sz -= esz - asz; - n2 = mandoc_realloc(n1, buf->sz); - cp = n2 + (cp - n1) + asz; - n1 = n2; + *dst = '\0'; } - /* - * Replace the macro invocation - * by the expanded macro. - */ + /* Replace the macro invocation by the macro definition. */ free(buf->buf); - buf->buf = n1; + buf->buf = mandoc_strdup(r->current_string); + buf->sz = strlen(buf->buf) + 1; *offs = 0; return buf->sz > 1 && buf->buf[buf->sz - 2] == '\n' ? - ROFF_REPARSE : ROFF_APPEND; + ROFF_USERCALL : ROFF_APPEND; } /* |