xref: /freebsd/contrib/libdiff/lib/diff_output.c (revision f7dbbbd1762f8f74d5a585230c56eca92e4fdb8b)
1  /* Common parts for printing diff output */
2  /*
3   * Copyright (c) 2020 Neels Hofmeyr <neels@hofmeyr.de>
4   *
5   * Permission to use, copy, modify, and distribute this software for any
6   * purpose with or without fee is hereby granted, provided that the above
7   * copyright notice and this permission notice appear in all copies.
8   *
9   * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10   * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11   * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
12   * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13   * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14   * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15   * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
16   */
17  
18  #include <ctype.h>
19  #include <errno.h>
20  #include <stdbool.h>
21  #include <stdint.h>
22  #include <stdio.h>
23  #include <stdlib.h>
24  #include <string.h>
25  #include <unistd.h>
26  
27  #include <arraylist.h>
28  #include <diff_main.h>
29  #include <diff_output.h>
30  
31  #include "diff_internal.h"
32  
33  static int
34  get_atom_byte(int *ch, struct diff_atom *atom, off_t off)
35  {
36  	off_t cur;
37  
38  	if (atom->at != NULL) {
39  		*ch = atom->at[off];
40  		return 0;
41  	}
42  
43  	cur = ftello(atom->root->f);
44  	if (cur == -1)
45  		return errno;
46  
47  	if (cur != atom->pos + off &&
48  	    fseeko(atom->root->f, atom->pos + off, SEEK_SET) == -1)
49  		return errno;
50  
51  	*ch = fgetc(atom->root->f);
52  	if (*ch == EOF && ferror(atom->root->f))
53  		return errno;
54  
55  	return 0;
56  }
57  
58  #define DIFF_OUTPUT_BUF_SIZE	512
59  
60  int
61  diff_output_lines(struct diff_output_info *outinfo, FILE *dest,
62  		  const char *prefix, struct diff_atom *start_atom,
63  		  unsigned int count)
64  {
65  	struct diff_atom *atom;
66  	off_t outoff = 0, *offp;
67  	uint8_t *typep;
68  	int rc;
69  
70  	if (outinfo && outinfo->line_offsets.len > 0) {
71  		unsigned int idx = outinfo->line_offsets.len - 1;
72  		outoff = outinfo->line_offsets.head[idx];
73  	}
74  
75  	foreach_diff_atom(atom, start_atom, count) {
76  		off_t outlen = 0;
77  		int i, ch, nbuf = 0;
78  		unsigned int len = atom->len;
79  		unsigned char buf[DIFF_OUTPUT_BUF_SIZE + 1 /* '\n' */];
80  		size_t n;
81  
82  		n = strlcpy(buf, prefix, sizeof(buf));
83  		if (n >= DIFF_OUTPUT_BUF_SIZE) /* leave room for '\n' */
84  			return ENOBUFS;
85  		nbuf += n;
86  
87  		if (len) {
88  			rc = get_atom_byte(&ch, atom, len - 1);
89  			if (rc)
90  				return rc;
91  			if (ch == '\n')
92  				len--;
93  		}
94  
95  		for (i = 0; i < len; i++) {
96  			rc = get_atom_byte(&ch, atom, i);
97  			if (rc)
98  				return rc;
99  			if (nbuf >= DIFF_OUTPUT_BUF_SIZE) {
100  				rc = fwrite(buf, 1, nbuf, dest);
101  				if (rc != nbuf)
102  					return errno;
103  				outlen += rc;
104  				nbuf = 0;
105  			}
106  			buf[nbuf++] = ch;
107  		}
108  		buf[nbuf++] = '\n';
109  		rc = fwrite(buf, 1, nbuf, dest);
110  		if (rc != nbuf)
111  			return errno;
112  		outlen += rc;
113  		if (outinfo) {
114  			ARRAYLIST_ADD(offp, outinfo->line_offsets);
115  			if (offp == NULL)
116  				return ENOMEM;
117  			outoff += outlen;
118  			*offp = outoff;
119  			ARRAYLIST_ADD(typep, outinfo->line_types);
120  			if (typep == NULL)
121  				return ENOMEM;
122  			*typep = *prefix == ' ' ? DIFF_LINE_CONTEXT :
123  			    *prefix == '-' ? DIFF_LINE_MINUS :
124  			    *prefix == '+' ? DIFF_LINE_PLUS : DIFF_LINE_NONE;
125  		}
126  	}
127  
128  	return DIFF_RC_OK;
129  }
130  
131  int
132  diff_output_chunk_left_version(struct diff_output_info **output_info,
133  			       FILE *dest,
134  			       const struct diff_input_info *info,
135  			       const struct diff_result *result,
136  			       const struct diff_chunk_context *cc)
137  {
138  	int rc, c_idx;
139  	struct diff_output_info *outinfo = NULL;
140  
141  	if (diff_range_empty(&cc->left))
142  		return DIFF_RC_OK;
143  
144  	if (output_info) {
145  		*output_info = diff_output_info_alloc();
146  		if (*output_info == NULL)
147  			return ENOMEM;
148  		outinfo = *output_info;
149  	}
150  
151  	/* Write out all chunks on the left side. */
152  	for (c_idx = cc->chunk.start; c_idx < cc->chunk.end; c_idx++) {
153  		const struct diff_chunk *c = &result->chunks.head[c_idx];
154  
155  		if (c->left_count) {
156  			rc = diff_output_lines(outinfo, dest, "",
157  			    c->left_start, c->left_count);
158  			if (rc)
159  				return rc;
160  		}
161  	}
162  
163  	return DIFF_RC_OK;
164  }
165  
166  int
167  diff_output_chunk_right_version(struct diff_output_info **output_info,
168  				FILE *dest,
169  				const struct diff_input_info *info,
170  				const struct diff_result *result,
171  				const struct diff_chunk_context *cc)
172  {
173  	int rc, c_idx;
174  	struct diff_output_info *outinfo = NULL;
175  
176  	if (diff_range_empty(&cc->right))
177  		return DIFF_RC_OK;
178  
179  	if (output_info) {
180  		*output_info = diff_output_info_alloc();
181  		if (*output_info == NULL)
182  			return ENOMEM;
183  		outinfo = *output_info;
184  	}
185  
186  	/* Write out all chunks on the right side. */
187  	for (c_idx = cc->chunk.start; c_idx < cc->chunk.end; c_idx++) {
188  		const struct diff_chunk *c = &result->chunks.head[c_idx];
189  
190  		if (c->right_count) {
191  			rc = diff_output_lines(outinfo, dest, "", c->right_start,
192  			    c->right_count);
193  			if (rc)
194  				return rc;
195  		}
196  	}
197  
198  	return DIFF_RC_OK;
199  }
200  
201  int
202  diff_output_trailing_newline_msg(struct diff_output_info *outinfo, FILE *dest,
203  				 const struct diff_chunk *c)
204  {
205  	enum diff_chunk_type chunk_type = diff_chunk_type(c);
206  	struct diff_atom *atom, *start_atom;
207  	unsigned int atom_count;
208  	int rc, ch;
209  	off_t outoff = 0, *offp;
210  	uint8_t *typep;
211  
212  
213  	if (chunk_type == CHUNK_MINUS || chunk_type == CHUNK_SAME) {
214  		start_atom = c->left_start;
215  		atom_count = c->left_count;
216  	} else if (chunk_type == CHUNK_PLUS) {
217  		start_atom = c->right_start;
218  		atom_count = c->right_count;
219  	} else
220  		return EINVAL;
221  
222  	/* Locate the last atom. */
223  	if (atom_count == 0)
224  		return EINVAL;
225  	atom = &start_atom[atom_count - 1];
226  
227  	rc = get_atom_byte(&ch, atom, atom->len - 1);
228  	if (rc != DIFF_RC_OK)
229  		return rc;
230  
231  	if (ch != '\n') {
232  		if (outinfo && outinfo->line_offsets.len > 0) {
233  			unsigned int idx = outinfo->line_offsets.len - 1;
234  			outoff = outinfo->line_offsets.head[idx];
235  		}
236  		rc = fprintf(dest, "\\ No newline at end of file\n");
237  		if (rc < 0)
238  			return errno;
239  		if (outinfo) {
240  			ARRAYLIST_ADD(offp, outinfo->line_offsets);
241  			if (offp == NULL)
242  				return ENOMEM;
243  			outoff += rc;
244  			*offp = outoff;
245  			ARRAYLIST_ADD(typep, outinfo->line_types);
246  			if (typep == NULL)
247  				return ENOMEM;
248  			*typep = DIFF_LINE_NONE;
249  		}
250  	}
251  
252  	return DIFF_RC_OK;
253  }
254  
255  static bool
256  is_function_prototype(unsigned char ch)
257  {
258  	return (isalpha((unsigned char)ch) || ch == '_' || ch == '$');
259  }
260  
261  #define begins_with(s, pre) (strncmp(s, pre, sizeof(pre)-1) == 0)
262  
263  int
264  diff_output_match_function_prototype(char *prototype, size_t prototype_size,
265      int *last_prototype_idx, const struct diff_result *result,
266      const struct diff_chunk_context *cc)
267  {
268  	struct diff_atom *start_atom, *atom;
269  	const struct diff_data *data;
270  	unsigned char buf[DIFF_FUNCTION_CONTEXT_SIZE];
271  	const char *state = NULL;
272  	int rc, i, ch;
273  
274  	if (result->left->atoms.len > 0 && cc->left.start > 0) {
275  		data = result->left;
276  		start_atom = &data->atoms.head[cc->left.start - 1];
277  	} else
278  		return DIFF_RC_OK;
279  
280  	diff_data_foreach_atom_backwards_from(start_atom, atom, data) {
281  		int atom_idx = diff_atom_root_idx(data, atom);
282  		if (atom_idx < *last_prototype_idx)
283  			break;
284  		rc = get_atom_byte(&ch, atom, 0);
285  		if (rc)
286  			return rc;
287  		buf[0] = (unsigned char)ch;
288  		if (!is_function_prototype(buf[0]))
289  			continue;
290  		for (i = 1; i < atom->len && i < sizeof(buf) - 1; i++) {
291  			rc = get_atom_byte(&ch, atom, i);
292  			if (rc)
293  				return rc;
294  			if (ch == '\n')
295  				break;
296  			buf[i] = (unsigned char)ch;
297  		}
298  		buf[i] = '\0';
299  		if (begins_with(buf, "private:")) {
300  			if (!state)
301  				state = " (private)";
302  		} else if (begins_with(buf, "protected:")) {
303  			if (!state)
304  				state = " (protected)";
305  		} else if (begins_with(buf, "public:")) {
306  			if (!state)
307  				state = " (public)";
308  		} else {
309  			if (state)  /* don't care about truncation */
310  				strlcat(buf, state, sizeof(buf));
311  			strlcpy(prototype, buf, prototype_size);
312  			break;
313  		}
314  	}
315  
316  	*last_prototype_idx = diff_atom_root_idx(data, start_atom);
317  	return DIFF_RC_OK;
318  }
319  
320  struct diff_output_info *
321  diff_output_info_alloc(void)
322  {
323  	struct diff_output_info *output_info;
324  	off_t *offp;
325  	uint8_t *typep;
326  
327  	output_info = malloc(sizeof(*output_info));
328  	if (output_info != NULL) {
329  		ARRAYLIST_INIT(output_info->line_offsets, 128);
330  		ARRAYLIST_ADD(offp, output_info->line_offsets);
331  		if (offp == NULL) {
332  			diff_output_info_free(output_info);
333  			return NULL;
334  		}
335  		*offp = 0;
336  		ARRAYLIST_INIT(output_info->line_types, 128);
337  		ARRAYLIST_ADD(typep, output_info->line_types);
338  		if (typep == NULL) {
339  			diff_output_info_free(output_info);
340  			return NULL;
341  		}
342  		*typep = DIFF_LINE_NONE;
343  	}
344  	return output_info;
345  }
346  
347  void
348  diff_output_info_free(struct diff_output_info *output_info)
349  {
350  	ARRAYLIST_FREE(output_info->line_offsets);
351  	ARRAYLIST_FREE(output_info->line_types);
352  	free(output_info);
353  }
354  
355  const char *
356  diff_output_get_label_left(const struct diff_input_info *info)
357  {
358  	if (info->flags & DIFF_INPUT_LEFT_NONEXISTENT)
359  		return "/dev/null";
360  
361  	return info->left_path ? info->left_path : "a";
362  }
363  
364  const char *
365  diff_output_get_label_right(const struct diff_input_info *info)
366  {
367  	if (info->flags & DIFF_INPUT_RIGHT_NONEXISTENT)
368  		return "/dev/null";
369  
370  	return info->right_path ? info->right_path : "b";
371  }
372