1 /*- 2 * Copyright (c) 1992 Diomidis Spinellis. 3 * Copyright (c) 1992, 1993 4 * The Regents of the University of California. All rights reserved. 5 * 6 * This code is derived from software contributed to Berkeley by 7 * Diomidis Spinellis of Imperial College, University of London. 8 * 9 * Redistribution and use in source and binary forms, with or without 10 * modification, are permitted provided that the following conditions 11 * are met: 12 * 1. Redistributions of source code must retain the above copyright 13 * notice, this list of conditions and the following disclaimer. 14 * 2. Redistributions in binary form must reproduce the above copyright 15 * notice, this list of conditions and the following disclaimer in the 16 * documentation and/or other materials provided with the distribution. 17 * 3. All advertising materials mentioning features or use of this software 18 * must display the following acknowledgement: 19 * This product includes software developed by the University of 20 * California, Berkeley and its contributors. 21 * 4. Neither the name of the University nor the names of its contributors 22 * may be used to endorse or promote products derived from this software 23 * without specific prior written permission. 24 * 25 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND 26 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 27 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 28 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE 29 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 30 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 31 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 32 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 33 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 34 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 35 * SUCH DAMAGE. 36 */ 37 38 #ifndef lint 39 static char sccsid[] = "@(#)compile.c 8.1 (Berkeley) 6/6/93"; 40 #endif /* not lint */ 41 42 #include <sys/types.h> 43 #include <sys/stat.h> 44 45 #include <ctype.h> 46 #include <errno.h> 47 #include <fcntl.h> 48 #include <limits.h> 49 #include <regex.h> 50 #include <stdio.h> 51 #include <stdlib.h> 52 #include <string.h> 53 54 #include "defs.h" 55 #include "extern.h" 56 57 #define LHSZ 128 58 #define LHMASK (LHSZ - 1) 59 static struct labhash { 60 struct labhash *lh_next; 61 u_int lh_hash; 62 struct s_command *lh_cmd; 63 int lh_ref; 64 } *labels[LHSZ]; 65 66 static char *compile_addr __P((char *, struct s_addr *)); 67 static char *compile_delimited __P((char *, char *)); 68 static char *compile_flags __P((char *, struct s_subst *)); 69 static char *compile_re __P((char *, regex_t **)); 70 static char *compile_subst __P((char *, struct s_subst *)); 71 static char *compile_text __P((void)); 72 static char *compile_tr __P((char *, char **)); 73 static struct s_command 74 **compile_stream __P((char *, struct s_command **, char *)); 75 static char *duptoeol __P((char *, char *)); 76 static void enterlabel __P((struct s_command *)); 77 static struct s_command 78 *findlabel __P((char *)); 79 static void fixuplabel __P((struct s_command *, struct s_command *)); 80 static void uselabel __P((void)); 81 82 /* 83 * Command specification. This is used to drive the command parser. 84 */ 85 struct s_format { 86 char code; /* Command code */ 87 int naddr; /* Number of address args */ 88 enum e_args args; /* Argument type */ 89 }; 90 91 static struct s_format cmd_fmts[] = { 92 {'{', 2, GROUP}, 93 {'a', 1, TEXT}, 94 {'b', 2, BRANCH}, 95 {'c', 2, TEXT}, 96 {'d', 2, EMPTY}, 97 {'D', 2, EMPTY}, 98 {'g', 2, EMPTY}, 99 {'G', 2, EMPTY}, 100 {'h', 2, EMPTY}, 101 {'H', 2, EMPTY}, 102 {'i', 1, TEXT}, 103 {'l', 2, EMPTY}, 104 {'n', 2, EMPTY}, 105 {'N', 2, EMPTY}, 106 {'p', 2, EMPTY}, 107 {'P', 2, EMPTY}, 108 {'q', 1, EMPTY}, 109 {'r', 1, RFILE}, 110 {'s', 2, SUBST}, 111 {'t', 2, BRANCH}, 112 {'w', 2, WFILE}, 113 {'x', 2, EMPTY}, 114 {'y', 2, TR}, 115 {'!', 2, NONSEL}, 116 {':', 0, LABEL}, 117 {'#', 0, COMMENT}, 118 {'=', 1, EMPTY}, 119 {'\0', 0, COMMENT}, 120 }; 121 122 /* The compiled program. */ 123 struct s_command *prog; 124 125 /* 126 * Compile the program into prog. 127 * Initialise appends. 128 */ 129 void 130 compile() 131 { 132 *compile_stream(NULL, &prog, NULL) = NULL; 133 fixuplabel(prog, NULL); 134 uselabel(); 135 appends = xmalloc(sizeof(struct s_appends) * appendnum); 136 match = xmalloc((maxnsub + 1) * sizeof(regmatch_t)); 137 } 138 139 #define EATSPACE() do { \ 140 if (p) \ 141 while (*p && isascii(*p) && isspace(*p)) \ 142 p++; \ 143 } while (0) 144 145 static struct s_command ** 146 compile_stream(terminator, link, p) 147 char *terminator; 148 struct s_command **link; 149 register char *p; 150 { 151 static char lbuf[_POSIX2_LINE_MAX + 1]; /* To save stack */ 152 struct s_command *cmd, *cmd2; 153 struct s_format *fp; 154 int naddr; /* Number of addresses */ 155 156 if (p != NULL) 157 goto semicolon; 158 for (;;) { 159 if ((p = cu_fgets(lbuf, sizeof(lbuf))) == NULL) { 160 if (terminator != NULL) 161 err(COMPILE, "unexpected EOF (pending }'s)"); 162 return (link); 163 } 164 165 semicolon: EATSPACE(); 166 if (p && (*p == '#' || *p == '\0')) 167 continue; 168 if (*p == '}') { 169 if (terminator == NULL) 170 err(COMPILE, "unexpected }"); 171 return (link); 172 } 173 *link = cmd = xmalloc(sizeof(struct s_command)); 174 link = &cmd->next; 175 cmd->nonsel = cmd->inrange = 0; 176 /* First parse the addresses */ 177 naddr = 0; 178 cmd->a1 = cmd->a2 = NULL; 179 180 /* Valid characters to start an address */ 181 #define addrchar(c) (strchr("0123456789/\\$", (c))) 182 if (addrchar(*p)) { 183 naddr++; 184 cmd->a1 = xmalloc(sizeof(struct s_addr)); 185 p = compile_addr(p, cmd->a1); 186 EATSPACE(); /* EXTENSION */ 187 if (*p == ',') { 188 naddr++; 189 p++; 190 EATSPACE(); /* EXTENSION */ 191 cmd->a2 = xmalloc(sizeof(struct s_addr)); 192 p = compile_addr(p, cmd->a2); 193 } 194 } 195 196 nonsel: /* Now parse the command */ 197 EATSPACE(); 198 if (!*p) 199 err(COMPILE, "command expected"); 200 cmd->code = *p; 201 for (fp = cmd_fmts; fp->code; fp++) 202 if (fp->code == *p) 203 break; 204 if (!fp->code) 205 err(COMPILE, "invalid command code %c", *p); 206 if (naddr > fp->naddr) 207 err(COMPILE, 208 "command %c expects up to %d address(es), found %d", *p, fp->naddr, naddr); 209 switch (fp->args) { 210 case NONSEL: /* ! */ 211 cmd->nonsel = ! cmd->nonsel; 212 p++; 213 goto nonsel; 214 case GROUP: /* { */ 215 p++; 216 EATSPACE(); 217 if (!*p) 218 p = NULL; 219 cmd2 = xmalloc(sizeof(struct s_command)); 220 cmd2->nonsel = cmd2->inrange = 0; 221 cmd2->a1 = cmd2->a2 = NULL; 222 cmd2->code = '}'; 223 *compile_stream("}", &cmd->u.c, p) = cmd2; 224 cmd->next = cmd2; 225 link = &cmd2->next; 226 break; 227 case EMPTY: /* d D g G h H l n N p P q x = \0 */ 228 p++; 229 EATSPACE(); 230 if (*p == ';') { 231 p++; 232 link = &cmd->next; 233 goto semicolon; 234 } 235 if (*p) 236 err(COMPILE, 237 "extra characters at the end of %c command", cmd->code); 238 break; 239 case TEXT: /* a c i */ 240 p++; 241 EATSPACE(); 242 if (*p != '\\') 243 err(COMPILE, 244 "command %c expects \\ followed by text", cmd->code); 245 p++; 246 EATSPACE(); 247 if (*p) 248 err(COMPILE, 249 "extra characters after \\ at the end of %c command", cmd->code); 250 cmd->t = compile_text(); 251 break; 252 case COMMENT: /* \0 # */ 253 break; 254 case WFILE: /* w */ 255 p++; 256 EATSPACE(); 257 if (*p == '\0') 258 err(COMPILE, "filename expected"); 259 cmd->t = duptoeol(p, "w command"); 260 if (aflag) 261 cmd->u.fd = -1; 262 else if ((cmd->u.fd = open(p, 263 O_WRONLY|O_APPEND|O_CREAT|O_TRUNC, 264 DEFFILEMODE)) == -1) 265 err(FATAL, "%s: %s\n", p, strerror(errno)); 266 break; 267 case RFILE: /* r */ 268 p++; 269 EATSPACE(); 270 if (*p == '\0') 271 err(COMPILE, "filename expected"); 272 else 273 cmd->t = duptoeol(p, "read command"); 274 break; 275 case BRANCH: /* b t */ 276 p++; 277 EATSPACE(); 278 if (*p == '\0') 279 cmd->t = NULL; 280 else 281 cmd->t = duptoeol(p, "branch"); 282 break; 283 case LABEL: /* : */ 284 p++; 285 EATSPACE(); 286 cmd->t = duptoeol(p, "label"); 287 if (strlen(p) == 0) 288 err(COMPILE, "empty label"); 289 enterlabel(cmd); 290 break; 291 case SUBST: /* s */ 292 p++; 293 if (*p == '\0' || *p == '\\') 294 err(COMPILE, 295 "substitute pattern can not be delimited by newline or backslash"); 296 cmd->u.s = xmalloc(sizeof(struct s_subst)); 297 p = compile_re(p, &cmd->u.s->re); 298 if (p == NULL) 299 err(COMPILE, "unterminated substitute pattern"); 300 --p; 301 p = compile_subst(p, cmd->u.s); 302 p = compile_flags(p, cmd->u.s); 303 EATSPACE(); 304 if (*p == ';') { 305 p++; 306 link = &cmd->next; 307 goto semicolon; 308 } 309 break; 310 case TR: /* y */ 311 p++; 312 p = compile_tr(p, (char **)&cmd->u.y); 313 EATSPACE(); 314 if (*p == ';') { 315 p++; 316 link = &cmd->next; 317 goto semicolon; 318 } 319 if (*p) 320 err(COMPILE, 321 "extra text at the end of a transform command"); 322 break; 323 } 324 } 325 } 326 327 /* 328 * Get a delimited string. P points to the delimeter of the string; d points 329 * to a buffer area. Newline and delimiter escapes are processed; other 330 * escapes are ignored. 331 * 332 * Returns a pointer to the first character after the final delimiter or NULL 333 * in the case of a non-terminated string. The character array d is filled 334 * with the processed string. 335 */ 336 static char * 337 compile_delimited(p, d) 338 char *p, *d; 339 { 340 char c; 341 342 c = *p++; 343 if (c == '\0') 344 return (NULL); 345 else if (c == '\\') 346 err(COMPILE, "\\ can not be used as a string delimiter"); 347 else if (c == '\n') 348 err(COMPILE, "newline can not be used as a string delimiter"); 349 while (*p) { 350 if (*p == '\\' && p[1] == c) 351 p++; 352 else if (*p == '\\' && p[1] == 'n') { 353 *d++ = '\n'; 354 p += 2; 355 continue; 356 } else if (*p == '\\' && p[1] == '\\') 357 *d++ = *p++; 358 else if (*p == c) { 359 *d = '\0'; 360 return (p + 1); 361 } 362 *d++ = *p++; 363 } 364 return (NULL); 365 } 366 367 /* 368 * Get a regular expression. P points to the delimiter of the regular 369 * expression; repp points to the address of a regexp pointer. Newline 370 * and delimiter escapes are processed; other escapes are ignored. 371 * Returns a pointer to the first character after the final delimiter 372 * or NULL in the case of a non terminated regular expression. The regexp 373 * pointer is set to the compiled regular expression. 374 * Cflags are passed to regcomp. 375 */ 376 static char * 377 compile_re(p, repp) 378 char *p; 379 regex_t **repp; 380 { 381 int eval; 382 char re[_POSIX2_LINE_MAX + 1]; 383 384 p = compile_delimited(p, re); 385 if (p && strlen(re) == 0) { 386 *repp = NULL; 387 return (p); 388 } 389 *repp = xmalloc(sizeof(regex_t)); 390 if (p && (eval = regcomp(*repp, re, 0)) != 0) 391 err(COMPILE, "RE error: %s", strregerror(eval, *repp)); 392 if (maxnsub < (*repp)->re_nsub) 393 maxnsub = (*repp)->re_nsub; 394 return (p); 395 } 396 397 /* 398 * Compile the substitution string of a regular expression and set res to 399 * point to a saved copy of it. Nsub is the number of parenthesized regular 400 * expressions. 401 */ 402 static char * 403 compile_subst(p, s) 404 char *p; 405 struct s_subst *s; 406 { 407 static char lbuf[_POSIX2_LINE_MAX + 1]; 408 int asize, ref, size; 409 char c, *text, *op, *sp; 410 411 c = *p++; /* Terminator character */ 412 if (c == '\0') 413 return (NULL); 414 415 s->maxbref = 0; 416 s->linenum = linenum; 417 asize = 2 * _POSIX2_LINE_MAX + 1; 418 text = xmalloc(asize); 419 size = 0; 420 do { 421 op = sp = text + size; 422 for (; *p; p++) { 423 if (*p == '\\') { 424 p++; 425 if (strchr("123456789", *p) != NULL) { 426 *sp++ = '\\'; 427 ref = *p - '0'; 428 if (s->re != NULL && 429 ref > s->re->re_nsub) 430 err(COMPILE, 431 "\\%c not defined in the RE", *p); 432 if (s->maxbref < ref) 433 s->maxbref = ref; 434 } else if (*p == '&' || *p == '\\') 435 *sp++ = '\\'; 436 } else if (*p == c) { 437 p++; 438 *sp++ = '\0'; 439 size += sp - op; 440 s->new = xrealloc(text, size); 441 return (p); 442 } else if (*p == '\n') { 443 err(COMPILE, 444 "unescaped newline inside substitute pattern"); 445 /* NOTREACHED */ 446 } 447 *sp++ = *p; 448 } 449 size += sp - op; 450 if (asize - size < _POSIX2_LINE_MAX + 1) { 451 asize *= 2; 452 text = xmalloc(asize); 453 } 454 } while (cu_fgets(p = lbuf, sizeof(lbuf))); 455 err(COMPILE, "unterminated substitute in regular expression"); 456 /* NOTREACHED */ 457 } 458 459 /* 460 * Compile the flags of the s command 461 */ 462 static char * 463 compile_flags(p, s) 464 char *p; 465 struct s_subst *s; 466 { 467 int gn; /* True if we have seen g or n */ 468 char wfile[_POSIX2_LINE_MAX + 1], *q; 469 470 s->n = 1; /* Default */ 471 s->p = 0; 472 s->wfile = NULL; 473 s->wfd = -1; 474 for (gn = 0;;) { 475 EATSPACE(); /* EXTENSION */ 476 switch (*p) { 477 case 'g': 478 if (gn) 479 err(COMPILE, 480 "more than one number or 'g' in substitute flags"); 481 gn = 1; 482 s->n = 0; 483 break; 484 case '\0': 485 case '\n': 486 case ';': 487 return (p); 488 case 'p': 489 s->p = 1; 490 break; 491 case '1': case '2': case '3': 492 case '4': case '5': case '6': 493 case '7': case '8': case '9': 494 if (gn) 495 err(COMPILE, 496 "more than one number or 'g' in substitute flags"); 497 gn = 1; 498 /* XXX Check for overflow */ 499 s->n = (int)strtol(p, &p, 10); 500 break; 501 case 'w': 502 p++; 503 #ifdef HISTORIC_PRACTICE 504 if (*p != ' ') { 505 err(WARNING, "space missing before w wfile"); 506 return (p); 507 } 508 #endif 509 EATSPACE(); 510 q = wfile; 511 while (*p) { 512 if (*p == '\n') 513 break; 514 *q++ = *p++; 515 } 516 *q = '\0'; 517 if (q == wfile) 518 err(COMPILE, "no wfile specified"); 519 s->wfile = strdup(wfile); 520 if (!aflag && (s->wfd = open(wfile, 521 O_WRONLY|O_APPEND|O_CREAT|O_TRUNC, 522 DEFFILEMODE)) == -1) 523 err(FATAL, "%s: %s\n", wfile, strerror(errno)); 524 return (p); 525 default: 526 err(COMPILE, 527 "bad flag in substitute command: '%c'", *p); 528 break; 529 } 530 p++; 531 } 532 } 533 534 /* 535 * Compile a translation set of strings into a lookup table. 536 */ 537 static char * 538 compile_tr(p, transtab) 539 char *p; 540 char **transtab; 541 { 542 int i; 543 char *lt, *op, *np; 544 char old[_POSIX2_LINE_MAX + 1]; 545 char new[_POSIX2_LINE_MAX + 1]; 546 547 if (*p == '\0' || *p == '\\') 548 err(COMPILE, 549 "transform pattern can not be delimited by newline or backslash"); 550 p = compile_delimited(p, old); 551 if (p == NULL) { 552 err(COMPILE, "unterminated transform source string"); 553 return (NULL); 554 } 555 p = compile_delimited(--p, new); 556 if (p == NULL) { 557 err(COMPILE, "unterminated transform target string"); 558 return (NULL); 559 } 560 EATSPACE(); 561 if (strlen(new) != strlen(old)) { 562 err(COMPILE, "transform strings are not the same length"); 563 return (NULL); 564 } 565 /* We assume characters are 8 bits */ 566 lt = xmalloc(UCHAR_MAX); 567 for (i = 0; i <= UCHAR_MAX; i++) 568 lt[i] = (char)i; 569 for (op = old, np = new; *op; op++, np++) 570 lt[(u_char)*op] = *np; 571 *transtab = lt; 572 return (p); 573 } 574 575 /* 576 * Compile the text following an a or i command. 577 */ 578 static char * 579 compile_text() 580 { 581 int asize, size; 582 char *text, *p, *op, *s; 583 char lbuf[_POSIX2_LINE_MAX + 1]; 584 585 asize = 2 * _POSIX2_LINE_MAX + 1; 586 text = xmalloc(asize); 587 size = 0; 588 while (cu_fgets(lbuf, sizeof(lbuf))) { 589 op = s = text + size; 590 p = lbuf; 591 EATSPACE(); 592 for (; *p; p++) { 593 if (*p == '\\') 594 p++; 595 *s++ = *p; 596 } 597 size += s - op; 598 if (p[-2] != '\\') { 599 *s = '\0'; 600 break; 601 } 602 if (asize - size < _POSIX2_LINE_MAX + 1) { 603 asize *= 2; 604 text = xmalloc(asize); 605 } 606 } 607 return (xrealloc(text, size + 1)); 608 } 609 610 /* 611 * Get an address and return a pointer to the first character after 612 * it. Fill the structure pointed to according to the address. 613 */ 614 static char * 615 compile_addr(p, a) 616 char *p; 617 struct s_addr *a; 618 { 619 char *end; 620 621 switch (*p) { 622 case '\\': /* Context address */ 623 ++p; 624 /* FALLTHROUGH */ 625 case '/': /* Context address */ 626 p = compile_re(p, &a->u.r); 627 if (p == NULL) 628 err(COMPILE, "unterminated regular expression"); 629 a->type = AT_RE; 630 return (p); 631 632 case '$': /* Last line */ 633 a->type = AT_LAST; 634 return (p + 1); 635 /* Line number */ 636 case '0': case '1': case '2': case '3': case '4': 637 case '5': case '6': case '7': case '8': case '9': 638 a->type = AT_LINE; 639 a->u.l = strtol(p, &end, 10); 640 return (end); 641 default: 642 err(COMPILE, "expected context address"); 643 return (NULL); 644 } 645 } 646 647 /* 648 * duptoeol -- 649 * Return a copy of all the characters up to \n or \0. 650 */ 651 static char * 652 duptoeol(s, ctype) 653 register char *s; 654 char *ctype; 655 { 656 size_t len; 657 int ws; 658 char *start; 659 660 ws = 0; 661 for (start = s; *s != '\0' && *s != '\n'; ++s) 662 ws = isspace(*s); 663 *s = '\0'; 664 if (ws) 665 err(WARNING, "whitespace after %s", ctype); 666 len = s - start + 1; 667 return (memmove(xmalloc(len), start, len)); 668 } 669 670 /* 671 * Convert goto label names to addresses, and count a and r commands, in 672 * the given subset of the script. Free the memory used by labels in b 673 * and t commands (but not by :). 674 * 675 * TODO: Remove } nodes 676 */ 677 static void 678 fixuplabel(cp, end) 679 struct s_command *cp, *end; 680 { 681 682 for (; cp != end; cp = cp->next) 683 switch (cp->code) { 684 case 'a': 685 case 'r': 686 appendnum++; 687 break; 688 case 'b': 689 case 't': 690 /* Resolve branch target. */ 691 if (cp->t == NULL) { 692 cp->u.c = NULL; 693 break; 694 } 695 if ((cp->u.c = findlabel(cp->t)) == NULL) 696 err(COMPILE2, "undefined label '%s'", cp->t); 697 free(cp->t); 698 break; 699 case '{': 700 /* Do interior commands. */ 701 fixuplabel(cp->u.c, cp->next); 702 break; 703 } 704 } 705 706 /* 707 * Associate the given command label for later lookup. 708 */ 709 static void 710 enterlabel(cp) 711 struct s_command *cp; 712 { 713 register struct labhash **lhp, *lh; 714 register u_char *p; 715 register u_int h, c; 716 717 for (h = 0, p = (u_char *)cp->t; (c = *p) != 0; p++) 718 h = (h << 5) + h + c; 719 lhp = &labels[h & LHMASK]; 720 for (lh = *lhp; lh != NULL; lh = lh->lh_next) 721 if (lh->lh_hash == h && strcmp(cp->t, lh->lh_cmd->t) == 0) 722 err(COMPILE2, "duplicate label '%s'", cp->t); 723 lh = xmalloc(sizeof *lh); 724 lh->lh_next = *lhp; 725 lh->lh_hash = h; 726 lh->lh_cmd = cp; 727 lh->lh_ref = 0; 728 *lhp = lh; 729 } 730 731 /* 732 * Find the label contained in the command l in the command linked 733 * list cp. L is excluded from the search. Return NULL if not found. 734 */ 735 static struct s_command * 736 findlabel(name) 737 char *name; 738 { 739 register struct labhash *lh; 740 register u_char *p; 741 register u_int h, c; 742 743 for (h = 0, p = (u_char *)name; (c = *p) != 0; p++) 744 h = (h << 5) + h + c; 745 for (lh = labels[h & LHMASK]; lh != NULL; lh = lh->lh_next) { 746 if (lh->lh_hash == h && strcmp(name, lh->lh_cmd->t) == 0) { 747 lh->lh_ref = 1; 748 return (lh->lh_cmd); 749 } 750 } 751 return (NULL); 752 } 753 754 /* 755 * Warn about any unused labels. As a side effect, release the label hash 756 * table space. 757 */ 758 static void 759 uselabel() 760 { 761 register struct labhash *lh, *next; 762 register int i; 763 764 for (i = 0; i < LHSZ; i++) { 765 for (lh = labels[i]; lh != NULL; lh = next) { 766 next = lh->lh_next; 767 if (!lh->lh_ref) 768 err(WARNING, "unused label '%s'", 769 lh->lh_cmd->t); 770 free(lh); 771 } 772 } 773 } 774