summaryrefslogblamecommitdiffstats
path: root/text.c
blob: 440ed7db612a70b35cb161ca4cb3d8cc43dcdf34 (plain) (tree)
1
2
3
4
5
6
7
8
9
10
11
12
13



                                                         








                                                                                        














































































                                                                                                                         

                                                                                                   

































                                                                                                                        

                                                                             






















                                                                                                 

                                                          

         









                                
/* See LICENSE file for copyright and license details. */
#include "common.h"


static void
print_long_line_tip(enum warning_class class)
{
	printtipf(class, "you can put a <backslash> at the end of the line to continue "
	                 "it on the next line, except in or immediately proceeding an "
	                 "include line");
}


struct line *
load_text_file(int fd, const char *fname, int nest_level, size_t *nlinesp)
{
	struct line *lines;
	char *buf = NULL, *p;
	size_t size = 0;
	size_t len = 0;
	size_t i;
	ssize_t r;

	/* getline(3) may seem like the best way to read line by line,
	 * however, it may terminate before the end of the line is
	 * reached, which we would have to deal with, additionally,
	 * we want to check for null bytes. Therefore we will keep
	 * this simple and use read(3) and scan manually; and as a
	 * bonus we can leave the file descriptor open, and let the
	 * caller than created it close it.
	 */

	i = 0;
	*nlinesp = 0;
	for (;;) {
		if (len == size)
			buf = erealloc(buf, size += 2048);
		r = read(fd, &buf[len], size - len);
		if (r > 0)
			len += (size_t)r;
		else if (!r)
			break;
		else if (errno == EINTR)
			continue;
		else
			eprintf("read %s:", fname);

		for (; i < len; i++) {
			if (buf[i] == '\n') {
				*nlinesp += 1;
				buf[i] = '\0';
			} else if (buf[i] == '\0') {
				/* https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap03.html#tag_03_403 */
				warnf_undefined(WC_TEXT, "%s:%zu: file contains a NUL byte, this is disallowed, because "
				                         "input files are text files, and causes undefined behaviour",
				                fname, *nlinesp + 1);
				/* make(1) should probably just abort */
				printinfof(WC_TEXT, "this implementation will replace it with a <space>");
				buf[i] = ' ';
			}
		}
	}

	if (len && buf[len - 1] != '\0') { /* LF has been converted to NUL above */
		/* https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap03.html#tag_03_403 */
		warnf_undefined(WC_TEXT, "%s:%zu: is non-empty but does not end with a <newline>, which is "
		                         "required because input files are text files, and omission of it "
		                         "causes undefined behaviour",
		                fname, *nlinesp + 1);
		/* make(1) should probably just abort */
		printinfof(WC_TEXT, "this implementation will add the missing <newline>");
		buf = erealloc(buf, len + 1);
		buf[len++] = '\0';
		*nlinesp += 1;
	}

	lines = *nlinesp ? ecalloc(*nlinesp, sizeof(*lines)) : NULL;
	for (p = buf, i = 0; i < *nlinesp; i++) {
		lines[i].lineno = i + 1;
		lines[i].path = fname;
		lines[i].len = strlen(p);
		lines[i].data = ememdup(p, lines[i].len + 1);
		lines[i].eof = i + 1 == *nlinesp;
		lines[i].nest_level = nest_level;

		if (lines[i].len + 1 > 2048) {
			/* https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap03.html#tag_03_403 */
			warnf_undefined(WC_TEXT, "%s:%zu: line is, including the <newline> character, longer than "
			                         "2048 bytes which causes undefined behaviour as input files are "
			                         "text files and POSIX only guarantees support for lines up to 2048 "
			                         "bytes long including the <newline> character in text files",
			                fname, *nlinesp + 1);
			printinfof(WC_TEXT, "this implementation supports arbitrarily long lines");
			print_long_line_tip(WC_TEXT);
		}
		p += lines[i].len + 1;
	}

	free(buf);
	return lines;
}


void
check_utf8_encoding(struct line *line)
{
	size_t off, r;
	uint_least32_t codepoint;
#if GRAPHEME_INVALID_CODEPOINT == 0xFFFD
	unsigned char invalid_codepoint_encoding[] = {0xEF, 0xBF, 0xBD};
#endif

	for (off = 0; off < line->len; off += r) {
		r = grapheme_decode_utf8(&line->data[off], line->len - off, &codepoint);

		if (codepoint == GRAPHEME_INVALID_CODEPOINT &&
		    (r != ELEMSOF(invalid_codepoint_encoding) ||
		     memcmp(&line->data[off], invalid_codepoint_encoding, r))) {

			warnf_unspecified(WC_ENCODING, "%s:%zu: line contains invalid UTF-8", line->path, line->lineno);
			printinfof(WC_ENCODING, "this implementation will replace it the "
			                        "Unicode replacement character (U+FFFD)");

			line->data = erealloc(line->data, line->len - r + ELEMSOF(invalid_codepoint_encoding));
			memmove(&line->data[off + ELEMSOF(invalid_codepoint_encoding)],
			        &line->data[off + r],
			        line->len - off - r);
			memcpy(&line->data[off], invalid_codepoint_encoding, ELEMSOF(invalid_codepoint_encoding));
			line->len -= r;
			line->len += r = ELEMSOF(invalid_codepoint_encoding);
		}
	}
}


void
check_column_count(struct line *line)
{
	size_t columns = 0;
	size_t off, r;
	uint_least32_t codepoint;

	if (line->len <= style.max_line_length) /* Column count cannot be more than byte count */
		return;

	for (off = 0; off < line->len; off += r) {
		r = grapheme_decode_utf8(&line->data[off], line->len - off, &codepoint);
		columns += (size_t)abs(wcwidth((wchar_t)codepoint));
	}

	if (columns > style.max_line_length) {
		warnf_style(WC_LONG_LINE, "%s:%zu: line is longer than %zu columns",
		            line->path, line->lineno, columns);
		if (line->len + 1 <= 2048)
			print_long_line_tip(WC_LONG_LINE);
	}
}


int
is_line_blank(struct line *line)
{
	char *s = line->data;
	while (isspace(*s))
		s++;
	return !!*s;
}