void propagate_types_among_used_variables(RzCore *core, HtUP *op_cache, RzAnalysisFunction *fcn, RzAnalysisBlock *bb, RzAnalysisOp *aop, struct TypeAnalysisCtx *ctx) {
	RzPVector *used_vars = rz_analysis_function_get_vars_used_at(fcn, aop->addr);
	bool chk_constraint = rz_config_get_b(core->config, "analysis.types.constraint");
	RzAnalysisOp *next_op = op_cache_get(op_cache, core, aop->addr + aop->size);
	void **uvit;
	RzType *prev_type = NULL;
	int prev_idx = 0;
	bool prev_var = false;
	char *fcn_name = NULL;
	bool userfnc = false;
	bool prop = false;
	ut32 type = aop->type & RZ_ANALYSIS_OP_TYPE_MASK;
	RzAnalysis *analysis = core->analysis;

	RzAnalysisEsilTrace *etrace = core->analysis->esil->trace;
	RzILTraceInstruction *cur_instr_trace = rz_analysis_esil_get_instruction_trace(etrace, ctx->cur_idx);

	if (aop->type == RZ_ANALYSIS_OP_TYPE_CALL || aop->type & RZ_ANALYSIS_OP_TYPE_UCALL) {
		char *full_name = NULL;
		ut64 callee_addr;
		if (aop->type == RZ_ANALYSIS_OP_TYPE_CALL) {
			RzAnalysisFunction *fcn_call = rz_analysis_get_fcn_in(core->analysis, aop->jump, -1);
			if (fcn_call) {
				full_name = fcn_call->name;
				callee_addr = fcn_call->addr;
			}
		} else if (aop->ptr != UT64_MAX) {
			RzFlagItem *flag = rz_flag_get_by_spaces(core->flags, aop->ptr, RZ_FLAGS_FS_IMPORTS, NULL);
			if (flag && flag->realname) {
				full_name = flag->realname;
				callee_addr = aop->ptr;
			}
		}
		// TODO: Apart from checking the types database, we should also derive the information
		// from the RzAnalysisFunction if nothing was found in the RzTypeDB
		if (full_name) {
			if (rz_type_func_exist(core->analysis->typedb, full_name)) {
				fcn_name = strdup(full_name);
			} else {
				fcn_name = rz_analysis_function_name_guess(core->analysis->typedb, full_name);
			}
			if (!fcn_name) {
				fcn_name = strdup(full_name);
				userfnc = true;
			}
			const char *Cc = rz_analysis_cc_func(core->analysis, fcn_name);
			if (Cc && rz_analysis_cc_exist(core->analysis, Cc)) {
				char *cc = strdup(Cc);
				type_match(core, fcn_name, aop->addr, bb->addr, cc, prev_idx, userfnc, callee_addr, op_cache);
				prev_idx = ctx->cur_idx;
				ctx->retctx->ret_type = rz_type_func_ret(core->analysis->typedb, fcn_name);
				RZ_FREE(ctx->retctx->ret_reg);
				const char *rr = rz_analysis_cc_ret(core->analysis, cc);
				if (rr) {
					ctx->retctx->ret_reg = strdup(rr);
				}
				ctx->retctx->resolved = false;
				free(cc);
			}
			if (!strcmp(fcn_name, "__stack_chk_fail")) {
				handle_stack_canary(core, aop, ctx->cur_idx);
			}
			free(fcn_name);
		}
	} else if (return_type_analysis_context_unresolved(ctx->retctx)) {
		// Forward propagation of function return type
		propagate_return_type(core, aop, next_op, cur_instr_trace, ctx->retctx, used_vars);
	}
	// Type propagation using instruction access pattern
	if (used_vars && !rz_pvector_empty(used_vars)) {
		rz_pvector_foreach (used_vars, uvit) {
			RzAnalysisVar *var = *uvit;
			bool sign = false;
			if ((type == RZ_ANALYSIS_OP_TYPE_CMP) && next_op) {
				if (next_op->sign) {
					sign = true;
				} else {
					// cmp [local_ch], rax ; jb
					// set to "unsigned"
					var_type_set_sign(core->analysis, var, false);
				}
			}
			// cmp [local_ch], rax ; jge
			if (sign || aop->sign) {
				// set to "signed"
				var_type_set_sign(core->analysis, var, true);
			}
			// lea rax , str.hello  ; mov [local_ch], rax;
			// mov rdx , [local_4h] ; mov [local_8h], rdx;
			if (ctx->prev_dest && (type == RZ_ANALYSIS_OP_TYPE_MOV || type == RZ_ANALYSIS_OP_TYPE_STORE)) {
				char reg[REGNAME_SIZE] = { 0 };
				get_src_regname(core, aop->addr, reg, sizeof(reg));
				bool match = strstr(ctx->prev_dest, reg) != NULL;
				if (ctx->str_flag && match) {
					var_type_set_str(core->analysis, var, "const char *", false);
				}
				if (prop && match && prev_var && prev_type) {
					// Here we clone the type
					var_type_set(core->analysis, var, prev_type, false);
				}
			}
			if (chk_constraint && var && (type == RZ_ANALYSIS_OP_TYPE_CMP && aop->disp != UT64_MAX) && next_op && next_op->type == RZ_ANALYSIS_OP_TYPE_CJMP) {
				bool jmp = false;
				RzAnalysisOp *jmp_op = { 0 };
				ut64 jmp_addr = next_op->jump;
				RzAnalysisBlock *jmpbb = rz_analysis_fcn_bbget_in(core->analysis, fcn, jmp_addr);

				// Check exit status of jmp branch
				int i;
				for (i = 0; i < MAX_INSTR; i++) {
					jmp_op = rz_core_analysis_op(core, jmp_addr, RZ_ANALYSIS_OP_MASK_BASIC);
					if (!jmp_op) {
						break;
					}
					if ((jmp_op->type == RZ_ANALYSIS_OP_TYPE_RET && rz_analysis_block_contains(jmpbb, jmp_addr)) || jmp_op->type == RZ_ANALYSIS_OP_TYPE_CJMP) {
						jmp = true;
						rz_analysis_op_free(jmp_op);
						break;
					}
					jmp_addr += jmp_op->size;
					rz_analysis_op_free(jmp_op);
				}
				RzTypeConstraint constr = {
					.cond = jmp ? rz_type_cond_invert(next_op->cond) : next_op->cond,
					.val = aop->val
				};
				rz_analysis_var_add_constraint(var, &constr);
			}
		}
	}
	prev_var = (used_vars && !rz_pvector_empty(used_vars) && aop->direction == RZ_ANALYSIS_OP_DIR_READ);
	ctx->str_flag = false;
	prop = false;
	ctx->prev_dest = NULL;
	switch (type) {
	case RZ_ANALYSIS_OP_TYPE_MOV:
	case RZ_ANALYSIS_OP_TYPE_LEA:
	case RZ_ANALYSIS_OP_TYPE_LOAD:
		if (aop->ptr && aop->refptr && aop->ptr != UT64_MAX) {
			if (type == RZ_ANALYSIS_OP_TYPE_LOAD) {
				ut8 buf[256] = { 0 };
				rz_io_read_at(core->io, aop->ptr, buf, sizeof(buf) - 1);
				ut64 ptr = rz_read_ble(buf, core->print->big_endian, aop->refptr * 8);
				if (ptr && ptr != UT64_MAX) {
					RzFlagItem *f = rz_flag_get_by_spaces(core->flags, ptr, RZ_FLAGS_FS_STRINGS, NULL);
					if (f) {
						ctx->str_flag = true;
					}
				}
			} else if (rz_flag_exist_at(core->flags, "str", 3, aop->ptr)) {
				ctx->str_flag = true;
			}
		}

		// Assert : reg write only once here
		RzILTraceRegOp *w_reg = NULL;
		if (cur_instr_trace) {
			if (cur_instr_trace->stats & RZ_IL_TRACE_INS_HAS_REG_W) {
				w_reg = rz_pvector_at(cur_instr_trace->write_reg_ops, 0);
				if (w_reg) {
					ctx->prev_dest = rz_str_constpool_get(&analysis->constpool, w_reg->reg_name);
				}
			}
		}

		if (used_vars && !rz_pvector_empty(used_vars)) {
			rz_pvector_foreach (used_vars, uvit) {
				RzAnalysisVar *var = *uvit;
				// mov dword [local_4h], str.hello;
				if (ctx->str_flag) {
					var_type_set_str(core->analysis, var, "const char *", false);
				}
				prev_type = var->type;
				prop = true;
			}
		}
	}
}