Home | History | Annotate | Line # | Download | only in libterminfo
compile.c revision 1.21
      1 /* $NetBSD: compile.c,v 1.21 2020/03/29 18:54:57 roy Exp $ */
      2 
      3 /*
      4  * Copyright (c) 2009, 2010, 2011, 2020 The NetBSD Foundation, Inc.
      5  *
      6  * This code is derived from software contributed to The NetBSD Foundation
      7  * by Roy Marples.
      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  *
     18  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
     19  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
     20  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
     21  * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
     22  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
     23  * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
     24  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
     25  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
     26  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
     27  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
     28  */
     29 
     30 #if HAVE_NBTOOL_CONFIG_H
     31 #include "nbtool_config.h"
     32 #endif
     33 
     34 #include <sys/cdefs.h>
     35 __RCSID("$NetBSD: compile.c,v 1.21 2020/03/29 18:54:57 roy Exp $");
     36 
     37 #if !HAVE_NBTOOL_CONFIG_H || HAVE_SYS_ENDIAN_H
     38 #include <sys/endian.h>
     39 #endif
     40 
     41 #include <assert.h>
     42 #include <ctype.h>
     43 #include <err.h>
     44 #include <errno.h>
     45 #include <limits.h>
     46 #include <stdarg.h>
     47 #include <stdlib.h>
     48 #include <stdint.h>
     49 #include <stdio.h>
     50 #include <string.h>
     51 #include <term_private.h>
     52 #include <term.h>
     53 
     54 static void __printflike(2, 3)
     55 dowarn(int flags, const char *fmt, ...)
     56 {
     57 	va_list va;
     58 
     59 	errno = EINVAL;
     60 	if (flags & TIC_WARNING) {
     61 		va_start(va, fmt);
     62 		vwarnx(fmt, va);
     63 		va_end(va);
     64 	}
     65 }
     66 
     67 char *
     68 _ti_grow_tbuf(TBUF *tbuf, size_t len)
     69 {
     70 	char *buf;
     71 	size_t l;
     72 
     73 	_DIAGASSERT(tbuf != NULL);
     74 
     75 	l = tbuf->bufpos + len;
     76 	if (l > tbuf->buflen) {
     77 		if (tbuf->buflen == 0)
     78 			buf = malloc(l);
     79 		else
     80 			buf = realloc(tbuf->buf, l);
     81 		if (buf == NULL)
     82 			return NULL;
     83 		tbuf->buf = buf;
     84 		tbuf->buflen = l;
     85 	}
     86 	return tbuf->buf;
     87 }
     88 
     89 const char *
     90 _ti_find_cap(TIC *tic, TBUF *tbuf, char type, short ind)
     91 {
     92 	size_t n;
     93 	uint16_t num;
     94 	const char *cap;
     95 
     96 	_DIAGASSERT(tbuf != NULL);
     97 
     98 	cap = tbuf->buf;
     99 	for (n = tbuf->entries; n > 0; n--) {
    100 		num = _ti_decode_16(&cap);
    101 		if ((short)num == ind)
    102 			return cap;
    103 		switch (type) {
    104 		case 'f':
    105 			cap++;
    106 			break;
    107 		case 'n':
    108 			cap += _ti_numsize(tic);
    109 			break;
    110 		case 's':
    111 			num = _ti_decode_16(&cap);
    112 			cap += num;
    113 			break;
    114 		}
    115 	}
    116 
    117 	errno = ESRCH;
    118 	return NULL;
    119 }
    120 
    121 const char *
    122 _ti_find_extra(TIC *tic, TBUF *tbuf, const char *code)
    123 {
    124 	size_t n;
    125 	uint16_t num;
    126 	const char *cap;
    127 
    128 	_DIAGASSERT(tbuf != NULL);
    129 	_DIAGASSERT(code != NULL);
    130 
    131 	cap = tbuf->buf;
    132 	for (n = tbuf->entries; n > 0; n--) {
    133 		num = _ti_decode_16(&cap);
    134 		if (strcmp(cap, code) == 0)
    135 			return cap + num;
    136 		cap += num;
    137 		switch (*cap++) {
    138 		case 'f':
    139 			cap++;
    140 			break;
    141 		case 'n':
    142 			cap += _ti_numsize(tic);
    143 			break;
    144 		case 's':
    145 			num = _ti_decode_16(&cap);
    146 			cap += num;
    147 			break;
    148 		}
    149 	}
    150 
    151 	errno = ESRCH;
    152 	return NULL;
    153 }
    154 
    155 char *
    156 _ti_getname(int rtype, const char *orig)
    157 {
    158 	const char *delim;
    159 	char *name;
    160 	const char *verstr;
    161 	size_t diff, vlen;
    162 
    163 	switch (rtype) {
    164 	case TERMINFO_RTYPE:
    165 		verstr = TERMINFO_VDELIMSTR "v3";
    166 		break;
    167 	case TERMINFO_RTYPE_O1:
    168 		verstr = "";
    169 		break;
    170 	default:
    171 		errno = EINVAL;
    172 		return NULL;
    173 	}
    174 
    175 	delim = orig;
    176 	while (*delim != '\0' && *delim != TERMINFO_VDELIM)
    177 		delim++;
    178 	diff = delim - orig;
    179 	vlen = strlen(verstr);
    180 	name = malloc(diff + vlen + 1);
    181 	if (name == NULL)
    182 		return NULL;
    183 
    184 	memcpy(name, orig, diff);
    185 	memcpy(name + diff, verstr, vlen + 1);
    186 	return name;
    187 }
    188 
    189 size_t
    190 _ti_store_extra(TIC *tic, int wrn, const char *id, char type, char flag,
    191     int num, const char *str, size_t strl, int flags)
    192 {
    193 	size_t l;
    194 
    195 	_DIAGASSERT(tic != NULL);
    196 
    197 	if (strcmp(id, "use") != 0) {
    198 		if (_ti_find_extra(tic, &tic->extras, id) != NULL)
    199 			return 0;
    200 		if (!(flags & TIC_EXTRA)) {
    201 			if (wrn != 0)
    202 				dowarn(flags, "%s: %s: unknown capability",
    203 				    tic->name, id);
    204 			return 0;
    205 		}
    206 	}
    207 
    208 	l = strlen(id) + 1;
    209 	if (l > UINT16_T_MAX) {
    210 		dowarn(flags, "%s: %s: cap name is too long", tic->name, id);
    211 		return 0;
    212 	}
    213 
    214 	if (!_ti_grow_tbuf(&tic->extras,
    215 		l + strl + sizeof(uint16_t) + _ti_numsize(tic) + 1))
    216 		return 0;
    217 	_ti_encode_buf_count_str(&tic->extras, id, l);
    218 	tic->extras.buf[tic->extras.bufpos++] = type;
    219 	switch (type) {
    220 	case 'f':
    221 		tic->extras.buf[tic->extras.bufpos++] = flag;
    222 		break;
    223 	case 'n':
    224 		_ti_encode_buf_num(&tic->extras, num, tic->rtype);
    225 		break;
    226 	case 's':
    227 		_ti_encode_buf_count_str(&tic->extras, str, strl);
    228 		break;
    229 	}
    230 	tic->extras.entries++;
    231 	return 1;
    232 }
    233 
    234 static void
    235 _ti_encode_buf(char **cap, const TBUF *buf)
    236 {
    237 	if (buf->entries == 0) {
    238 		_ti_encode_16(cap, 0);
    239 	} else {
    240 		_ti_encode_16(cap, buf->bufpos + sizeof(uint16_t));
    241 		_ti_encode_16(cap, buf->entries);
    242 		_ti_encode_str(cap, buf->buf, buf->bufpos);
    243 	}
    244 }
    245 
    246 ssize_t
    247 _ti_flatten(uint8_t **buf, const TIC *tic)
    248 {
    249 	size_t buflen, len, alen, dlen;
    250 	char *cap;
    251 
    252 	_DIAGASSERT(buf != NULL);
    253 	_DIAGASSERT(tic != NULL);
    254 
    255 	len = strlen(tic->name) + 1;
    256 	if (tic->alias == NULL)
    257 		alen = 0;
    258 	else
    259 		alen = strlen(tic->alias) + 1;
    260 	if (tic->desc == NULL)
    261 		dlen = 0;
    262 	else
    263 		dlen = strlen(tic->desc) + 1;
    264 
    265 	buflen = sizeof(char) +
    266 	    sizeof(uint16_t) + len +
    267 	    sizeof(uint16_t) + alen +
    268 	    sizeof(uint16_t) + dlen +
    269 	    (sizeof(uint16_t) * 2) + tic->flags.bufpos +
    270 	    (sizeof(uint16_t) * 2) + tic->nums.bufpos +
    271 	    (sizeof(uint16_t) * 2) + tic->strs.bufpos +
    272 	    (sizeof(uint16_t) * 2) + tic->extras.bufpos;
    273 
    274 	*buf = malloc(buflen);
    275 	if (*buf == NULL)
    276 		return -1;
    277 
    278 	cap = (char *)*buf;
    279 	*cap++ = tic->rtype;
    280 
    281 	_ti_encode_count_str(&cap, tic->name, len);
    282 	_ti_encode_count_str(&cap, tic->alias, alen);
    283 	_ti_encode_count_str(&cap, tic->desc, dlen);
    284 
    285 	_ti_encode_buf(&cap, &tic->flags);
    286 
    287 	_ti_encode_buf(&cap, &tic->nums);
    288 	_ti_encode_buf(&cap, &tic->strs);
    289 	_ti_encode_buf(&cap, &tic->extras);
    290 
    291 	return (uint8_t *)cap - *buf;
    292 }
    293 
    294 static int
    295 encode_string(const char *term, const char *cap, TBUF *tbuf, const char *str,
    296     int flags)
    297 {
    298 	int slash, i, num;
    299 	char ch, *p, *s, last;
    300 
    301 	if (_ti_grow_tbuf(tbuf, strlen(str) + 1) == NULL)
    302 		return -1;
    303 	p = s = tbuf->buf + tbuf->bufpos;
    304 	slash = 0;
    305 	last = '\0';
    306 	/* Convert escape codes */
    307 	while ((ch = *str++) != '\0') {
    308 		if (ch == '\n') {
    309 			/* Following a newline, strip leading whitespace from
    310 			 * capability strings. */
    311 			while (isspace((unsigned char)*str))
    312 				str++;
    313 			continue;
    314 		}
    315 		if (slash == 0 && ch == '\\') {
    316 			slash = 1;
    317 			continue;
    318 		}
    319 		if (slash == 0) {
    320 			if (last != '%' && ch == '^') {
    321 				ch = *str++;
    322 				if (((unsigned char)ch) >= 128)
    323 					dowarn(flags,
    324 					    "%s: %s: illegal ^ character",
    325 					    term, cap);
    326 				if (ch == '\0')
    327 					break;
    328 				if (ch == '?')
    329 					ch = '\177';
    330 				else if ((ch &= 037) == 0)
    331 					ch = (char)128;
    332 			} else if (!isprint((unsigned char)ch))
    333 				dowarn(flags,
    334 				    "%s: %s: unprintable character",
    335 				    term, cap);
    336 			*p++ = ch;
    337 			last = ch;
    338 			continue;
    339 		}
    340 		slash = 0;
    341 		if (ch >= '0' && ch <= '7') {
    342 			num = ch - '0';
    343 			for (i = 0; i < 2; i++) {
    344 				if (*str < '0' || *str > '7') {
    345 					if (isdigit((unsigned char)*str))
    346 						dowarn(flags,
    347 						    "%s: %s: non octal"
    348 						    " digit", term, cap);
    349 					else
    350 						break;
    351 				}
    352 				num = num * 8 + *str++ - '0';
    353 			}
    354 			if (num == 0)
    355 				num = 0200;
    356 			*p++ = (char)num;
    357 			continue;
    358 		}
    359 		switch (ch) {
    360 		case 'a':
    361 			*p++ = '\a';
    362 			break;
    363 		case 'b':
    364 			*p++ = '\b';
    365 			break;
    366 		case 'e': /* FALLTHROUGH */
    367 		case 'E':
    368 			*p++ = '\033';
    369 			break;
    370 		case 'f':
    371 			*p++ = '\014';
    372 			break;
    373 		case 'l': /* FALLTHROUGH */
    374 		case 'n':
    375 			*p++ = '\n';
    376 			break;
    377 		case 'r':
    378 			*p++ = '\r';
    379 			break;
    380 		case 's':
    381 			*p++ = ' ';
    382 			break;
    383 		case 't':
    384 			*p++ = '\t';
    385 			break;
    386 		default:
    387 			/* We should warn here */
    388 		case '^':
    389 		case ',':
    390 		case ':':
    391 		case '|':
    392 			*p++ = ch;
    393 			break;
    394 		}
    395 		last = ch;
    396 	}
    397 	*p++ = '\0';
    398 	tbuf->bufpos += (size_t)(p - s);
    399 	return 0;
    400 }
    401 
    402 char *
    403 _ti_get_token(char **cap, char sep)
    404 {
    405 	char esc, *token;
    406 
    407 	while (isspace((unsigned char)**cap))
    408 		(*cap)++;
    409 	if (**cap == '\0')
    410 		return NULL;
    411 
    412 	/* We can't use stresep(3) as ^ we need two escape chars */
    413 	esc = '\0';
    414 	for (token = *cap;
    415 	     **cap != '\0' && (esc != '\0' || **cap != sep);
    416 	     (*cap)++)
    417 	{
    418 		if (esc == '\0') {
    419 			if (**cap == '\\' || **cap == '^')
    420 				esc = **cap;
    421 		} else {
    422 			/* termcap /E/ is valid */
    423 			if (sep == ':' && esc == '\\' && **cap == 'E')
    424 				esc = 'x';
    425 			else
    426 				esc = '\0';
    427 		}
    428 	}
    429 
    430 	if (**cap != '\0')
    431 		*(*cap)++ = '\0';
    432 
    433 	return token;
    434 }
    435 
    436 static int
    437 _ti_find_rtype(const char *cap)
    438 {
    439 	const char *ptr;
    440 
    441 	for (ptr = cap; (ptr = strchr(ptr, '#')) != NULL;) {
    442 		if (strtol(++ptr, NULL, 0) > SHRT_MAX) {
    443 			return TERMINFO_RTYPE;
    444 		}
    445 	}
    446 	return TERMINFO_RTYPE_O1;
    447 }
    448 
    449 int
    450 _ti_encode_buf_id_num(TBUF *tbuf, int ind, int num, size_t len)
    451 {
    452 	if (!_ti_grow_tbuf(tbuf, sizeof(uint16_t) + len))
    453 		return 0;
    454 	_ti_encode_buf_16(tbuf, ind);
    455 	if (len == sizeof(uint32_t))
    456 		_ti_encode_buf_32(tbuf, num);
    457 	else
    458 		_ti_encode_buf_16(tbuf, num);
    459 	tbuf->entries++;
    460 	return 1;
    461 }
    462 
    463 int
    464 _ti_encode_buf_id_count_str(TBUF *tbuf, int ind, const void *buf, size_t len)
    465 {
    466 	if (!_ti_grow_tbuf(tbuf, 2 * sizeof(uint16_t) + len))
    467 		return 0;
    468 	_ti_encode_buf_16(tbuf, ind);
    469 	_ti_encode_buf_count_str(tbuf, buf, len);
    470 	tbuf->entries++;
    471 	return 1;
    472 }
    473 
    474 int
    475 _ti_encode_buf_id_flags(TBUF *tbuf, int ind, int flag)
    476 {
    477 	if (!_ti_grow_tbuf(tbuf, sizeof(uint16_t) + 1))
    478 		return 0;
    479 	_ti_encode_buf_16(tbuf, ind);
    480 	tbuf->buf[tbuf->bufpos++] = flag;
    481 	tbuf->entries++;
    482 	return 1;
    483 }
    484 
    485 TIC *
    486 _ti_compile(char *cap, int flags)
    487 {
    488 	char *token, *p, *e, *name, *desc, *alias;
    489 	signed char flag;
    490 	long cnum;
    491 	short ind;
    492 	int num;
    493 	size_t len;
    494 	TBUF buf;
    495 	TIC *tic;
    496 
    497 	_DIAGASSERT(cap != NULL);
    498 
    499 	name = _ti_get_token(&cap, ',');
    500 	if (name == NULL) {
    501 		dowarn(flags, "no separator found: %s", cap);
    502 		return NULL;
    503 	}
    504 	desc = strrchr(name, '|');
    505 	if (desc != NULL)
    506 		*desc++ = '\0';
    507 	alias = strchr(name, '|');
    508 	if (alias != NULL)
    509 		*alias++ = '\0';
    510 
    511 	if (strlen(name) > UINT16_MAX - 1) {
    512 		dowarn(flags, "%s: name too long", name);
    513 		return NULL;
    514 	}
    515 	if (desc != NULL && strlen(desc) > UINT16_MAX - 1) {
    516 		dowarn(flags, "%s: description too long: %s", name, desc);
    517 		return NULL;
    518 	}
    519 	if (alias != NULL && strlen(alias) > UINT16_MAX - 1) {
    520 		dowarn(flags, "%s: alias too long: %s", name, alias);
    521 		return NULL;
    522 	}
    523 
    524 	tic = calloc(sizeof(*tic), 1);
    525 	if (tic == NULL)
    526 		return NULL;
    527 
    528 	tic->rtype = (flags & TIC_COMPAT_V1) ? TERMINFO_RTYPE_O1 :
    529 	    _ti_find_rtype(cap);
    530 	buf.buf = NULL;
    531 	buf.buflen = 0;
    532 
    533 	tic->name = _ti_getname(tic->rtype, name);
    534 	if (tic->name == NULL)
    535 		goto error;
    536 	if (alias != NULL && flags & TIC_ALIAS) {
    537 		tic->alias = _ti_getname(tic->rtype, alias);
    538 		if (tic->alias == NULL)
    539 			goto error;
    540 	}
    541 	if (desc != NULL && flags & TIC_DESCRIPTION) {
    542 		tic->desc = strdup(desc);
    543 		if (tic->desc == NULL)
    544 			goto error;
    545 	}
    546 
    547 	for (token = _ti_get_token(&cap, ',');
    548 	     token != NULL && *token != '\0';
    549 	     token = _ti_get_token(&cap, ','))
    550 	{
    551 		/* Skip commented caps */
    552 		if (!(flags & TIC_COMMENT) && token[0] == '.')
    553 			continue;
    554 
    555 		/* Obsolete entries */
    556 		if (token[0] == 'O' && token[1] == 'T') {
    557 			if (!(flags & TIC_EXTRA))
    558 				continue;
    559 			token += 2;
    560 		}
    561 
    562 		/* str cap */
    563 		p = strchr(token, '=');
    564 		if (p != NULL) {
    565 			*p++ = '\0';
    566 			/* Don't use the string if we already have it */
    567 			ind = (short)_ti_strindex(token);
    568 			if (ind != -1 &&
    569 			    _ti_find_cap(tic, &tic->strs, 's', ind) != NULL)
    570 				continue;
    571 
    572 			/* Encode the string to our scratch buffer */
    573 			buf.bufpos = 0;
    574 			if (encode_string(tic->name, token,
    575 				&buf, p, flags) == -1)
    576 				goto error;
    577 			if (buf.bufpos > UINT16_MAX - 1) {
    578 				dowarn(flags, "%s: %s: string is too long",
    579 				    tic->name, token);
    580 				continue;
    581 			}
    582 			if (!VALID_STRING(buf.buf)) {
    583 				dowarn(flags, "%s: %s: invalid string",
    584 				    tic->name, token);
    585 				continue;
    586 			}
    587 
    588 			if (ind == -1) {
    589 				if (!_ti_store_extra(tic, 1, token, 's', -1, -2,
    590 				    buf.buf, buf.bufpos, flags))
    591 					goto error;
    592 			} else {
    593 				if (!_ti_encode_buf_id_count_str(&tic->strs,
    594 				    ind, buf.buf, buf.bufpos))
    595 					goto error;
    596 			}
    597 			continue;
    598 		}
    599 
    600 		/* num cap */
    601 		p = strchr(token, '#');
    602 		if (p != NULL) {
    603 			*p++ = '\0';
    604 			/* Don't use the number if we already have it */
    605 			ind = (short)_ti_numindex(token);
    606 			if (ind != -1 &&
    607 			    _ti_find_cap(tic, &tic->nums, 'n', ind) != NULL)
    608 				continue;
    609 
    610 			cnum = strtol(p, &e, 0);
    611 			if (*e != '\0') {
    612 				dowarn(flags, "%s: %s: not a number",
    613 				    tic->name, token);
    614 				continue;
    615 			}
    616 			if (!VALID_NUMERIC(cnum) || cnum > INT32_MAX) {
    617 				dowarn(flags, "%s: %s: number %ld out of range",
    618 				    tic->name, token, cnum);
    619 				continue;
    620 			}
    621 
    622 			num = (int)cnum;
    623 			if (ind == -1) {
    624 				if (!_ti_store_extra(tic, 1, token, 'n', -1,
    625 				    num, NULL, 0, flags))
    626 					goto error;
    627 			} else {
    628 				if (!_ti_encode_buf_id_num(&tic->nums,
    629 				    ind, num, _ti_numsize(tic)))
    630 					    goto error;
    631 			}
    632 			continue;
    633 		}
    634 
    635 		flag = 1;
    636 		len = strlen(token) - 1;
    637 		if (token[len] == '@') {
    638 			flag = CANCELLED_BOOLEAN;
    639 			token[len] = '\0';
    640 		}
    641 		ind = (short)_ti_flagindex(token);
    642 		if (ind == -1 && flag == CANCELLED_BOOLEAN) {
    643 			if ((ind = (short)_ti_numindex(token)) != -1) {
    644 				if (_ti_find_cap(tic, &tic->nums, 'n', ind)
    645 				    != NULL)
    646 					continue;
    647 				if (!_ti_encode_buf_id_num(&tic->nums, ind,
    648 				    CANCELLED_NUMERIC, _ti_numsize(tic)))
    649 					goto error;
    650 				continue;
    651 			} else if ((ind = (short)_ti_strindex(token)) != -1) {
    652 				if (_ti_find_cap(tic, &tic->strs, 's', ind)
    653 				    != NULL)
    654 					continue;
    655 				if (!_ti_encode_buf_id_num(
    656 				    &tic->strs, ind, 0, sizeof(uint16_t)))
    657 					goto error;
    658 				continue;
    659 			}
    660 		}
    661 		if (ind == -1) {
    662 			if (!_ti_store_extra(tic, 1, token, 'f', flag, 0, NULL,
    663 			    0, flags))
    664 				goto error;
    665 		} else if (_ti_find_cap(tic, &tic->flags, 'f', ind) == NULL) {
    666 			if (!_ti_encode_buf_id_flags(&tic->flags, ind, flag))
    667 				goto error;
    668 		}
    669 	}
    670 
    671 	free(buf.buf);
    672 	return tic;
    673 
    674 error:
    675 	free(buf.buf);
    676 	_ti_freetic(tic);
    677 	return NULL;
    678 }
    679 
    680 void
    681 _ti_freetic(TIC *tic)
    682 {
    683 
    684 	if (tic != NULL) {
    685 		free(tic->name);
    686 		free(tic->alias);
    687 		free(tic->desc);
    688 		free(tic->extras.buf);
    689 		free(tic->flags.buf);
    690 		free(tic->nums.buf);
    691 		free(tic->strs.buf);
    692 		free(tic);
    693 	}
    694 }
    695