• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

ascii-boxes / boxes / 25991660202

17 May 2026 01:04PM UTC coverage: 82.822%. Remained the same
25991660202

push

github

tsjensen
Fix a Heisenbug in u32_insert_space_at() in unicode.c

2776 of 3695 branches covered (75.13%)

Branch coverage included in aggregate %.

5 of 7 new or added lines in 1 file covered. (71.43%)

297 existing lines in 19 files now uncovered.

4345 of 4903 relevant lines covered (88.62%)

98937.49 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

74.34
/src/input.c
1
/*
2
 * boxes - Command line filter to draw/remove ASCII boxes around text
3
 * SPDX-FileCopyrightText: Copyright (c) 1999-2026 Thomas Jensen and the boxes contributors
4
 * SPDX-License-Identifier: GPL-3.0-only
5
 *
6
 * This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public
7
 * License, version 3, as published by the Free Software Foundation.
8
 * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
9
 * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
10
 * details.
11
 * You should have received a copy of the GNU General Public License along with this program.
12
 * If not, see <https://www.gnu.org/licenses/>.
13
 *
14
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
15
 */
16

17
/*
18
 * Read and analyze input text.
19
 */
20

21
#include "config.h"
22

23
#include <errno.h>
24
#include <string.h>
25
#include <unistr.h>
26
#include <unitypes.h>
27

28
#include "boxes.h"
29
#include "input.h"
30
#include "logging.h"
31
#include "regulex.h"
32
#include "tools.h"
33
#include "unicode.h"
34

35

36

37
/**
38
 * Determine if the given line of raw text is ended by a line break.
39
 * @param s the string to check
40
 * @param len length of s in characters
41
 * @returns != 0 if line break found;
42
 *          == 0 if line break not found
43
 */
44
static int has_linebreak(const uint32_t *s, const int len)
2,635✔
45
{
46
    int result = 0;
2,635✔
47
    if (s != NULL && len > 0) {
2,635!
48
        ucs4_t the_last = s[len - 1];
2,635✔
49
        result = u32_cmp(&char_cr, &the_last, 1) == 0 || u32_cmp(&char_newline, &the_last, 1) == 0;
2,635!
50
        log_debug(__FILE__, MAIN, "has_linebreak: (%#010x) %d\n", (int) the_last, result);
2,635✔
51
    }
52
    return result;
2,635✔
53
}
54

55

56

57
/**
58
 * Determine indentation of given lines in spaces. Lines are assumed to be free of trailing whitespace.
59
 * @param lines the lines to examine
60
 * @param lines_size number of lines to examine
61
 * @returns >= 0: indentation in spaces; < 0: error
62
 */
63
static int get_indent(const line_t *lines, const size_t lines_size)
424✔
64
{
65
    int res = LINE_MAX_BYTES; /* result */
424✔
66
    int nonblank = 0;         /* true if one non-blank line found */
424✔
67

68
    if (lines == NULL) {
424!
69
        bx_fprintf(stderr, "%s: internal error\n", PROJECT);
×
UNCOV
70
        return -1;
×
71
    }
72
    if (lines_size == 0) {
424!
UNCOV
73
        return 0;
×
74
    }
75

76
    for (size_t j = 0; j < lines_size; ++j) {
3,447✔
77
        if (lines[j].text->num_columns > 0) {
3,023✔
78
            nonblank = 1;
3,004✔
79
            size_t ispc = lines[j].text->indent;
3,004✔
80
            if ((int) ispc < res) {
3,004✔
81
                res = ispc;
630✔
82
            }
83
        }
84
    }
85

86
    if (nonblank) {
424!
87
        return res; /* success */
424✔
88
    }
89
    else {
UNCOV
90
        return 0; /* success, but only blank lines */
×
91
    }
92
}
93

94

95

96
int apply_substitutions(input_t *result, const int mode)
423✔
97
{
98
    size_t num_rules;
99
    reprule_t *rules;
100
    size_t j, k;
101

102
    if (opt.design == NULL) {
423!
UNCOV
103
        return 1;
×
104
    }
105

106
    if (mode == 0) {
423✔
107
        num_rules = opt.design->num_reprules;
263✔
108
        rules = opt.design->reprules;
263✔
109
    }
110
    else if (mode == 1) {
160!
111
        num_rules = opt.design->num_revrules;
160✔
112
        rules = opt.design->revrules;
160✔
113
    }
114
    else {
115
        bx_fprintf(stderr, "%s: internal error\n", PROJECT);
×
UNCOV
116
        return 2;
×
117
    }
118

119
    /*
120
     *  Compile regular expressions
121
     */
122
    log_debug(__FILE__, REGEXP, "Compiling %d %s rule patterns\n", (int) num_rules, mode ? "reversion" : "replacement");
423✔
123
    errno = 0;
423✔
124
    opt.design->current_rule = rules;
423✔
125
    for (j = 0; j < num_rules; ++j, ++(opt.design->current_rule)) {
586✔
126
        rules[j].prog = u32_compile_pattern(rules[j].search->memory);
163✔
127
        if (rules[j].prog == NULL) {
163!
UNCOV
128
            return 5;
×
129
        }
130
    }
131
    opt.design->current_rule = NULL;
423✔
132
    if (errno) {
423!
UNCOV
133
        return 3;
×
134
    }
135

136
    /*
137
     *  Apply regular expression substitutions to input lines
138
     */
139
    for (k = 0; k < result->num_lines; ++k) {
2,564✔
140
        opt.design->current_rule = rules;
2,141✔
141
        for (j = 0; j < num_rules; ++j, ++(opt.design->current_rule)) {
3,001✔
142
            if (is_debug_logging(REGEXP)) {
860✔
143
                char *outtext = bxs_to_output(result->lines[k].text);
1✔
144
                char *outrepstr = bxs_to_output(rules[j].repstr);
1✔
145
                log_debug(__FILE__, REGEXP, "regex_replace(0x%p, \"%s\", \"%s\", %d, \'%c\') == ", rules[j].prog,
1✔
146
                    outrepstr, outtext, (int) result->lines[k].text->num_chars, rules[j].mode);
1✔
147
                BFREE(outtext);
1!
148
                BFREE(outrepstr);
1!
149
            }
150
            uint32_t *newtext = u32_regex_replace(rules[j].prog, rules[j].repstr->memory, result->lines[k].text->memory,
860✔
151
                    result->lines[k].text->num_chars, rules[j].mode == 'g');
860✔
152
            if (is_debug_logging(REGEXP)) {
860✔
153
                char *outnewtext = newtext ? u32_strconv_to_output(newtext) : strdup("NULL");
1!
154
                log_debug_cont(REGEXP, "\"%s\"\n", outnewtext);
1✔
155
                BFREE(outnewtext);
1!
156
            }
157
            if (newtext == NULL) {
860!
UNCOV
158
                return 1;
×
159
            }
160

161
            bxs_free(result->lines[k].text);
860✔
162
            result->lines[k].text = bxs_from_unicode(newtext);
860✔
163

164
            analyze_line_ascii(result, result->lines + k);   /* update maxline value */
860✔
165

166
            if (is_debug_logging(REGEXP)) {
860✔
167
                char *outtext2 = bxs_to_output(result->lines[k].text);
1✔
168
                log_debug(__FILE__, REGEXP, "result->lines[%d] == {%d, \"%s\"}\n",
1✔
169
                    (int) k, (int) result->lines[k].text->num_chars, outtext2);
1✔
170
                BFREE(outtext2);
1!
171
            }
172
        }
173
        opt.design->current_rule = NULL;
2,141✔
174
    }
175

176
    /*
177
     *  If text indentation was part of the lines processed, indentation
178
     *  may now be different -> recalculate result->indent.
179
     */
180
    if (opt.design->indentmode == 't') {
423!
181
        int rc;
182
        rc = get_indent(result->lines, result->num_lines);
×
183
        if (rc >= 0) {
×
UNCOV
184
            result->indent = (size_t) rc;
×
185
        }
186
        else {
UNCOV
187
            return 4;
×
188
        }
189
    }
190

191
    return 0;
423✔
192
}
193

194

195

196
static void trim_trailing_ws_carefully(uint32_t *mbtemp, size_t *len_chars)
2,635✔
197
{
198
    if (opt.r) {
2,635✔
199
        /* remove only trailing line breaks, but keep the space */
200
        if (is_char_at(mbtemp, *len_chars - 1, char_newline)) {
1,673!
201
            set_char_at(mbtemp, *len_chars - 1, char_nul);
1,673✔
202
            --(*len_chars);
1,673✔
203
        }
204
        if (is_char_at(mbtemp, *len_chars - 1, char_cr)) {
1,673!
205
            set_char_at(mbtemp, *len_chars - 1, char_nul);
×
UNCOV
206
            --(*len_chars);
×
207
        }
208
    }
209
    else {
210
        /* remove all trailing whitespace, including unicode whitespace */
211
        btrim32(mbtemp, len_chars);
962✔
212
    }
213
}
2,635✔
214

215

216

217
input_t *read_all_input()
351✔
218
{
219
    char buf[LINE_MAX_BYTES + 3];      /* static input buffer incl. newline + zero terminator */
220
    size_t input_size = 0;             /* number of elements allocated */
351✔
221

222
    input_t *result = (input_t *) calloc(1, sizeof(input_t));
351✔
223
    result->indent = LINE_MAX_BYTES;
351✔
224

225
    while (fgets(buf, LINE_MAX_BYTES + 2, opt.infile)) {
2,986✔
226
        if (result->num_lines % 100 == 0) {
2,635✔
227
            input_size += 100;
353✔
228
            line_t *tmp = (line_t *) realloc(result->lines, input_size * sizeof(line_t));
353✔
229
            if (tmp == NULL) {
353!
230
                perror(PROJECT);
×
231
                BFREE(result->lines);
×
UNCOV
232
                return NULL;
×
233
            }
234
            result->lines = tmp;
353✔
235
        }
236

237
        memset(result->lines + result->num_lines, 0, sizeof(line_t));
2,635✔
238

239
        uint32_t *mbtemp = u32_strconv_from_input(buf);
2,635✔
240
        size_t len_chars = u32_strlen(mbtemp);
2,635✔
241
        result->final_newline = has_linebreak(mbtemp, len_chars);
2,635✔
242
        trim_trailing_ws_carefully(mbtemp, &len_chars);
2,635✔
243

244
        /*
245
         * Expand tabs
246
         */
247
        if (len_chars > 0) {
2,635✔
248
            uint32_t *temp = NULL;
2,616✔
249
            len_chars = expand_tabs_into(mbtemp, opt.tabstop, &temp, &(result->lines[result->num_lines].tabpos),
5,232✔
250
                    &(result->lines[result->num_lines].tabpos_len));
2,616✔
251
            if (len_chars == 0) {
2,616!
252
                perror(PROJECT);
×
253
                BFREE(result->lines);
×
UNCOV
254
                return NULL;
×
255
            }
256
            result->lines[result->num_lines].text = bxs_from_unicode(temp);
2,616✔
257
            BFREE(temp);
2,616!
258
        }
259
        else {
260
            result->lines[result->num_lines].text = bxs_new_empty_string();
19✔
261
        }
262

263
        BFREE(mbtemp);
2,635!
264
        ++result->num_lines;
2,635✔
265
    }
266

267
    if (ferror(stdin)) {
351!
268
        perror(PROJECT);
×
269
        BFREE(result->lines);
×
UNCOV
270
        return NULL;
×
271
    }
272
    return result;
351✔
273
}
274

275

276

277
int analyze_input(input_t *result)
424✔
278
{
279
    result->indent = LINE_MAX_BYTES;
424✔
280
    result->maxline = 0;
424✔
281

282
    /*
283
     * Build ASCII equivalent of the multi-byte string, update line stats
284
     */
285
    for (size_t i = 0; i < result->num_lines; ++i) {
3,447✔
286
        analyze_line_ascii(result, result->lines + i);
3,023✔
287
    }
288

289
    /*
290
     *  Exit if there was no input at all
291
     */
292
    if (result->lines == NULL || result->lines[0].text == NULL) {
424!
UNCOV
293
        return 0;
×
294
    }
295

296
    /*
297
     *  Compute indentation
298
     */
299
    int rc = get_indent(result->lines, result->num_lines);
424✔
300
    if (rc >= 0) {
424!
301
        result->indent = (size_t) rc;
424✔
302
    }
303
    else {
UNCOV
304
        return 1;
×
305
    }
306

307
    /*
308
     *  Remove indentation, unless we want to preserve it (when removing
309
     *  a box or if the user wants to retain it inside the box)
310
     */
311
    if (opt.design->indentmode != 't' && opt.r == 0) {
424!
312
        for (size_t i = 0; i < result->num_lines; ++i) {
1,613✔
313
            if (result->lines[i].text->num_columns >= result->indent) {
1,350!
314
                /*
315
                 * We should really remove *columns* rather than *characters*, but since the removed characters are
316
                 * spaces (indentation), and there are no double-wide spaces in Unicode, both actions are equivalent.
317
                 */
318
                bxstr_t *unindented = bxs_cut_front(result->lines[i].text, result->indent);
1,350✔
319
                bxs_free(result->lines[i].text);
1,350✔
320
                result->lines[i].text = unindented;
1,350✔
321
            }
322
            if (is_debug_logging(MAIN)) {
1,350✔
323
                char *outtext = bxs_to_output(result->lines[i].text);
3✔
324
                log_debug(__FILE__, MAIN, "%2d: text = \"%s\" (%d chars, %d visible, %d invisible, %d columns)\n",
3✔
325
                    (int) i, outtext,
326
                    (int) result->lines[i].text->num_chars, (int) result->lines[i].text->num_chars_visible,
3✔
327
                    (int) result->lines[i].text->num_chars_invisible, (int) result->lines[i].text->num_columns);
3✔
328
                log_debug(__FILE__, MAIN, "    ascii = \"%s\"\n", result->lines[i].text->ascii);
3✔
329
                BFREE(outtext);
3!
330
            }
331
        }
332
        result->maxline -= result->indent;
263✔
333
    }
334

335
    /*
336
     *  Apply regular expression substitutions
337
     */
338
    if (opt.r == 0) {
424✔
339
        if (apply_substitutions(result, 0) != 0) {
263!
UNCOV
340
            return 1;
×
341
        }
342
    }
343

344
    return 0;
424✔
345
}
346

347

348
/* vim: set sw=4: */
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc