Rewrite rz-diff and internal diffing library (#1126)
This commit is contained in:
parent
0aa1ff92c4
commit
6fec281af7
31 changed files with 3786 additions and 2448 deletions
|
|
@ -4,6 +4,7 @@ if get_option('enable_rz_test')
|
|||
include_directories: [platform_inc],
|
||||
dependencies: [
|
||||
rz_util_dep,
|
||||
rz_diff_dep,
|
||||
lrt,
|
||||
],
|
||||
install: true,
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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
31
librz/diff/bytes_diff.c
Normal 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
875
librz/diff/diff.c
Normal 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
114
librz/diff/distance.c
Normal 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
76
librz/diff/lines_diff.c
Normal 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
31
librz/diff/meson.build
Normal 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
277
librz/diff/unified_diff.c
Normal 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;
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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 */
|
||||
|
|
|
|||
|
|
@ -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; \
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
|
|
|
|||
|
|
@ -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 }
|
||||
};
|
||||
|
|
|
|||
2681
librz/main/rz-diff.c
2681
librz/main/rz-diff.c
File diff suppressed because it is too large
Load diff
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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',
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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 Needleman–Wunsch 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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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'),
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue