Rewrite rz-diff and internal diffing library (#1126)

This commit is contained in:
Giovanni 2021-05-14 13:47:30 +02:00 committed by GitHub
parent 0aa1ff92c4
commit 6fec281af7
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
31 changed files with 3786 additions and 2448 deletions

View file

@ -4,6 +4,7 @@ if get_option('enable_rz_test')
include_directories: [platform_inc],
dependencies: [
rz_util_dep,
rz_diff_dep,
lrt,
],
install: true,

View file

@ -3,6 +3,14 @@
#include "rz_test.h"
#include <assert.h>
#include <rz_cons.h>
#define Color_INSERT Color_BGREEN
#define Color_DELETE Color_BRED
#define Color_BGINSERT "\x1b[48;5;22m"
#define Color_BGDELETE "\x1b[48;5;52m"
#define Color_HLINSERT Color_BGINSERT Color_INSERT
#define Color_HLDELETE Color_BGDELETE Color_DELETE
#define WORKERS_DEFAULT 8
#define RIZIN_CMD_DEFAULT "rizin"
@ -608,84 +616,33 @@ static RzThreadFunctionRet worker_th(RzThread *th) {
return RZ_TH_STOP;
}
static void print_diff(const char *actual, const char *expected, bool diffchar, const char *regexp) {
RzDiff *d = rz_diff_new();
#ifdef __WINDOWS__
static const char *diff_cmd[] = {
"git", "diff", "--no-index", NULL
};
d->diff_cmd = diff_cmd;
#endif
static void print_diff(const char *actual, const char *expected, const char *regexp) {
RzDiff *d = NULL;
char *uni = NULL;
const char *output = actual;
if (regexp) {
RzList *matches = rz_regex_get_match_list(regexp, "e", actual);
output = rz_list_to_str(matches, '\0');
rz_list_free(matches);
}
if (diffchar) {
RzDiffChar *diff = rz_diffchar_new((const ut8 *)expected, (const ut8 *)output);
if (diff) {
rz_diff_free(d);
rz_diffchar_print(diff);
rz_diffchar_free(diff);
goto cleanup;
}
static const char *diff_cmd_char[] = {
"git", "diff", "--no-index", "--word-diff=porcelain", "--word-diff-regex=.", NULL
};
d->diff_cmd = diff_cmd_char;
d = rz_diff_lines_new(expected, output, NULL);
if (!d) {
goto cleanup;
}
char *uni = rz_diff_buffers_to_string(d, (const ut8 *)expected, (int)strlen(expected),
(const ut8 *)output, (int)strlen(output));
rz_diff_free(d);
RzList *lines = rz_str_split_duplist(uni, "\n", false);
RzListIter *it;
char *line;
bool header_found = false;
rz_list_foreach (lines, it, line) {
if (!header_found) {
if (rz_str_startswith(line, "+++ ")) {
header_found = true;
}
continue;
}
if (rz_str_startswith(line, "@@ ") && rz_str_endswith(line, " @@")) {
printf("%s%s%s\n", Color_CYAN, line, Color_RESET);
continue;
}
bool color = true;
char c = *line;
switch (c) {
case '+':
printf("%s" Color_INSERT, diffchar ? Color_BGINSERT : "");
break;
case '-':
printf("%s" Color_DELETE, diffchar ? Color_BGDELETE : "");
break;
case '~': // can't happen if !diffchar
printf("\n");
continue;
default:
color = false;
break;
}
if (diffchar) {
printf("%s", *line ? line + 1 : "");
} else {
printf("%s\n", line);
}
if (color) {
printf("%s", Color_RESET);
}
uni = rz_diff_unified_text(d, "expected", "actual", false, true);
if (!uni) {
goto cleanup;
}
rz_list_free(lines);
puts(uni);
free(uni);
printf("\n");
cleanup:
rz_diff_free(d);
if (regexp) {
RZ_FREE(output);
free((char *)output);
}
}
@ -721,14 +678,14 @@ static void print_result_diff(RzTestRunConfig *config, RzTestResultInfo *result)
const char *regexp_out = result->test->cmd_test->regexp_out.value;
if (expect && !rz_test_cmp_cmd_output(out, expect, regexp_out)) {
printf("-- stdout\n");
print_diff(out, expect, false, regexp_out);
print_diff(out, expect, regexp_out);
}
expect = result->test->cmd_test->expect_err.value;
const char *err = result->proc_out->err;
const char *regexp_err = result->test->cmd_test->regexp_err.value;
if (expect && !rz_test_cmp_cmd_output(err, expect, regexp_err)) {
printf("-- stderr\n");
print_diff(err, expect, false, regexp_err);
print_diff(err, expect, regexp_err);
} else if (*err) {
printf("-- stderr\n%s\n", err);
}
@ -743,7 +700,7 @@ static void print_result_diff(RzTestRunConfig *config, RzTestResultInfo *result)
const char *actual = result->asm_out->disasm;
if (expect && actual && strcmp(actual, expect)) {
printf("-- disassembly\n");
print_diff(actual, expect, false, NULL);
print_diff(actual, expect, NULL);
}
}
// TODO: assembly
@ -1134,5 +1091,5 @@ static void interact_diffchar(RzTestResultInfo *result) {
const char *expected = result->test->cmd_test->expect.value;
const char *regexp_out = result->test->cmd_test->regexp_out.value;
printf("-- stdout\n");
print_diff(actual, expected, true, regexp_out);
print_diff(actual, expected, regexp_out);
}

View file

@ -128,8 +128,7 @@ RZ_API bool rz_analysis_diff_bb(RzAnalysis *analysis, RzAnalysisFunction *fcn, R
mbb = mbb2 = NULL;
rz_list_foreach (fcn2->bbs, iter2, bb2) {
if (!bb2->diff || bb2->diff->type == RZ_ANALYSIS_DIFF_TYPE_NULL) {
rz_diff_buffers_distance(NULL, bb->fingerprint, bb->size,
bb2->fingerprint, bb2->size, NULL, &t);
rz_diff_levenstein_distance(bb->fingerprint, bb->size, bb2->fingerprint, bb2->size, NULL, &t);
if (t > analysis->diff_thbb && t > ot) {
ot = t;
mbb = bb;
@ -191,9 +190,8 @@ RZ_API int rz_analysis_diff_fcn(RzAnalysis *analysis, RzList *fcns, RzList *fcns
if (fcn->name && fcn2->name && strcmp(fcn->name, fcn2->name)) {
continue;
}
rz_diff_buffers_distance(NULL, fcn->fingerprint, fcn->fingerprint_size,
fcn2->fingerprint, fcn2->fingerprint_size,
NULL, &t);
rz_diff_levenstein_distance(fcn->fingerprint, fcn->fingerprint_size,
fcn2->fingerprint, fcn2->fingerprint_size, NULL, &t);
/* Set flag in matched functions */
fcn->diff->type = fcn2->diff->type = (t >= 1)
? RZ_ANALYSIS_DIFF_TYPE_MATCH
@ -254,7 +252,7 @@ RZ_API int rz_analysis_diff_fcn(RzAnalysis *analysis, RzList *fcns, RzList *fcns
eprintf("Function %s type not supported\n", fcn2->name);
continue;
}
rz_diff_buffers_distance(NULL, fcn->fingerprint, fcn->fingerprint_size, fcn2->fingerprint, fcn2->fingerprint_size, NULL, &t);
rz_diff_levenstein_distance(fcn->fingerprint, fcn->fingerprint_size, fcn2->fingerprint, fcn2->fingerprint_size, NULL, &t);
fcn->diff->dist = fcn2->diff->dist = t;
if (t > analysis->diff_thfcn && t > ot) {
ot = t;

View file

@ -183,6 +183,7 @@ rz_analysis = library('rz_analysis', rz_analysis_sources,
rz_cons_dep,
rz_flag_dep,
rz_hash_dep,
rz_diff_dep,
rz_parse_dep,
rz_asm_dep,
rz_bin_dep,
@ -213,6 +214,7 @@ pkgconfig_mod.generate(rz_analysis,
'rz_syscall',
'rz_search',
'rz_cons',
'rz_diff',
'rz_bin',
'rz_flag',
'rz_type'

View file

@ -1173,7 +1173,7 @@ static double cmp_bytesig_to_buff(RzSignBytes *sig, ut8 *buf, int len) {
ut8 *sigbuf = build_combined_bytes(sig);
double sim = -1.0;
if (sigbuf) {
rz_diff_buffers_distance(NULL, sigbuf, sig->size, buf, len, NULL, &sim);
rz_diff_levenstein_distance(sigbuf, sig->size, buf, len, NULL, &sim);
free(sigbuf);
}
return sim;

View file

@ -214,7 +214,7 @@ RZ_API void rz_bin_string_free(void *_str) {
}
RZ_API RzBinFile *rz_bin_open(RzBin *bin, const char *file, RzBinOptions *opt) {
rz_return_val_if_fail(bin && bin->iob.io && opt, false);
rz_return_val_if_fail(bin && bin->iob.io && opt, NULL);
RzIOBind *iob = &(bin->iob);
if (!iob->desc_get(iob->io, opt->fd)) {
@ -222,7 +222,7 @@ RZ_API RzBinFile *rz_bin_open(RzBin *bin, const char *file, RzBinOptions *opt) {
}
if (opt->fd < 0) {
eprintf("Couldn't open bin for file '%s'\n", file);
return false;
return NULL;
}
opt->sz = 0;
opt->pluginname = NULL;
@ -230,7 +230,7 @@ RZ_API RzBinFile *rz_bin_open(RzBin *bin, const char *file, RzBinOptions *opt) {
}
RZ_API RzBinFile *rz_bin_reload(RzBin *bin, RzBinFile *bf, ut64 baseaddr) {
rz_return_val_if_fail(bin && bf, false);
rz_return_val_if_fail(bin && bf, NULL);
RzBinOptions opt;
rz_bin_options_init(&opt, bf->fd, baseaddr, bf->loadaddr, bin->rawstr);
opt.filename = bf->file;

View file

@ -2281,7 +2281,6 @@ static int core_analysis_graph_construct_nodes(RzCore *core, RzAnalysisFunction
RzConfigHold *hc = rz_config_hold_new(core->config);
rz_config_hold_i(hc, "scr.color", "scr.utf8", "asm.offset", "asm.lines",
"asm.cmt.right", "asm.lines.fcn", "asm.bytes", NULL);
RzDiff *d = rz_diff_new();
rz_config_set_i(core->config, "scr.utf8", 0);
rz_config_set_i(core->config, "asm.offset", 0);
rz_config_set_i(core->config, "asm.lines", 0);
@ -2293,15 +2292,20 @@ static int core_analysis_graph_construct_nodes(RzCore *core, RzAnalysisFunction
}
if (bbi->diff && bbi->diff->type != RZ_ANALYSIS_DIFF_TYPE_MATCH && core->c2) {
char dff_from[32], dff_to[32];
RzCore *c = core->c2;
RzConfig *oc = c->config;
char *str = rz_core_cmd_strf(core, "pdb @ 0x%08" PFMT64x, bbi->addr);
c->config = core->config;
// XXX. the bbi->addr doesnt needs to be in the same address in core2
char *str2 = rz_core_cmd_strf(c, "pdb @ 0x%08" PFMT64x, bbi->diff->addr);
char *diffstr = rz_diff_buffers_to_string(d,
(const ut8 *)str, strlen(str),
(const ut8 *)str2, strlen(str2));
snprintf(dff_from, sizeof(dff_from), "0x%08" PFMT64x, bbi->addr);
snprintf(dff_to, sizeof(dff_to), "0x%08" PFMT64x, bbi->diff->addr);
RzDiff *dff = rz_diff_lines_new(str, str2, NULL);
char *diffstr = rz_diff_unified_text(dff, dff_from, dff_to, false, false);
rz_diff_free(dff);
if (diffstr) {
char *nl = strchr(diffstr, '\n');
@ -2319,14 +2323,12 @@ static int core_analysis_graph_construct_nodes(RzCore *core, RzAnalysisFunction
if (is_star) {
char *title = get_title(bbi->addr);
if (!title) {
rz_diff_free(d);
rz_config_hold_free(hc);
return false;
}
char *body_b64 = rz_base64_encode_dyn((const ut8 *)diffstr, strlen(diffstr));
if (!body_b64) {
free(title);
rz_diff_free(d);
rz_config_hold_free(hc);
return false;
}
@ -2353,7 +2355,6 @@ static int core_analysis_graph_construct_nodes(RzCore *core, RzAnalysisFunction
if (!title || !body_b64) {
free(body_b64);
free(title);
rz_diff_free(d);
rz_config_hold_free(hc);
return false;
}
@ -2368,7 +2369,6 @@ static int core_analysis_graph_construct_nodes(RzCore *core, RzAnalysisFunction
bbi->addr, difftype, str, font, fcn->name, bbi->addr);
}
}
rz_diff_free(d);
rz_config_set_i(core->config, "scr.color", 1);
rz_config_hold_free(hc);
}

View file

@ -4968,19 +4968,19 @@ RZ_IPI int rz_cmd_debug(void *data, const char *input) {
char *arg2 = strchr(arg, ' ');
if (arg2) {
*arg2++ = 0;
ut8 *a = getFileData(core, arg);
ut8 *b = getFileData(core, arg2);
char *a = (char *)getFileData(core, arg);
char *b = (char *)getFileData(core, arg2);
if (a && b) {
int al = strlen((const char *)a);
int bl = strlen((const char *)b);
RzDiff *d = rz_diff_new();
char *uni = rz_diff_buffers_to_string(d, a, al, b, bl);
RzDiff *dff = rz_diff_lines_new(a, b, NULL);
char *uni = rz_diff_unified_text(dff, arg, arg2, false, false);
rz_diff_free(dff);
rz_cons_printf("%s\n", uni);
rz_diff_free(d);
free(uni);
} else {
eprintf("Cannot open those alias files\n");
}
free(a);
free(b);
}
free(arg);
} else {

View file

@ -81,6 +81,7 @@ rz_core_inc = [platform_inc, include_directories(rz_core_inc)]
rz_core_deps = [
rz_util_dep,
rz_diff_dep,
rz_magic_dep,
rz_socket_dep,
rz_flag_dep,
@ -134,6 +135,7 @@ pkgconfig_mod.generate(
libraries: pkgcfg_sanitize_libs,
requires: pkgconfig_magic_requires + [
'rz_util',
'rz_diff',
'rz_reg',
'rz_syscall',
'rz_search',

31
librz/diff/bytes_diff.c Normal file
View file

@ -0,0 +1,31 @@
// SPDX-FileCopyrightText: 2021 RizinOrg <info@rizin.re>
// SPDX-FileCopyrightText: 2021 deroad <wargio@libero.it>
// SPDX-License-Identifier: LGPL-3.0-only
/* Helpers for handling bytes */
#define DIFF_IS_BYTES_METHOD(x) (x.elem_at == methods_bytes.elem_at)
static const void *byte_elem_at(const ut8 *array, ut32 index) {
return &array[index];
}
static int byte_compare(const ut8 *a_elem, const ut8 *b_elem) {
return ((int)b_elem[0]) - ((int)a_elem[0]);
}
static ut32 byte_hash(const char *elem) {
return elem[0];
}
static void byte_stringify(const ut8 *a_elem, RzStrBuf *sb) {
rz_strbuf_setf(sb, "%02x", *a_elem);
}
static const MethodsInternal methods_bytes = {
.elem_at /* */ = (RzDiffMethodElemAt)byte_elem_at,
.elem_hash /**/ = (RzDiffMethodElemHash)byte_hash,
.compare /* */ = (RzDiffMethodCompare)byte_compare,
.stringify /**/ = (RzDiffMethodStringify)byte_stringify,
.ignore /* */ = fake_ignore,
.free /* */ = NULL,
};

875
librz/diff/diff.c Normal file
View file

@ -0,0 +1,875 @@
// SPDX-FileCopyrightText: 2021 RizinOrg <info@rizin.re>
// SPDX-FileCopyrightText: 2021 deroad <wargio@libero.it>
// 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 <ht_pp.h>
#include <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 struct methods_internal_t {
RzDiffMethodElemAt elem_at;
RzDiffMethodElemHash elem_hash;
RzDiffMethodCompare compare;
RzDiffMethodIgnore ignore;
RzDiffMethodStringify stringify;
RzDiffMethodFree free;
} MethodsInternal;
struct rz_diff_t {
const void *a;
const void *b;
ut32 a_size;
ut32 b_size;
HtPP *b_hits;
MethodsInternal methods;
};
/**
* \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;
}
#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 free_hits(HtPPKv *kv) {
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 */
ht_pp_free(diff->b_hits);
diff->b_hits = ht_pp_new(NULL, free_hits, NULL);
diff->b_hits->opt.cmp /* */ = diff->methods.compare;
diff->b_hits->opt.calcsizeK /**/ = default_ksize;
diff->b_hits->opt.dupkey /* */ = NULL; // avoid to duplicate key
diff->b_hits->opt.hashfn /* */ = diff->methods.elem_hash;
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_NULLABLE RzDiffIgnoreByte ignore) {
rz_return_val_if_fail(a && b, NULL);
RzDiff *diff = RZ_NEW0(RzDiff);
if (!diff) {
return NULL;
}
diff->methods = methods_bytes;
if (ignore) {
diff->methods.ignore = (RzDiffMethodIgnore)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 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.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 *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 *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;
}
static RzDiffMatch *find_longest_match(RzDiff *diff, Block *block) {
rz_return_val_if_fail(diff && diff->methods.elem_at && diff->methods.compare && diff->methods.ignore, false);
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_new0();
if (!len_map) {
RZ_LOG_ERROR("find_longest_match: cannot allocate len_map\n");
goto 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_new0();
if (!tmp) {
RZ_LOG_ERROR("find_longest_match: cannot allocate tmp\n");
goto 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("find_longest_match: cannot allocate RzDiffMatch\n");
goto find_longest_match_fail;
}
ht_uu_free(len_map);
return match;
find_longest_match_fail:
ht_uu_free(tmp);
ht_uu_free(len_map);
return NULL;
}
static int cmp_matches(RzDiffMatch *m0, RzDiffMatch *m1) {
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 = find_longest_match(diff, block);
if (!match) {
continue;
}
if (match->size > 0) {
if (!rz_list_append(matches, match)) {
RZ_LOG_ERROR("rz_diff_matches_new: cannot append match into matches\n");
free(match);
goto rz_diff_matches_new_fail;
}
if (block->a_low < match->a && block->b_low < match->b) {
if (!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");
goto rz_diff_matches_new_fail;
}
}
if (match->a + match->size < block->a_hi && match->b + match->size < block->b_hi) {
if (!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");
goto rz_diff_matches_new_fail;
}
}
} else {
free(match);
}
free(block);
}
rz_list_sort(matches, (RzListComparator)cmp_matches);
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) {
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 *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(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(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_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);
goto rz_diff_opcodes_grouped_new_fail;
}
}
op = rz_list_first(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;
}

114
librz/diff/distance.c Normal file
View file

@ -0,0 +1,114 @@
// SPDX-FileCopyrightText: 2017 Fangrui Song <i@maskray.me>
// SPDX-FileCopyrightText: 2016 NikolaiHampton <nikolaih@3583bytesready.net>
// SPDX-License-Identifier: LGPL-3.0-only
#include <rz_diff.h>
#include <rz_util/rz_assert.h>
/**
* \brief Calculates the distance between two buffers using the Myers algorithm
*
* Calculates the distance between two buffers using the Eugene W. Myers' O(ND) diff algorithm.
* - distance: is the minimum number of edits needed to transform A into B
* - similarity: is a number that defines how similar/identical the 2 buffers are.
* */
RZ_API bool rz_diff_myers_distance(RZ_NONNULL const ut8 *a, ut32 la, RZ_NONNULL const ut8 *b, ut32 lb, RZ_NULLABLE ut32 *distance, RZ_NULLABLE double *similarity) {
rz_return_val_if_fail(a && b, false);
const ut32 length = la + lb;
const ut8 *ea = a + la, *eb = b + lb;
for (; a < ea && b < eb && *a == *b; a++, b++) {
}
for (; a < ea && b < eb && ea[-1] == eb[-1]; ea--, eb--) {
}
la = ea - a;
lb = eb - b;
ut32 *v0, *v;
st64 m = (st64)la + lb, di = 0, low, high, i, x, y;
if (m + 2 > SIZE_MAX / sizeof(st64) || !(v0 = malloc((m + 2) * sizeof(ut32)))) {
return false;
}
v = v0 + lb;
v[1] = 0;
for (di = 0; di <= m; di++) {
low = -di + 2 * RZ_MAX(0, di - (st64)lb);
high = di - 2 * RZ_MAX(0, di - (st64)la);
for (i = low; i <= high; i += 2) {
x = i == -di || (i != di && v[i - 1] < v[i + 1]) ? v[i + 1] : v[i - 1] + 1;
y = x - i;
while (x < la && y < lb && a[x] == b[y]) {
x++;
y++;
}
v[i] = x;
if (x == la && y == lb) {
goto out;
}
}
}
out:
free(v0);
if (distance) {
*distance = di;
}
if (similarity) {
*similarity = length ? 1.0 - (double)di / length : 1.0;
}
return true;
}
/**
* \brief Calculates the distance between two buffers using the Levenshtein algorithm
*
* Calculates the distance between two buffers using the Levenshtein distance algorithm.
* - distance: is the minimum number of edits needed to transform A into B
* - similarity: is a number that defines how similar/identical the 2 buffers are.
* */
RZ_API bool rz_diff_levenstein_distance(RZ_NONNULL const ut8 *a, ut32 la, RZ_NONNULL const ut8 *b, ut32 lb, RZ_NULLABLE ut32 *distance, RZ_NULLABLE double *similarity) {
rz_return_val_if_fail(a && b, false);
const ut32 length = RZ_MAX(la, lb);
const ut8 *ea = a + la, *eb = b + lb, *t;
ut32 *d, i, j;
for (; a < ea && b < eb && *a == *b; a++, b++) {
}
for (; a < ea && b < eb && ea[-1] == eb[-1]; ea--, eb--) {
}
la = ea - a;
lb = eb - b;
if (la < lb) {
i = la;
la = lb;
lb = i;
t = a;
a = b;
b = t;
}
if (sizeof(ut32) > SIZE_MAX / (lb + 1) || !(d = malloc((lb + 1) * sizeof(ut32)))) {
return false;
}
for (i = 0; i <= lb; i++) {
d[i] = i;
}
for (i = 0; i < la; i++) {
ut32 ul = d[0];
d[0] = i + 1;
for (j = 0; j < lb; j++) {
ut32 u = d[j + 1];
d[j + 1] = a[i] == b[j] ? ul : RZ_MIN(ul, RZ_MIN(d[j], u)) + 1;
ul = u;
}
}
if (distance) {
*distance = d[lb];
}
if (similarity) {
*similarity = length ? 1.0 - (double)d[lb] / length : 1.0;
}
free(d);
return true;
}

76
librz/diff/lines_diff.c Normal file
View file

@ -0,0 +1,76 @@
// SPDX-FileCopyrightText: 2021 RizinOrg <info@rizin.re>
// SPDX-FileCopyrightText: 2021 deroad <wargio@libero.it>
// SPDX-License-Identifier: LGPL-3.0-only
/* Helpers for handling lines */
static RzList *tokenize_lines(const char *string) {
RzList *lines = NULL;
size_t last = 0;
size_t size = 0;
char *line = NULL;
lines = rz_list_newf((RzListFree)free);
if (!lines) {
RZ_LOG_ERROR("rz_diff_line_new: cannot allocate list of lines\n");
goto tokenize_newlines_fail;
}
size = strlen(string);
for (size_t i = 0; i < size; ++i) {
if (string[i] == '\n') {
line = rz_str_ndup(string + last, (i + 1) - last);
if (!line || !rz_list_append(lines, line)) {
RZ_LOG_ERROR("rz_diff_line_new: cannot allocate line or add it to the list\n");
free(line);
goto tokenize_newlines_fail;
}
last = i + 1;
}
}
if (last < size) {
line = rz_str_ndup(string + last, size - last);
if (!line || !rz_list_append(lines, line)) {
RZ_LOG_ERROR("rz_diff_line_new: cannot allocate last line or add it to the list\n");
free(line);
goto tokenize_newlines_fail;
}
}
return lines;
tokenize_newlines_fail:
rz_list_free(lines);
return NULL;
}
static const void *line_elem_at(const RzList *array, ut32 index) {
return rz_list_get_n(array, index);
}
static int line_compare(const char *a_elem, const char *b_elem) {
return strcmp(a_elem, b_elem);
}
static ut32 line_hash(const char *elem) {
ut32 size = strlen(elem);
return rz_diff_hash_data((const ut8 *)elem, size);
}
static void line_stringify(const char *a_elem, RzStrBuf *sb) {
rz_strbuf_set(sb, a_elem);
}
static void line_free(RzList *array) {
rz_list_free(array);
}
static const MethodsInternal methods_lines = {
.elem_at /* */ = (RzDiffMethodElemAt)line_elem_at,
.elem_hash /**/ = (RzDiffMethodElemHash)line_hash,
.compare /* */ = (RzDiffMethodCompare)line_compare,
.stringify /**/ = (RzDiffMethodStringify)line_stringify,
.ignore /* */ = fake_ignore,
.free /* */ = (RzDiffMethodFree)line_free,
};

31
librz/diff/meson.build Normal file
View file

@ -0,0 +1,31 @@
rz_diff_sources = [
'diff.c',
'distance.c'
]
dependencies = [rz_util_dep]
rz_diff = library('rz_diff', rz_diff_sources,
include_directories: [platform_inc],
c_args: library_cflags,
dependencies: dependencies,
install: true,
implicit_include_directories: false,
install_rpath: rpath_lib,
soversion: rizin_libversion
)
rz_diff_dep = declare_dependency(link_with: rz_diff,
include_directories: [platform_inc])
pkgconfig_mod.generate(rz_diff,
subdirs: 'librz',
version: rizin_version,
name: 'rz_diff',
filebase: 'rz_diff',
libraries: pkgcfg_sanitize_libs,
requires: [
'rz_util'
],
description: 'rizin foundation libraries'
)

277
librz/diff/unified_diff.c Normal file
View file

@ -0,0 +1,277 @@
// SPDX-FileCopyrightText: 2021 RizinOrg <info@rizin.re>
// SPDX-FileCopyrightText: 2021 deroad <wargio@libero.it>
// SPDX-License-Identifier: LGPL-3.0-only
#include <rz_cons.h>
#define Color_RANGE Color_BBLUE
#define Color_INSERT Color_BGREEN
#define Color_DELETE Color_BRED
#define FAST_MOD2(x, y) ((x) & (y - 1))
#define FAST_MOD64(x) FAST_MOD2(x, 64)
#define DIFF_COLOR(prefix) (prefix == '+' ? Color_INSERT : (prefix == '-' ? Color_DELETE : ""))
static inline void diff_unified_append_ranges(RzList *opcodes, RzStrBuf *sb, bool color) {
const char *color_beg = color ? Color_RANGE : "";
const char *color_end = color ? Color_RESET : "";
RzDiffOp *first = rz_list_first(opcodes);
RzDiffOp *last = rz_list_last(opcodes);
st32 a_len = last->a_end - first->a_beg;
st32 b_len = last->b_end - first->b_beg;
rz_strbuf_appendf(sb, "%s@@ -%d,%d +%d,%d @@%s\n", color_beg, first->a_beg + 1, a_len, first->b_beg + 1, b_len, color_end);
}
static inline void diff_unified_json_ranges(RzList *opcodes, PJ *pj) {
RzDiffOp *first = rz_list_first(opcodes);
RzDiffOp *last = rz_list_last(opcodes);
st32 a_len = last->a_end - first->a_beg;
st32 b_len = last->b_end - first->b_beg;
pj_ka(pj, "from");
pj_N(pj, first->a_beg + 1);
pj_N(pj, a_len);
pj_end(pj);
pj_ka(pj, "to");
pj_N(pj, first->b_beg + 1);
pj_N(pj, b_len);
pj_end(pj);
}
static inline void diff_unified_append_data(RzDiff *diff, const void *array, st32 beg, st32 end, RzStrBuf *sb, char prefix, bool color) {
RzDiffMethodElemAt elem_at = diff->methods.elem_at;
RzDiffMethodStringify stringify = diff->methods.stringify;
int len = 0;
ut32 count = 0;
const char *p;
const void *elem;
RzStrBuf tmp;
bool newline = false;
bool is_bytes = DIFF_IS_BYTES_METHOD(diff->methods);
if (beg < 0) {
beg = 0;
}
const char *bcol = color ? DIFF_COLOR(prefix) : "";
const char *ecol = color ? (Color_RESET) : "";
rz_strbuf_appendf(sb, "%s%c", bcol, prefix);
for (st32 i = beg; i < end; ++i) {
if (newline || (is_bytes && count > 0 && !FAST_MOD64(count))) {
rz_strbuf_appendf(sb, "%s\n%s%c", bcol, ecol, prefix);
newline = false;
}
rz_strbuf_init(&tmp);
elem = elem_at(array, i);
stringify(elem, &tmp);
len = rz_strbuf_length(&tmp);
p = rz_strbuf_get(&tmp);
count += len;
if (len > 0 && p[len - 1] == '\n') {
len--;
newline = true;
}
rz_strbuf_append_n(sb, p, len);
rz_strbuf_fini(&tmp);
}
rz_strbuf_appendf(sb, "%s\n", ecol);
}
static inline void diff_unified_json_data(RzDiff *diff, const void *array, st32 beg, st32 end, PJ *pj, const char *op) {
RzDiffMethodElemAt elem_at = diff->methods.elem_at;
RzDiffMethodStringify stringify = diff->methods.stringify;
int len = 0;
ut32 count = 0;
const char *p;
const void *elem;
RzStrBuf tmp;
bool newline = false;
bool is_bytes = DIFF_IS_BYTES_METHOD(diff->methods);
if (beg < 0) {
beg = 0;
}
pj_o(pj);
pj_ks(pj, "op", op);
rz_strbuf_init(&tmp);
for (st32 i = beg; i < end; ++i) {
if (newline || (is_bytes && count > 0 && !FAST_MOD64(count))) {
pj_ks(pj, "value", rz_strbuf_get(&tmp));
pj_end(pj);
rz_strbuf_fini(&tmp);
rz_strbuf_init(&tmp);
pj_o(pj);
pj_ks(pj, "op", op);
newline = false;
}
elem = elem_at(array, i);
stringify(elem, &tmp);
len = rz_strbuf_length(&tmp);
p = rz_strbuf_get(&tmp);
count += len;
if (len > 0 && p[len - 1] == '\n') {
newline = true;
}
}
pj_ks(pj, "value", rz_strbuf_get(&tmp));
pj_end(pj);
rz_strbuf_fini(&tmp);
}
/**
* \brief Produces a diff output with A and B inputs presented immediately adjacent to each other.
*
* Produces a diff output with A and B inputs presented immediately adjacent to each other.
* It begins with range information and is immediately followed with the line additions,
* line deletions, and any number of the contextual lines.
* */
RZ_API RZ_OWN char *rz_diff_unified_text(RZ_NONNULL RzDiff *diff, RZ_NULLABLE const char *from, RZ_NULLABLE const char *to, bool show_time, bool color) {
rz_return_val_if_fail(diff && diff->methods.elem_at && diff->methods.stringify, NULL);
RzStrBuf *sb = NULL;
RzList *groups = NULL;
RzList *opcodes = NULL;
RzDiffOp *op = NULL;
RzListIter *itg = NULL;
RzListIter *ito = NULL;
if (!from) {
from = "/original";
}
if (!to) {
to = "/modified";
}
sb = rz_strbuf_new("");
if (!sb) {
RZ_LOG_ERROR("rz_diff_unified: cannot allocate sb\n");
goto rz_diff_unified_text_fail;
}
if (show_time) {
char *time = rz_time_to_string(rz_time_now());
rz_strbuf_appendf(sb, "--- %s %s\n+++ %s %s\n", from, (time ? time : ""), to, (time ? time : ""));
free(time);
} else {
rz_strbuf_appendf(sb, "--- %s\n+++ %s\n", from, to);
}
groups = rz_diff_opcodes_grouped_new(diff, RZ_DIFF_DEFAULT_N_GROUPS);
if (!groups) {
goto rz_diff_unified_text_fail;
}
rz_list_foreach (groups, itg, opcodes) {
if (rz_list_length(opcodes) < 1) {
continue;
}
diff_unified_append_ranges(opcodes, sb, color);
rz_list_foreach (opcodes, ito, op) {
if (op->type == RZ_DIFF_OP_EQUAL) {
diff_unified_append_data(diff, diff->a, op->a_beg, op->a_end, sb, ' ', color);
continue;
}
if (op->type == RZ_DIFF_OP_DELETE || op->type == RZ_DIFF_OP_REPLACE) {
diff_unified_append_data(diff, diff->a, op->a_beg, op->a_end, sb, '-', color);
}
if (op->type == RZ_DIFF_OP_INSERT || op->type == RZ_DIFF_OP_REPLACE) {
diff_unified_append_data(diff, diff->b, op->b_beg, op->b_end, sb, '+', color);
}
}
}
rz_list_free(groups);
return rz_strbuf_drain(sb);
rz_diff_unified_text_fail:
rz_strbuf_free(sb);
rz_list_free(groups);
return NULL;
}
/**
* \brief Produces a diff output to convert A in B in a JSON format.
*
* Produces a diff output with A and B inputs and contains the operations required
* to convert A in B and the values to remove, insert or keep.
* */
RZ_API RZ_OWN PJ *rz_diff_unified_json(RZ_NONNULL RzDiff *diff, RZ_NULLABLE const char *from, RZ_NULLABLE const char *to, bool show_time) {
rz_return_val_if_fail(diff && diff->methods.elem_at && diff->methods.stringify, NULL);
PJ *pj = NULL;
RzList *groups = NULL;
RzList *opcodes = NULL;
RzDiffOp *op = NULL;
RzListIter *itg = NULL;
RzListIter *ito = NULL;
if (!from) {
from = "/original";
}
if (!to) {
to = "/modified";
}
pj = pj_new();
if (!pj) {
RZ_LOG_ERROR("rz_diff_unified: failed to allocate json\n");
goto rz_diff_unified_json_fail;
}
pj_o(pj);
if (show_time) {
char *time = rz_time_to_string(rz_time_now());
if (!time) {
RZ_LOG_ERROR("rz_diff_unified: failed to allocate timestamp\n");
goto rz_diff_unified_json_fail;
}
pj_ks(pj, "timestamp", time);
free(time);
}
pj_ks(pj, "from", from);
pj_ks(pj, "to", to);
groups = rz_diff_opcodes_grouped_new(diff, RZ_DIFF_DEFAULT_N_GROUPS);
if (!groups) {
goto rz_diff_unified_json_fail;
}
pj_ka(pj, "diff");
rz_list_foreach (groups, itg, opcodes) {
if (rz_list_length(opcodes) < 1) {
continue;
}
pj_o(pj);
diff_unified_json_ranges(opcodes, pj);
pj_ka(pj, "ops");
rz_list_foreach (opcodes, ito, op) {
if (op->type == RZ_DIFF_OP_EQUAL) {
diff_unified_json_data(diff, diff->a, op->a_beg, op->a_end, pj, "equal");
continue;
}
if (op->type == RZ_DIFF_OP_DELETE || op->type == RZ_DIFF_OP_REPLACE) {
diff_unified_json_data(diff, diff->a, op->a_beg, op->a_end, pj, "delete");
}
if (op->type == RZ_DIFF_OP_INSERT || op->type == RZ_DIFF_OP_REPLACE) {
diff_unified_json_data(diff, diff->b, op->b_beg, op->b_end, pj, "insert");
}
}
pj_end(pj);
pj_end(pj);
}
pj_end(pj);
pj_end(pj);
rz_list_free(groups);
return pj;
rz_diff_unified_json_fail:
pj_free(pj);
rz_list_free(groups);
return NULL;
}

View file

@ -259,11 +259,11 @@ typedef struct rz_bin_object_t {
* This associates the name of every symbol where is_imported == true to the symbol itself.
*/
HtPP /*<const char *, RzBinSymbol>*/ *import_name_symbols; // currently only used for imports, but could be extended to all symbols if needed.
RzList /*<??>*/ *entries;
RzList /*<??>*/ *fields;
RzList /*<??>*/ *libs;
RzList /*<RzBinAddr>*/ *entries;
RzList /*<RzBinField>*/ *fields;
RzList /*<char*>*/ *libs;
RzBinRelocStorage *relocs;
RzList /*<??>*/ *strings;
RzList /*<RzBinString>*/ *strings;
RzList /*<RzBinClass>*/ *classes;
HtPP *classes_ht;
HtPP *methods_ht;

View file

@ -1,9 +1,14 @@
// SPDX-FileCopyrightText: 2021 RizinOrg <info@rizin.re>
// SPDX-FileCopyrightText: 2021 deroad <wargio@libero.it>
// SPDX-License-Identifier: LGPL-3.0-only
#ifndef RZ_DIFF_H
#define RZ_DIFF_H
#include <rz_types.h>
#include <rz_util.h>
#include <rz_cons.h>
#include <rz_list.h>
#include <rz_util/pj.h>
#include <rz_util/rz_strbuf.h>
#ifdef __cplusplus
extern "C" {
@ -11,76 +16,89 @@ extern "C" {
RZ_LIB_VERSION_HEADER(rz_diff);
#define Color_INSERT Color_BGREEN
#define Color_DELETE Color_BRED
#define Color_BGINSERT "\x1b[48;5;22m"
#define Color_BGDELETE "\x1b[48;5;52m"
#define Color_HLINSERT Color_BGINSERT Color_INSERT
#define Color_HLDELETE Color_BGDELETE Color_DELETE
typedef enum rz_diff_op_type_t {
RZ_DIFF_OP_INVALID = 0,
RZ_DIFF_OP_DELETE,
RZ_DIFF_OP_EQUAL,
RZ_DIFF_OP_INSERT,
RZ_DIFF_OP_REPLACE,
} RzDiffOpType;
/**
* This interface allows to analyze any data using the same algorithm
* elem_at(array, index) [required] must return the an element of the array at position 'index'
* elem_hash(elem) [required] must return the hash value of the element (use rz_diff_hash_data)
* compare(a_elem, b_elem) [required] must return true if the two elements are the same
* stringify(elem, sb) [required] appends into sb the stringified element of the array
* ignore(elem) [optional] must return true if the element matches the user define
* rule (if set to NULL, it will be considered as always false)
*/
typedef const void *(*RzDiffMethodElemAt)(RZ_BORROW const void *array, ut32 index);
typedef ut32 (*RzDiffMethodElemHash)(RZ_BORROW const void *elem);
typedef int (*RzDiffMethodCompare)(RZ_BORROW const void *a_elem, RZ_BORROW const void *b_elem);
typedef bool (*RzDiffMethodIgnore)(RZ_BORROW const void *elem);
typedef void (*RzDiffMethodStringify)(RZ_BORROW const void *elem, RZ_BORROW RzStrBuf *sb);
typedef struct rz_diff_methods_t {
RzDiffMethodElemAt elem_at; ///< can be either be an element of A or B
RzDiffMethodElemHash elem_hash; ///< can be either be an element of A or B
RzDiffMethodCompare compare; ///< elements from A and B
RzDiffMethodStringify stringify; ///< elements from A and B
RzDiffMethodIgnore ignore; ///< elements from A and B
} RzDiffMethods;
typedef struct rz_diff_op_t {
/* file A */
ut64 a_off;
const ut8 *a_buf;
ut32 a_len;
/* file B */
ut64 b_off;
const ut8 *b_buf;
ut32 b_len;
RzDiffOpType type;
st32 a_beg;
st32 a_end;
st32 b_beg;
st32 b_end;
} RzDiffOp;
//typedef struct rz_diff_t RzDiff;
#define RZ_DIFF_OP_SIZE_A(op) (((op)->a_end) - ((op)->a_beg))
#define RZ_DIFF_OP_SIZE_B(op) (((op)->b_end) - ((op)->b_beg))
#define RZ_DIFF_DEFAULT_N_GROUPS 3
typedef struct rz_diff_t {
ut64 off_a;
ut64 off_b;
int delta;
void *user;
bool verbose;
int type;
const char **diff_cmd; // null-terminated array of cmd+args
int (*callback)(struct rz_diff_t *diff, void *user, RzDiffOp *op);
} RzDiff;
typedef struct match_p_t {
ut32 a;
ut32 b;
ut32 size;
} RzDiffMatch;
typedef int (*RzDiffCallback)(RzDiff *diff, void *user, RzDiffOp *op);
typedef bool (*RzDiffIgnoreByte)(const ut64 byte);
typedef bool (*RzDiffIgnoreLine)(RZ_BORROW const char *line);
typedef struct rz_diffchar_t {
const ut8 *align_a;
const ut8 *align_b;
size_t len_buf;
size_t start_align;
} RzDiffChar;
typedef struct rz_diff_t RzDiff;
/* XXX: this api needs to be reviewed , constructor with offa+offb?? */
#ifdef RZ_API
RZ_API RzDiff *rz_diff_new(void);
RZ_API RzDiff *rz_diff_new_from(ut64 off_a, ut64 off_b);
RZ_API RzDiff *rz_diff_free(RzDiff *d);
RZ_API int rz_diff_buffers(RzDiff *d, const ut8 *a, ut32 la, const ut8 *b, ut32 lb);
RZ_API int rz_diff_buffers_static(RzDiff *d, const ut8 *a, int la, const ut8 *b, int lb);
RZ_API int rz_diff_buffers_radiff(RzDiff *d, const ut8 *a, int la, const ut8 *b, int lb);
RZ_API int rz_diff_buffers_delta(RzDiff *diff, const ut8 *sa, int la, const ut8 *sb, int lb);
RZ_API int rz_diff_buffers(RzDiff *d, const ut8 *a, ut32 la, const ut8 *b, ut32 lb);
RZ_API char *rz_diff_buffers_to_string(RzDiff *d, const ut8 *a, int la, const ut8 *b, int lb);
RZ_API int rz_diff_set_callback(RzDiff *d, RzDiffCallback callback, void *user);
RZ_API bool rz_diff_buffers_distance(RzDiff *d, const ut8 *a, ut32 la, const ut8 *b, ut32 lb, ut32 *distance, double *similarity);
RZ_API bool rz_diff_buffers_distance_myers(RzDiff *diff, const ut8 *a, ut32 la, const ut8 *b, ut32 lb, ut32 *distance, double *similarity);
RZ_API bool rz_diff_buffers_distance_levenshtein(RzDiff *d, const ut8 *a, ut32 la, const ut8 *b, ut32 lb, ut32 *distance, double *similarity);
RZ_API char *rz_diff_buffers_unified(RzDiff *d, const ut8 *a, int la, const ut8 *b, int lb);
/* static method !??! */
RZ_API int rz_diff_lines(const char *file1, const char *sa, int la, const char *file2, const char *sb, int lb);
RZ_API int rz_diff_set_delta(RzDiff *d, int delta);
RZ_API int rz_diff_gdiff(const char *file1, const char *file2, int rad, int va);
/* To calculate the hash of a complex structure made of
* various values, xor the results before returning the final value. */
RZ_API ut32 rz_diff_hash_data(RZ_NULLABLE const ut8 *buffer, ut32 size);
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_NULLABLE RzDiffIgnoreByte ignore);
RZ_API RZ_OWN RzDiff *rz_diff_lines_new(RZ_BORROW const char *a, RZ_BORROW const char *b, RZ_NULLABLE RzDiffIgnoreLine ignore);
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_API void rz_diff_free(RZ_NULLABLE RzDiff *diff);
RZ_API RZ_BORROW const void *rz_diff_get_a(RZ_NONNULL RzDiff *diff);
RZ_API RZ_BORROW const void *rz_diff_get_b(RZ_NONNULL RzDiff *diff);
RZ_API RZ_OWN RzList /*<RzDiffMatch>*/ *rz_diff_matches_new(RZ_NONNULL RzDiff *diff);
RZ_API RZ_OWN RzList /*<RzDiffOp>*/ *rz_diff_opcodes_new(RZ_NONNULL RzDiff *diff);
RZ_API RZ_OWN RzList /*<RzList<RzDiffOp>>*/ *rz_diff_opcodes_grouped_new(RZ_NONNULL RzDiff *diff, ut32 n_groups);
RZ_API bool rz_diff_ratio(RZ_NONNULL RzDiff *diff, RZ_NONNULL double *result);
RZ_API bool rz_diff_sizes_ratio(RZ_NONNULL RzDiff *diff, RZ_NONNULL double *result);
RZ_API RZ_OWN char *rz_diff_unified_text(RZ_NONNULL RzDiff *diff, RZ_NULLABLE const char *from, RZ_NULLABLE const char *to, bool show_time, bool color);
RZ_API RZ_OWN PJ *rz_diff_unified_json(RZ_NONNULL RzDiff *diff, RZ_NULLABLE const char *from, RZ_NULLABLE const char *to, bool show_time);
/* Distances algorithms */
RZ_API bool rz_diff_myers_distance(RZ_NONNULL const ut8 *a, ut32 size_a, RZ_NONNULL const ut8 *b, ut32 size_b, RZ_NULLABLE ut32 *distance, RZ_NULLABLE double *similarity);
RZ_API bool rz_diff_levenstein_distance(RZ_NONNULL const ut8 *a, ut32 size_a, RZ_NONNULL const ut8 *b, ut32 size_b, RZ_NULLABLE ut32 *distance, RZ_NULLABLE double *similarity);
RZ_API RzDiffChar *rz_diffchar_new(const ut8 *a, const ut8 *b);
RZ_API void rz_diffchar_print(RzDiffChar *diffchar);
RZ_API void rz_diffchar_free(RzDiffChar *diffchar);
#endif
#ifdef __cplusplus
}
#endif
#endif
#endif /* RZ_DIFF_H */

View file

@ -61,10 +61,9 @@ typedef struct rz_oflist_t {
#ifndef _R_LIST_C_
#define rz_list_push(x, y) rz_list_append(x, y)
#define rz_list_iterator(x) (x) ? (x)->head : NULL
// #define rz_list_empty(x) (!x || (!(x->head) && !(x->tail)))
#define rz_list_empty(x) (!(x) || !(x)->length)
#define rz_list_head(x) ((x) ? (x)->head : NULL)
#define rz_list_tail(x) ((x) ? (x)->tail : NULL)
#define rz_list_empty(x) (!(x) || !(x)->length)
#define rz_list_head(x) ((x) ? (x)->head : NULL)
#define rz_list_tail(x) ((x) ? (x)->tail : NULL)
#define rz_list_iter_get(x) \
x->data; \

View file

@ -21,7 +21,7 @@ RZ_API ut64 rz_time_now_mono(void);
RZ_API char *rz_time_stamp_to_str(ut32 timeStamp);
RZ_API ut32 rz_time_dos_time_stamp_to_posix(ut32 timeStamp);
RZ_API bool rz_time_stamp_is_dos_format(const ut32 certainPosixTimeStamp, const ut32 possiblePosixOrDosTimeStamp);
RZ_API const char *rz_time_to_string(ut64 ts);
RZ_API char *rz_time_to_string(ut64 ts);
// Thread-safe cross platform wrappers
RZ_API char *rz_asctime_r(const struct tm *tm, char *buf);

View file

@ -34,6 +34,7 @@ rz_main_deps = [
rz_config_dep,
rz_bin_dep,
rz_core_dep,
rz_diff_dep,
]
rz_main = library('rz_main', rz_main_sources,
@ -59,6 +60,7 @@ pkgconfig_mod.generate(rz_main,
requires: [
'rz_core',
'rz_asm',
'rz_diff',
'rz_syscall'
],
description: 'rizin foundation libraries'

View file

@ -57,6 +57,7 @@ static int rz_main_version_verify(int show) {
{ "rz_search", rz_search_version },
{ "rz_syscall", rz_syscall_version },
{ "rz_util", rz_util_version },
{ "rz_diff", rz_diff_version },
/* ... */
{ NULL, NULL }
};

File diff suppressed because it is too large Load diff

View file

@ -1,369 +0,0 @@
// SPDX-FileCopyrightText: 2005, 2006 Matt Mackall <mpm@selenic.com>
// SPDX-FileCopyrightText: 2009-2010 pancake <pancake@nopcode.org>
// SPDX-License-Identifier: GPL-2.0-or-later
/* Adapted code from:
bdiff.c - efficient binary diff extension for Mercurial
Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
This software may be used and distributed according to the terms of
the GNU General Public License, incorporated herein by reference.
Based roughly on Python difflib
*/
#include <rz_util.h>
#include <rz_diff.h>
#include <stdlib.h>
#include <string.h>
#include <limits.h>
struct line {
int h, len, n, e;
const char *l;
};
struct pos {
int pos, len;
};
struct hunk {
int a1, a2, b1, b2;
};
struct hunklist {
struct hunk *base, *head;
};
static int splitlines(const char *a, int len, struct line **lr) {
int h, i;
const char *p, *b = a;
const char * const plast = a + len - 1;
struct line *l;
if (!a) {
eprintf ("null pointer received\n");
return 0;
}
/* count the lines */
i = 1; /* extra line for sentinel */
for (p = a; p < a + len; p++) {
if (*p == '\n' || p == plast) {
i++;
}
}
*lr = l = (struct line *)malloc(sizeof(struct line) * i);
if (!l) {
return -1;
}
/* build the line array and calculate hashes */
h = 0;
for (p = a; p < a + len; p++) {
/* Leonid Yuriev's hash */
h = (h * 1664525) + *p + 1013904223;
if (*p == '\n' || p == plast) {
l->h = h;
h = 0;
l->len = p - b + 1;
l->l = b;
l->n = INT_MAX;
l++;
b = p + 1;
}
}
/* set up a sentinel */
l->h = l->len = 0;
l->l = a + len;
return i - 1;
}
inline static int cmp(struct line *a, struct line *b) {
return a->h != b->h || a->len != b->len || memcmp(a->l, b->l, a->len);
}
static int equatelines(struct line *a, int an, struct line *b, int bn) {
int i, j, t;
size_t scale, buckets = 1;
struct pos *h = NULL;
/* build a hash table of the next highest power of 2 */
while (buckets < bn + 1) {
buckets *= 2;
}
/* try to allocate a large hash table to avoid collisions */
for (scale = 4; scale; scale /= 2) {
h = (struct pos *)malloc(scale * buckets * sizeof(struct pos));
if (h) {
break;
}
}
if (!h) {
return 0;
}
buckets = buckets * scale - 1;
/* clear the hash table */
for (i = 0; i <= buckets; i++) {
h[i].pos = INT_MAX;
h[i].len = 0;
}
/* add lines to the hash table chains */
for (i = bn - 1; i >= 0; i--) {
/* find the equivalence class */
for (j = b[i].h & buckets; h[j].pos != INT_MAX;
j = (j + 1) & buckets) {
if (!cmp (b + i, b + h[j].pos)) {
break;
}
}
/* add to the head of the equivalence class */
b[i].n = h[j].pos;
b[i].e = j;
h[j].pos = i;
h[j].len++; /* keep track of popularity */
}
/* compute popularity threshold */
t = (bn >= 4000) ? bn / 1000 : bn + 1;
/* match items in a to their equivalence class in b */
for (i = 0; i < an; i++) {
/* find the equivalence class */
for (j = a[i].h & buckets; h[j].pos != INT_MAX;
j = (j + 1) & buckets) {
if (!cmp (a + i, b + h[j].pos)) {
break;
}
}
a[i].e = j; /* use equivalence class for quick compare */
if (h[j].len <= t) {
a[i].n = h[j].pos; /* point to head of match list */
} else {
a[i].n = INT_MAX; /* too popular */
}
}
/* discard hash tables */
free(h);
return 1;
}
static int longest_match(struct line *a, struct line *b, struct pos *pos,
int a1, int a2, int b1, int b2, int *omi, int *omj)
{
int mi = a1, mj = b1, mk = 0, mb = 0, i, j, k;
for (i = a1; i < a2; i++) {
/* skip things before the current block */
for (j = a[i].n; j < b1; j = b[j].n) {
;
}
/* loop through all lines match a[i] in b */
for (; j < b2; j = b[j].n) {
/* does this extend an earlier match? */
if (i > a1 && j > b1 && pos[j - 1].pos == i - 1) {
k = pos[j - 1].len + 1;
} else {
k = 1;
}
pos[j].pos = i;
pos[j].len = k;
/* best match so far? */
if (k > mk) {
mi = i;
mj = j;
mk = k;
}
}
}
if (mk) {
mi = mi - mk + 1;
mj = mj - mk + 1;
}
/* expand match to include neighboring popular lines */
while (mi - mb > a1 && mj - mb > b1 &&
a[mi - mb - 1].e == b[mj - mb - 1].e) {
mb++;
}
while (mi + mk < a2 && mj + mk < b2 &&
a[mi + mk].e == b[mj + mk].e) {
mk++;
}
*omi = mi - mb;
*omj = mj - mb;
return mk + mb;
}
static void recurse(struct line *a, struct line *b, struct pos *pos,
int a1, int a2, int b1, int b2, struct hunklist *l)
{
int i, j, k;
/* find the longest match in this chunk */
k = longest_match(a, b, pos, a1, a2, b1, b2, &i, &j);
if (!k) {
return;
}
/* and recurse on the remaining chunks on either side */
recurse(a, b, pos, a1, i, b1, j, l);
l->head->a1 = i;
l->head->a2 = i + k;
l->head->b1 = j;
l->head->b2 = j + k;
l->head++;
recurse(a, b, pos, i + k, a2, j + k, b2, l);
}
static struct hunklist diff(struct line *a, int an, struct line *b, int bn)
{
struct hunklist l;
struct hunk *curr;
struct pos *pos;
int t;
/* allocate and fill arrays */
t = equatelines(a, an, b, bn);
pos = (struct pos *)calloc(bn ? bn : 1, sizeof(struct pos));
/* we can't have more matches than lines in the shorter file */
l.head = l.base = (struct hunk *)malloc(sizeof(struct hunk) *
((an<bn ? an:bn) + 1));
if (pos && l.base && t) {
/* generate the matching block list */
recurse(a, b, pos, 0, an, 0, bn, &l);
l.head->a1 = l.head->a2 = an;
l.head->b1 = l.head->b2 = bn;
l.head++;
}
free(pos);
/* normalize the hunk list, try to push each hunk towards the end */
for (curr = l.base; curr != l.head; curr++) {
struct hunk *next = curr+1;
int shift = 0;
if (next == l.head) {
break;
}
if (curr->a2 == next->a1) {
while (curr->a2 + shift < an && curr->b2 + shift < bn && !cmp (a + curr->a2 + shift, b + curr->b2 + shift)) {
shift++;
}
} else if (curr->b2 == next->b1) {
while (curr->b2 + shift < bn && curr->a2 + shift < an && !cmp (b + curr->b2 + shift, a + curr->a2 + shift)) {
shift++;
}
}
if (!shift) {
continue;
}
curr->b2 += shift;
next->b1 += shift;
curr->a2 += shift;
next->a1 += shift;
}
return l;
}
//--
// TODO: implement the rz_diff_lines // we need to implement rz_file_line_at (file, off);
RZ_API int rz_diff_buffers_delta(RzDiff *d, const ut8 *sa, int la, const ut8 *sb, int lb) {
RzDiffOp dop;
struct line *al = NULL;
struct line *bl = NULL;
struct hunklist l = { NULL, NULL };
struct hunk *h;
int an, bn, offa, rlen, offb, len = 0;
int hits = -1;
an = splitlines ((const char *)sa, la, &al);
if (an<0) {
free (al);
return -1;
}
bn = splitlines ((const char *)sb, lb, &bl);
if (bn<0) {
free (al);
free (bl);
return -1;
}
if (!al || !bl) {
eprintf ("bindiff_buffers: Out of memory.\n");
goto beach;
}
l = diff (al, an, bl, bn);
if (!l.head) {
eprintf ("bindiff_buffers: Out of memory.\n");
goto beach;
}
hits = la = lb = 0;
for (h = l.base; h != l.head; h++) {
if (h->a1 != la || h->b1 != lb) {
len = bl[h->b1].l - bl[lb].l;
offa = al[la].l - al->l;
offb = al[h->a1].l - al->l;
rlen = offb-offa;
if (d->callback) {
/* source file */
dop.a_off = offa;
dop.a_buf = (ut8 *)al[la].l;
dop.a_len = rlen;
/* destination file */
dop.b_off = offa; // XXX offb not used??
dop.b_buf = (ut8 *)bl[lb].l;
dop.b_len = len;
if (!d->callback (d, d->user, &dop)) {
break;
}
}
#if 0
if (rlen > 0) {
//printf ("Remove %d byte(s) at %d\n", rlen, offa);
printf ("r-%d @ 0x%"PFMT64x"\n", rlen, (ut64)offa);
}
printf ("e file.write=true\n"); // XXX
printf ("wx ");
for(i=0;i<len;i++)
printf ("%02x", bl[lb].l[i]);
printf (" @ 0x%"PFMT64x"\n", (ut64)offa);
rb += 12 + len;
#endif
}
la = h->a2;
lb = h->b2;
}
beach:
free (al);
free (bl);
free (l.base);
return hits;
}

View file

@ -8,7 +8,6 @@ rz_util_sources = [
'print_code.c',
'base85.c',
'base91.c',
'bdiff.c',
'binheap.c',
'bitmap.c',
'buf.c',
@ -16,7 +15,6 @@ rz_util_sources = [
'calc.c',
'chmod.c',
'debruijn.c',
'udiff.c',
'event.c',
'file.c',
'flist.c',

View file

@ -60,6 +60,9 @@ RZ_API char *rz_print_randomart(const ut8 *dgst_raw, ut32 dgst_raw_len, ut64 add
// FLDSIZE_Y * (FLDSIZE_X+3) there is a loop that for each y iterates over the whole FLDSIZE_X
// The rest is counting the +--[0x%08"PFMT64x"]- and '\0'
retval = calloc(1, 2 * (FLDSIZE_X + 3) + (FLDSIZE_Y * (FLDSIZE_X + 3)) + 7 + sizeof(PFMT64x));
if (!retval) {
return NULL;
}
/* initialize field */
memset(field, 0, FLDSIZE_X * FLDSIZE_Y * sizeof(char));

View file

@ -733,6 +733,7 @@ RZ_API char *rz_str_trunc_ellipsis(const char *str, int len) {
}
RZ_API char *rz_str_newf(const char *fmt, ...) {
rz_return_val_if_fail(fmt, NULL);
va_list ap, ap2;
va_start(ap, fmt);
@ -754,6 +755,7 @@ RZ_API char *rz_str_newf(const char *fmt, ...) {
// Secure string copy with null terminator (like strlcpy or strscpy but ours
RZ_API size_t rz_str_ncpy(char *dst, const char *src, size_t n) {
rz_return_val_if_fail(dst && src, 0);
size_t i;
// do not do anything if n is 0
@ -772,6 +774,7 @@ RZ_API size_t rz_str_ncpy(char *dst, const char *src, size_t n) {
/* memccmp("foo.bar", "foo.cow, '.') == 0 */
// Returns 1 if src and dst are equal up until the first instance of ch in src.
RZ_API bool rz_str_ccmp(const char *dst, const char *src, int ch) {
rz_return_val_if_fail(dst && src, NULL);
int i;
for (i = 0; src[i] && src[i] != ch; i++) {
if (dst[i] != src[i]) {
@ -904,6 +907,7 @@ RZ_API char *rz_str_append(char *ptr, const char *string) {
}
RZ_API char *rz_str_appendf(char *ptr, const char *fmt, ...) {
rz_return_val_if_fail(fmt, NULL);
va_list ap, ap2;
va_start(ap, fmt);

View file

@ -193,9 +193,8 @@ RZ_API int rz_print_date_w32(RzPrint *p, const ut8 *buf, int len) {
return ret;
}
RZ_API const char *rz_time_to_string(ut64 ts) {
time_t l;
l = ts >> 20;
RZ_API char *rz_time_to_string(ut64 timestamp64) {
ut64 l = timestamp64 / RZ_USEC_PER_SEC;
return rz_time_stamp_to_str(l);
}

View file

@ -1,562 +0,0 @@
// SPDX-FileCopyrightText: 2009-2020 pancake <pancake@nopcode.org>
// SPDX-FileCopyrightText: 2009-2020 nikolai <nikolaih@3583bytesready.net>
// SPDX-License-Identifier: LGPL-3.0-only
#include <rz_diff.h>
// the non-system-diff doesnt work well
#define USE_SYSTEM_DIFF 1
static const char *diff_cmd_default[] = {
"diff", "-u", NULL
};
RZ_API RzDiff *rz_diff_new_from(ut64 off_a, ut64 off_b) {
RzDiff *d = RZ_NEW0(RzDiff);
if (d) {
d->delta = 1;
d->user = NULL;
d->off_a = off_a;
d->off_b = off_b;
d->diff_cmd = diff_cmd_default;
}
return d;
}
RZ_API RzDiff *rz_diff_new(void) {
return rz_diff_new_from(0, 0);
}
RZ_API RzDiff *rz_diff_free(RzDiff *d) {
free(d);
return NULL;
}
RZ_API int rz_diff_set_callback(RzDiff *d, RzDiffCallback callback, void *user) {
d->callback = callback;
d->user = user;
return 1;
}
RZ_API int rz_diff_set_delta(RzDiff *d, int delta) {
d->delta = delta;
return 1;
}
typedef struct {
RzDiff *d;
char *str;
} RzDiffUser;
#if USE_SYSTEM_DIFF
RZ_API char *rz_diff_buffers_to_string(RzDiff *d, const ut8 *a, int la, const ut8 *b, int lb) {
return rz_diff_buffers_unified(d, a, la, b, lb);
}
#else
// XXX buffers_static doesnt constructs the correct string in this callback
static int tostring(RzDiff *d, void *user, RzDiffOp *op) {
RzDiffUser *u = (RzDiffUser *)user;
if (op->a_len > 0) {
char *a_str = rz_str_ndup((const char *)op->a_buf + op->a_off, op->a_len);
u->str = rz_str_appendf(u->str, "+(%s)", a_str);
#if 0
char *bufasm = rz_str_prefix_all (a_str, "- ");
u->str = rz_str_appendf (u->str, "-(%s)", bufasm);
free (bufasm);
#endif
free(a_str);
}
if (op->b_len > 0) {
char *b_str = rz_str_ndup((const char *)op->b_buf + op->b_off, op->b_len);
u->str = rz_str_appendf(u->str, "+(%s)", b_str);
#if 0
char *bufasm = rz_str_prefix_all (b_str, "+ ");
u->str = rz_str_appendf (u->str, "+(%s)", bufasm);
free (bufasm);
#endif
free(b_str);
}
if (op->a_len == op->b_len) {
char *b_str = rz_str_ndup((const char *)op->a_buf + op->a_off, op->a_len);
// char *bufasm = rz_str_prefix_all (b_str, " ");
u->str = rz_str_appendf(u->str, "%s", b_str);
// free (bufasm);
free(b_str);
}
return 1;
}
RZ_API char *rz_diff_buffers_to_string(RzDiff *d, const ut8 *a, int la, const ut8 *b, int lb) {
// XXX buffers_static doesnt constructs the correct string in this callback
void *c = d->callback;
void *u = d->user;
RzDiffUser du = { d, strdup("") };
d->callback = &tostring;
d->user = &du;
rz_diff_buffers_static(d, a, la, b, lb);
d->callback = c;
d->user = u;
return du.str;
}
#endif
#define diffHit(void) \
{ \
const size_t i_hit = i - hit; \
int ra = la - i_hit; \
int rb = lb - i_hit; \
struct rz_diff_op_t o = { \
.a_off = d->off_a + i - hit, .a_buf = a + i - hit, .a_len = RZ_MIN(hit, ra), .b_off = d->off_b + i - hit, .b_buf = b + i - hit, .b_len = RZ_MIN(hit, rb) \
}; \
d->callback(d, d->user, &o); \
}
RZ_API int rz_diff_buffers_static(RzDiff *d, const ut8 *a, int la, const ut8 *b, int lb) {
int i, len;
int hit = 0;
la = RZ_ABS(la);
lb = RZ_ABS(lb);
if (la != lb) {
len = RZ_MIN(la, lb);
eprintf("Buffer truncated to %d byte(s) (%d not compared)\n", len, RZ_ABS(lb - la));
} else {
len = la;
}
for (i = 0; i < len; i++) {
if (a[i] != b[i]) {
hit++;
} else {
if (hit > 0) {
diffHit();
hit = 0;
}
}
}
if (hit > 0) {
diffHit();
}
return 0;
}
// XXX: temporary files are bad
RZ_API char *rz_diff_buffers_unified(RzDiff *d, const ut8 *a, int la, const ut8 *b, int lb) {
rz_return_val_if_fail(d && d->diff_cmd && *d->diff_cmd && a && b, NULL);
rz_file_dump(".a", a, la, 0);
rz_file_dump(".b", b, lb, 0);
char *out = NULL;
RzPVector args;
rz_pvector_init(&args, NULL);
for (const char **i = d->diff_cmd; *i; i++) {
rz_pvector_push(&args, (void *)*i);
}
rz_pvector_push(&args, ".a");
rz_pvector_push(&args, ".b");
RzSubprocess *proc = rz_subprocess_start(rz_pvector_at(&args, 0),
(const char **)rz_pvector_index_ptr(&args, 1), rz_pvector_len(&args) - 1, NULL, NULL, 0);
if (!proc) {
goto terria;
}
rz_subprocess_wait(proc, 500);
RzSubprocessOutput *pout = rz_subprocess_drain(proc);
rz_subprocess_free(proc);
if (pout) {
out = pout->out;
pout->out = NULL;
rz_subprocess_output_free(pout);
}
terria:
rz_file_rm(".a");
rz_file_rm(".b");
return out;
}
RZ_API int rz_diff_buffers(RzDiff *d, const ut8 *a, ut32 la, const ut8 *b, ut32 lb) {
return d->delta
? rz_diff_buffers_delta(d, a, la, b, lb)
: rz_diff_buffers_static(d, a, la, b, lb);
}
// Eugene W. Myers' O(ND) diff algorithm
// Returns edit distance with costs: insertion=1, deletion=1, no substitution
RZ_API bool rz_diff_buffers_distance_myers(RzDiff *diff, const ut8 *a, ut32 la, const ut8 *b, ut32 lb, ut32 *distance, double *similarity) {
const bool verbose = diff ? diff->verbose : false;
if (!a || !b) {
return false;
}
const ut32 length = la + lb;
const ut8 *ea = a + la, *eb = b + lb;
// Strip prefix
for (; a < ea && b < eb && *a == *b; a++, b++) {
}
// Strip suffix
for (; a < ea && b < eb && ea[-1] == eb[-1]; ea--, eb--) {
}
la = ea - a;
lb = eb - b;
ut32 *v0, *v;
st64 m = (st64)la + lb, di = 0, low, high, i, x, y;
if (m + 2 > SIZE_MAX / sizeof(st64) || !(v0 = malloc((m + 2) * sizeof(ut32)))) {
return false;
}
v = v0 + lb;
v[1] = 0;
for (di = 0; di <= m; di++) {
low = -di + 2 * RZ_MAX(0, di - (st64)lb);
high = di - 2 * RZ_MAX(0, di - (st64)la);
for (i = low; i <= high; i += 2) {
x = i == -di || (i != di && v[i - 1] < v[i + 1]) ? v[i + 1] : v[i - 1] + 1;
y = x - i;
while (x < la && y < lb && a[x] == b[y]) {
x++;
y++;
}
v[i] = x;
if (x == la && y == lb) {
goto out;
}
}
if (verbose && di % 10000 == 0) {
eprintf("\rProcessing dist %" PFMT64d " of max %" PFMT64d "\r", di, m);
}
}
out:
if (verbose) {
eprintf("\n");
}
free(v0);
//Clean up output on loop exit (purely aesthetic)
if (distance) {
*distance = di;
}
if (similarity) {
*similarity = length ? 1.0 - (double)di / length : 1.0;
}
return true;
}
RZ_API bool rz_diff_buffers_distance_levenstein(RzDiff *diff, const ut8 *a, ut32 la, const ut8 *b, ut32 lb, ut32 *distance, double *similarity) {
if (!a || !b) {
return false;
}
const bool verbose = diff ? diff->verbose : false;
const ut32 length = RZ_MAX(la, lb);
const ut8 *ea = a + la, *eb = b + lb, *t;
ut32 *d, i, j;
// Strip prefix
for (; a < ea && b < eb && *a == *b; a++, b++) {
}
// Strip suffix
for (; a < ea && b < eb && ea[-1] == eb[-1]; ea--, eb--) {
}
la = ea - a;
lb = eb - b;
if (la < lb) {
i = la;
la = lb;
lb = i;
t = a;
a = b;
b = t;
}
if (sizeof(ut32) > SIZE_MAX / (lb + 1) || !(d = malloc((lb + 1) * sizeof(ut32)))) {
return false;
}
for (i = 0; i <= lb; i++) {
d[i] = i;
}
for (i = 0; i < la; i++) {
ut32 ul = d[0];
d[0] = i + 1;
for (j = 0; j < lb; j++) {
ut32 u = d[j + 1];
d[j + 1] = a[i] == b[j] ? ul : RZ_MIN(ul, RZ_MIN(d[j], u)) + 1;
ul = u;
}
if (verbose && i % 10000 == 0) {
eprintf("\rProcessing %" PFMT32u " of %" PFMT32u "\r", i, la);
}
}
if (verbose) {
eprintf("\n");
}
if (distance) {
*distance = d[lb];
}
if (similarity) {
*similarity = length ? 1.0 - (double)d[lb] / length : 1.0;
}
free(d);
return true;
}
RZ_API bool rz_diff_buffers_distance(RzDiff *d, const ut8 *a, ut32 la, const ut8 *b, ut32 lb, ut32 *distance, double *similarity) {
if (d) {
switch (d->type) {
case 'm':
return rz_diff_buffers_distance_myers(d, a, la, b, lb, distance, similarity);
case 'l':
default:
break;
}
}
return rz_diff_buffers_distance_levenstein(d, a, la, b, lb, distance, similarity);
}
// Use NeedlemanWunsch to diffchar.
// This is an O(mn) algo in both space and time.
// Note that 64KB * 64KB * 2 = 8GB.
// TODO Discard common prefix and suffix
RZ_API RzDiffChar *rz_diffchar_new(const ut8 *a, const ut8 *b) {
rz_return_val_if_fail(a && b, NULL);
RzDiffChar *diffchar = RZ_NEW0(RzDiffChar);
if (!diffchar) {
return NULL;
}
const size_t len_a = strlen((const char *)a);
const size_t len_b = strlen((const char *)b);
const size_t len_long = len_a > len_b ? len_a : len_b;
const size_t dim = len_long + 1;
char *dup_a = malloc(len_long);
char *dup_b = malloc(len_long);
st16 *align_table = malloc(dim * dim * sizeof(st16));
ut8 *align_a = malloc(2 * len_long);
ut8 *align_b = malloc(2 * len_long);
if (!(dup_a && dup_b && align_table && align_a && align_b)) {
free(dup_a);
free(dup_b);
free(align_table);
free(align_a);
free(align_b);
free(diffchar);
return NULL;
}
snprintf(dup_a, len_long, "%s", a);
a = (const ut8 *)dup_a;
snprintf(dup_b, len_long, "%s", b);
b = (const ut8 *)dup_b;
// Fill table
size_t row, col;
*align_table = 0;
for (row = 1; row < dim; row++) {
// TODO Clamping [ST16_MIN + 1, .]
*(align_table + row) = *(align_table + row * dim) = -(st16)row;
}
const st16 match = 1;
const st16 match_nl = 2;
const st16 mismatch = -2;
const st16 gap = -1;
for (row = 1; row < dim; row++) {
for (col = 1; col < dim; col++) {
// TODO Clamping [ST16_MIN + 1, ST16_MAX]
const ut8 a_ch = a[col - 1];
const ut8 b_ch = b[row - 1];
const st16 tl_score = *(align_table + (row - 1) * dim + col - 1) + (a_ch == b_ch ? (a_ch == '\n' ? match_nl : match) : mismatch);
const st16 t_score = *(align_table + (row - 1) * dim + col) + gap;
const st16 l_score = *(align_table + row * dim + col - 1) + gap;
st16 score;
if (tl_score >= t_score && tl_score >= l_score) {
score = tl_score;
} else if (t_score >= tl_score && t_score >= l_score) {
score = t_score;
} else {
score = l_score;
}
*(align_table + row * dim + col) = score;
}
}
#if 0
// Print table (Debug)
char char_str[3] = { ' ' };
printf ("%4s ", char_str);
for (col = 0; col < dim; col++) {
if (col && a[col - 1] == '\n') {
char_str[0] = '\\';
char_str[1] = 'n';
} else {
char_str[0] = col ? a[col - 1] : ' ';
char_str[1] = 0;
}
printf ("%4s ", char_str);
}
printf ("\n");
for (row = 0; row < dim; row++) {
if (row && b[row - 1] == '\n') {
char_str[0] = '\\';
char_str[1] = 'n';
} else {
char_str[0] = row ? b[row - 1] : ' ';
char_str[1] = 0;
}
printf ("%4s ", char_str);
for (col = 0; col < dim; col++) {
printf ("%4d ", *(align_table + row * dim + col));
}
printf ("\n");
}
#endif
// Do alignment
size_t idx_a = len_long - 1;
size_t idx_b = len_long - 1;
size_t idx_align = 2 * len_long - 1;
size_t pos_row = dim - 1;
size_t pos_col = dim - 1;
while (pos_row || pos_col) {
const st16 tl_score = (pos_row > 0 && pos_col > 0) ? *(align_table + (pos_row - 1) * dim + pos_col - 1) : ST16_MIN;
const st16 t_score = pos_row > 0 ? *(align_table + (pos_row - 1) * dim + pos_col) : ST16_MIN;
const st16 l_score = pos_col > 0 ? *(align_table + pos_row * dim + pos_col - 1) : ST16_MIN;
const bool match = a[idx_a] == b[idx_b];
if (t_score >= l_score && (!match || t_score >= tl_score)) {
align_a[idx_align] = 0;
align_b[idx_align] = b[idx_b--];
idx_align--;
pos_row--;
} else if (l_score >= t_score && (!match || l_score >= tl_score)) {
align_a[idx_align] = a[idx_a--];
align_b[idx_align] = 0;
idx_align--;
pos_col--;
} else {
align_a[idx_align] = a[idx_a--];
align_b[idx_align] = b[idx_b--];
idx_align--;
pos_row--;
pos_col--;
}
}
idx_align++;
const size_t start_align = idx_align;
#if 0
// Print alignment (Debug)
for (; idx_align < 2 * len_long; idx_align++) {
const ut8 ch = align_a[idx_align];
if (align_b[idx_align] == '\n' && ch != '\n') {
printf (ch ? " " : "-");
}
if (ch == 0) {
printf ("-");
} else if (ch == '\n') {
printf ("\\n");
} else {
printf ("%c", ch);
}
}
printf ("\n");
for (idx_align = start_align; idx_align < 2 * len_long; idx_align++) {
const ut8 ch = align_b[idx_align];
if (align_a[idx_align] == '\n' && ch != '\n') {
printf (ch ? " " : "-");
}
if (ch == 0) {
printf ("-");
} else if (ch == '\n') {
printf ("\\n");
} else {
printf ("%c", ch);
}
}
printf ("\n");
#endif
diffchar->align_a = align_a;
diffchar->align_b = align_b;
diffchar->len_buf = len_long;
diffchar->start_align = start_align;
free(dup_a);
free(dup_b);
free(align_table);
return diffchar;
}
typedef enum {
RZ_TEST_ALIGN_MATCH,
RZ_TEST_ALIGN_MISMATCH,
RZ_TEST_ALIGN_TOP_GAP,
RZ_TEST_ALIGN_BOTTOM_GAP
} RzTestCharAlignment;
typedef enum {
RZ_TEST_DIFF_MATCH,
RZ_TEST_DIFF_DELETE,
RZ_TEST_DIFF_INSERT
} RzTestPrintDiffMode;
RZ_API void rz_diffchar_print(RzDiffChar *diffchar) {
rz_return_if_fail(diffchar);
RzTestPrintDiffMode cur_mode = RZ_TEST_DIFF_MATCH;
RzTestCharAlignment cur_align;
size_t idx_align = diffchar->start_align;
while (idx_align < 2 * diffchar->len_buf) {
const ut8 a_ch = diffchar->align_a[idx_align];
const ut8 b_ch = diffchar->align_b[idx_align];
if (a_ch && !b_ch) {
cur_align = RZ_TEST_ALIGN_BOTTOM_GAP;
} else if (!a_ch && b_ch) {
cur_align = RZ_TEST_ALIGN_TOP_GAP;
} else if (a_ch != b_ch) {
eprintf("Internal error: mismatch detected!\n");
cur_align = RZ_TEST_ALIGN_MISMATCH;
} else {
cur_align = RZ_TEST_ALIGN_MATCH;
}
if (cur_mode == RZ_TEST_DIFF_MATCH) {
if (cur_align == RZ_TEST_ALIGN_MATCH) {
if (a_ch) {
printf("%c", a_ch);
}
} else if (cur_align == RZ_TEST_ALIGN_BOTTOM_GAP) {
printf(a_ch == '\n' ? "%c" Color_HLDELETE : Color_HLDELETE "%c", a_ch);
cur_mode = RZ_TEST_DIFF_DELETE;
} else if (cur_align == RZ_TEST_ALIGN_TOP_GAP) {
printf(b_ch == '\n' ? "%c" Color_HLINSERT : Color_HLINSERT "%c", b_ch);
cur_mode = RZ_TEST_DIFF_INSERT;
}
} else if (cur_mode == RZ_TEST_DIFF_DELETE) {
if (cur_align == RZ_TEST_ALIGN_MATCH) {
printf(Color_RESET);
if (a_ch) {
printf("%c", a_ch);
}
cur_mode = RZ_TEST_DIFF_MATCH;
} else if (cur_align == RZ_TEST_ALIGN_BOTTOM_GAP) {
printf(a_ch == '\n' ? Color_RESET "%c" Color_HLDELETE : "%c", a_ch);
} else if (cur_align == RZ_TEST_ALIGN_TOP_GAP) {
printf(b_ch == '\n' ? Color_RESET "%c" Color_HLINSERT : Color_HLINSERT "%c", b_ch);
cur_mode = RZ_TEST_DIFF_INSERT;
}
} else if (cur_mode == RZ_TEST_DIFF_INSERT) {
if (cur_align == RZ_TEST_ALIGN_MATCH) {
printf(Color_RESET);
if (a_ch) {
printf("%c", a_ch);
}
cur_mode = RZ_TEST_DIFF_MATCH;
} else if (cur_align == RZ_TEST_ALIGN_BOTTOM_GAP) {
printf(a_ch == '\n' ? Color_RESET "%c" Color_HLDELETE : Color_HLDELETE "%c", a_ch);
cur_mode = RZ_TEST_DIFF_DELETE;
} else if (cur_align == RZ_TEST_ALIGN_TOP_GAP) {
printf(b_ch == '\n' ? Color_RESET "%c" Color_HLINSERT : "%c", b_ch);
}
}
idx_align++;
}
printf(Color_RESET "\n");
}
RZ_API void rz_diffchar_free(RzDiffChar *diffchar) {
if (diffchar) {
free((ut8 *)diffchar->align_a);
free((ut8 *)diffchar->align_b);
free(diffchar);
}
}

View file

@ -490,6 +490,7 @@ if host_machine.system() == 'windows'
build_root / 'librz' / 'config',
build_root / 'librz' / 'cons',
build_root / 'librz' / 'core',
build_root / 'librz' / 'diff',
build_root / 'librz' / 'crypto',
build_root / 'librz' / 'debug',
build_root / 'librz' / 'egg',
@ -516,6 +517,7 @@ subdir('librz/crypto')
subdir('shlr')
subdir('librz/cons')
subdir('librz/diff')
subdir('shlr/gdb')
subdir('librz/io')
subdir('librz/bp')
@ -595,6 +597,7 @@ if meson.is_subproject()
rz_socket_dep,
rz_syscall_dep,
rz_type_dep,
rz_diff_dep,
rz_util_dep
],
include_directories: include_directories('.', 'librz/include'),

View file

@ -1,24 +1,16 @@
NAME=rz-diff -c
NAME=rz-diff empty first file
FILE==
CMDS=!rz-diff -c bins/other/rz-diff/rz-diff_c_1 bins/other/rz-diff/rz-diff_c_2
EXPECT=<<EOF
1
CMDS=!rz-diff -t bytes "" bins/other/rz-diff/rz-diff_c_2
EXPECT_ERR=<<EOF
ERROR: rz-diff: error, cannot open a file without a name.
EOF
RUN
NAME=rz-diff -a -O x86
NAME=rz-diff empty second file
FILE==
CMDS=!rz-diff -a x86 -O bins/other/rz-diff/rz-diff_c_1 bins/other/rz-diff/rz-diff_c_2
EXPECT=<<EOF
0x00000000 91 => 90 0x00000000
EOF
RUN
NAME=rz-diff -g
FILE==
CMDS=!!rz-diff -g main -m d bins/other/rz-diff/false bins/other/rz-diff/true~digrap
EXPECT=<<EOF
digraph code {
CMDS=!rz-diff -t bytes bins/other/rz-diff/rz-diff_c_1 ""
EXPECT_ERR=<<EOF
ERROR: rz-diff: error, cannot open a file without a name.
EOF
RUN
@ -38,69 +30,610 @@ EXPECT=<<EOF
EOF
RUN
NAME=rz-diff string comparison
NAME=rz-diff distance comparison (leven)
FILE==
CMDS=!!rz-diff -z bins/elf/elf_one_symbol_shdr bins/elf/elf_one_symbol_shdr1
CMDS=!rz-diff -d leven bins/java/Main.java.1.7.class bins/java/Main.java.15.class
EXPECT=<<EOF
0x00000000 48656c6c => 41414141 0x00000000
similarity: 0.637
distance: 529
EOF
RUN
NAME=rz-diff unified string comparison
NAME=rz-diff distance comparison (leven) JSON
FILE==
CMDS=!!rz-diff -quz bins/elf/elf_one_symbol_shdr bins/elf/elf_one_symbol_shdr1
CMDS=!rz-diff -jd leven bins/java/Main.java.1.7.class bins/java/Main.java.15.class
EXPECT=<<EOF
-0x00000000:48 65 6c 6c
+0x00000000:41 41 41 41
{"similarity":0.636676,"distance":529}
EOF
RUN
NAME=rz-diff gnu unified string comparison
NAME=rz-diff distance comparison (leven) QUIET
FILE==
BROKEN=1
CMDS=!!rz-diff -Uz bins/elf/elf_one_symbol_shdr bins/elf/elf_one_symbol_shdr1 | tail -n 2
CMDS=!rz-diff -qd leven bins/java/Main.java.1.7.class bins/java/Main.java.15.class
EXPECT=<<EOF
0.637
529
EOF
RUN
NAME=rz-diff distance comparison (myers)
FILE==
CMDS=!rz-diff -d myers bins/java/Main.java.1.7.class bins/java/Main.java.15.class
EXPECT=<<EOF
similarity: 0.769
distance: 602
EOF
RUN
NAME=rz-diff distance comparison (myers) JSON
FILE==
CMDS=!rz-diff -jd myers bins/java/Main.java.1.7.class bins/java/Main.java.15.class
EXPECT=<<EOF
{"similarity":0.768995,"distance":602}
EOF
RUN
NAME=rz-diff distance comparison (myers) QUIET
FILE==
CMDS=!rz-diff -qd myers bins/java/Main.java.1.7.class bins/java/Main.java.15.class
EXPECT=<<EOF
0.769
602
EOF
RUN
NAME=rz-diff bytes comparison
FILE==
CMDS=!rz-diff -t bytes bins/other/rz-diff/rz-diff_c_1 bins/other/rz-diff/rz-diff_c_2
EXPECT=<<EOF
--- bins/other/rz-diff/rz-diff_c_1
+++ bins/other/rz-diff/rz-diff_c_2
@@ -1,1 +1,1 @@
-91
+90
EOF
RUN
NAME=rz-diff bytes comparison JSON
FILE==
CMDS=!rz-diff -jt bytes bins/other/rz-diff/rz-diff_c_1 bins/other/rz-diff/rz-diff_c_2
EXPECT=<<EOF
{"from":"bins/other/rz-diff/rz-diff_c_1","to":"bins/other/rz-diff/rz-diff_c_2","diff":[{"from":[1,1],"to":[1,1],"ops":[{"op":"delete","value":"91"},{"op":"insert","value":"90"}]}]}
EOF
RUN
NAME=rz-diff strings comparison
FILE==
CMDS=!rz-diff -t strings bins/elf/elf_one_symbol_shdr bins/elf/elf_one_symbol_shdr1
EXPECT=<<EOF
--- bins/elf/elf_one_symbol_shdr
+++ bins/elf/elf_one_symbol_shdr1
@@ -1,1 +1,1 @@
-Hello world!
+AAAAo world!
EOF
RUN
NAME=rz-diff -AC (elf files)
NAME=rz-diff strings comparison JSON
FILE==
CMDS=!!rz-diff -AC bins/other/rz-diff/true bins/other/rz-diff/false~?\(1.000000\)
CMDS=!rz-diff -jt strings bins/elf/elf_one_symbol_shdr bins/elf/elf_one_symbol_shdr1
EXPECT=<<EOF
54
{"from":"bins/elf/elf_one_symbol_shdr","to":"bins/elf/elf_one_symbol_shdr1","diff":[{"from":[1,1],"to":[1,1],"ops":[{"op":"delete","value":"Hello world!\n"},{"op":"insert","value":"AAAAo world!\n"}]}]}
EOF
RUN
NAME=rz-diff -AC (mach0 fat files)
NAME=rz-diff strings comparison with addresses
FILE==
CMDS=!!rz-diff -AC bins/other/rz-diff/hellocxx-osx-fat-intel_1 bins/other/rz-diff/hellocxx-osx-fat-intel_2~?\(1.000000\)
CMDS=!rz-diff -At strings bins/elf/elf_one_symbol_shdr bins/elf/elf_one_symbol_shdr1
EXPECT=<<EOF
17
--- bins/elf/elf_one_symbol_shdr
+++ bins/elf/elf_one_symbol_shdr1
@@ -1,1 +1,1 @@
-virt: 0x00000000080484b0 phys: 0x00000000000004b0 Hello world!
+virt: 0x00000000080484b0 phys: 0x00000000000004b0 AAAAo world!
EOF
RUN
NAME=rz-diff -B (GDIFF support) #1
NAME=rz-diff strings comparison with addresses JSON
FILE==
CMDS=!!rz-diff -B bins/other/rz-diff/rz-diff_c_1 bins/other/rz-diff/rz-diff_c_2 | rz-ax -S
CMDS=!rz-diff -Ajt strings bins/elf/elf_one_symbol_shdr bins/elf/elf_one_symbol_shdr1
EXPECT=<<EOF
d1ffd1ff04019000
{"from":"bins/elf/elf_one_symbol_shdr","to":"bins/elf/elf_one_symbol_shdr1","diff":[{"from":[1,1],"to":[1,1],"ops":[{"op":"delete","value":"virt: 0x00000000080484b0 phys: 0x00000000000004b0 Hello world!\n"},{"op":"insert","value":"virt: 0x00000000080484b0 phys: 0x00000000000004b0 AAAAo world!\n"}]}]}
EOF
RUN
NAME=rz-diff empty first file
NAME=rz-diff functions comparison
FILE==
CMDS=!rz-diff "" bins/other/rz-diff/rz-diff_c_2
CMDS=!rz-diff -t functions bins/elf/hello_world bins/elf/hello_world32
EXPECT=<<EOF
--- bins/elf/hello_world
+++ bins/elf/hello_world32
@@ -1,9 +1,14 @@
-instrs: 7 bits: 64 sym._init
-instrs: 13 bits: 64 sym.deregister_tm_clones
-instrs: 1 bits: 64 sym.imp.__cxa_finalize
-instrs: 1 bits: 64 sym.imp.free
-instrs: 1 bits: 64 sym.imp.malloc
-instrs: 1 bits: 64 sym.imp.puts
-instrs: 1 bits: 64 sym.imp.strcat
-instrs: 1 bits: 64 sym.imp.strcpy
-instrs: 1 bits: 64 sym.imp.strlen
+instrs: 1 bits: 32 fcn.000004c8
+instrs: 2 bits: 32 fcn.00000502
+instrs: 1 bits: 32 sym..plt.got
+instrs: 2 bits: 32 sym.__x86.get_pc_thunk.bx
+instrs: 2 bits: 32 sym.__x86.get_pc_thunk.dx
+instrs: 11 bits: 32 sym._init
+instrs: 18 bits: 32 sym.deregister_tm_clones
+instrs: 1 bits: 32 sym.imp.__libc_start_main
+instrs: 1 bits: 32 sym.imp.free
+instrs: 1 bits: 32 sym.imp.malloc
+instrs: 1 bits: 32 sym.imp.puts
+instrs: 1 bits: 32 sym.imp.strcat
+instrs: 1 bits: 32 sym.imp.strcpy
+instrs: 1 bits: 32 sym.imp.strlen
EOF
RUN
NAME=rz-diff functions comparison JSON
FILE==
CMDS=!rz-diff -jt functions bins/elf/hello_world bins/elf/hello_world32
EXPECT=<<EOF
{"from":"bins/elf/hello_world","to":"bins/elf/hello_world32","diff":[{"from":[1,9],"to":[1,14],"ops":[{"op":"delete","value":"instrs: 7 bits: 64 sym._init\n"},{"op":"delete","value":"instrs: 13 bits: 64 sym.deregister_tm_clones\n"},{"op":"delete","value":"instrs: 1 bits: 64 sym.imp.__cxa_finalize\n"},{"op":"delete","value":"instrs: 1 bits: 64 sym.imp.free\n"},{"op":"delete","value":"instrs: 1 bits: 64 sym.imp.malloc\n"},{"op":"delete","value":"instrs: 1 bits: 64 sym.imp.puts\n"},{"op":"delete","value":"instrs: 1 bits: 64 sym.imp.strcat\n"},{"op":"delete","value":"instrs: 1 bits: 64 sym.imp.strcpy\n"},{"op":"delete","value":"instrs: 1 bits: 64 sym.imp.strlen\n"},{"op":"insert","value":"instrs: 1 bits: 32 fcn.000004c8\n"},{"op":"insert","value":"instrs: 2 bits: 32 fcn.00000502\n"},{"op":"insert","value":"instrs: 1 bits: 32 sym..plt.got\n"},{"op":"insert","value":"instrs: 2 bits: 32 sym.__x86.get_pc_thunk.bx\n"},{"op":"insert","value":"instrs: 2 bits: 32 sym.__x86.get_pc_thunk.dx\n"},{"op":"insert","value":"instrs: 11 bits: 32 sym._init\n"},{"op":"insert","value":"instrs: 18 bits: 32 sym.deregister_tm_clones\n"},{"op":"insert","value":"instrs: 1 bits: 32 sym.imp.__libc_start_main\n"},{"op":"insert","value":"instrs: 1 bits: 32 sym.imp.free\n"},{"op":"insert","value":"instrs: 1 bits: 32 sym.imp.malloc\n"},{"op":"insert","value":"instrs: 1 bits: 32 sym.imp.puts\n"},{"op":"insert","value":"instrs: 1 bits: 32 sym.imp.strcat\n"},{"op":"insert","value":"instrs: 1 bits: 32 sym.imp.strcpy\n"},{"op":"insert","value":"instrs: 1 bits: 32 sym.imp.strlen\n"}]}]}
EOF
RUN
NAME=rz-diff functions comparison with addresses
FILE==
CMDS=!rz-diff -At functions bins/elf/hello_world bins/elf/hello_world32
EXPECT=<<EOF
--- bins/elf/hello_world
+++ bins/elf/hello_world32
@@ -1,9 +1,14 @@
-0x0000000000000608 instrs: 7 bits: 64 sym._init
-0x00000000000006d0 instrs: 13 bits: 64 sym.deregister_tm_clones
-0x0000000000000690 instrs: 1 bits: 64 sym.imp.__cxa_finalize
-0x0000000000000630 instrs: 1 bits: 64 sym.imp.free
-0x0000000000000670 instrs: 1 bits: 64 sym.imp.malloc
-0x0000000000000650 instrs: 1 bits: 64 sym.imp.puts
-0x0000000000000680 instrs: 1 bits: 64 sym.imp.strcat
-0x0000000000000640 instrs: 1 bits: 64 sym.imp.strcpy
-0x0000000000000660 instrs: 1 bits: 64 sym.imp.strlen
+0x00000000000004c8 instrs: 1 bits: 32 fcn.000004c8
+0x0000000000000502 instrs: 2 bits: 32 fcn.00000502
+0x00000000000004c0 instrs: 1 bits: 32 sym..plt.got
+0x0000000000000510 instrs: 2 bits: 32 sym.__x86.get_pc_thunk.bx
+0x0000000000000609 instrs: 2 bits: 32 sym.__x86.get_pc_thunk.dx
+0x000000000000041c instrs: 11 bits: 32 sym._init
+0x0000000000000520 instrs: 18 bits: 32 sym.deregister_tm_clones
+0x00000000000004b0 instrs: 1 bits: 32 sym.imp.__libc_start_main
+0x0000000000000450 instrs: 1 bits: 32 sym.imp.free
+0x0000000000000480 instrs: 1 bits: 32 sym.imp.malloc
+0x0000000000000490 instrs: 1 bits: 32 sym.imp.puts
+0x0000000000000460 instrs: 1 bits: 32 sym.imp.strcat
+0x0000000000000470 instrs: 1 bits: 32 sym.imp.strcpy
+0x00000000000004a0 instrs: 1 bits: 32 sym.imp.strlen
EOF
RUN
NAME=rz-diff functions comparison with addresses JSON
FILE==
CMDS=!rz-diff -Ajt functions bins/elf/hello_world bins/elf/hello_world32
EXPECT=<<EOF
{"from":"bins/elf/hello_world","to":"bins/elf/hello_world32","diff":[{"from":[1,9],"to":[1,14],"ops":[{"op":"delete","value":"0x0000000000000608 instrs: 7 bits: 64 sym._init\n"},{"op":"delete","value":"0x00000000000006d0 instrs: 13 bits: 64 sym.deregister_tm_clones\n"},{"op":"delete","value":"0x0000000000000690 instrs: 1 bits: 64 sym.imp.__cxa_finalize\n"},{"op":"delete","value":"0x0000000000000630 instrs: 1 bits: 64 sym.imp.free\n"},{"op":"delete","value":"0x0000000000000670 instrs: 1 bits: 64 sym.imp.malloc\n"},{"op":"delete","value":"0x0000000000000650 instrs: 1 bits: 64 sym.imp.puts\n"},{"op":"delete","value":"0x0000000000000680 instrs: 1 bits: 64 sym.imp.strcat\n"},{"op":"delete","value":"0x0000000000000640 instrs: 1 bits: 64 sym.imp.strcpy\n"},{"op":"delete","value":"0x0000000000000660 instrs: 1 bits: 64 sym.imp.strlen\n"},{"op":"insert","value":"0x00000000000004c8 instrs: 1 bits: 32 fcn.000004c8\n"},{"op":"insert","value":"0x0000000000000502 instrs: 2 bits: 32 fcn.00000502\n"},{"op":"insert","value":"0x00000000000004c0 instrs: 1 bits: 32 sym..plt.got\n"},{"op":"insert","value":"0x0000000000000510 instrs: 2 bits: 32 sym.__x86.get_pc_thunk.bx\n"},{"op":"insert","value":"0x0000000000000609 instrs: 2 bits: 32 sym.__x86.get_pc_thunk.dx\n"},{"op":"insert","value":"0x000000000000041c instrs: 11 bits: 32 sym._init\n"},{"op":"insert","value":"0x0000000000000520 instrs: 18 bits: 32 sym.deregister_tm_clones\n"},{"op":"insert","value":"0x00000000000004b0 instrs: 1 bits: 32 sym.imp.__libc_start_main\n"},{"op":"insert","value":"0x0000000000000450 instrs: 1 bits: 32 sym.imp.free\n"},{"op":"insert","value":"0x0000000000000480 instrs: 1 bits: 32 sym.imp.malloc\n"},{"op":"insert","value":"0x0000000000000490 instrs: 1 bits: 32 sym.imp.puts\n"},{"op":"insert","value":"0x0000000000000460 instrs: 1 bits: 32 sym.imp.strcat\n"},{"op":"insert","value":"0x0000000000000470 instrs: 1 bits: 32 sym.imp.strcpy\n"},{"op":"insert","value":"0x00000000000004a0 instrs: 1 bits: 32 sym.imp.strlen\n"}]}]}
EOF
RUN
NAME=rz-diff entries comparison
FILE==
CMDS=!rz-diff -t entries bins/elf/hello_world bins/elf/hello_world32
EXPECT=<<EOF
--- bins/elf/hello_world
+++ bins/elf/hello_world32
@@ -1,3 +1,3 @@
-virt: 0x00000000000007a0 phys: 0x00000000000007a0 entry init
-virt: 0x0000000000000760 phys: 0x0000000000000760 entry fini
-virt: 0x00000000000006a0 phys: 0x00000000000006a0 entry program
+virt: 0x0000000000000600 phys: 0x0000000000000600 entry init
+virt: 0x00000000000005b0 phys: 0x00000000000005b0 entry fini
+virt: 0x00000000000004d0 phys: 0x00000000000004d0 entry program
EOF
RUN
NAME=rz-diff entries comparison JSON
FILE==
CMDS=!rz-diff -jt entries bins/elf/hello_world bins/elf/hello_world32
EXPECT=<<EOF
{"from":"bins/elf/hello_world","to":"bins/elf/hello_world32","diff":[{"from":[1,3],"to":[1,3],"ops":[{"op":"delete","value":"virt: 0x00000000000007a0 phys: 0x00000000000007a0 entry init\n"},{"op":"delete","value":"virt: 0x0000000000000760 phys: 0x0000000000000760 entry fini\n"},{"op":"delete","value":"virt: 0x00000000000006a0 phys: 0x00000000000006a0 entry program\n"},{"op":"insert","value":"virt: 0x0000000000000600 phys: 0x0000000000000600 entry init\n"},{"op":"insert","value":"virt: 0x00000000000005b0 phys: 0x00000000000005b0 entry fini\n"},{"op":"insert","value":"virt: 0x00000000000004d0 phys: 0x00000000000004d0 entry program\n"}]}]}
EOF
RUN
NAME=rz-diff imports comparison
FILE==
CMDS=!rz-diff -t imports bins/elf/hello_world bins/other/rz-diff/true
EXPECT=<<EOF
--- bins/elf/hello_world
+++ bins/other/rz-diff/true
@@ -1,11 +1,49 @@
-WEAK NOTYPE _ITM_deregisterTMCloneTable
-WEAK NOTYPE _ITM_registerTMCloneTable
-WEAK FUNC __cxa_finalize
+GLOBAL FUNC __ctype_b_loc
+GLOBAL FUNC __ctype_get_mb_cur_max
+GLOBAL FUNC __cxa_atexit
+GLOBAL FUNC __errno_location
+GLOBAL FUNC __fpending
+GLOBAL FUNC __fprintf_chk
+GLOBAL FUNC __freading
WEAK NOTYPE __gmon_start__
GLOBAL FUNC __libc_start_main
+GLOBAL FUNC __printf_chk
+GLOBAL FUNC __stack_chk_fail
+GLOBAL FUNC __uflow
+GLOBAL FUNC _exit
+GLOBAL FUNC abort
+GLOBAL FUNC bindtextdomain
+GLOBAL FUNC calloc
+GLOBAL FUNC close
+GLOBAL FUNC dcgettext
+GLOBAL FUNC error
+GLOBAL FUNC exit
+GLOBAL FUNC fclose
+GLOBAL FUNC fdopen
+GLOBAL FUNC fflush
+GLOBAL FUNC fileno
+GLOBAL FUNC fputs_unlocked
GLOBAL FUNC free
+GLOBAL FUNC fscanf
+GLOBAL FUNC fseeko
+GLOBAL FUNC fwrite
+GLOBAL FUNC getenv
+GLOBAL FUNC iswprint
+GLOBAL FUNC lseek
GLOBAL FUNC malloc
-GLOBAL FUNC puts
-GLOBAL FUNC strcat
+GLOBAL FUNC mbrtowc
+GLOBAL FUNC mbsinit
+GLOBAL FUNC memcmp
+GLOBAL FUNC memcpy
+GLOBAL FUNC memset
+GLOBAL FUNC nl_langinfo
+GLOBAL FUNC open
+GLOBAL FUNC realloc
+GLOBAL FUNC setlocale
+GLOBAL FUNC strcmp
GLOBAL FUNC strcpy
GLOBAL FUNC strlen
+GLOBAL FUNC strncmp
+GLOBAL FUNC strrchr
+GLOBAL FUNC textdomain
+GLOBAL FUNC ungetc
EOF
RUN
NAME=rz-diff imports comparison JSON
FILE==
CMDS=!rz-diff -jt imports bins/elf/hello_world bins/other/rz-diff/true
EXPECT=<<EOF
{"from":"bins/elf/hello_world","to":"bins/other/rz-diff/true","diff":[{"from":[1,11],"to":[1,49],"ops":[{"op":"delete","value":"WEAK NOTYPE _ITM_deregisterTMCloneTable\n"},{"op":"delete","value":"WEAK NOTYPE _ITM_registerTMCloneTable\n"},{"op":"delete","value":"WEAK FUNC __cxa_finalize\n"},{"op":"insert","value":"GLOBAL FUNC __ctype_b_loc\n"},{"op":"insert","value":"GLOBAL FUNC __ctype_get_mb_cur_max\n"},{"op":"insert","value":"GLOBAL FUNC __cxa_atexit\n"},{"op":"insert","value":"GLOBAL FUNC __errno_location\n"},{"op":"insert","value":"GLOBAL FUNC __fpending\n"},{"op":"insert","value":"GLOBAL FUNC __fprintf_chk\n"},{"op":"insert","value":"GLOBAL FUNC __freading\n"},{"op":"equal","value":"WEAK NOTYPE __gmon_start__\n"},{"op":"equal","value":"GLOBAL FUNC __libc_start_main\n"},{"op":"insert","value":"GLOBAL FUNC __printf_chk\n"},{"op":"insert","value":"GLOBAL FUNC __stack_chk_fail\n"},{"op":"insert","value":"GLOBAL FUNC __uflow\n"},{"op":"insert","value":"GLOBAL FUNC _exit\n"},{"op":"insert","value":"GLOBAL FUNC abort\n"},{"op":"insert","value":"GLOBAL FUNC bindtextdomain\n"},{"op":"insert","value":"GLOBAL FUNC calloc\n"},{"op":"insert","value":"GLOBAL FUNC close\n"},{"op":"insert","value":"GLOBAL FUNC dcgettext\n"},{"op":"insert","value":"GLOBAL FUNC error\n"},{"op":"insert","value":"GLOBAL FUNC exit\n"},{"op":"insert","value":"GLOBAL FUNC fclose\n"},{"op":"insert","value":"GLOBAL FUNC fdopen\n"},{"op":"insert","value":"GLOBAL FUNC fflush\n"},{"op":"insert","value":"GLOBAL FUNC fileno\n"},{"op":"insert","value":"GLOBAL FUNC fputs_unlocked\n"},{"op":"equal","value":"GLOBAL FUNC free\n"},{"op":"insert","value":"GLOBAL FUNC fscanf\n"},{"op":"insert","value":"GLOBAL FUNC fseeko\n"},{"op":"insert","value":"GLOBAL FUNC fwrite\n"},{"op":"insert","value":"GLOBAL FUNC getenv\n"},{"op":"insert","value":"GLOBAL FUNC iswprint\n"},{"op":"insert","value":"GLOBAL FUNC lseek\n"},{"op":"equal","value":"GLOBAL FUNC malloc\n"},{"op":"delete","value":"GLOBAL FUNC puts\n"},{"op":"delete","value":"GLOBAL FUNC strcat\n"},{"op":"insert","value":"GLOBAL FUNC mbrtowc\n"},{"op":"insert","value":"GLOBAL FUNC mbsinit\n"},{"op":"insert","value":"GLOBAL FUNC memcmp\n"},{"op":"insert","value":"GLOBAL FUNC memcpy\n"},{"op":"insert","value":"GLOBAL FUNC memset\n"},{"op":"insert","value":"GLOBAL FUNC nl_langinfo\n"},{"op":"insert","value":"GLOBAL FUNC open\n"},{"op":"insert","value":"GLOBAL FUNC realloc\n"},{"op":"insert","value":"GLOBAL FUNC setlocale\n"},{"op":"insert","value":"GLOBAL FUNC strcmp\n"},{"op":"equal","value":"GLOBAL FUNC strcpy\n"},{"op":"equal","value":"GLOBAL FUNC strlen\n"},{"op":"insert","value":"GLOBAL FUNC strncmp\n"},{"op":"insert","value":"GLOBAL FUNC strrchr\n"},{"op":"insert","value":"GLOBAL FUNC textdomain\n"},{"op":"insert","value":"GLOBAL FUNC ungetc\n"}]}]}
EOF
RUN
NAME=rz-diff fields comparison
FILE==
CMDS=!rz-diff -t fields bins/java/Main.java.11.class bins/java/Hello.class
EXPECT=<<EOF
--- bins/java/Main.java.11.class
+++ bins/java/Hello.class
@@ -1,0 +1,1 @@
+Ljava/lang/String; who
EOF
RUN
NAME=rz-diff fields comparison JSON
FILE==
CMDS=!rz-diff -jt fields bins/java/Main.java.11.class bins/java/Hello.class
EXPECT=<<EOF
{"from":"bins/java/Main.java.11.class","to":"bins/java/Hello.class","diff":[{"from":[1,0],"to":[1,1],"ops":[{"op":"insert","value":"Ljava/lang/String; who\n"}]}]}
EOF
RUN
NAME=rz-diff fields comparison with addresses
FILE==
CMDS=!rz-diff -At fields bins/java/Main.java.11.class bins/java/Hello.class
EXPECT=<<EOF
--- bins/java/Main.java.11.class
+++ bins/java/Hello.class
@@ -1,0 +1,1 @@
+virt: 0x000000000000020b phys: 0x000000000000020b Ljava/lang/String; who
EOF
RUN
NAME=rz-diff fields comparison with addresses JSON
FILE==
CMDS=!rz-diff -Ajt fields bins/java/Main.java.11.class bins/java/Hello.class
EXPECT=<<EOF
{"from":"bins/java/Main.java.11.class","to":"bins/java/Hello.class","diff":[{"from":[1,0],"to":[1,1],"ops":[{"op":"insert","value":"virt: 0x000000000000020b phys: 0x000000000000020b Ljava/lang/String; who\n"}]}]}
EOF
RUN
NAME=rz-diff libraries comparison
FILE==
CMDS=!rz-diff -t libraries bins/java/Main.java.11.class bins/java/Hello.class
EXPECT=<<EOF
--- bins/java/Main.java.11.class
+++ bins/java/Hello.class
@@ -1,12 +1,5 @@
-Main
-[Ljava/lang/String;
-java/io/BufferedReader
-java/io/FileReader
+Hello
java/io/PrintStream
-java/lang/Exception
java/lang/Object
-java/lang/String
+java/lang/StringBuilder
java/lang/System
-java/lang/invoke/MethodHandles
-java/lang/invoke/MethodHandles$Lookup
-java/lang/invoke/StringConcatFactory
EOF
RUN
NAME=rz-diff libraries comparison JSON
FILE==
CMDS=!rz-diff -jt libraries bins/java/Main.java.11.class bins/java/Hello.class
EXPECT=<<EOF
{"from":"bins/java/Main.java.11.class","to":"bins/java/Hello.class","diff":[{"from":[1,12],"to":[1,5],"ops":[{"op":"delete","value":"Main\n"},{"op":"delete","value":"[Ljava/lang/String;\n"},{"op":"delete","value":"java/io/BufferedReader\n"},{"op":"delete","value":"java/io/FileReader\n"},{"op":"insert","value":"Hello\n"},{"op":"equal","value":"java/io/PrintStream\n"},{"op":"delete","value":"java/lang/Exception\n"},{"op":"equal","value":"java/lang/Object\n"},{"op":"delete","value":"java/lang/String\n"},{"op":"insert","value":"java/lang/StringBuilder\n"},{"op":"equal","value":"java/lang/System\n"},{"op":"delete","value":"java/lang/invoke/MethodHandles\n"},{"op":"delete","value":"java/lang/invoke/MethodHandles$Lookup\n"},{"op":"delete","value":"java/lang/invoke/StringConcatFactory\n"}]}]}
EOF
RUN
NAME=rz-diff sections comparison
FILE==
CMDS=!rz-diff -t sections bins/java/Main.java.11.class bins/java/Hello.class
EXPECT=<<EOF
--- bins/java/Main.java.11.class
+++ bins/java/Hello.class
@@ -0,8 +0,12 @@
align: 0x00000000 -r-- class.attr
align: 0x00000000 -r-- class.constant_pool
+align: 0x00000000 -r-- class.fields
+align: 0x00000000 -r-- class.fields.who.attr
align: 0x00000000 -r-- class.methods
align: 0x00000000 -r-- class.methods.<init>.attr
align: 0x00000000 -r-x class.methods.<init>.attr.0.code
align: 0x00000000 -r-- class.methods.main.attr
align: 0x00000000 -r-x class.methods.main.attr.0.code
+align: 0x00000000 -r-- class.methods.say.attr
+align: 0x00000000 -r-x class.methods.say.attr.0.code
EOF
RUN
NAME=rz-diff sections comparison JSON
FILE==
CMDS=!rz-diff -jt sections bins/java/Main.java.11.class bins/java/Hello.class
EXPECT=<<EOF
{"from":"bins/java/Main.java.11.class","to":"bins/java/Hello.class","diff":[{"from":[0,8],"to":[0,12],"ops":[{"op":"equal","value":"align: 0x00000000 -r-- class.attr\n"},{"op":"equal","value":"align: 0x00000000 -r-- class.constant_pool\n"},{"op":"insert","value":"align: 0x00000000 -r-- class.fields\n"},{"op":"insert","value":"align: 0x00000000 -r-- class.fields.who.attr\n"},{"op":"equal","value":"align: 0x00000000 -r-- class.methods\n"},{"op":"equal","value":"align: 0x00000000 -r-- class.methods.<init>.attr\n"},{"op":"equal","value":"align: 0x00000000 -r-x class.methods.<init>.attr.0.code\n"},{"op":"equal","value":"align: 0x00000000 -r-- class.methods.main.attr\n"},{"op":"equal","value":"align: 0x00000000 -r-x class.methods.main.attr.0.code\n"},{"op":"insert","value":"align: 0x00000000 -r-- class.methods.say.attr\n"},{"op":"insert","value":"align: 0x00000000 -r-x class.methods.say.attr.0.code\n"}]}]}
EOF
RUN
NAME=rz-diff sections comparison with addresses
FILE==
CMDS=!rz-diff -At sections bins/java/Main.java.11.class bins/java/Hello.class
EXPECT=<<EOF
--- bins/java/Main.java.11.class
+++ bins/java/Hello.class
@@ -1,7 +1,11 @@
-virt: 0x000000000000058a:0x0026 phys: 0x000000000000058a:0x0026 align: 0x00000000 -r-- class.attr
-virt: 0x000000000000000a:0x0480 phys: 0x000000000000000a:0x0480 align: 0x00000000 -r-- class.constant_pool
-virt: 0x000000000000048e:0x00fc phys: 0x000000000000048e:0x00fc align: 0x00000000 -r-- class.methods
-virt: 0x000000000000048e:0x002b phys: 0x000000000000048e:0x002b align: 0x00000000 -r-- class.methods.<init>.attr
-virt: 0x00000000000004a4:0x001d phys: 0x00000000000004a4:0x001d align: 0x00000000 -r-x class.methods.<init>.attr.0.code
-virt: 0x00000000000004b9:0x00d1 phys: 0x00000000000004b9:0x00d1 align: 0x00000000 -r-- class.methods.main.attr
-virt: 0x00000000000004cf:0x00c1 phys: 0x00000000000004cf:0x00c1 align: 0x00000000 -r-x class.methods.main.attr.0.code
+virt: 0x00000000000002cd:0x0008 phys: 0x00000000000002cd:0x0008 align: 0x00000000 -r-- class.attr
+virt: 0x000000000000000a:0x01ff phys: 0x000000000000000a:0x01ff align: 0x00000000 -r-- class.constant_pool
+virt: 0x000000000000020b:0x000a phys: 0x000000000000020b:0x000a align: 0x00000000 -r-- class.fields
+virt: 0x000000000000020b:0x000a phys: 0x000000000000020b:0x000a align: 0x00000000 -r-- class.fields.who.attr
+virt: 0x0000000000000215:0x00b8 phys: 0x0000000000000215:0x00b8 align: 0x00000000 -r-- class.methods
+virt: 0x0000000000000215:0x0038 phys: 0x0000000000000215:0x0038 align: 0x00000000 -r-- class.methods.<init>.attr
+virt: 0x000000000000022b:0x002a phys: 0x000000000000022b:0x002a align: 0x00000000 -r-x class.methods.<init>.attr.0.code
+virt: 0x0000000000000294:0x0039 phys: 0x0000000000000294:0x0039 align: 0x00000000 -r-- class.methods.main.attr
+virt: 0x00000000000002aa:0x0029 phys: 0x00000000000002aa:0x0029 align: 0x00000000 -r-x class.methods.main.attr.0.code
+virt: 0x000000000000024d:0x0047 phys: 0x000000000000024d:0x0047 align: 0x00000000 -r-- class.methods.say.attr
+virt: 0x0000000000000263:0x0039 phys: 0x0000000000000263:0x0039 align: 0x00000000 -r-x class.methods.say.attr.0.code
EOF
RUN
NAME=rz-diff sections comparison with addresses JSON
FILE==
CMDS=!rz-diff -Ajt sections bins/java/Main.java.11.class bins/java/Hello.class
EXPECT=<<EOF
{"from":"bins/java/Main.java.11.class","to":"bins/java/Hello.class","diff":[{"from":[1,7],"to":[1,11],"ops":[{"op":"delete","value":"virt: 0x000000000000058a:0x0026 phys: 0x000000000000058a:0x0026 align: 0x00000000 -r-- class.attr\n"},{"op":"delete","value":"virt: 0x000000000000000a:0x0480 phys: 0x000000000000000a:0x0480 align: 0x00000000 -r-- class.constant_pool\n"},{"op":"delete","value":"virt: 0x000000000000048e:0x00fc phys: 0x000000000000048e:0x00fc align: 0x00000000 -r-- class.methods\n"},{"op":"delete","value":"virt: 0x000000000000048e:0x002b phys: 0x000000000000048e:0x002b align: 0x00000000 -r-- class.methods.<init>.attr\n"},{"op":"delete","value":"virt: 0x00000000000004a4:0x001d phys: 0x00000000000004a4:0x001d align: 0x00000000 -r-x class.methods.<init>.attr.0.code\n"},{"op":"delete","value":"virt: 0x00000000000004b9:0x00d1 phys: 0x00000000000004b9:0x00d1 align: 0x00000000 -r-- class.methods.main.attr\n"},{"op":"delete","value":"virt: 0x00000000000004cf:0x00c1 phys: 0x00000000000004cf:0x00c1 align: 0x00000000 -r-x class.methods.main.attr.0.code\n"},{"op":"insert","value":"virt: 0x00000000000002cd:0x0008 phys: 0x00000000000002cd:0x0008 align: 0x00000000 -r-- class.attr\n"},{"op":"insert","value":"virt: 0x000000000000000a:0x01ff phys: 0x000000000000000a:0x01ff align: 0x00000000 -r-- class.constant_pool\n"},{"op":"insert","value":"virt: 0x000000000000020b:0x000a phys: 0x000000000000020b:0x000a align: 0x00000000 -r-- class.fields\n"},{"op":"insert","value":"virt: 0x000000000000020b:0x000a phys: 0x000000000000020b:0x000a align: 0x00000000 -r-- class.fields.who.attr\n"},{"op":"insert","value":"virt: 0x0000000000000215:0x00b8 phys: 0x0000000000000215:0x00b8 align: 0x00000000 -r-- class.methods\n"},{"op":"insert","value":"virt: 0x0000000000000215:0x0038 phys: 0x0000000000000215:0x0038 align: 0x00000000 -r-- class.methods.<init>.attr\n"},{"op":"insert","value":"virt: 0x000000000000022b:0x002a phys: 0x000000000000022b:0x002a align: 0x00000000 -r-x class.methods.<init>.attr.0.code\n"},{"op":"insert","value":"virt: 0x0000000000000294:0x0039 phys: 0x0000000000000294:0x0039 align: 0x00000000 -r-- class.methods.main.attr\n"},{"op":"insert","value":"virt: 0x00000000000002aa:0x0029 phys: 0x00000000000002aa:0x0029 align: 0x00000000 -r-x class.methods.main.attr.0.code\n"},{"op":"insert","value":"virt: 0x000000000000024d:0x0047 phys: 0x000000000000024d:0x0047 align: 0x00000000 -r-- class.methods.say.attr\n"},{"op":"insert","value":"virt: 0x0000000000000263:0x0039 phys: 0x0000000000000263:0x0039 align: 0x00000000 -r-x class.methods.say.attr.0.code\n"}]}]}
EOF
RUN
NAME=rz-diff symbols comparison
FILE==
CMDS=!rz-diff -t symbols bins/java/Main.java.11.class bins/java/Main.java.1.7.class
EXPECT=<<EOF
--- bins/java/Main.java.11.class
+++ bins/java/Main.java.1.7.class
@@ -8,6 +8,8 @@
java.io.PrintStream.println
java.lang.Exception.printStackTrace
java.lang.Object.<init>
+java.lang.StringBuilder.<init>
+java.lang.StringBuilder.append
+java.lang.StringBuilder.toString
java.lang.System.err
java.lang.System.out
-java.lang.invoke.StringConcatFactory.makeConcatWithConstants
EOF
RUN
NAME=rz-diff symbols comparison JSON
FILE==
CMDS=!rz-diff -jt symbols bins/java/Main.java.11.class bins/java/Main.java.1.7.class
EXPECT=<<EOF
{"from":"bins/java/Main.java.11.class","to":"bins/java/Main.java.1.7.class","diff":[{"from":[8,6],"to":[8,8],"ops":[{"op":"equal","value":"java.io.PrintStream.println\n"},{"op":"equal","value":"java.lang.Exception.printStackTrace\n"},{"op":"equal","value":"java.lang.Object.<init>\n"},{"op":"insert","value":"java.lang.StringBuilder.<init>\n"},{"op":"insert","value":"java.lang.StringBuilder.append\n"},{"op":"insert","value":"java.lang.StringBuilder.toString\n"},{"op":"equal","value":"java.lang.System.err\n"},{"op":"equal","value":"java.lang.System.out\n"},{"op":"delete","value":"java.lang.invoke.StringConcatFactory.makeConcatWithConstants\n"}]}]}
EOF
RUN
NAME=rz-diff symbols comparison with addresses
FILE==
CMDS=!rz-diff -At symbols bins/java/Main.java.11.class bins/java/Main.java.1.7.class
EXPECT=<<EOF
--- bins/java/Main.java.11.class
+++ bins/java/Main.java.1.7.class
@@ -1,13 +1,15 @@
-virt: 0x00000000000004a4 phys: 0x00000000000004a4 Main.<init>
-virt: 0x00000000000004cf phys: 0x00000000000004cf Main.main
+virt: 0x0000000000000383 phys: 0x0000000000000383 Main.<init>
+virt: 0x00000000000003ae phys: 0x00000000000003ae Main.main
virt: 0x000000000000011e phys: 0x000000000000011e java.io.BufferedReader.<init>
-virt: 0x00000000000001b7 phys: 0x00000000000001b7 java.io.BufferedReader.close
+virt: 0x00000000000001f0 phys: 0x00000000000001f0 java.io.BufferedReader.close
virt: 0x000000000000013e phys: 0x000000000000013e java.io.BufferedReader.readLine
virt: 0x0000000000000114 phys: 0x0000000000000114 java.io.FileReader.<init>
-virt: 0x000000000000021f phys: 0x000000000000021f java.io.PrintStream.format
+virt: 0x0000000000000258 phys: 0x0000000000000258 java.io.PrintStream.format
virt: 0x0000000000000088 phys: 0x0000000000000088 java.io.PrintStream.println
-virt: 0x0000000000000271 phys: 0x0000000000000271 java.lang.Exception.printStackTrace
+virt: 0x00000000000002aa phys: 0x00000000000002aa java.lang.Exception.printStackTrace
virt: 0x000000000000000a phys: 0x000000000000000a java.lang.Object.<init>
-virt: 0x00000000000001e2 phys: 0x00000000000001e2 java.lang.System.err
+virt: 0x0000000000000187 phys: 0x0000000000000187 java.lang.StringBuilder.<init>
+virt: 0x0000000000000198 phys: 0x0000000000000198 java.lang.StringBuilder.append
+virt: 0x00000000000001db phys: 0x00000000000001db java.lang.StringBuilder.toString
+virt: 0x000000000000021b phys: 0x000000000000021b java.lang.System.err
virt: 0x0000000000000039 phys: 0x0000000000000039 java.lang.System.out
-virt: 0x000000000000033f phys: 0x000000000000033f java.lang.invoke.StringConcatFactory.makeConcatWithConstants
EOF
RUN
NAME=rz-diff symbols comparison with addresses JSON
FILE==
CMDS=!rz-diff -Ajt symbols bins/java/Main.java.11.class bins/java/Main.java.1.7.class
EXPECT=<<EOF
{"from":"bins/java/Main.java.11.class","to":"bins/java/Main.java.1.7.class","diff":[{"from":[1,13],"to":[1,15],"ops":[{"op":"delete","value":"virt: 0x00000000000004a4 phys: 0x00000000000004a4 Main.<init>\n"},{"op":"delete","value":"virt: 0x00000000000004cf phys: 0x00000000000004cf Main.main\n"},{"op":"insert","value":"virt: 0x0000000000000383 phys: 0x0000000000000383 Main.<init>\n"},{"op":"insert","value":"virt: 0x00000000000003ae phys: 0x00000000000003ae Main.main\n"},{"op":"equal","value":"virt: 0x000000000000011e phys: 0x000000000000011e java.io.BufferedReader.<init>\n"},{"op":"delete","value":"virt: 0x00000000000001b7 phys: 0x00000000000001b7 java.io.BufferedReader.close\n"},{"op":"insert","value":"virt: 0x00000000000001f0 phys: 0x00000000000001f0 java.io.BufferedReader.close\n"},{"op":"equal","value":"virt: 0x000000000000013e phys: 0x000000000000013e java.io.BufferedReader.readLine\n"},{"op":"equal","value":"virt: 0x0000000000000114 phys: 0x0000000000000114 java.io.FileReader.<init>\n"},{"op":"delete","value":"virt: 0x000000000000021f phys: 0x000000000000021f java.io.PrintStream.format\n"},{"op":"insert","value":"virt: 0x0000000000000258 phys: 0x0000000000000258 java.io.PrintStream.format\n"},{"op":"equal","value":"virt: 0x0000000000000088 phys: 0x0000000000000088 java.io.PrintStream.println\n"},{"op":"delete","value":"virt: 0x0000000000000271 phys: 0x0000000000000271 java.lang.Exception.printStackTrace\n"},{"op":"insert","value":"virt: 0x00000000000002aa phys: 0x00000000000002aa java.lang.Exception.printStackTrace\n"},{"op":"equal","value":"virt: 0x000000000000000a phys: 0x000000000000000a java.lang.Object.<init>\n"},{"op":"delete","value":"virt: 0x00000000000001e2 phys: 0x00000000000001e2 java.lang.System.err\n"},{"op":"insert","value":"virt: 0x0000000000000187 phys: 0x0000000000000187 java.lang.StringBuilder.<init>\n"},{"op":"insert","value":"virt: 0x0000000000000198 phys: 0x0000000000000198 java.lang.StringBuilder.append\n"},{"op":"insert","value":"virt: 0x00000000000001db phys: 0x00000000000001db java.lang.StringBuilder.toString\n"},{"op":"insert","value":"virt: 0x000000000000021b phys: 0x000000000000021b java.lang.System.err\n"},{"op":"equal","value":"virt: 0x0000000000000039 phys: 0x0000000000000039 java.lang.System.out\n"},{"op":"delete","value":"virt: 0x000000000000033f phys: 0x000000000000033f java.lang.invoke.StringConcatFactory.makeConcatWithConstants\n"}]}]}
EOF
RUN
NAME=rz-diff command with zero argument
FILE==
CMDS=!rz-diff -t command bins/java/Main.java.11.class bins/java/Hello.class
EXPECT_ERR=<<EOF
Cannot open empty path
ERROR: rz-diff: error, option -t 'command' requires -0 <command>.
EOF
RUN
NAME=rz-diff empty second file
NAME=rz-diff command with one argument
FILE==
CMDS=!rz-diff bins/other/rz-diff/rz-diff_c_1 ""
EXPECT_ERR=<<EOF
Cannot open empty path
CMDS=!rz-diff -0 javac -t command bins/java/Main.java.11.class bins/java/Main.java.15.class
EXPECT=<<EOF
--- bins/java/Main.java.11.class
+++ bins/java/Main.java.15.class
@@ -1,4 +1,4 @@
-Version: (55.0) Java SE 11
+Version: (59.0) Java SE 15
Flags: (0x0021) public super
Class: (#57) Main
Super: (#2) java/lang/Object
EOF
RUN
NAME=rz-diff command with one argument JSON
FILE==
CMDS=!rz-diff -j -0 javac -t command bins/java/Main.java.11.class bins/java/Main.java.15.class
EXPECT=<<EOF
{"from":"bins/java/Main.java.11.class","to":"bins/java/Main.java.15.class","diff":[{"from":[1,4],"to":[1,4],"ops":[{"op":"delete","value":"Version: (55.0) Java SE 11\n"},{"op":"insert","value":"Version: (59.0) Java SE 15\n"},{"op":"equal","value":"Flags: (0x0021) public super\n"},{"op":"equal","value":"Class: (#57) Main\n"},{"op":"equal","value":"Super: (#2) java/lang/Object\n"}]}]}
EOF
RUN
NAME=rz-diff command with two arguments
FILE==
CMDS=!rz-diff -0 "pi 20 @ main" -1 "pi 20 @ sym.main" -t command bins/elf/hello_world bins/elf/hello_world32
EXPECT=<<EOF
--- bins/elf/hello_world
+++ bins/elf/hello_world32
@@ -1,20 +1,20 @@
-push rbp
-mov rbp, rsp
-sub rsp, 0x20
-lea rax, str.Hello
-mov qword [rbp - 0x18], rax
-lea rax, str.r2_folks
-mov qword [rbp - 0x10], rax
-mov rax, qword [rbp - 0x18]
-mov rdi, rax
+lea ecx, [esp + 4]
+and esp, 0xfffffff0
+push dword [ecx - 4]
+push ebp
+mov ebp, esp
+push ebx
+push ecx
+sub esp, 0x20
+call sym.__x86.get_pc_thunk.bx
+add ebx, 0x19a0
+lea eax, [ebx - 0x1874]
+mov dword [ebp - 0x1c], eax
+lea eax, [ebx - 0x186e]
+mov dword [ebp - 0x18], eax
+sub esp, 0xc
+push dword [ebp - 0x1c]
call sym.imp.strlen
-mov dword [rbp - 0x20], eax
-mov rax, qword [rbp - 0x10]
-mov rdi, rax
-call sym.imp.strlen
-mov dword [rbp - 0x1c], eax
-mov edx, dword [rbp - 0x20]
-mov eax, dword [rbp - 0x1c]
-add eax, edx
-add eax, 1
-cdqe
+add esp, 0x10
+mov dword [ebp - 0x14], eax
+sub esp, 0xc
EOF
RUN
NAME=rz-diff command with two arguments JSON
FILE==
CMDS=!rz-diff -j -0 "pi 20 @ main" -1 "pi 20 @ sym.main" -t command bins/elf/hello_world bins/elf/hello_world32
EXPECT=<<EOF
{"from":"bins/elf/hello_world","to":"bins/elf/hello_world32","diff":[{"from":[1,20],"to":[1,20],"ops":[{"op":"delete","value":"push rbp\n"},{"op":"delete","value":"mov rbp, rsp\n"},{"op":"delete","value":"sub rsp, 0x20\n"},{"op":"delete","value":"lea rax, str.Hello\n"},{"op":"delete","value":"mov qword [rbp - 0x18], rax\n"},{"op":"delete","value":"lea rax, str.r2_folks\n"},{"op":"delete","value":"mov qword [rbp - 0x10], rax\n"},{"op":"delete","value":"mov rax, qword [rbp - 0x18]\n"},{"op":"delete","value":"mov rdi, rax\n"},{"op":"insert","value":"lea ecx, [esp + 4]\n"},{"op":"insert","value":"and esp, 0xfffffff0\n"},{"op":"insert","value":"push dword [ecx - 4]\n"},{"op":"insert","value":"push ebp\n"},{"op":"insert","value":"mov ebp, esp\n"},{"op":"insert","value":"push ebx\n"},{"op":"insert","value":"push ecx\n"},{"op":"insert","value":"sub esp, 0x20\n"},{"op":"insert","value":"call sym.__x86.get_pc_thunk.bx\n"},{"op":"insert","value":"add ebx, 0x19a0\n"},{"op":"insert","value":"lea eax, [ebx - 0x1874]\n"},{"op":"insert","value":"mov dword [ebp - 0x1c], eax\n"},{"op":"insert","value":"lea eax, [ebx - 0x186e]\n"},{"op":"insert","value":"mov dword [ebp - 0x18], eax\n"},{"op":"insert","value":"sub esp, 0xc\n"},{"op":"insert","value":"push dword [ebp - 0x1c]\n"},{"op":"equal","value":"call sym.imp.strlen\n"},{"op":"delete","value":"mov dword [rbp - 0x20], eax\n"},{"op":"delete","value":"mov rax, qword [rbp - 0x10]\n"},{"op":"delete","value":"mov rdi, rax\n"},{"op":"delete","value":"call sym.imp.strlen\n"},{"op":"delete","value":"mov dword [rbp - 0x1c], eax\n"},{"op":"delete","value":"mov edx, dword [rbp - 0x20]\n"},{"op":"delete","value":"mov eax, dword [rbp - 0x1c]\n"},{"op":"delete","value":"add eax, edx\n"},{"op":"delete","value":"add eax, 1\n"},{"op":"delete","value":"cdqe\n"},{"op":"insert","value":"add esp, 0x10\n"},{"op":"insert","value":"mov dword [ebp - 0x14], eax\n"},{"op":"insert","value":"sub esp, 0xc\n"}]}]}
EOF
RUN

View file

@ -1,4 +1,5 @@
// SPDX-FileCopyrightText: 2017 lonetech <yann-github@vernier.se>
// SPDX-FileCopyrightText: 2021 RizinOrg <info@rizin.re>
// SPDX-FileCopyrightText: 2021 deroad <wargio@libero.it>
// SPDX-License-Identifier: LGPL-3.0-only
#include <math.h>
@ -6,59 +7,260 @@
#include "minunit.h"
#define R(a, b, c, d) \
{ (const ut8 *)a, (const ut8 *)b, (int)c, (int)d }
{ (const ut8 *)a, (const ut8 *)b, c, d }
static struct {
const ut8 *a;
const ut8 *b;
int di_distance;
int dis_distance;
ut32 myers;
ut32 levenstein;
} tests[] = {
R("", "zzz", 3, 3),
R("meow", "", 4, 4),
R("a", "b", 2, 1),
R("aaa", "aaa", 0, 0),
R("aaaaa", "aabaa", 2, 1),
R("aaaa", "aabaa", 1, 1),
R("aaba", "babca", 3, 2),
R("foo", "foobar", 3, 3),
R("wallaby", "wallet", 5, 3),
R("identity", "identity", 0, 0),
{ NULL, NULL, 0, 0 }
R("", "zzz", 3.0, 3.0),
R("meow", "", 4.0, 4.0),
R("a", "b", 2.0, 1.0),
R("aaa", "aaa", 0.0, 0.0),
R("aaaaa", "aabaa", 2.0, 1.0),
R("aaaa", "aabaa", 1.0, 1.0),
R("aaba", "babca", 3.0, 2.0),
R("foo", "foobar", 3.0, 3.0),
R("wallaby", "wallet", 5.0, 3.0),
R("identity", "identity", 0.0, 0.0),
{ NULL, NULL, 0.0, 0.0 }
};
bool test_rz_diff_buffers_distance(void) {
char msg[128];
RzDiff *diff = rz_diff_new();
if (!diff) {
return false;
}
unsigned int distance;
int i;
bool test_rz_diff_distances(void) {
ut32 distance;
bool boolean;
// Levenshtein edit distance (deletion/insertion/substitution)
diff->type = 'l';
for (i = 0; tests[i].a; i++) {
size_t la = strlen((const char *)tests[i].a), lb = strlen((const char *)tests[i].b);
rz_diff_buffers_distance(diff, tests[i].a, la, tests[i].b, lb, &distance, NULL);
snprintf(msg, sizeof msg, "levenshtein %s/%s distance", tests[i].a, tests[i].b);
mu_assert_eq(distance, tests[i].dis_distance, msg);
}
for (ut32 i = 0; tests[i].a; i++) {
size_t la = strlen((const char *)tests[i].a);
size_t lb = strlen((const char *)tests[i].b);
// Eugene W. Myers' O(ND) diff algorithm, deletion/insertion edit distance
diff->type = 'm';
for (i = 0; tests[i].a; i++) {
size_t la = strlen((const char *)tests[i].a), lb = strlen((const char *)tests[i].b);
rz_diff_buffers_distance(diff, tests[i].a, la, tests[i].b, lb, &distance, NULL);
snprintf(msg, sizeof msg, "myers %s/%s distance", tests[i].a, tests[i].b);
mu_assert_eq(distance, tests[i].di_distance, msg);
}
boolean = rz_diff_levenstein_distance(tests[i].a, la, tests[i].b, lb, &distance, NULL);
mu_assert_true(boolean, "rz_diff_levenstein_distance");
mu_assert_eq(distance, tests[i].levenstein, "levenstein distance");
boolean = rz_diff_myers_distance(tests[i].a, la, tests[i].b, lb, &distance, NULL);
mu_assert_true(boolean, "rz_diff_myers_distance");
mu_assert_eq(distance, tests[i].myers, "myers distance");
}
mu_end;
}
bool test_rz_diff_unified_lines(void) {
RzDiff *diff = NULL;
char *result = NULL;
// clang-format off
const char *a = ""
"This part of the\n"
"document has stayed the\n"
"same from version to\n"
"version. It shouldn't\n"
"be shown if it doesn't\n"
"change. Otherwise, that\n"
"would not be helping to\n"
"compress the size of the\n"
"changes.\n"
"\n"
"This paragraph contains\n"
"text that is outdated.\n"
"It will be deleted in the\n"
"near future.\n"
"\n"
"It is important to spell\n"
"check this dokument. On\n"
"the other hand, a\n"
"misspelled word isn't\n"
"the end of the world.\n"
"Nothing in the rest of\n"
"this paragraph needs to\n"
"be changed. Things can\n"
"be added after it.";
const char *b = ""
"This is an important\n"
"notice! It should\n"
"therefore be located at\n"
"the beginning of this\n"
"document!\n"
"\n"
"This part of the\n"
"document has stayed the\n"
"same from version to\n"
"version. It shouldn't\n"
"be shown if it doesn't\n"
"change. Otherwise, that\n"
"would not be helping to\n"
"compress the size of the\n"
"changes.\n"
"\n"
"It is important to spell\n"
"check this document. On\n"
"the other hand, a\n"
"misspelled word isn't\n"
"the end of the world.\n"
"Nothing in the rest of\n"
"this paragraph needs to\n"
"be changed. Things can\n"
"be added after it.\n"
"\n"
"This paragraph contains\n"
"important new additions\n"
"to this document.";
const char *expected = ""
"--- /original\n"
"+++ /modified\n"
"@@ -1,3 +1,9 @@\n"
"+This is an important\n"
"+notice! It should\n"
"+therefore be located at\n"
"+the beginning of this\n"
"+document!\n"
"+\n"
" This part of the\n"
" document has stayed the\n"
" same from version to\n"
"@@ -8,17 +14,16 @@\n"
" compress the size of the\n"
" changes.\n"
" \n"
"-This paragraph contains\n"
"-text that is outdated.\n"
"-It will be deleted in the\n"
"-near future.\n"
"-\n"
" It is important to spell\n"
"-check this dokument. On\n"
"+check this document. On\n"
" the other hand, a\n"
" misspelled word isn't\n"
" the end of the world.\n"
" Nothing in the rest of\n"
" this paragraph needs to\n"
" be changed. Things can\n"
"-be added after it.\n"
"+be added after it.\n"
"+\n"
"+This paragraph contains\n"
"+important new additions\n"
"+to this document.\n";
// clang-format on
diff = rz_diff_lines_new(a, b, NULL);
result = rz_diff_unified_text(diff, NULL, NULL, false, false);
rz_diff_free(diff);
mu_assert_notnull(result, "rz_diff_unified result not null");
printf("\n\n%s\n\n", expected);
mu_assert_streq(result, expected, "rz_diff_unified on lines");
free(result);
mu_end;
}
bool test_rz_diff_unified_bytes(void) {
RzDiff *diff = NULL;
char *result = NULL;
// clang-format off
const char *a = ""
"This part of the\n"
"document has stayed the\n"
"same from version to\n"
"version. It shouldn't\n"
"be shown if it doesn't\n"
"change. Otherwise, that\n"
"would not be helping to\n"
"compress the size of the\n"
"changes.\n"
"\n"
"This paragraph contains\n"
"text that is outdated.\n"
"It will be deleted in the\n"
"near future.\n"
"\n"
"It is important to spell\n"
"check this dokument. On\n"
"the other hand, a\n"
"misspelled word isn't\n"
"the end of the world.\n"
"Nothing in the rest of\n"
"this paragraph needs to\n"
"be changed. Things can\n"
"be added after it.";
const char *b = ""
"This is an important\n"
"notice! It should\n"
"therefore be located at\n"
"the beginning of this\n"
"document!\n"
"\n"
"This part of the\n"
"document has stayed the\n"
"same from version to\n"
"version. It shouldn't\n"
"be shown if it doesn't\n"
"change. Otherwise, that\n"
"would not be helping to\n"
"compress the size of the\n"
"changes.\n"
"\n"
"It is important to spell\n"
"check this document. On\n"
"the other hand, a\n"
"misspelled word isn't\n"
"the end of the world.\n"
"Nothing in the rest of\n"
"this paragraph needs to\n"
"be changed. Things can\n"
"be added after it.\n"
"\n"
"This paragraph contains\n"
"important new additions\n"
"to this document.";
const char *expected = ""
"--- /original\n"
"+++ /modified\n"
"@@ -1,3 +1,99 @@\n"
"+5468697320697320616e20696d706f7274616e740a6e6f746963652120497420\n"
"+73686f756c640a7468657265666f7265206265206c6f63617465642061740a74\n"
"+686520626567696e6e696e67206f6620746869730a646f63756d656e74210a0a\n"
" 546869\n"
"@@ -190,93 +286,6 @@\n"
" 2e0a0a\n"
"-546869732070617261677261706820636f6e7461696e730a7465787420746861\n"
"-74206973206f757464617465642e0a49742077696c6c2062652064656c657465\n"
"-6420696e207468650a6e656172206675747572652e0a0a\n"
" 497420\n"
"@@ -315,7 +324,7 @@\n"
" 20646f\n"
"-6b\n"
"+63\n"
" 756d65\n"
"@@ -476,3 +485,70 @@\n"
" 69742e\n"
"+0a0a546869732070617261677261706820636f6e7461696e730a696d706f7274\n"
"+616e74206e6577206164646974696f6e730a746f207468697320646f63756d65\n"
"+6e742e\n";
// clang-format on
diff = rz_diff_bytes_new((const ut8 *)a, strlen(a), (const ut8 *)b, strlen(b), NULL);
result = rz_diff_unified_text(diff, NULL, NULL, false, false);
rz_diff_free(diff);
mu_assert_notnull(result, "rz_diff_unified result not null");
mu_assert_streq(result, expected, "rz_diff_unified on bytes");
free(result);
mu_end;
}
int all_tests() {
mu_run_test(test_rz_diff_buffers_distance);
mu_run_test(test_rz_diff_distances);
mu_run_test(test_rz_diff_unified_lines);
mu_run_test(test_rz_diff_unified_bytes);
return tests_passed != tests_run;
}