1086 lines
39 KiB
C
1086 lines
39 KiB
C
// SPDX-FileCopyrightText: 2022 Florian Märkl <info@florianmaerkl.de>
|
|
// SPDX-License-Identifier: LGPL-3.0-only
|
|
|
|
#include <rz_il/rz_il_validate.h>
|
|
#include <rz_util/ht_uu.h>
|
|
|
|
/////////////////////////////////////////////////////////
|
|
// ---------------------- context -----------------------
|
|
|
|
/**
|
|
* Global (immutable) context
|
|
*/
|
|
struct rz_il_validate_global_context_t {
|
|
HtSP /*<const char *, RzILSortPure *>*/ *global_vars;
|
|
HtUU /*<RzILMemIndex, ut32:ut32>*/ *mems;
|
|
ut32 pc_len;
|
|
}; /* RzILValidateGlobalContext */
|
|
|
|
/**
|
|
* Create a new global context for validation
|
|
* Vars and mems can be added manually with rz_il_validate_global_context_add_* functions.
|
|
*/
|
|
RZ_API RzILValidateGlobalContext *rz_il_validate_global_context_new_empty(ut32 pc_len) {
|
|
rz_return_val_if_fail(pc_len, NULL);
|
|
RzILValidateGlobalContext *ctx = RZ_NEW0(RzILValidateGlobalContext);
|
|
if (!ctx) {
|
|
return NULL;
|
|
}
|
|
ctx->pc_len = pc_len;
|
|
ctx->global_vars = ht_sp_new(HT_STR_DUP, NULL, free);
|
|
if (!ctx->global_vars) {
|
|
free(ctx);
|
|
return NULL;
|
|
}
|
|
ctx->mems = ht_uu_new();
|
|
if (!ctx->mems) {
|
|
ht_sp_free(ctx->global_vars);
|
|
free(ctx);
|
|
return NULL;
|
|
}
|
|
return ctx;
|
|
}
|
|
|
|
/**
|
|
* Define a new global variable in \p ctx
|
|
*/
|
|
RZ_API void rz_il_validate_global_context_add_var(RzILValidateGlobalContext *ctx, RZ_NONNULL const char *name, RzILSortPure sort) {
|
|
rz_return_if_fail(ctx && name);
|
|
RzILSortPure *hts = RZ_NEW(RzILSortPure);
|
|
if (!hts) {
|
|
return;
|
|
}
|
|
*hts = sort;
|
|
ht_sp_update(ctx->global_vars, name, hts);
|
|
}
|
|
|
|
/**
|
|
* Define a new memory in \p ctx
|
|
*/
|
|
RZ_API void rz_il_validate_global_context_add_mem(RzILValidateGlobalContext *ctx, RzILMemIndex idx, ut32 key_len, ut32 val_len) {
|
|
rz_return_if_fail(ctx && key_len && val_len);
|
|
ht_uu_update(ctx->mems, idx, ((ut64)key_len << 32) | (ut64)val_len);
|
|
}
|
|
|
|
/**
|
|
* Create a new context for IL validation based on the global vars and mems in \p vm
|
|
*/
|
|
RZ_API RzILValidateGlobalContext *rz_il_validate_global_context_new_from_vm(RZ_NONNULL RzILVM *vm) {
|
|
rz_return_val_if_fail(vm, NULL);
|
|
RzILValidateGlobalContext *ctx = rz_il_validate_global_context_new_empty(rz_il_vm_get_pc_len(vm));
|
|
RzPVector *vars = rz_il_vm_get_all_vars(vm, RZ_IL_VAR_KIND_GLOBAL);
|
|
if (vars) {
|
|
void **it;
|
|
rz_pvector_foreach (vars, it) {
|
|
RzILVar *var = *it;
|
|
rz_il_validate_global_context_add_var(ctx, var->name, var->sort);
|
|
}
|
|
rz_pvector_free(vars);
|
|
}
|
|
size_t idx;
|
|
void **it;
|
|
rz_pvector_enumerate (&vm->vm_memory, it, idx) {
|
|
RzILMem *mem = *it;
|
|
if (!mem) {
|
|
continue;
|
|
}
|
|
rz_il_validate_global_context_add_mem(ctx, (RzILMemIndex)idx, rz_il_mem_key_len(mem), rz_il_mem_value_len(mem));
|
|
}
|
|
return ctx;
|
|
}
|
|
|
|
RZ_API void rz_il_validate_global_context_free(RzILValidateGlobalContext *ctx) {
|
|
if (!ctx) {
|
|
return;
|
|
}
|
|
ht_sp_free(ctx->global_vars);
|
|
ht_uu_free(ctx->mems);
|
|
free(ctx);
|
|
}
|
|
|
|
typedef struct {
|
|
const RzILValidateGlobalContext *global_ctx;
|
|
|
|
/**
|
|
* all vars' types that were encountered somewhere before, for enforcing vars always have the
|
|
* same type, even if they are not available anymore.
|
|
* This must always be a superset of `local_vars_available`.
|
|
* This owns all values, local_vars_available borrows them.
|
|
*/
|
|
HtSP /*<const char *, RzILSortPure *>*/ *local_vars_known;
|
|
|
|
HtSP /*<const char *, RzILSortPure *>*/ *local_vars_available; ///< vars that can be accessed right now
|
|
} LocalContext;
|
|
|
|
static bool local_context_init(LocalContext *ctx, const RzILValidateGlobalContext *global_ctx) {
|
|
ctx->global_ctx = global_ctx;
|
|
ctx->local_vars_known = ht_sp_new(HT_STR_DUP, NULL, free);
|
|
if (!ctx->local_vars_known) {
|
|
return false;
|
|
}
|
|
ctx->local_vars_available = ht_sp_new(HT_STR_DUP, NULL, NULL);
|
|
if (!ctx->local_vars_available) {
|
|
ht_sp_free(ctx->local_vars_known);
|
|
ctx->local_vars_known = NULL;
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
static void local_context_fini(LocalContext *ctx) {
|
|
ht_sp_free(ctx->local_vars_known);
|
|
ht_sp_free(ctx->local_vars_available);
|
|
ctx->local_vars_known = NULL;
|
|
ctx->local_vars_available = NULL;
|
|
}
|
|
|
|
static bool local_var_copy_known_cb(RZ_NONNULL void *user, const char *k, const void *v) {
|
|
LocalContext *dst = user;
|
|
RzILSortPure *sort = RZ_NEW(RzILSortPure);
|
|
if (!sort) {
|
|
return false;
|
|
}
|
|
*sort = *(RzILSortPure *)v;
|
|
ht_sp_update(dst->local_vars_known, k, sort);
|
|
return true;
|
|
}
|
|
|
|
static bool local_var_copy_avail_cb(RZ_NONNULL void *user, const char *k, const void *v) {
|
|
LocalContext *dst = user;
|
|
RzILSortPure *sort = ht_sp_find(dst->local_vars_known, k, NULL);
|
|
// known is superset of avail, so we can assert this:
|
|
rz_return_val_if_fail(sort && rz_il_sort_pure_eq(*sort, *(RzILSortPure *)v), false);
|
|
ht_sp_update(dst->local_vars_available, k, sort);
|
|
return true;
|
|
}
|
|
|
|
static bool local_context_copy(LocalContext *dst, LocalContext *src) {
|
|
if (!local_context_init(dst, src->global_ctx)) {
|
|
return false;
|
|
}
|
|
ht_sp_foreach(src->local_vars_known, local_var_copy_known_cb, dst);
|
|
ht_sp_foreach(src->local_vars_available, local_var_copy_avail_cb, dst);
|
|
return true;
|
|
}
|
|
|
|
typedef struct {
|
|
bool failed;
|
|
RzStrBuf *report_builder;
|
|
const char *op_name;
|
|
LocalContext *dst;
|
|
LocalContext *src;
|
|
} LocalContextMeet;
|
|
|
|
// called on src, take the union of the known types or fail if they don't agree
|
|
static bool local_var_meet_known_cb(RZ_NONNULL void *user, const char *k, const void *v) {
|
|
LocalContextMeet *meet = user;
|
|
RzILSortPure src_sort = *(RzILSortPure *)v;
|
|
RzILSortPure *dst_sort = ht_sp_find(meet->dst->local_vars_known, k, NULL);
|
|
if (dst_sort && !rz_il_sort_pure_eq(src_sort, *dst_sort)) {
|
|
char *src_sort_s = rz_il_sort_pure_stringify(src_sort);
|
|
char *dst_sort_s = rz_il_sort_pure_stringify(*dst_sort);
|
|
rz_strbuf_appendf(meet->report_builder, "Control flow paths from %s op do not agree on the type of local variable \"%s\": %s vs. %s.\n",
|
|
meet->op_name, (const char *)k, src_sort_s, dst_sort_s);
|
|
free(src_sort_s);
|
|
free(dst_sort_s);
|
|
meet->failed = true;
|
|
return false;
|
|
}
|
|
if (!dst_sort) {
|
|
dst_sort = RZ_NEW(RzILSortPure);
|
|
if (!dst_sort) {
|
|
meet->failed = true;
|
|
return false;
|
|
}
|
|
*dst_sort = src_sort;
|
|
ht_sp_update(meet->dst->local_vars_known, k, dst_sort);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
// called on dst, remove all vars from dst that do not appear in src (intersection)
|
|
static bool local_var_meet_avail_cb(RZ_NONNULL void *user, const char *k, const void *v) {
|
|
LocalContextMeet *meet = user;
|
|
RzILSortPure *src_sort = ht_sp_find(meet->src->local_vars_available, k, NULL);
|
|
if (!src_sort) {
|
|
ht_sp_delete(meet->dst->local_vars_available, k);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Meet (intersection) of two contexts, usually when two control flow paths meet.
|
|
* Known types for local vars are always kept from both, to ensure they always have the
|
|
* same type in the entire expression.
|
|
* Available local vars are intersected.
|
|
* This fails when \p a and \p b both know about a local var, but don't agree about its type.
|
|
*
|
|
* \p a First input operand, this will also be modified in-place to contain the result.
|
|
* \p b Second input operand, will not be modified.
|
|
* \p report_builder On failure, will be appended with an error message
|
|
* \p op_name Name of the op in which the meet is being performed, for the error message
|
|
* \return whether the meet succeeded
|
|
*/
|
|
static bool local_context_meet(RZ_INOUT LocalContext *a, RZ_IN LocalContext *b, RzStrBuf *report_builder, const char *op_name) {
|
|
LocalContextMeet meet = {
|
|
.failed = false,
|
|
.report_builder = report_builder,
|
|
.op_name = op_name,
|
|
.dst = a,
|
|
.src = b
|
|
};
|
|
ht_sp_foreach(b->local_vars_known, local_var_meet_known_cb, &meet);
|
|
if (meet.failed) {
|
|
return false;
|
|
}
|
|
ht_sp_foreach(a->local_vars_available, local_var_meet_avail_cb, &meet);
|
|
return true;
|
|
}
|
|
|
|
/////////////////////////////////////////////////////////
|
|
/**
|
|
* \name Pure
|
|
* @{
|
|
*/
|
|
|
|
/**
|
|
* Linked list (stack) of let-bound vars
|
|
*
|
|
* While descending, new var definitions are pushed, thus shadowing other vars
|
|
* of the same name when searching from the head.
|
|
*/
|
|
typedef struct local_pure_var_t {
|
|
const char *name;
|
|
RzILSortPure sort;
|
|
struct local_pure_var_t *next;
|
|
} LocalPureVar;
|
|
|
|
// clang-format off
|
|
#define VALIDATOR_PURE_ARGS \
|
|
RZ_NULLABLE RzILOpPure *op, \
|
|
RZ_NONNULL RzILSortPure *sort_out, \
|
|
RZ_NONNULL RzStrBuf *report_builder, \
|
|
RZ_NONNULL const LocalContext *ctx, \
|
|
RZ_NULLABLE LocalPureVar *local_pure_var_stack
|
|
// clang-format on
|
|
|
|
static bool validate_pure(VALIDATOR_PURE_ARGS);
|
|
typedef bool (*ValidatePureFn)(VALIDATOR_PURE_ARGS);
|
|
|
|
#define VALIDATOR_PURE_NAME(op) validate_pure_##op
|
|
#define VALIDATOR_PURE(op) static bool VALIDATOR_PURE_NAME(op)(VALIDATOR_PURE_ARGS)
|
|
#define VALIDATOR_ASSERT(condition, ...) \
|
|
do { \
|
|
if (!(condition)) { \
|
|
rz_warn_if_reached(); \
|
|
rz_strbuf_appendf(report_builder, __VA_ARGS__); \
|
|
return false; \
|
|
} \
|
|
} while (0)
|
|
#define VALIDATOR_DESCEND(op, sort) \
|
|
do { \
|
|
if (!validate_pure(op, sort, report_builder, ctx, local_pure_var_stack)) { \
|
|
return false; \
|
|
} \
|
|
} while (0)
|
|
|
|
VALIDATOR_PURE(var) {
|
|
RzILOpArgsVar *args = &op->op.var;
|
|
VALIDATOR_ASSERT(args->v, "Var name of var op is NULL.\n");
|
|
switch (args->kind) {
|
|
case RZ_IL_VAR_KIND_GLOBAL: {
|
|
RzILSortPure *sort = ht_sp_find(ctx->global_ctx->global_vars, args->v, NULL);
|
|
VALIDATOR_ASSERT(sort, "Global variable \"%s\" referenced by var op does not exist.\n", args->v);
|
|
*sort_out = *sort;
|
|
return true;
|
|
}
|
|
case RZ_IL_VAR_KIND_LOCAL: {
|
|
RzILSortPure *sort = ht_sp_find(ctx->local_vars_available, args->v, NULL);
|
|
VALIDATOR_ASSERT(sort, "Local variable \"%s\" is not available at var op.\n", args->v);
|
|
*sort_out = *sort;
|
|
return true;
|
|
}
|
|
case RZ_IL_VAR_KIND_LOCAL_PURE: {
|
|
for (LocalPureVar *loc = local_pure_var_stack; loc; loc = loc->next) {
|
|
if (!strcmp(loc->name, args->v)) {
|
|
*sort_out = loc->sort;
|
|
return true;
|
|
}
|
|
}
|
|
VALIDATOR_ASSERT(false, "Local pure variable \"%s\" unbound at var op.\n", args->v);
|
|
return false;
|
|
}
|
|
default:
|
|
VALIDATOR_ASSERT(false, "Var op has invalid kind.\n");
|
|
}
|
|
return true;
|
|
}
|
|
|
|
VALIDATOR_PURE(bool_const) {
|
|
*sort_out = rz_il_sort_pure_bool();
|
|
return true;
|
|
}
|
|
|
|
VALIDATOR_PURE(bitv) {
|
|
RzBitVector *bv = op->op.bitv.value;
|
|
if (!bv) {
|
|
rz_strbuf_appendf(report_builder, "Bitvector in constant bitvector op is NULL.\n");
|
|
return false;
|
|
}
|
|
*sort_out = rz_il_sort_pure_bv(rz_bv_len(bv));
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* 'a bitv -> 'a bitv -> 'a bitv ops
|
|
* e.g. add, sub, mul, div, ...
|
|
*/
|
|
VALIDATOR_PURE(bitv_binop) {
|
|
RzILOpPure *x = op->op.add.x; // just add is fine, all ops in here use the same struct
|
|
RzILOpPure *y = op->op.add.y;
|
|
RzILSortPure sx;
|
|
VALIDATOR_DESCEND(x, &sx);
|
|
VALIDATOR_ASSERT(sx.type == RZ_IL_TYPE_PURE_BITVECTOR, "Left operand of %s op is not a bitvector.\n", rz_il_op_pure_code_stringify(op->code));
|
|
RzILSortPure sy;
|
|
VALIDATOR_DESCEND(y, &sy);
|
|
VALIDATOR_ASSERT(sy.type == RZ_IL_TYPE_PURE_BITVECTOR, "Right operand of %s op is not a bitvector.\n", rz_il_op_pure_code_stringify(op->code));
|
|
VALIDATOR_ASSERT(sx.props.bv.length == sy.props.bv.length, "Operand sizes of %s op do not agree: %u vs. %u.\n",
|
|
rz_il_op_pure_code_stringify(op->code), (unsigned int)sx.props.bv.length, (unsigned int)sy.props.bv.length);
|
|
*sort_out = sx;
|
|
return true;
|
|
}
|
|
|
|
VALIDATOR_PURE(ite) {
|
|
RzILOpArgsIte *args = &op->op.ite;
|
|
RzILSortPure sc;
|
|
VALIDATOR_DESCEND(args->condition, &sc);
|
|
VALIDATOR_ASSERT(sc.type == RZ_IL_TYPE_PURE_BOOL, "Condition of ite op is not boolean.\n");
|
|
RzILSortPure sx;
|
|
VALIDATOR_DESCEND(args->x, &sx);
|
|
RzILSortPure sy;
|
|
VALIDATOR_DESCEND(args->y, &sy);
|
|
VALIDATOR_ASSERT(rz_il_sort_pure_eq(sx, sy), "Types of ite branches do not agree: %s vs. %s.\n",
|
|
rz_il_sort_pure_stringify(sx),
|
|
rz_il_sort_pure_stringify(sy));
|
|
|
|
*sort_out = sx;
|
|
return true;
|
|
}
|
|
|
|
VALIDATOR_PURE(let) {
|
|
RzILOpArgsLet *args = &op->op.let;
|
|
VALIDATOR_ASSERT(args->name, "Var name of let op is NULL.\n");
|
|
VALIDATOR_ASSERT(args->exp, "Expression of let op is NULL.\n");
|
|
VALIDATOR_ASSERT(args->body, "Body of let op is NULL.\n");
|
|
RzILSortPure sort;
|
|
VALIDATOR_DESCEND(args->exp, &sort);
|
|
LocalPureVar var = {
|
|
.name = args->name,
|
|
.sort = sort,
|
|
.next = local_pure_var_stack
|
|
};
|
|
return validate_pure(args->body, sort_out, report_builder, ctx, &var);
|
|
}
|
|
|
|
VALIDATOR_PURE(inv) {
|
|
RzILOpArgsBoolInv *args = &op->op.boolinv;
|
|
RzILSortPure sort;
|
|
VALIDATOR_DESCEND(args->x, &sort);
|
|
VALIDATOR_ASSERT(sort.type == RZ_IL_TYPE_PURE_BOOL, "Operand of boolean inv op is not boolean.\n");
|
|
*sort_out = rz_il_sort_pure_bool();
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* bool -> bool -> bool ops
|
|
* e.g. and, or, ...
|
|
*/
|
|
VALIDATOR_PURE(bool_binop) {
|
|
RzILOpPure *x = op->op.booland.x; // just booland is fine, all ops in here use the same struct
|
|
RzILOpPure *y = op->op.booland.y;
|
|
RzILSortPure sx;
|
|
VALIDATOR_DESCEND(x, &sx);
|
|
VALIDATOR_ASSERT(sx.type == RZ_IL_TYPE_PURE_BOOL, "Left operand of %s op is not bool.\n", rz_il_op_pure_code_stringify(op->code));
|
|
RzILSortPure sy;
|
|
VALIDATOR_DESCEND(y, &sy);
|
|
VALIDATOR_ASSERT(sy.type == RZ_IL_TYPE_PURE_BOOL, "Right operand of %s op is not bool.\n", rz_il_op_pure_code_stringify(op->code));
|
|
*sort_out = rz_il_sort_pure_bool();
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* 'a bitv -> bool ops
|
|
* e.g. msb, lsb
|
|
*/
|
|
VALIDATOR_PURE(bitv_bool_unop) {
|
|
RzILOpPure *x = op->op.msb.bv; // just msb is fine, all ops in here use the same struct
|
|
RzILSortPure sx;
|
|
VALIDATOR_DESCEND(x, &sx);
|
|
VALIDATOR_ASSERT(sx.type == RZ_IL_TYPE_PURE_BITVECTOR, "Operand of %s op is not a bitvector.\n", rz_il_op_pure_code_stringify(op->code));
|
|
*sort_out = rz_il_sort_pure_bool();
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* 'a bitv -> 'a bitv ops
|
|
* e.g. bitwise negation
|
|
*/
|
|
VALIDATOR_PURE(bitv_unop) {
|
|
RzILOpPure *x = op->op.lognot.bv; // just lognot is fine, all ops in here use the same struct
|
|
RzILSortPure sx;
|
|
VALIDATOR_DESCEND(x, &sx);
|
|
VALIDATOR_ASSERT(sx.type == RZ_IL_TYPE_PURE_BITVECTOR, "Operand of %s op is not a bitvector.\n", rz_il_op_pure_code_stringify(op->code));
|
|
*sort_out = sx;
|
|
return true;
|
|
}
|
|
|
|
VALIDATOR_PURE(shift) {
|
|
RzILOpArgsShiftLeft *args = &op->op.shiftl;
|
|
RzILSortPure sf;
|
|
VALIDATOR_DESCEND(args->fill_bit, &sf);
|
|
VALIDATOR_ASSERT(sf.type == RZ_IL_TYPE_PURE_BOOL, "Fill operand of %s op is not bool.\n", rz_il_op_pure_code_stringify(op->code));
|
|
RzILSortPure sx;
|
|
VALIDATOR_DESCEND(args->x, &sx);
|
|
VALIDATOR_ASSERT(sx.type == RZ_IL_TYPE_PURE_BITVECTOR, "Value operand of %s op is not a bitvector.\n", rz_il_op_pure_code_stringify(op->code));
|
|
RzILSortPure sy;
|
|
VALIDATOR_DESCEND(args->y, &sy);
|
|
VALIDATOR_ASSERT(sy.type == RZ_IL_TYPE_PURE_BITVECTOR, "Distance operand of %s op is not a bitvector.\n", rz_il_op_pure_code_stringify(op->code));
|
|
*sort_out = sx;
|
|
return true;
|
|
}
|
|
|
|
VALIDATOR_PURE(cmp) {
|
|
RzILOpArgsEq *args = &op->op.eq;
|
|
RzILSortPure sx;
|
|
VALIDATOR_DESCEND(args->x, &sx);
|
|
VALIDATOR_ASSERT(sx.type == RZ_IL_TYPE_PURE_BITVECTOR, "Left operand of %s op is not a bitvector.\n", rz_il_op_pure_code_stringify(op->code));
|
|
RzILSortPure sy;
|
|
VALIDATOR_DESCEND(args->y, &sy);
|
|
VALIDATOR_ASSERT(sy.type == RZ_IL_TYPE_PURE_BITVECTOR, "Right operand of %s op is not a bitvector.\n", rz_il_op_pure_code_stringify(op->code));
|
|
VALIDATOR_ASSERT(sx.props.bv.length == sy.props.bv.length, "Operand sizes of %s op do not agree: %u vs. %u.\n",
|
|
rz_il_op_pure_code_stringify(op->code), (unsigned int)sx.props.bv.length, (unsigned int)sy.props.bv.length);
|
|
*sort_out = rz_il_sort_pure_bool();
|
|
return true;
|
|
}
|
|
|
|
VALIDATOR_PURE(cast) {
|
|
RzILOpArgsCast *args = &op->op.cast;
|
|
VALIDATOR_ASSERT(args->length, "Length of cast op is 0.\n");
|
|
RzILSortPure sf;
|
|
VALIDATOR_DESCEND(args->fill, &sf);
|
|
VALIDATOR_ASSERT(sf.type == RZ_IL_TYPE_PURE_BOOL, "Fill operand of cast op is not bool.\n");
|
|
RzILSortPure sx;
|
|
VALIDATOR_DESCEND(args->val, &sx);
|
|
VALIDATOR_ASSERT(sx.type == RZ_IL_TYPE_PURE_BITVECTOR, "Value operand of %s op is not a bitvector.\n", rz_il_op_pure_code_stringify(op->code));
|
|
*sort_out = rz_il_sort_pure_bv(args->length);
|
|
return true;
|
|
}
|
|
|
|
VALIDATOR_PURE(append) {
|
|
RzILOpPure *x = op->op.append.high;
|
|
RzILOpPure *y = op->op.append.low;
|
|
RzILSortPure sx;
|
|
VALIDATOR_DESCEND(x, &sx);
|
|
VALIDATOR_ASSERT(sx.type == RZ_IL_TYPE_PURE_BITVECTOR, "High operand of append op is not a bitvector.\n");
|
|
RzILSortPure sy;
|
|
VALIDATOR_DESCEND(y, &sy);
|
|
VALIDATOR_ASSERT(sy.type == RZ_IL_TYPE_PURE_BITVECTOR, "Low operand of append op is not a bitvector.\n");
|
|
*sort_out = rz_il_sort_pure_bv(sx.props.bv.length + sy.props.bv.length);
|
|
return true;
|
|
}
|
|
|
|
VALIDATOR_PURE(load) {
|
|
RzILOpArgsLoad *args = &op->op.load;
|
|
bool found = false;
|
|
ut64 htm = ht_uu_find(ctx->global_ctx->mems, args->mem, &found);
|
|
VALIDATOR_ASSERT(found, "Mem %u referenced by load op does not exist.\n", (unsigned int)args->mem);
|
|
ut32 key_len = htm >> 32;
|
|
ut32 val_len = htm & UT32_MAX;
|
|
RzILSortPure sk;
|
|
VALIDATOR_DESCEND(args->key, &sk);
|
|
VALIDATOR_ASSERT(sk.type == RZ_IL_TYPE_PURE_BITVECTOR, "Key operand of load op is not a bitvector.\n");
|
|
VALIDATOR_ASSERT(sk.props.bv.length == key_len, "Length of key operand (%u) of load op is not equal to key length %u of mem %u.\n",
|
|
(unsigned int)sk.props.bv.length, (unsigned int)key_len, (unsigned int)args->mem);
|
|
*sort_out = rz_il_sort_pure_bv(val_len);
|
|
return true;
|
|
}
|
|
|
|
VALIDATOR_PURE(loadw) {
|
|
RzILOpArgsLoadW *args = &op->op.loadw;
|
|
VALIDATOR_ASSERT(args->n_bits, "Length of loadw op is 0.\n");
|
|
bool found = false;
|
|
ut64 htm = ht_uu_find(ctx->global_ctx->mems, args->mem, &found);
|
|
VALIDATOR_ASSERT(found, "Mem %u referenced by loadw op does not exist.\n", (unsigned int)args->mem);
|
|
ut32 key_len = htm >> 32;
|
|
RzILSortPure sk;
|
|
VALIDATOR_DESCEND(args->key, &sk);
|
|
VALIDATOR_ASSERT(sk.type == RZ_IL_TYPE_PURE_BITVECTOR, "Key operand of loadw op is not a bitvector.\n");
|
|
VALIDATOR_ASSERT(sk.props.bv.length == key_len, "Length of key operand (%u) of loadw op is not equal to key length %u of mem %u.\n",
|
|
(unsigned int)sk.props.bv.length, (unsigned int)key_len, (unsigned int)args->mem);
|
|
*sort_out = rz_il_sort_pure_bv(args->n_bits);
|
|
return true;
|
|
}
|
|
|
|
VALIDATOR_PURE(float) {
|
|
RzILOpArgsFloat *args = &op->op.float_;
|
|
RzILSortPure sort;
|
|
|
|
VALIDATOR_DESCEND(args->bv, &sort);
|
|
VALIDATOR_ASSERT(sort.type == RZ_IL_TYPE_PURE_BITVECTOR, "Float bv operand is not bitvector.\n");
|
|
*sort_out = rz_il_sort_pure_float(args->r);
|
|
return true;
|
|
}
|
|
|
|
VALIDATOR_PURE(fbits) {
|
|
RzILOpArgsFbits *args = &op->op.fbits;
|
|
RzILSortPure sort;
|
|
|
|
VALIDATOR_DESCEND(args->f, &sort);
|
|
VALIDATOR_ASSERT(sort.type == RZ_IL_TYPE_PURE_FLOAT, "operand of %s op is not a float.\n", rz_il_op_pure_code_stringify(op->code));
|
|
*sort_out = rz_il_sort_pure_bv(rz_float_get_format_info(sort.props.f.format, RZ_FLOAT_INFO_TOTAL_LEN));
|
|
return true;
|
|
}
|
|
|
|
// float -> bool
|
|
VALIDATOR_PURE(float_bool_uop) {
|
|
RzILOpArgsIsFinite *args = &op->op.is_finite;
|
|
RzILSortPure sort;
|
|
|
|
VALIDATOR_DESCEND(args->f, &sort);
|
|
VALIDATOR_ASSERT(sort.type == RZ_IL_TYPE_PURE_FLOAT, "operand of %s op is not a float.\n", rz_il_op_pure_code_stringify(op->code));
|
|
*sort_out = rz_il_sort_pure_bool();
|
|
return true;
|
|
}
|
|
|
|
VALIDATOR_PURE(float_uop) {
|
|
RzILOpArgsFneg *args = &op->op.fneg;
|
|
RzILSortPure sort;
|
|
|
|
VALIDATOR_DESCEND(args->f, &sort);
|
|
VALIDATOR_ASSERT(sort.type == RZ_IL_TYPE_PURE_FLOAT, "operand of %s op is not a float.\n", rz_il_op_pure_code_stringify(op->code));
|
|
*sort_out = sort;
|
|
return true;
|
|
}
|
|
|
|
VALIDATOR_PURE(fcast_to_int) {
|
|
RzILOpArgsFCastint *args = &op->op.fcast_int;
|
|
RzILSortPure sort;
|
|
|
|
VALIDATOR_DESCEND(args->f, &sort);
|
|
VALIDATOR_ASSERT(sort.type == RZ_IL_TYPE_PURE_FLOAT, "operand of %s op is not a float.\n", rz_il_op_pure_code_stringify(op->code));
|
|
VALIDATOR_ASSERT(args->length != 0, "length of casted bitvector should not be 0.\n");
|
|
*sort_out = rz_il_sort_pure_bv(args->length);
|
|
return true;
|
|
}
|
|
|
|
VALIDATOR_PURE(icast_to_float) {
|
|
RzILOpArgsFCastfloat *args = &op->op.fcast_float;
|
|
RzILSortPure sort;
|
|
|
|
VALIDATOR_DESCEND(args->bv, &sort);
|
|
VALIDATOR_ASSERT(sort.type == RZ_IL_TYPE_PURE_BITVECTOR, "operand of %s op is not a bitvector.\n", rz_il_op_pure_code_stringify(op->code));
|
|
|
|
*sort_out = rz_il_sort_pure_float(args->format);
|
|
return true;
|
|
}
|
|
|
|
VALIDATOR_PURE(fconvert) {
|
|
RzILOpArgsFconvert *args = &op->op.fconvert;
|
|
RzILSortPure sort;
|
|
|
|
VALIDATOR_DESCEND(args->f, &sort);
|
|
VALIDATOR_ASSERT(sort.type == RZ_IL_TYPE_PURE_FLOAT, "operand of %s op is not a float.\n", rz_il_op_pure_code_stringify(op->code));
|
|
|
|
*sort_out = rz_il_sort_pure_float(args->format);
|
|
return true;
|
|
}
|
|
|
|
VALIDATOR_PURE(forder) {
|
|
RzILOpArgsForder *args = &op->op.forder;
|
|
RzILSortPure sx, sy;
|
|
|
|
VALIDATOR_DESCEND(args->x, &sx);
|
|
VALIDATOR_ASSERT(sx.type == RZ_IL_TYPE_PURE_FLOAT, "Left operand of %s op is not a float.\n", rz_il_op_pure_code_stringify(op->code));
|
|
|
|
VALIDATOR_DESCEND(args->y, &sy);
|
|
VALIDATOR_ASSERT(sy.type == RZ_IL_TYPE_PURE_FLOAT, "Right operand of %s op is not a float.\n", rz_il_op_pure_code_stringify(op->code));
|
|
|
|
// flatten validator assert
|
|
VALIDATOR_ASSERT(sx.props.f.format == sy.props.f.format, "Op %s formats of left operand (%s) and right operand (%s) do not agree.\n",
|
|
rz_il_op_pure_code_stringify(op->code),
|
|
rz_il_sort_pure_stringify(sx),
|
|
rz_il_sort_pure_stringify(sy));
|
|
|
|
*sort_out = rz_il_sort_pure_bool();
|
|
return true;
|
|
}
|
|
|
|
VALIDATOR_PURE(frequal) {
|
|
*sort_out = rz_il_sort_pure_bool();
|
|
return true;
|
|
}
|
|
|
|
VALIDATOR_PURE(float_uop_with_round) {
|
|
RzILOpArgsFround *args = &op->op.fround;
|
|
RzILSortPure sort;
|
|
|
|
VALIDATOR_DESCEND(args->f, &sort);
|
|
VALIDATOR_ASSERT(sort.type == RZ_IL_TYPE_PURE_FLOAT, "operand of %s op is not a float.\n", rz_il_op_pure_code_stringify(op->code));
|
|
|
|
*sort_out = sort;
|
|
return true;
|
|
}
|
|
|
|
VALIDATOR_PURE(float_uop_except) {
|
|
RzILOpArgsFexcept *args = &op->op.fexcept;
|
|
RzILSortPure sort;
|
|
|
|
VALIDATOR_DESCEND(args->x, &sort);
|
|
VALIDATOR_ASSERT(sort.type == RZ_IL_TYPE_PURE_FLOAT, "operand of %s op is not a float.\n", rz_il_op_pure_code_stringify(op->code));
|
|
|
|
*sort_out = rz_il_sort_pure_bool();
|
|
return true;
|
|
}
|
|
|
|
VALIDATOR_PURE(float_binop_with_round) {
|
|
RzILOpArgsFadd *args = &op->op.fadd;
|
|
RzILSortPure sx, sy;
|
|
|
|
VALIDATOR_DESCEND(args->x, &sx);
|
|
VALIDATOR_ASSERT(sx.type == RZ_IL_TYPE_PURE_FLOAT, "Left operand of %s op is not a float.\n", rz_il_op_pure_code_stringify(op->code));
|
|
VALIDATOR_DESCEND(args->y, &sy);
|
|
VALIDATOR_ASSERT(sy.type == RZ_IL_TYPE_PURE_FLOAT, "Right operand of %s op is not a float.\n", rz_il_op_pure_code_stringify(op->code));
|
|
|
|
// flatten validator assert
|
|
VALIDATOR_ASSERT(sx.props.f.format == sy.props.f.format, "Op %s formats of left operand (%s) and right operand (%s) do not agree.\n",
|
|
rz_il_op_pure_code_stringify(op->code),
|
|
rz_il_sort_pure_stringify(sx),
|
|
rz_il_sort_pure_stringify(sy));
|
|
|
|
*sort_out = sx;
|
|
return true;
|
|
}
|
|
|
|
VALIDATOR_PURE(float_terop_with_round) {
|
|
RzILOpArgsFmad *args = &op->op.fmad;
|
|
RzILSortPure sx, sy, sz;
|
|
|
|
VALIDATOR_DESCEND(args->x, &sx);
|
|
VALIDATOR_ASSERT(sx.type == RZ_IL_TYPE_PURE_FLOAT, "1st operand of %s op is not a float.\n", rz_il_op_pure_code_stringify(op->code));
|
|
|
|
VALIDATOR_DESCEND(args->y, &sy);
|
|
VALIDATOR_ASSERT(sy.type == RZ_IL_TYPE_PURE_FLOAT, "2nd operand of %s op is not a float.\n", rz_il_op_pure_code_stringify(op->code));
|
|
|
|
VALIDATOR_DESCEND(args->z, &sz);
|
|
VALIDATOR_ASSERT(sz.type == RZ_IL_TYPE_PURE_FLOAT, "3rd operand of %s op is not a float.\n", rz_il_op_pure_code_stringify(op->code));
|
|
|
|
VALIDATOR_ASSERT((sx.props.f.format == sy.props.f.format) && (sx.props.f.format == sz.props.f.format),
|
|
"types of operand in op %s do not agree: operand1 (%s) operand2 (%s) operand3 (%s)",
|
|
rz_il_op_pure_code_stringify(op->code),
|
|
rz_il_sort_pure_stringify(sx),
|
|
rz_il_sort_pure_stringify(sy),
|
|
rz_il_sort_pure_stringify(sz));
|
|
|
|
*sort_out = sx;
|
|
return true;
|
|
}
|
|
|
|
VALIDATOR_PURE(float_hybridop_with_round) {
|
|
RzILOpArgsFcompound *args = &op->op.fcompound;
|
|
RzILSortPure fs, bs;
|
|
|
|
VALIDATOR_DESCEND(args->f, &fs);
|
|
VALIDATOR_ASSERT(fs.type == RZ_IL_TYPE_PURE_FLOAT, "1st operand of %s op is not a float.\n", rz_il_op_pure_code_stringify(op->code));
|
|
VALIDATOR_DESCEND(args->n, &bs);
|
|
VALIDATOR_ASSERT(bs.type == RZ_IL_TYPE_PURE_BITVECTOR, "2nd operand of %s op is not a bitv. \n", rz_il_op_pure_code_stringify(op->code));
|
|
|
|
*sort_out = fs;
|
|
return true;
|
|
}
|
|
|
|
static ValidatePureFn validate_pure_table[RZ_IL_OP_PURE_MAX] = {
|
|
[RZ_IL_OP_VAR] = VALIDATOR_PURE_NAME(var),
|
|
[RZ_IL_OP_ITE] = VALIDATOR_PURE_NAME(ite),
|
|
[RZ_IL_OP_LET] = VALIDATOR_PURE_NAME(let),
|
|
[RZ_IL_OP_B0] = VALIDATOR_PURE_NAME(bool_const),
|
|
[RZ_IL_OP_B1] = VALIDATOR_PURE_NAME(bool_const),
|
|
[RZ_IL_OP_INV] = VALIDATOR_PURE_NAME(inv),
|
|
[RZ_IL_OP_AND] = VALIDATOR_PURE_NAME(bool_binop),
|
|
[RZ_IL_OP_OR] = VALIDATOR_PURE_NAME(bool_binop),
|
|
[RZ_IL_OP_XOR] = VALIDATOR_PURE_NAME(bool_binop),
|
|
[RZ_IL_OP_BITV] = VALIDATOR_PURE_NAME(bitv),
|
|
[RZ_IL_OP_MSB] = VALIDATOR_PURE_NAME(bitv_bool_unop),
|
|
[RZ_IL_OP_LSB] = VALIDATOR_PURE_NAME(bitv_bool_unop),
|
|
[RZ_IL_OP_IS_ZERO] = VALIDATOR_PURE_NAME(bitv_bool_unop),
|
|
[RZ_IL_OP_NEG] = VALIDATOR_PURE_NAME(bitv_unop),
|
|
[RZ_IL_OP_LOGNOT] = VALIDATOR_PURE_NAME(bitv_unop),
|
|
[RZ_IL_OP_ADD] = VALIDATOR_PURE_NAME(bitv_binop),
|
|
[RZ_IL_OP_SUB] = VALIDATOR_PURE_NAME(bitv_binop),
|
|
[RZ_IL_OP_MUL] = VALIDATOR_PURE_NAME(bitv_binop),
|
|
[RZ_IL_OP_DIV] = VALIDATOR_PURE_NAME(bitv_binop),
|
|
[RZ_IL_OP_MOD] = VALIDATOR_PURE_NAME(bitv_binop),
|
|
[RZ_IL_OP_SDIV] = VALIDATOR_PURE_NAME(bitv_binop),
|
|
[RZ_IL_OP_SMOD] = VALIDATOR_PURE_NAME(bitv_binop),
|
|
[RZ_IL_OP_LOGAND] = VALIDATOR_PURE_NAME(bitv_binop),
|
|
[RZ_IL_OP_LOGOR] = VALIDATOR_PURE_NAME(bitv_binop),
|
|
[RZ_IL_OP_LOGXOR] = VALIDATOR_PURE_NAME(bitv_binop),
|
|
[RZ_IL_OP_SHIFTR] = VALIDATOR_PURE_NAME(shift),
|
|
[RZ_IL_OP_SHIFTL] = VALIDATOR_PURE_NAME(shift),
|
|
[RZ_IL_OP_EQ] = VALIDATOR_PURE_NAME(cmp),
|
|
[RZ_IL_OP_SLE] = VALIDATOR_PURE_NAME(cmp),
|
|
[RZ_IL_OP_ULE] = VALIDATOR_PURE_NAME(cmp),
|
|
[RZ_IL_OP_CAST] = VALIDATOR_PURE_NAME(cast),
|
|
[RZ_IL_OP_APPEND] = VALIDATOR_PURE_NAME(append),
|
|
[RZ_IL_OP_LOAD] = VALIDATOR_PURE_NAME(load),
|
|
[RZ_IL_OP_LOADW] = VALIDATOR_PURE_NAME(loadw),
|
|
[RZ_IL_OP_FLOAT] = VALIDATOR_PURE_NAME(float),
|
|
[RZ_IL_OP_FBITS] = VALIDATOR_PURE_NAME(fbits),
|
|
[RZ_IL_OP_IS_FINITE] = VALIDATOR_PURE_NAME(float_bool_uop),
|
|
[RZ_IL_OP_IS_NAN] = VALIDATOR_PURE_NAME(float_bool_uop),
|
|
[RZ_IL_OP_IS_INF] = VALIDATOR_PURE_NAME(float_bool_uop),
|
|
[RZ_IL_OP_IS_FZERO] = VALIDATOR_PURE_NAME(float_bool_uop),
|
|
[RZ_IL_OP_IS_FNEG] = VALIDATOR_PURE_NAME(float_bool_uop),
|
|
[RZ_IL_OP_IS_FPOS] = VALIDATOR_PURE_NAME(float_bool_uop),
|
|
[RZ_IL_OP_FNEG] = VALIDATOR_PURE_NAME(float_uop),
|
|
[RZ_IL_OP_FABS] = VALIDATOR_PURE_NAME(float_uop),
|
|
[RZ_IL_OP_FREQUAL] = VALIDATOR_PURE_NAME(frequal),
|
|
[RZ_IL_OP_FSUCC] = VALIDATOR_PURE_NAME(float_uop),
|
|
[RZ_IL_OP_FPRED] = VALIDATOR_PURE_NAME(float_uop),
|
|
[RZ_IL_OP_FORDER] = VALIDATOR_PURE_NAME(forder),
|
|
[RZ_IL_OP_FROUND] = VALIDATOR_PURE_NAME(float_uop_with_round),
|
|
[RZ_IL_OP_FSQRT] = VALIDATOR_PURE_NAME(float_uop_with_round),
|
|
[RZ_IL_OP_FRSQRT] = VALIDATOR_PURE_NAME(float_uop_with_round),
|
|
[RZ_IL_OP_FEXCEPT] = VALIDATOR_PURE_NAME(float_uop_except),
|
|
[RZ_IL_OP_FADD] = VALIDATOR_PURE_NAME(float_binop_with_round),
|
|
[RZ_IL_OP_FSUB] = VALIDATOR_PURE_NAME(float_binop_with_round),
|
|
[RZ_IL_OP_FMUL] = VALIDATOR_PURE_NAME(float_binop_with_round),
|
|
[RZ_IL_OP_FDIV] = VALIDATOR_PURE_NAME(float_binop_with_round),
|
|
[RZ_IL_OP_FMOD] = VALIDATOR_PURE_NAME(float_binop_with_round),
|
|
[RZ_IL_OP_FMAD] = VALIDATOR_PURE_NAME(float_terop_with_round),
|
|
[RZ_IL_OP_FCAST_INT] = VALIDATOR_PURE_NAME(fcast_to_int),
|
|
[RZ_IL_OP_FCAST_SINT] = VALIDATOR_PURE_NAME(fcast_to_int),
|
|
[RZ_IL_OP_FCAST_FLOAT] = VALIDATOR_PURE_NAME(icast_to_float),
|
|
[RZ_IL_OP_FCAST_SFLOAT] = VALIDATOR_PURE_NAME(icast_to_float),
|
|
[RZ_IL_OP_FCONVERT] = VALIDATOR_PURE_NAME(fconvert),
|
|
|
|
// unimplemented
|
|
[RZ_IL_OP_FHYPOT] = VALIDATOR_PURE_NAME(float_binop_with_round),
|
|
[RZ_IL_OP_FPOW] = VALIDATOR_PURE_NAME(float_binop_with_round),
|
|
[RZ_IL_OP_FROOTN] = VALIDATOR_PURE_NAME(float_hybridop_with_round),
|
|
[RZ_IL_OP_FPOWN] = VALIDATOR_PURE_NAME(float_hybridop_with_round),
|
|
[RZ_IL_OP_FCOMPOUND] = VALIDATOR_PURE_NAME(float_hybridop_with_round),
|
|
};
|
|
|
|
static bool validate_pure(VALIDATOR_PURE_ARGS) {
|
|
VALIDATOR_ASSERT(op, "Encountered NULL for pure op.\n");
|
|
ValidatePureFn validator = validate_pure_table[op->code];
|
|
rz_return_val_if_fail(validator, false);
|
|
return validator(op, sort_out, report_builder, ctx, local_pure_var_stack);
|
|
}
|
|
|
|
/**
|
|
* Run validation (type-checking and other checks) on a pure expression and determine its sort.
|
|
* \p op the op to be checked. May be null, which will always be reported as invalid.
|
|
* \p ctx global context, defining available global vars and mems
|
|
* \p sort_out optionally returns the sort of the expression, if it is valid
|
|
* \p report_out optionally returns a readable report containing details about why the validation failed
|
|
* \return whether the given op is valid under \p ctx
|
|
*/
|
|
RZ_API bool rz_il_validate_pure(RZ_NULLABLE RzILOpPure *op, RZ_NONNULL RzILValidateGlobalContext *ctx,
|
|
RZ_NULLABLE RZ_OUT RzILSortPure *sort_out, RZ_NULLABLE RZ_OUT RzILValidateReport *report_out) {
|
|
LocalContext local_ctx;
|
|
if (!local_context_init(&local_ctx, ctx)) {
|
|
if (report_out) {
|
|
*report_out = NULL;
|
|
}
|
|
return false;
|
|
}
|
|
RzStrBuf report_builder;
|
|
rz_strbuf_init(&report_builder);
|
|
RzILSortPure sort;
|
|
bool valid = validate_pure(op, &sort, &report_builder, &local_ctx, NULL);
|
|
local_context_fini(&local_ctx);
|
|
if (sort_out) {
|
|
*sort_out = sort;
|
|
}
|
|
if (report_out) {
|
|
*report_out = rz_strbuf_is_empty(&report_builder) ? NULL : rz_str_trim_tail(rz_strbuf_drain_nofree(&report_builder));
|
|
}
|
|
rz_strbuf_fini(&report_builder);
|
|
return valid;
|
|
}
|
|
|
|
/// @}
|
|
|
|
/////////////////////////////////////////////////////////
|
|
/**
|
|
* \name Effect
|
|
* @{
|
|
*/
|
|
|
|
#undef VALIDATOR_PURE_ARGS
|
|
#undef VALIDATOR_PURE_NAME
|
|
#undef VALIDATOR_PURE
|
|
#undef VALIDATOR_DESCEND
|
|
|
|
// clang-format off
|
|
#define VALIDATOR_EFFECT_ARGS \
|
|
RZ_NULLABLE RzILOpEffect *op, \
|
|
RZ_NONNULL RzILTypeEffect *type_out, \
|
|
RZ_NONNULL RzStrBuf *report_builder, \
|
|
RZ_NONNULL LocalContext *ctx
|
|
// clang-format on
|
|
|
|
static bool validate_effect(VALIDATOR_EFFECT_ARGS);
|
|
typedef bool (*ValidateEffectFn)(VALIDATOR_EFFECT_ARGS);
|
|
|
|
#define VALIDATOR_EFFECT_NAME(op) validate_effect_##op
|
|
#define VALIDATOR_EFFECT(op) static bool VALIDATOR_EFFECT_NAME(op)(VALIDATOR_EFFECT_ARGS)
|
|
#define VALIDATOR_DESCEND_PURE(op, sort) \
|
|
do { \
|
|
if (!validate_pure(op, sort, report_builder, ctx, NULL)) { \
|
|
return false; \
|
|
} \
|
|
} while (0)
|
|
#define VALIDATOR_DESCEND_EFFECT(op, etype, ectx, cleanup) \
|
|
do { \
|
|
if (!validate_effect(op, etype, report_builder, ectx)) { \
|
|
cleanup return false; \
|
|
} \
|
|
} while (0)
|
|
|
|
VALIDATOR_EFFECT(empty) {
|
|
*type_out = RZ_IL_TYPE_EFFECT_NONE;
|
|
return true;
|
|
}
|
|
|
|
VALIDATOR_EFFECT(nop) {
|
|
*type_out = RZ_IL_TYPE_EFFECT_NONE;
|
|
return true;
|
|
}
|
|
|
|
VALIDATOR_EFFECT(store) {
|
|
RzILOpArgsStore *args = &op->op.store;
|
|
bool found = false;
|
|
ut64 htm = ht_uu_find(ctx->global_ctx->mems, args->mem, &found);
|
|
VALIDATOR_ASSERT(found, "Mem %u referenced by store op does not exist.\n", (unsigned int)args->mem);
|
|
ut32 key_len = htm >> 32;
|
|
ut32 val_len = htm & UT32_MAX;
|
|
RzILSortPure sk;
|
|
VALIDATOR_DESCEND_PURE(args->key, &sk);
|
|
VALIDATOR_ASSERT(sk.type == RZ_IL_TYPE_PURE_BITVECTOR, "Key operand of store op is not a bitvector.\n");
|
|
VALIDATOR_ASSERT(sk.props.bv.length == key_len, "Length of key operand (%u) of store op is not equal to key length %u of mem %u.\n",
|
|
(unsigned int)sk.props.bv.length, (unsigned int)key_len, (unsigned int)args->mem);
|
|
RzILSortPure sv;
|
|
VALIDATOR_DESCEND_PURE(args->value, &sv);
|
|
VALIDATOR_ASSERT(sv.type == RZ_IL_TYPE_PURE_BITVECTOR, "Value operand of store op is not a bitvector.\n");
|
|
VALIDATOR_ASSERT(sv.props.bv.length == val_len, "Length of value operand (%u) of store op is not equal to value length %u of mem %u.\n",
|
|
(unsigned int)sv.props.bv.length, (unsigned int)val_len, (unsigned int)args->mem);
|
|
*type_out = RZ_IL_TYPE_EFFECT_DATA;
|
|
return true;
|
|
}
|
|
|
|
VALIDATOR_EFFECT(storew) {
|
|
RzILOpArgsStoreW *args = &op->op.storew;
|
|
bool found = false;
|
|
ut64 htm = ht_uu_find(ctx->global_ctx->mems, args->mem, &found);
|
|
VALIDATOR_ASSERT(found, "Mem %u referenced by storew op does not exist.\n", (unsigned int)args->mem);
|
|
ut32 key_len = htm >> 32;
|
|
RzILSortPure sk;
|
|
VALIDATOR_DESCEND_PURE(args->key, &sk);
|
|
VALIDATOR_ASSERT(sk.type == RZ_IL_TYPE_PURE_BITVECTOR, "Key operand of storew op is not a bitvector.\n");
|
|
VALIDATOR_ASSERT(sk.props.bv.length == key_len, "Length of key operand (%u) of storew op is not equal to key length %u of mem %u.\n",
|
|
(unsigned int)sk.props.bv.length, (unsigned int)key_len, (unsigned int)args->mem);
|
|
RzILSortPure sv;
|
|
VALIDATOR_DESCEND_PURE(args->value, &sv);
|
|
VALIDATOR_ASSERT(sv.type == RZ_IL_TYPE_PURE_BITVECTOR, "Value operand of storew op is not a bitvector.\n");
|
|
*type_out = RZ_IL_TYPE_EFFECT_DATA;
|
|
return true;
|
|
}
|
|
|
|
VALIDATOR_EFFECT(set) {
|
|
RzILOpArgsSet *args = &op->op.set;
|
|
VALIDATOR_ASSERT(args->v, "Var name of set op is NULL.\n");
|
|
RzILSortPure sx;
|
|
VALIDATOR_DESCEND_PURE(args->x, &sx);
|
|
RzILSortPure *sort = ht_sp_find(
|
|
args->is_local ? ctx->local_vars_known : ctx->global_ctx->global_vars, args->v, NULL);
|
|
VALIDATOR_ASSERT(args->is_local || sort, "Global variable \"%s\" referenced by set op does not exist.\n", args->v);
|
|
if (sort && !rz_il_sort_pure_eq(*sort, sx)) {
|
|
char *svs = rz_il_sort_pure_stringify(*sort);
|
|
char *sxs = rz_il_sort_pure_stringify(sx);
|
|
rz_strbuf_appendf(report_builder, "Types of %sal variable \"%s\" and set op do not agree: %s vs. %s.\n",
|
|
args->is_local ? "loc" : "glob",
|
|
args->v, rz_str_get_null(svs), rz_str_get_null(sxs));
|
|
free(svs);
|
|
free(sxs);
|
|
return false;
|
|
}
|
|
if (args->is_local) {
|
|
if (!sort) {
|
|
sort = RZ_NEW(RzILSortPure);
|
|
if (!sort) {
|
|
return false;
|
|
}
|
|
*sort = sx;
|
|
ht_sp_update(ctx->local_vars_known, args->v, sort);
|
|
}
|
|
ht_sp_update(ctx->local_vars_available, args->v, sort);
|
|
}
|
|
*type_out = RZ_IL_TYPE_EFFECT_DATA;
|
|
return true;
|
|
}
|
|
|
|
VALIDATOR_EFFECT(jmp) {
|
|
RzILOpArgsJmp *args = &op->op.jmp;
|
|
RzILSortPure sd;
|
|
VALIDATOR_DESCEND_PURE(args->dst, &sd);
|
|
VALIDATOR_ASSERT(sd.type == RZ_IL_TYPE_PURE_BITVECTOR, "Dst operand of jmp op is not a bitvector.\n");
|
|
VALIDATOR_ASSERT(sd.props.bv.length == ctx->global_ctx->pc_len,
|
|
"Length of dst operand (%u) of jmp op is not equal to pc length %u.\n",
|
|
(unsigned int)sd.props.bv.length, (unsigned int)ctx->global_ctx->pc_len);
|
|
*type_out = RZ_IL_TYPE_EFFECT_CTRL;
|
|
return true;
|
|
}
|
|
|
|
VALIDATOR_EFFECT(goto) {
|
|
RzILOpArgsGoto *args = &op->op.goto_;
|
|
VALIDATOR_ASSERT(args->lbl, "Label of goto op is NULL.\n");
|
|
// So far, no restrictions on goto because labels are dynamically created. This might change in the future.
|
|
*type_out = RZ_IL_TYPE_EFFECT_CTRL;
|
|
return true;
|
|
}
|
|
|
|
VALIDATOR_EFFECT(seq) {
|
|
RzILOpArgsSeq *args = &op->op.seq;
|
|
RzILTypeEffect tx;
|
|
VALIDATOR_DESCEND_EFFECT(args->x, &tx, ctx, {});
|
|
RzILTypeEffect ty;
|
|
VALIDATOR_DESCEND_EFFECT(args->y, &ty, ctx, {});
|
|
// Code after a jmp/goto makes no sense because the jmp naturally jumps somewhere else already.
|
|
// Intuitively, this could be considered just dead code and valid, but because it is not practically useful,
|
|
// we reject such code completely for now, which gives us more freedom if in the future we do want to define
|
|
// semantics for code after ctrl in some way.
|
|
VALIDATOR_ASSERT(!(tx & RZ_IL_TYPE_EFFECT_CTRL) || !ty, "Encountered further effects after a ctrl effect in seq op.");
|
|
*type_out = tx | ty;
|
|
return true;
|
|
}
|
|
|
|
VALIDATOR_EFFECT(blk) {
|
|
RzILOpArgsBlk *args = &op->op.blk;
|
|
// Semantics of blk are still somewhat undefined in RzIL
|
|
RzILTypeEffect td;
|
|
VALIDATOR_DESCEND_EFFECT(args->data_eff, &td, ctx, {});
|
|
VALIDATOR_ASSERT((td | RZ_IL_TYPE_EFFECT_DATA) == RZ_IL_TYPE_EFFECT_DATA, "Data effect operand of blk op does not only perform data effects.");
|
|
RzILTypeEffect tc;
|
|
VALIDATOR_DESCEND_EFFECT(args->ctrl_eff, &tc, ctx, {});
|
|
VALIDATOR_ASSERT((tc | RZ_IL_TYPE_EFFECT_CTRL) == RZ_IL_TYPE_EFFECT_CTRL, "Control effect operand of blk op does not only perform control effects.");
|
|
*type_out = td | tc;
|
|
return true;
|
|
}
|
|
|
|
VALIDATOR_EFFECT(repeat) {
|
|
RzILOpArgsRepeat *args = &op->op.repeat;
|
|
RzILSortPure sc;
|
|
VALIDATOR_DESCEND_PURE(args->condition, &sc);
|
|
VALIDATOR_ASSERT(sc.type == RZ_IL_TYPE_PURE_BOOL, "Condition of repeat op is not boolean.\n");
|
|
LocalContext loop_ctx;
|
|
if (!local_context_copy(&loop_ctx, ctx)) {
|
|
return false;
|
|
}
|
|
RzILTypeEffect t;
|
|
VALIDATOR_DESCEND_EFFECT(args->data_eff, &t, ctx, { local_context_fini(&loop_ctx); });
|
|
// Enforce (by overapproximation) that there are no effects after a ctrl effect, like in seq.
|
|
// In a loop, we just reject ctrl completely. This also matches BAP's `repeat : bool -> data eff -> data eff`.
|
|
if (!((t | RZ_IL_TYPE_EFFECT_DATA) == RZ_IL_TYPE_EFFECT_DATA)) {
|
|
rz_strbuf_appendf(report_builder, "Body operand of repeat op does not only perform data effects.");
|
|
local_context_fini(&loop_ctx);
|
|
return false;
|
|
}
|
|
|
|
bool val = local_context_meet(ctx, &loop_ctx, report_builder, "repeat");
|
|
local_context_fini(&loop_ctx);
|
|
*type_out = t;
|
|
return val;
|
|
}
|
|
|
|
VALIDATOR_EFFECT(branch) {
|
|
RzILOpArgsBranch *args = &op->op.branch;
|
|
RzILSortPure sc;
|
|
VALIDATOR_DESCEND_PURE(args->condition, &sc);
|
|
VALIDATOR_ASSERT(sc.type == RZ_IL_TYPE_PURE_BOOL, "Condition of branch op is not boolean.\n");
|
|
LocalContext false_ctx;
|
|
if (!local_context_copy(&false_ctx, ctx)) {
|
|
return false;
|
|
}
|
|
RzILTypeEffect tt;
|
|
VALIDATOR_DESCEND_EFFECT(args->true_eff, &tt, ctx, { local_context_fini(&false_ctx); });
|
|
RzILTypeEffect tf;
|
|
VALIDATOR_DESCEND_EFFECT(args->false_eff, &tf, &false_ctx, { local_context_fini(&false_ctx); });
|
|
bool val = local_context_meet(ctx, &false_ctx, report_builder, "branch");
|
|
local_context_fini(&false_ctx);
|
|
*type_out = tt | tf;
|
|
return val;
|
|
}
|
|
|
|
static ValidateEffectFn validate_effect_table[RZ_IL_OP_EFFECT_MAX] = {
|
|
[RZ_IL_OP_EMPTY] = VALIDATOR_EFFECT_NAME(empty),
|
|
[RZ_IL_OP_STORE] = VALIDATOR_EFFECT_NAME(store),
|
|
[RZ_IL_OP_STOREW] = VALIDATOR_EFFECT_NAME(storew),
|
|
[RZ_IL_OP_NOP] = VALIDATOR_EFFECT_NAME(nop),
|
|
[RZ_IL_OP_SET] = VALIDATOR_EFFECT_NAME(set),
|
|
[RZ_IL_OP_JMP] = VALIDATOR_EFFECT_NAME(jmp),
|
|
[RZ_IL_OP_GOTO] = VALIDATOR_EFFECT_NAME(goto),
|
|
[RZ_IL_OP_SEQ] = VALIDATOR_EFFECT_NAME(seq),
|
|
[RZ_IL_OP_BLK] = VALIDATOR_EFFECT_NAME(blk),
|
|
[RZ_IL_OP_REPEAT] = VALIDATOR_EFFECT_NAME(repeat),
|
|
[RZ_IL_OP_BRANCH] = VALIDATOR_EFFECT_NAME(branch)
|
|
};
|
|
|
|
static bool validate_effect(VALIDATOR_EFFECT_ARGS) {
|
|
VALIDATOR_ASSERT(op, "Encountered NULL for effect op.\n");
|
|
ValidateEffectFn validator = validate_effect_table[op->code];
|
|
rz_return_val_if_fail(validator, false);
|
|
return validator(op, type_out, report_builder, ctx);
|
|
}
|
|
|
|
/**
|
|
* Run validation (type-checking and other checks) on an effect.
|
|
* \p op the op to be checked. May be null, which will always be reported as invalid.
|
|
* \p ctx global context, defining available global vars and mems
|
|
* \p local_var_sorts_out optionally returns a map of local variable names defined in the effect to their sorts
|
|
* \p type_put optionally returns the type of effects that the ops perform, i.e. ctrl, data, both or none
|
|
* \p report_out optionally returns a readable report containing details about why the validation failed
|
|
* \return whether the given op is valid under \p ctx
|
|
*/
|
|
RZ_API bool rz_il_validate_effect(RZ_NULLABLE RzILOpEffect *op, RZ_NONNULL RzILValidateGlobalContext *ctx,
|
|
RZ_NULLABLE RZ_OUT HtSP /*<const char *, RzILSortPure *>*/ **local_var_sorts_out,
|
|
RZ_NULLABLE RZ_OUT RzILTypeEffect *type_out,
|
|
RZ_NULLABLE RZ_OUT RzILValidateReport *report_out) {
|
|
LocalContext local_ctx;
|
|
if (!local_context_init(&local_ctx, ctx)) {
|
|
if (report_out) {
|
|
*report_out = NULL;
|
|
}
|
|
return false;
|
|
}
|
|
RzILTypeEffect type = RZ_IL_TYPE_EFFECT_NONE;
|
|
RzStrBuf report_builder;
|
|
rz_strbuf_init(&report_builder);
|
|
bool valid = validate_effect(op, &type, &report_builder, &local_ctx);
|
|
if (valid && local_var_sorts_out) {
|
|
*local_var_sorts_out = local_ctx.local_vars_known;
|
|
local_ctx.local_vars_known = NULL;
|
|
}
|
|
local_context_fini(&local_ctx);
|
|
if (type_out) {
|
|
*type_out = type;
|
|
}
|
|
if (report_out) {
|
|
*report_out = rz_strbuf_is_empty(&report_builder) ? NULL : rz_str_trim_tail(rz_strbuf_drain_nofree(&report_builder));
|
|
}
|
|
rz_strbuf_fini(&report_builder);
|
|
return valid;
|
|
}
|
|
|
|
/// @}
|