Blob


1 /* Generic infrastructure to implement various diff algorithms (implementation). */
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 */
19 #include <sys/queue.h>
20 #include <ctype.h>
21 #include <errno.h>
22 #include <stdint.h>
23 #include <stdlib.h>
24 #include <stdbool.h>
25 #include <stdio.h>
26 #include <string.h>
27 #include <limits.h>
28 #include <unistd.h>
30 #include <assert.h>
32 #include <arraylist.h>
33 #include <diff_main.h>
35 #include "diff_internal.h"
36 #include "diff_debug.h"
38 static int
39 read_at(FILE *f, off_t at_pos, unsigned char *buf, size_t len)
40 {
41 int r;
42 if (fseeko(f, at_pos, SEEK_SET) == -1)
43 return errno;
44 r = fread(buf, sizeof(char), len, f);
45 if ((r == 0 || r < len) && ferror(f))
46 return errno;
47 if (r != len)
48 return EIO;
49 return 0;
50 }
52 static int
53 buf_cmp(const unsigned char *left, size_t left_len,
54 const unsigned char *right, size_t right_len,
55 bool ignore_whitespace)
56 {
57 int cmp;
59 if (ignore_whitespace) {
60 int il = 0, ir = 0;
61 while (il < left_len && ir < right_len) {
62 unsigned char cl = left[il];
63 unsigned char cr = right[ir];
65 if (isspace(cl) && il < left_len) {
66 il++;
67 continue;
68 }
69 if (isspace(cr) && ir < right_len) {
70 ir++;
71 continue;
72 }
74 if (cl > cr)
75 return 1;
76 if (cr > cl)
77 return -1;
78 il++;
79 ir++;
80 }
81 while (il < left_len) {
82 unsigned char cl = left[il++];
83 if (!isspace(cl))
84 return 1;
85 }
86 while (ir < right_len) {
87 unsigned char cr = right[ir++];
88 if (!isspace(cr))
89 return -1;
90 }
92 return 0;
93 }
95 cmp = memcmp(left, right, MIN(left_len, right_len));
96 if (cmp)
97 return cmp;
98 if (left_len == right_len)
99 return 0;
100 return (left_len > right_len) ? 1 : -1;
103 int
104 diff_atom_cmp(int *cmp,
105 const struct diff_atom *left,
106 const struct diff_atom *right)
108 off_t remain_left, remain_right;
109 int flags = (left->d->root->diff_flags | right->d->root->diff_flags);
110 bool ignore_whitespace = (flags & DIFF_FLAG_IGNORE_WHITESPACE);
112 if (!ignore_whitespace) {
113 if (!left->len && !right->len) {
114 *cmp = 0;
115 return 0;
117 if (!right->len) {
118 *cmp = 1;
119 return 0;
121 if (!left->len) {
122 *cmp = -1;
123 return 0;
127 if (left->at != NULL && right->at != NULL) {
128 *cmp = buf_cmp(left->at, left->len, right->at, right->len,
129 ignore_whitespace);
130 return 0;
133 remain_left = left->len;
134 remain_right = right->len;
135 while (remain_left > 0 || remain_right > 0) {
136 const size_t chunksz = 8192;
137 unsigned char buf_left[chunksz], buf_right[chunksz];
138 const uint8_t *p_left, *p_right;
139 off_t n_left, n_right;
140 ssize_t r;
142 if (!remain_right) {
143 *cmp = 1;
144 return 0;
146 if (!remain_left) {
147 *cmp = -1;
148 return 0;
151 n_left = MIN(chunksz, remain_left);
152 n_right = MIN(chunksz, remain_right);
154 if (left->at == NULL) {
155 r = read_at(left->d->root->f,
156 left->pos + (left->len - remain_left),
157 buf_left, n_left);
158 if (r) {
159 *cmp = 0;
160 return r;
162 p_left = buf_left;
163 } else {
164 p_left = left->at + (left->len - remain_left);
167 if (right->at == NULL) {
168 r = read_at(right->d->root->f,
169 right->pos + (right->len - remain_right),
170 buf_right, n_right);
171 if (r) {
172 *cmp = 0;
173 return r;
175 p_right = buf_right;
176 } else {
177 p_right = right->at + (right->len - remain_right);
180 r = buf_cmp(p_left, n_left, p_right, n_right,
181 ignore_whitespace);
182 if (r) {
183 *cmp = r;
184 return 0;
187 remain_left -= n_left;
188 remain_right -= n_right;
191 *cmp = 0;
192 return 0;
195 int
196 diff_atom_same(bool *same,
197 const struct diff_atom *left,
198 const struct diff_atom *right)
200 int cmp;
201 int r = diff_atom_cmp(&cmp, left, right);
202 if (r) {
203 *same = true;
204 return r;
206 *same = (cmp == 0);
207 return 0;
210 /* Even if a left or right side is empty, diff output may need to know the
211 * position in that file.
212 * So left_start or right_start must never be NULL -- pass left_count or
213 * right_count as zero to indicate staying at that position without consuming
214 * any lines. */
215 struct diff_chunk *
216 diff_state_add_chunk(struct diff_state *state, bool solved,
217 struct diff_atom *left_start, unsigned int left_count,
218 struct diff_atom *right_start, unsigned int right_count)
220 diff_chunk_arraylist_t *result = NULL;
221 struct diff_chunk *new_chunk;
222 struct diff_chunk chunk = {
223 .solved = solved,
224 .left_start = left_start,
225 .left_count = left_count,
226 .right_start = right_start,
227 .right_count = right_count,
228 };
229 enum diff_chunk_type last_t;
230 enum diff_chunk_type prev_last_t;
231 enum diff_chunk_type new_t;
233 if (!solved || state->temp_result.len) {
234 /* Append to temp_result */
235 result = &state->temp_result;
236 } else if (!state->result->chunks.len) {
237 /* Append to final result */
238 result = &state->result->chunks;
240 if (result) {
241 ARRAYLIST_ADD(new_chunk, *result);
242 if (!new_chunk)
243 return NULL;
244 *new_chunk = chunk;
245 goto chunk_added;
248 /* Append to solved chunks; make sure that adjacent chunks of same type are combined, and that a minus chunk
249 * never directly follows a plus chunk. */
250 result = &state->result->chunks;
252 prev_last_t = result->len > 1 ?
253 diff_chunk_type(&result->head[result->len - 2]) : CHUNK_EMPTY;
254 last_t = diff_chunk_type(&result->head[result->len - 1]);
255 new_t = diff_chunk_type(&chunk);
257 if (new_t == last_t) {
258 new_chunk = &result->head[result->len - 1];
259 new_chunk->left_count += chunk.left_count;
260 new_chunk->right_count += chunk.right_count;
261 } else if (last_t == CHUNK_PLUS && new_t == CHUNK_MINUS) {
262 /* If a minus-chunk follows a plus-chunk, place it above the plus-chunk.
263 * Is the one before that also a minus? combine. */
264 if (prev_last_t == CHUNK_MINUS) {
265 new_chunk = &result->head[result->len - 2];
266 new_chunk->left_count += chunk.left_count;
267 new_chunk->right_count += chunk.right_count;
268 } else {
269 ARRAYLIST_INSERT(new_chunk, *result, result->len - 2);
270 if (!new_chunk)
271 return NULL;
272 *new_chunk = chunk;
274 } else {
275 ARRAYLIST_ADD(new_chunk, *result);
276 if (!new_chunk)
277 return NULL;
278 *new_chunk = chunk;
279 goto chunk_added;
282 chunk_added:
283 debug("Add %s chunk:\n", new_chunk->solved ? "solved" : "UNSOLVED");
284 debug("L\n");
285 debug_dump_atoms(&state->left, new_chunk->left_start, new_chunk->left_count);
286 debug("R\n");
287 debug_dump_atoms(&state->right, new_chunk->right_start, new_chunk->right_count);
288 return new_chunk;
291 void
292 diff_data_init_root(struct diff_data *d, FILE *f, const uint8_t *data,
293 unsigned long long len, int diff_flags)
295 *d = (struct diff_data){
296 .f = f,
297 .pos = 0,
298 .data = data,
299 .len = len,
300 .root = d,
301 .diff_flags = diff_flags,
302 };
305 void
306 diff_data_init_subsection(struct diff_data *d, struct diff_data *parent,
307 struct diff_atom *from_atom, unsigned int atoms_count)
309 struct diff_atom *last_atom;
311 if (atoms_count == 0) {
312 *d = (struct diff_data){
313 .f = NULL,
314 .pos = 0,
315 .data = "",
316 .len = 0,
317 .root = parent->root,
318 .atoms.head = NULL,
319 .atoms.len = atoms_count,
320 };
322 return;
325 last_atom = from_atom + atoms_count - 1;
326 *d = (struct diff_data){
327 .f = NULL,
328 .pos = from_atom->pos,
329 .data = from_atom->at,
330 .len = (last_atom->pos + last_atom->len) - from_atom->pos,
331 .root = parent->root,
332 .atoms.head = from_atom,
333 .atoms.len = atoms_count,
334 };
336 debug("subsection:\n");
337 debug_dump(d);
340 void
341 diff_data_free(struct diff_data *diff_data)
343 if (!diff_data)
344 return;
345 if (diff_data->atoms.allocated)
346 ARRAYLIST_FREE(diff_data->atoms);
349 int
350 diff_algo_none(const struct diff_algo_config *algo_config,
351 struct diff_state *state)
353 debug("\n** %s\n", __func__);
354 debug("left:\n");
355 debug_dump(&state->left);
356 debug("right:\n");
357 debug_dump(&state->right);
358 debug_dump_myers_graph(&state->left, &state->right, NULL, NULL, 0, NULL,
359 0);
361 /* Add a chunk of equal lines, if any */
362 unsigned int equal_atoms = 0;
363 while (equal_atoms < state->left.atoms.len
364 && equal_atoms < state->right.atoms.len) {
365 int r;
366 bool same;
367 r = diff_atom_same(&same, &state->left.atoms.head[equal_atoms],
368 &state->right.atoms.head[equal_atoms]);
369 if (r)
370 return r;
371 if (!same)
372 break;
373 equal_atoms++;
375 if (equal_atoms) {
376 if (!diff_state_add_chunk(state, true,
377 &state->left.atoms.head[0],
378 equal_atoms,
379 &state->right.atoms.head[0],
380 equal_atoms))
381 return ENOMEM;
384 /* Add a "minus" chunk with all lines from the left. */
385 if (equal_atoms < state->left.atoms.len) {
386 if (!diff_state_add_chunk(state, true,
387 &state->left.atoms.head[equal_atoms],
388 state->left.atoms.len - equal_atoms,
389 NULL, 0))
390 return ENOMEM;
393 /* Add a "plus" chunk with all lines from the right. */
394 if (equal_atoms < state->right.atoms.len) {
395 if (!diff_state_add_chunk(state, true,
396 NULL, 0,
397 &state->right.atoms.head[equal_atoms],
398 state->right.atoms.len - equal_atoms))
399 return ENOMEM;
401 return DIFF_RC_OK;
404 int
405 diff_run_algo(const struct diff_algo_config *algo_config,
406 struct diff_state *state)
408 int rc;
409 ARRAYLIST_FREE(state->temp_result);
411 if (!algo_config || !algo_config->impl
412 || !state->recursion_depth_left) {
413 debug("MAX RECURSION REACHED, just dumping diff chunks\n");
414 return diff_algo_none(algo_config, state);
417 ARRAYLIST_INIT(state->temp_result, DIFF_RESULT_ALLOC_BLOCKSIZE);
418 rc = algo_config->impl(algo_config, state);
419 switch (rc) {
420 case DIFF_RC_USE_DIFF_ALGO_FALLBACK:
421 debug("Got DIFF_RC_USE_DIFF_ALGO_FALLBACK (%p)\n",
422 algo_config->fallback_algo);
423 rc = diff_run_algo(algo_config->fallback_algo, state);
424 goto return_rc;
426 case DIFF_RC_OK:
427 /* continue below */
428 break;
430 default:
431 /* some error happened */
432 goto return_rc;
435 /* Pick up any diff chunks that are still unsolved and feed to
436 * inner_algo. inner_algo will solve unsolved chunks and append to
437 * result, and subsequent solved chunks on this level are then appended
438 * to result afterwards. */
439 int i;
440 for (i = 0; i < state->temp_result.len; i++) {
441 struct diff_chunk *c = &state->temp_result.head[i];
442 if (c->solved) {
443 struct diff_chunk *final_c;
444 ARRAYLIST_ADD(final_c, state->result->chunks);
445 if (!final_c) {
446 rc = ENOMEM;
447 goto return_rc;
449 *final_c = *c;
450 continue;
453 /* c is an unsolved chunk, feed to inner_algo */
454 struct diff_state inner_state = {
455 .result = state->result,
456 .recursion_depth_left = state->recursion_depth_left - 1,
457 };
458 diff_data_init_subsection(&inner_state.left, &state->left,
459 c->left_start, c->left_count);
460 diff_data_init_subsection(&inner_state.right, &state->right,
461 c->right_start, c->right_count);
463 rc = diff_run_algo(algo_config->inner_algo, &inner_state);
465 if (rc != DIFF_RC_OK)
466 goto return_rc;
469 rc = DIFF_RC_OK;
470 return_rc:
471 ARRAYLIST_FREE(state->temp_result);
472 return rc;
475 struct diff_result *
476 diff_main(const struct diff_config *config,
477 FILE *left_f, const uint8_t *left_data, off_t left_len,
478 FILE *right_f, const uint8_t *right_data, off_t right_len,
479 int diff_flags)
481 struct diff_result *result = malloc(sizeof(struct diff_result));
482 if (!result)
483 return NULL;
485 *result = (struct diff_result){};
486 diff_data_init_root(&result->left, left_f, left_data, left_len,
487 diff_flags);
488 diff_data_init_root(&result->right, right_f, right_data, right_len,
489 diff_flags);
491 if (!config->atomize_func) {
492 result->rc = EINVAL;
493 return result;
496 result->rc = config->atomize_func(config->atomize_func_data,
497 &result->left, &result->right);
498 if (result->rc != DIFF_RC_OK)
499 return result;
501 struct diff_state state = {
502 .result = result,
503 .recursion_depth_left = config->max_recursion_depth ? : 32,
504 };
505 diff_data_init_subsection(&state.left, &result->left,
506 result->left.atoms.head,
507 result->left.atoms.len);
508 diff_data_init_subsection(&state.right, &result->right,
509 result->right.atoms.head,
510 result->right.atoms.len);
512 result->rc = diff_run_algo(config->algo, &state);
514 return result;
517 void
518 diff_result_free(struct diff_result *result)
520 if (!result)
521 return;
522 diff_data_free(&result->left);
523 diff_data_free(&result->right);
524 ARRAYLIST_FREE(result->chunks);
525 free(result);