889 lines
24 KiB
C
889 lines
24 KiB
C
// SPDX-FileCopyrightText: 2021-2026 RizinOrg <info@rizin.re>
|
|
// SPDX-FileCopyrightText: 2021-2026 deroad <deroad@kumo.xn--q9jyb4c>
|
|
// SPDX-License-Identifier: LGPL-3.0-only
|
|
|
|
/** \file diff.c
|
|
* Ratcliff/Obershelp Pattern Recognition
|
|
* Ratcliff/Obershelp Pattern Recognition algorithm applied to generic data.
|
|
*
|
|
* The code for diffing is quite simple, given 2 arrays containing
|
|
* data, you calculate the longest sequences of data that matches
|
|
* between the two inputs; to do that you need to create a map in
|
|
* which you will store all the hits found within an array:
|
|
* - as key, each single element of one of the arrays.
|
|
* - as value, a list of all the locations of which each element
|
|
* appears within the array itself.
|
|
*
|
|
* Once this map is created, you will need to find the longest
|
|
* subsequence that can be found in both arrays by using the hit-map.
|
|
* then you remove that subsequence from the area of search, and
|
|
* search again for the 2nd longest subsequence (excluding the area
|
|
* of the first subsequence).
|
|
* Then you keep doing this, till all areas and longest matches have
|
|
* been found.
|
|
*
|
|
* Now that you know all the matching areas, you can generate a series
|
|
* of steps/operations which can transform the first array into the
|
|
* second one, by removing the non matching areas in the 1st array
|
|
* and inserting the missing areas from the 2nd array.
|
|
|
|
* Example:
|
|
* array_a = [A,B,C,D,E,F,G,H,I]
|
|
* array_b = [Y,Z,B,C,D,L,Z,N,H,I]
|
|
*
|
|
* 1: create map of hits and their positions:
|
|
* - hit_map(array_b) = {
|
|
* B: [2]
|
|
* C: [3]
|
|
* D: [4]
|
|
* H: [8]
|
|
* I: [9]
|
|
* L: [5]
|
|
* N: [7]
|
|
* Y: [0]
|
|
* Z: [1,6]
|
|
* }
|
|
*
|
|
* 2: find all matching areas using the hit-map:
|
|
* - match_0 = [B,C,D] from array_a[1] to array_a[3] and from array_b[3] to array_b[4]
|
|
* - match_1 = [H,I] from array_a[1] to array_a[8] and from array_b[8] to array_b[9]
|
|
|
|
* 3: create the steps to convert array_a in array_b
|
|
* - remove [A] at 0
|
|
* - insert [Y,Z] at 0
|
|
* - keep [B,C,D] at 1
|
|
* - remove [E,F,G] at 4
|
|
* - insert [L,Z,N] at 4
|
|
* - keep [H,I] at 8
|
|
*/
|
|
|
|
#include <rz_diff.h>
|
|
#include <rz_util.h>
|
|
/**/
|
|
#include <rz_util/ht_pp.h>
|
|
#include <rz_util/ht_uu.h>
|
|
|
|
#define NUM2PTR(x) ((void *)(intptr_t)(x))
|
|
#define PTR2NUM(x) ((intptr_t)(void *)(x))
|
|
|
|
RZ_LIB_VERSION(rz_diff);
|
|
|
|
typedef struct block_t {
|
|
ut32 a_low;
|
|
ut32 a_hi;
|
|
ut32 b_low;
|
|
ut32 b_hi;
|
|
} Block;
|
|
|
|
typedef void (*RzDiffMethodFree)(const void *array);
|
|
typedef RzDiffMatch *(*RzDiffFindLongestMatch)(RzDiff *diff, Block *block);
|
|
|
|
typedef struct methods_internal_t {
|
|
RzDiffMethodElemAt elem_at;
|
|
RzDiffMethodElemHash elem_hash;
|
|
RzDiffMethodCompare compare;
|
|
RzDiffMethodIgnore ignore;
|
|
RzDiffMethodStringify stringify;
|
|
RzDiffMethodFree free;
|
|
RzDiffFindLongestMatch find_longest_match;
|
|
} MethodsInternal;
|
|
|
|
struct rz_diff_t {
|
|
const void *a;
|
|
const void *b;
|
|
HtPP /*<void *, RzList *>*/ *b_hits;
|
|
size_t a_size;
|
|
size_t b_size;
|
|
size_t block_size;
|
|
MethodsInternal methods;
|
|
};
|
|
|
|
static RzDiffMatch *generic_find_longest_match(RzDiff *diff, Block *block);
|
|
|
|
/**
|
|
* \brief Calculates the hash of any given data
|
|
*
|
|
* Calculates the hash of any given data with a user defined size.
|
|
* */
|
|
RZ_API ut32 rz_diff_hash_data(RZ_NULLABLE const ut8 *buffer, ut32 size) {
|
|
ut32 h = 5381;
|
|
if (!buffer || !size) {
|
|
return h;
|
|
}
|
|
for (ut32 i = 0; i < size; ++i) {
|
|
h = (h + (h << 5)) ^ buffer[i];
|
|
}
|
|
return h;
|
|
}
|
|
|
|
static ut32 default_ksize(const void *a) {
|
|
return sizeof(ut32);
|
|
}
|
|
|
|
static bool fake_ignore(const void *value) {
|
|
return false;
|
|
}
|
|
|
|
static RzDiffMatch *match_new(ut32 a, ut32 b, ut32 size) {
|
|
RzDiffMatch *match = RZ_NEW0(RzDiffMatch);
|
|
if (!match) {
|
|
return NULL;
|
|
}
|
|
|
|
match->a = a;
|
|
match->b = b;
|
|
match->size = size;
|
|
return match;
|
|
}
|
|
|
|
#include "bytes_diff.c"
|
|
#include "lines_diff.c"
|
|
#include "unified_diff.c"
|
|
|
|
static bool set_a(RzDiff *diff, const void *a, ut32 a_size) {
|
|
rz_return_val_if_fail(a, false);
|
|
|
|
diff->a = a;
|
|
diff->a_size = a_size;
|
|
return true;
|
|
}
|
|
|
|
static void fini_hits_kv(HtPPKv *kv, RZ_UNUSED void *user) {
|
|
rz_list_free(kv->value);
|
|
}
|
|
|
|
static bool set_b(RzDiff *diff, const void *b, ut32 b_size) {
|
|
rz_return_val_if_fail(b && diff->methods.elem_at && diff->methods.elem_hash && diff->methods.compare && diff->methods.ignore, false);
|
|
|
|
diff->b = b;
|
|
diff->b_size = b_size;
|
|
|
|
RzList *list = NULL;
|
|
RzDiffMethodElemAt elem_at = diff->methods.elem_at;
|
|
RzDiffMethodIgnore ignore = diff->methods.ignore;
|
|
|
|
/* we need to generate the hits list for B */
|
|
HtPPOptions opts = {
|
|
.cmp = diff->methods.compare,
|
|
.hashfn = diff->methods.elem_hash,
|
|
.dupkey = NULL, // avoid to duplicate key
|
|
.dupvalue = NULL,
|
|
.calcsizeK = default_ksize,
|
|
.calcsizeV = NULL,
|
|
.finiKV = fini_hits_kv,
|
|
.finiKV_user = NULL,
|
|
.elem_size = 0,
|
|
};
|
|
ht_pp_free(diff->b_hits);
|
|
diff->b_hits = ht_pp_new_opt(&opts);
|
|
|
|
for (ut64 i = 0; i < diff->b_size; ++i) {
|
|
const void *elem = elem_at(diff->b, i);
|
|
if (ignore && ignore(elem)) {
|
|
continue;
|
|
}
|
|
|
|
list = ht_pp_find(diff->b_hits, elem, NULL);
|
|
if (!list) {
|
|
list = rz_list_newf(NULL);
|
|
if (!list) {
|
|
RZ_LOG_ERROR("rz_diff_set_b: cannot allocate list\n");
|
|
return false;
|
|
}
|
|
ht_pp_insert(diff->b_hits, elem, list);
|
|
}
|
|
|
|
if (!rz_list_append(list, NUM2PTR(i))) {
|
|
RZ_LOG_ERROR("rz_diff_set_b: cannot append index to list\n");
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* \brief Returns the structure needed to diff buffers of ut8
|
|
*
|
|
* Allocates the internal structure needed to diff buffers by
|
|
* using the methods defined in methods_bytes.
|
|
* Allows to define an callback function to ignore bytes.
|
|
* */
|
|
RZ_API RZ_OWN RzDiff *rz_diff_bytes_new(RZ_BORROW const ut8 *a, ut32 a_size, RZ_BORROW const ut8 *b, ut32 b_size) {
|
|
rz_return_val_if_fail(a && b, NULL);
|
|
|
|
RzDiff *diff = RZ_NEW0(RzDiff);
|
|
if (!diff) {
|
|
return NULL;
|
|
}
|
|
|
|
diff->methods = methods_bytes;
|
|
diff->a = a;
|
|
diff->b = b;
|
|
diff->a_size = a_size;
|
|
diff->b_size = b_size;
|
|
|
|
return diff;
|
|
}
|
|
|
|
/**
|
|
* \brief Returns the structure needed to diff lines
|
|
*
|
|
* Allocates the internal structure needed to diff strings with new lines
|
|
* using the methods defined in methods_lines.
|
|
* Allows to define an callback function to ignore lines.
|
|
* */
|
|
RZ_API RZ_OWN RzDiff *rz_diff_lines_new(RZ_BORROW const char *a, RZ_BORROW const char *b, RZ_NULLABLE RzDiffIgnoreLine ignore) {
|
|
rz_return_val_if_fail(a && b, NULL);
|
|
|
|
RzDiff *diff = RZ_NEW0(RzDiff);
|
|
if (!diff) {
|
|
return NULL;
|
|
}
|
|
|
|
RzList *a_lines = tokenize_lines(a);
|
|
RzList *b_lines = tokenize_lines(b);
|
|
if (!a_lines || !b_lines) {
|
|
rz_list_free(a_lines);
|
|
rz_list_free(b_lines);
|
|
free(diff);
|
|
return NULL;
|
|
}
|
|
|
|
diff->methods = methods_lines;
|
|
if (ignore) {
|
|
diff->methods.ignore = (RzDiffMethodIgnore)ignore;
|
|
}
|
|
|
|
if (!set_a(diff, a_lines, rz_list_length(a_lines))) {
|
|
rz_diff_free(diff);
|
|
return NULL;
|
|
}
|
|
if (!set_b(diff, b_lines, rz_list_length(b_lines))) {
|
|
rz_diff_free(diff);
|
|
return NULL;
|
|
}
|
|
return diff;
|
|
}
|
|
|
|
/**
|
|
* \brief Returns the structure needed to diff arrays of user defined types
|
|
*
|
|
* Allocates the internal structure needed to diff any user defined array
|
|
* of any types by using the methods provided by the user calling this C api.
|
|
* */
|
|
RZ_API RZ_OWN RzDiff *rz_diff_generic_new(RZ_BORROW const void *a, ut32 a_size, RZ_BORROW const void *b, ut32 b_size, RZ_NONNULL RzDiffMethods *methods) {
|
|
rz_return_val_if_fail(a && b && methods && methods->elem_at && methods->elem_hash && methods->compare && methods->stringify, NULL);
|
|
|
|
RzDiff *diff = RZ_NEW0(RzDiff);
|
|
if (!diff) {
|
|
return NULL;
|
|
}
|
|
|
|
diff->methods.find_longest_match = generic_find_longest_match;
|
|
diff->methods.free = NULL;
|
|
diff->methods.elem_at = methods->elem_at;
|
|
diff->methods.elem_hash = methods->elem_hash;
|
|
diff->methods.compare = methods->compare;
|
|
diff->methods.stringify = methods->stringify;
|
|
|
|
if (methods->ignore) {
|
|
diff->methods.ignore = methods->ignore;
|
|
} else {
|
|
diff->methods.ignore = fake_ignore;
|
|
}
|
|
|
|
if (!set_a(diff, a, a_size)) {
|
|
rz_diff_free(diff);
|
|
return NULL;
|
|
}
|
|
if (!set_b(diff, b, b_size)) {
|
|
rz_diff_free(diff);
|
|
return NULL;
|
|
}
|
|
return diff;
|
|
}
|
|
|
|
/**
|
|
* \brief frees the diff structure
|
|
*
|
|
* frees any internal structure and the diff structure.
|
|
* */
|
|
RZ_API void rz_diff_free(RZ_NULLABLE RzDiff *diff) {
|
|
if (!diff) {
|
|
return;
|
|
}
|
|
if (diff->methods.free) {
|
|
diff->methods.free(diff->a);
|
|
diff->methods.free(diff->b);
|
|
}
|
|
ht_pp_free(diff->b_hits);
|
|
free(diff);
|
|
}
|
|
|
|
/**
|
|
* \brief returns the pointer of the A array that passed to rz_diff_XXX_new()
|
|
*
|
|
* returns the pointer of the A array that passed to rz_diff_XXX_new()
|
|
* */
|
|
RZ_API RZ_BORROW const void *rz_diff_get_a(RZ_NONNULL RzDiff *diff) {
|
|
rz_return_val_if_fail(diff, NULL);
|
|
return diff->a;
|
|
}
|
|
|
|
/**
|
|
* \brief returns the pointer of the B array that passed to rz_diff_XXX_new()
|
|
*
|
|
* returns the pointer of the B array that passed to rz_diff_XXX_new()
|
|
* */
|
|
RZ_API RZ_BORROW const void *rz_diff_get_b(RZ_NONNULL RzDiff *diff) {
|
|
rz_return_val_if_fail(diff, NULL);
|
|
return diff->b;
|
|
}
|
|
|
|
static inline bool stack_append_block(RzList /*<RzDiffOp *>*/ *stack, ut32 a_low, ut32 a_hi, ut32 b_low, ut32 b_hi) {
|
|
Block *block = RZ_NEW0(Block);
|
|
if (!block) {
|
|
return false;
|
|
}
|
|
|
|
block->a_low = a_low;
|
|
block->a_hi = a_hi;
|
|
block->b_low = b_low;
|
|
block->b_hi = b_hi;
|
|
|
|
if (!rz_list_append(stack, block)) {
|
|
free(block);
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
static RzDiffMatch *generic_find_longest_match(RzDiff *diff, Block *block) {
|
|
rz_return_val_if_fail(diff && diff->methods.elem_at && diff->methods.compare && diff->methods.ignore, NULL);
|
|
RzList *list = NULL;
|
|
RzListIter *it = NULL;
|
|
RzDiffMatch *match = NULL;
|
|
HtUU *tmp = NULL;
|
|
HtUU *len_map = NULL;
|
|
void *pnum = NULL;
|
|
const ut8 *a = diff->a;
|
|
const ut8 *b = diff->b;
|
|
const void *elem_a = NULL;
|
|
const void *elem_b = NULL;
|
|
RzDiffMethodIgnore ignore = diff->methods.ignore;
|
|
RzDiffMethodElemAt elem_at = diff->methods.elem_at;
|
|
RzDiffMethodCompare compare = diff->methods.compare;
|
|
|
|
ut32 a_low = block->a_low;
|
|
ut32 a_hi = block->a_hi;
|
|
ut32 b_low = block->b_low;
|
|
ut32 b_hi = block->b_hi;
|
|
|
|
ut32 hit_a = a_low;
|
|
ut32 hit_b = b_low;
|
|
ut32 hit_size = 0;
|
|
|
|
len_map = ht_uu_new();
|
|
if (!len_map) {
|
|
RZ_LOG_ERROR("generic_find_longest_match: cannot allocate len_map\n");
|
|
goto generic_find_longest_match_fail;
|
|
}
|
|
|
|
for (ut32 a_pos = a_low; a_pos < a_hi; ++a_pos) {
|
|
elem_a = elem_at(a, a_pos);
|
|
tmp = ht_uu_new();
|
|
if (!tmp) {
|
|
RZ_LOG_ERROR("generic_find_longest_match: cannot allocate tmp\n");
|
|
goto generic_find_longest_match_fail;
|
|
}
|
|
|
|
list = ht_pp_find(diff->b_hits, elem_a, NULL);
|
|
rz_list_foreach (list, it, pnum) {
|
|
ut64 b_pos = PTR2NUM(pnum);
|
|
if (b_pos < b_low) {
|
|
continue;
|
|
} else if (b_pos >= b_hi) {
|
|
break;
|
|
}
|
|
ut32 len = ht_uu_find(len_map, b_pos - 1, NULL) + 1;
|
|
ht_uu_insert(tmp, b_pos, len);
|
|
if (len > hit_size) {
|
|
hit_a = a_pos - len + 1;
|
|
hit_b = b_pos - len + 1;
|
|
hit_size = len;
|
|
}
|
|
}
|
|
|
|
ht_uu_free(len_map);
|
|
len_map = tmp;
|
|
tmp = NULL;
|
|
}
|
|
|
|
// Now let's handle the without the ignored chars.
|
|
while (hit_a > a_low && hit_b > b_low) {
|
|
elem_a = elem_at(a, hit_a - 1);
|
|
elem_b = elem_at(b, hit_b - 1);
|
|
if (ignore(elem_b) || compare(elem_a, elem_b)) {
|
|
break;
|
|
}
|
|
hit_a--;
|
|
hit_b--;
|
|
hit_size++;
|
|
}
|
|
|
|
while (hit_a + hit_size < a_hi && hit_b + hit_size < b_hi) {
|
|
elem_a = elem_at(a, hit_a + hit_size);
|
|
elem_b = elem_at(b, hit_b + hit_size);
|
|
if (ignore(elem_b) || compare(elem_a, elem_b)) {
|
|
break;
|
|
}
|
|
hit_size++;
|
|
}
|
|
|
|
// Now let's handle the ignored chars.
|
|
while (hit_a > a_low && hit_b > b_low) {
|
|
elem_a = elem_at(a, hit_a - 1);
|
|
elem_b = elem_at(b, hit_b - 1);
|
|
if (!ignore(elem_b) || compare(elem_a, elem_b)) {
|
|
break;
|
|
}
|
|
hit_a--;
|
|
hit_b--;
|
|
hit_size++;
|
|
}
|
|
|
|
while (hit_a + hit_size < a_hi && hit_b + hit_size < b_hi) {
|
|
elem_a = elem_at(a, hit_a + hit_size);
|
|
elem_b = elem_at(b, hit_b + hit_size);
|
|
if (!ignore(elem_b) || compare(elem_a, elem_b)) {
|
|
break;
|
|
}
|
|
hit_size++;
|
|
}
|
|
|
|
match = match_new(hit_a, hit_b, hit_size);
|
|
if (!match) {
|
|
RZ_LOG_ERROR("generic_find_longest_match: cannot allocate RzDiffMatch\n");
|
|
goto generic_find_longest_match_fail;
|
|
}
|
|
|
|
ht_uu_free(len_map);
|
|
return match;
|
|
|
|
generic_find_longest_match_fail:
|
|
ht_uu_free(tmp);
|
|
ht_uu_free(len_map);
|
|
return NULL;
|
|
}
|
|
|
|
static int cmp_matches(RzDiffMatch *m0, RzDiffMatch *m1, void *user) {
|
|
if (m0->a > m1->a) {
|
|
return 1;
|
|
} else if (m0->a < m1->a) {
|
|
return -1;
|
|
} else if (m0->b > m1->b) {
|
|
return 1;
|
|
} else if (m0->b < m1->b) {
|
|
return -1;
|
|
} else if (m0->size > m1->size) {
|
|
return 1;
|
|
} else if (m0->size < m1->size) {
|
|
return -1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* \brief generates a list of matching blocks
|
|
*
|
|
* Generates a list of matching blocks that are found in both inputs.
|
|
* If non are found it returns a match result with size of 0
|
|
* */
|
|
RZ_API RZ_OWN RzList /*<RzDiffMatch *>*/ *rz_diff_matches_new(RZ_NONNULL RzDiff *diff) {
|
|
rz_return_val_if_fail(diff, NULL);
|
|
RzList *stack = NULL;
|
|
RzList *matches = NULL;
|
|
RzList *non_adjacent = NULL;
|
|
RzListIter *it = NULL;
|
|
Block *block = NULL;
|
|
RzDiffMatch *match = NULL;
|
|
ut32 adj_a = 0, adj_b = 0, adj_size = 0;
|
|
|
|
matches = rz_list_newf((RzListFree)free);
|
|
if (!matches) {
|
|
RZ_LOG_ERROR("rz_diff_matches_new: cannot allocate matches\n");
|
|
goto rz_diff_matches_new_fail;
|
|
}
|
|
non_adjacent = rz_list_newf((RzListFree)free);
|
|
if (!matches) {
|
|
RZ_LOG_ERROR("rz_diff_matches_new: cannot allocate non_adjacent\n");
|
|
goto rz_diff_matches_new_fail;
|
|
}
|
|
|
|
stack = rz_list_newf((RzListFree)free);
|
|
if (!stack) {
|
|
RZ_LOG_ERROR("rz_diff_matches_new: cannot allocate stack\n");
|
|
goto rz_diff_matches_new_fail;
|
|
}
|
|
|
|
if (!stack_append_block(stack, 0, diff->a_size, 0, diff->b_size)) {
|
|
RZ_LOG_ERROR("rz_diff_matches_new: cannot append initial block "
|
|
"into stack\n");
|
|
goto rz_diff_matches_new_fail;
|
|
}
|
|
|
|
while (rz_list_length(stack) > 0) {
|
|
block = (Block *)rz_list_pop(stack);
|
|
match = diff->methods.find_longest_match(diff, block);
|
|
if (!match || match->size < 1) {
|
|
free(match);
|
|
free(block);
|
|
continue;
|
|
}
|
|
|
|
// add the match
|
|
if (!rz_list_add_sorted(matches, match, (RzListComparator)cmp_matches, NULL)) {
|
|
RZ_LOG_ERROR("rz_diff_matches_new: cannot append match into matches\n");
|
|
free(match);
|
|
free(block);
|
|
goto rz_diff_matches_new_fail;
|
|
}
|
|
if (block->a_low < match->a && block->b_low < match->b &&
|
|
!stack_append_block(stack, block->a_low, match->a, block->b_low, match->b)) {
|
|
RZ_LOG_ERROR("rz_diff_matches_new: cannot append low block into stack\n");
|
|
free(block);
|
|
goto rz_diff_matches_new_fail;
|
|
}
|
|
if (match->a + match->size < block->a_hi && match->b + match->size < block->b_hi &&
|
|
!stack_append_block(stack, match->a + match->size, block->a_hi, match->b + match->size, block->b_hi)) {
|
|
RZ_LOG_ERROR("rz_diff_matches_new: cannot append high block into stack\n");
|
|
free(block);
|
|
goto rz_diff_matches_new_fail;
|
|
}
|
|
free(block);
|
|
}
|
|
|
|
adj_a = 0;
|
|
adj_b = 0;
|
|
adj_size = 0;
|
|
rz_list_foreach (matches, it, match) {
|
|
if ((adj_a + adj_size) == match->a && (adj_b + adj_size) == match->b) {
|
|
adj_size += match->size;
|
|
} else {
|
|
RzDiffMatch *m = adj_size ? match_new(adj_a, adj_b, adj_size) : NULL;
|
|
if (adj_size && (!m || !rz_list_append(non_adjacent, m))) {
|
|
RZ_LOG_ERROR("rz_diff_matches_new: cannot append match into non_adjacent\n");
|
|
free(m);
|
|
goto rz_diff_matches_new_fail;
|
|
}
|
|
adj_a = match->a;
|
|
adj_b = match->b;
|
|
adj_size = match->size;
|
|
}
|
|
}
|
|
match = adj_size ? match_new(adj_a, adj_b, adj_size) : NULL;
|
|
if (adj_size && (!match || !rz_list_append(non_adjacent, match))) {
|
|
RZ_LOG_ERROR("rz_diff_matches_new: cannot append match into non_adjacent\n");
|
|
free(match);
|
|
goto rz_diff_matches_new_fail;
|
|
}
|
|
|
|
match = match_new(diff->a_size, diff->b_size, 0);
|
|
if (!match || !rz_list_append(non_adjacent, match)) {
|
|
RZ_LOG_ERROR("rz_diff_matches_new: cannot append match into non_adjacent\n");
|
|
free(match);
|
|
goto rz_diff_matches_new_fail;
|
|
}
|
|
|
|
rz_list_free(matches);
|
|
rz_list_free(stack);
|
|
return non_adjacent;
|
|
|
|
rz_diff_matches_new_fail:
|
|
rz_list_free(non_adjacent);
|
|
rz_list_free(matches);
|
|
rz_list_free(stack);
|
|
return NULL;
|
|
}
|
|
|
|
static RzDiffOp *opcode_new(RzDiffOpType type, st32 a_beg, st32 a_end, st32 b_beg, st32 b_end) {
|
|
RzDiffOp *op = RZ_NEW0(RzDiffOp);
|
|
if (!op) {
|
|
return NULL;
|
|
}
|
|
op->type = type;
|
|
op->a_beg = a_beg;
|
|
op->a_end = a_end;
|
|
op->b_beg = b_beg;
|
|
op->b_end = b_end;
|
|
return op;
|
|
}
|
|
|
|
static void opcode_set(RzDiffOp *op, RzDiffOpType type, st32 a_beg, st32 a_end, st32 b_beg, st32 b_end) {
|
|
op->type = type;
|
|
op->a_beg = a_beg;
|
|
op->a_end = a_end;
|
|
op->b_beg = b_beg;
|
|
op->b_end = b_end;
|
|
}
|
|
|
|
/**
|
|
* \brief Generates a list of steps needed to go from A to B
|
|
*
|
|
* Generates a list of opcodes that are needed to convert A to B.
|
|
* */
|
|
RZ_API RZ_OWN RzList /*<RzDiffOp *>*/ *rz_diff_opcodes_new(RZ_NONNULL RzDiff *diff) {
|
|
rz_return_val_if_fail(diff, NULL);
|
|
ut32 a = 0, b = 0;
|
|
RzDiffOpType type = RZ_DIFF_OP_INVALID;
|
|
RzDiffOp *op = NULL;
|
|
RzDiffMatch *match = NULL;
|
|
RzListIter *it = NULL;
|
|
RzList *matches = NULL;
|
|
RzList *opcodes = NULL;
|
|
|
|
matches = rz_diff_matches_new(diff);
|
|
if (!matches) {
|
|
goto rz_diff_opcodes_new_fail;
|
|
}
|
|
|
|
opcodes = rz_list_newf((RzListFree)free);
|
|
if (!opcodes) {
|
|
RZ_LOG_ERROR("rz_diff_opcodes_new: cannot allocate opcodes\n");
|
|
goto rz_diff_opcodes_new_fail;
|
|
}
|
|
|
|
a = 0;
|
|
b = 0;
|
|
|
|
rz_list_foreach (matches, it, match) {
|
|
type = RZ_DIFF_OP_INVALID;
|
|
|
|
if (a < match->a && b < match->b) {
|
|
type = RZ_DIFF_OP_REPLACE;
|
|
} else if (a < match->a) {
|
|
type = RZ_DIFF_OP_DELETE;
|
|
} else if (b < match->b) {
|
|
type = RZ_DIFF_OP_INSERT;
|
|
}
|
|
|
|
if (type != RZ_DIFF_OP_INVALID) {
|
|
op = opcode_new(type, a, match->a, b, match->b);
|
|
if (!op) {
|
|
RZ_LOG_ERROR("rz_diff_opcodes_new: cannot allocate op\n");
|
|
goto rz_diff_opcodes_new_fail;
|
|
} else if (!rz_list_append(opcodes, op)) {
|
|
RZ_LOG_ERROR("rz_diff_opcodes_new: cannot append op into opcodes\n");
|
|
free(op);
|
|
goto rz_diff_opcodes_new_fail;
|
|
}
|
|
}
|
|
a = match->a + match->size;
|
|
b = match->b + match->size;
|
|
|
|
if (match->size > 0) {
|
|
if (op && op->type == RZ_DIFF_OP_EQUAL) {
|
|
// last op is equal we merge.
|
|
op->a_end = a;
|
|
op->b_end = b;
|
|
continue;
|
|
}
|
|
op = opcode_new(RZ_DIFF_OP_EQUAL, match->a, a, match->b, b);
|
|
if (!op) {
|
|
RZ_LOG_ERROR("rz_diff_opcodes_new: cannot allocate op\n");
|
|
goto rz_diff_opcodes_new_fail;
|
|
} else if (!rz_list_append(opcodes, op)) {
|
|
RZ_LOG_ERROR("rz_diff_opcodes_new: cannot append op into opcodes\n");
|
|
free(op);
|
|
goto rz_diff_opcodes_new_fail;
|
|
}
|
|
}
|
|
}
|
|
|
|
rz_list_free(matches);
|
|
return opcodes;
|
|
|
|
rz_diff_opcodes_new_fail:
|
|
rz_list_free(matches);
|
|
rz_list_free(opcodes);
|
|
return NULL;
|
|
}
|
|
|
|
static void group_op_free(RzList /*<RzDiffOp *>*/ *ops) {
|
|
rz_list_free(ops);
|
|
}
|
|
|
|
/**
|
|
* \brief Generates groups of opcodes needed to go from A to B.
|
|
*
|
|
* Generates groups of opcodes needed to go from A to B, but
|
|
* each group will end with N common EQUAL ops (if possible).
|
|
* default is 3 equals ops before splitting the group.
|
|
* */
|
|
RZ_API RZ_OWN RzList /*<RzList<RzDiffOp *> *>*/ *rz_diff_opcodes_grouped_new(RZ_NONNULL RzDiff *diff, ut32 n_groups) {
|
|
rz_return_val_if_fail(diff && n_groups > 1, NULL);
|
|
RzDiffOp *op = NULL;
|
|
RzListIter *it = NULL;
|
|
RzList *group = NULL;
|
|
RzList *groups = NULL;
|
|
RzList *opcodes = NULL;
|
|
st32 a_beg = 0, b_beg = 0, max_groups = 0;
|
|
|
|
max_groups = n_groups << 1;
|
|
|
|
groups = rz_list_newf((RzListFree)group_op_free);
|
|
if (!groups) {
|
|
RZ_LOG_ERROR("rz_diff_opcodes_grouped_new: cannot allocate groups\n");
|
|
goto rz_diff_opcodes_grouped_new_fail;
|
|
}
|
|
|
|
opcodes = rz_diff_opcodes_new(diff);
|
|
if (!opcodes) {
|
|
goto rz_diff_opcodes_grouped_new_fail;
|
|
}
|
|
|
|
if (rz_list_length(opcodes) < 1) {
|
|
op = opcode_new(RZ_DIFF_OP_EQUAL, 0, 1, 0, 1);
|
|
if (!op) {
|
|
RZ_LOG_ERROR("rz_diff_opcodes_grouped_new: cannot allocate op for opcodes\n");
|
|
goto rz_diff_opcodes_grouped_new_fail;
|
|
} else if (!rz_list_append(opcodes, op)) {
|
|
RZ_LOG_ERROR("rz_diff_opcodes_grouped_new: cannot append op into opcodes\n");
|
|
free(op);
|
|
goto rz_diff_opcodes_grouped_new_fail;
|
|
}
|
|
}
|
|
|
|
op = rz_list_first_val(opcodes);
|
|
if (op->type == RZ_DIFF_OP_EQUAL) {
|
|
opcode_set(op, op->type, RZ_MAX(op->a_beg, op->a_end - n_groups), op->a_end, RZ_MAX(op->b_beg, op->b_end - n_groups), op->b_end);
|
|
}
|
|
|
|
op = rz_list_last_val(opcodes);
|
|
if (op->type == RZ_DIFF_OP_EQUAL) {
|
|
opcode_set(op, op->type, op->a_beg, RZ_MIN(op->a_end, op->a_beg + n_groups), op->b_beg, RZ_MIN(op->b_end, op->b_beg + n_groups));
|
|
}
|
|
|
|
group = rz_list_newf((RzListFree)free);
|
|
if (!group) {
|
|
RZ_LOG_ERROR("rz_diff_opcodes_grouped_new: cannot allocate group\n");
|
|
goto rz_diff_opcodes_grouped_new_fail;
|
|
}
|
|
|
|
rz_list_foreach (opcodes, it, op) {
|
|
a_beg = op->a_beg;
|
|
b_beg = op->b_beg;
|
|
|
|
if (op->type == RZ_DIFF_OP_EQUAL && (op->a_end - a_beg) > max_groups) {
|
|
// append the last op of the group, append group to groups and create a new group.
|
|
RzDiffOp *op2 = opcode_new(RZ_DIFF_OP_EQUAL, a_beg, RZ_MIN(op->a_end, a_beg + n_groups), b_beg, RZ_MIN(op->b_end, b_beg + n_groups));
|
|
if (!op2) {
|
|
RZ_LOG_ERROR("rz_diff_opcodes_grouped_new: cannot allocate op for group\n");
|
|
goto rz_diff_opcodes_grouped_new_fail;
|
|
} else if (!rz_list_append(group, op2)) {
|
|
RZ_LOG_ERROR("rz_diff_opcodes_grouped_new: cannot append op into group\n");
|
|
free(op2);
|
|
goto rz_diff_opcodes_grouped_new_fail;
|
|
} else if (!rz_list_append(groups, group)) {
|
|
RZ_LOG_ERROR("rz_diff_opcodes_grouped_new: cannot append group into groups\n");
|
|
rz_list_free(group);
|
|
goto rz_diff_opcodes_grouped_new_fail;
|
|
}
|
|
|
|
group = rz_list_newf((RzListFree)free);
|
|
if (!group) {
|
|
RZ_LOG_ERROR("rz_diff_opcodes_grouped_new: cannot allocate new group\n");
|
|
goto rz_diff_opcodes_grouped_new_fail;
|
|
}
|
|
a_beg = RZ_MAX(a_beg, op->a_end - n_groups);
|
|
b_beg = RZ_MAX(b_beg, op->b_end - n_groups);
|
|
}
|
|
|
|
op = opcode_new(op->type, a_beg, op->a_end, b_beg, op->b_end);
|
|
if (!op) {
|
|
rz_list_free(group);
|
|
RZ_LOG_ERROR("rz_diff_opcodes_grouped_new: cannot allocate op for group\n");
|
|
goto rz_diff_opcodes_grouped_new_fail;
|
|
} else if (!rz_list_append(group, op)) {
|
|
RZ_LOG_ERROR("rz_diff_opcodes_grouped_new: cannot append op into group\n");
|
|
free(op);
|
|
rz_list_free(group);
|
|
goto rz_diff_opcodes_grouped_new_fail;
|
|
}
|
|
}
|
|
|
|
op = rz_list_first_val(opcodes);
|
|
if (!(rz_list_length(opcodes) == 1 && op->type == RZ_DIFF_OP_EQUAL)) {
|
|
if (!rz_list_append(groups, group)) {
|
|
RZ_LOG_ERROR("rz_diff_opcodes_grouped_new: cannot append group into groups\n");
|
|
rz_list_free(group);
|
|
goto rz_diff_opcodes_grouped_new_fail;
|
|
}
|
|
} else {
|
|
rz_list_free(group);
|
|
}
|
|
|
|
rz_list_free(opcodes);
|
|
return groups;
|
|
|
|
rz_diff_opcodes_grouped_new_fail:
|
|
rz_list_free(groups);
|
|
rz_list_free(opcodes);
|
|
return NULL;
|
|
}
|
|
|
|
/**
|
|
* \brief Calculates the similarity ratio between A and B.
|
|
*
|
|
* Calculates the similarity ratio between A and B.
|
|
* Returns a number between 0 and 1; closer to 1 the result
|
|
* is more similar/identical the 2 arrays are.
|
|
* */
|
|
RZ_API bool rz_diff_ratio(RZ_NONNULL RzDiff *diff, RZ_NONNULL double *result) {
|
|
rz_return_val_if_fail(diff && result, false);
|
|
RzList *matches = NULL;
|
|
RzDiffMatch *match = NULL;
|
|
RzListIter *it = NULL;
|
|
ut32 hits = 0;
|
|
|
|
matches = rz_diff_matches_new(diff);
|
|
if (!matches) {
|
|
return false;
|
|
}
|
|
rz_list_foreach (matches, it, match) {
|
|
hits += match->size;
|
|
}
|
|
rz_list_free(matches);
|
|
|
|
/* simple cast to avoid math issues */
|
|
double d_hits = hits;
|
|
double d_size = diff->a_size + diff->b_size;
|
|
if (d_size > 0.0) {
|
|
*result = (2.0 * d_hits) / d_size;
|
|
} else {
|
|
*result = 1.0;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* \brief Calculates the size ratio between A and B.
|
|
*
|
|
* Works like the rz_diff_ratio, but this checks only
|
|
* how similar are the sizes between the two arrays.
|
|
* Returns a number between 0 and 1, like above.
|
|
* */
|
|
RZ_API bool rz_diff_sizes_ratio(RZ_NONNULL RzDiff *diff, RZ_NONNULL double *result) {
|
|
rz_return_val_if_fail(diff && result, false);
|
|
|
|
/* simple cast to avoid math issues */
|
|
double d_hits = RZ_MIN(diff->a_size, diff->b_size);
|
|
double d_size = diff->a_size + diff->b_size;
|
|
if (d_size > 0.0) {
|
|
*result = (2.0 * d_hits) / d_size;
|
|
} else {
|
|
*result = 1.0;
|
|
}
|
|
return true;
|
|
}
|