rizin/librz/diff/diff.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;
}