From 99713aacc3eb0ee2029f6db9af0bf9194d4b31bd Mon Sep 17 00:00:00 2001 From: smurf3tte <75271109+smurf3tte@users.noreply.github.com> Date: Wed, 16 Dec 2020 15:40:20 -0800 Subject: [PATCH] Debugger: Initial implementation of conditional breakpoints --- CMakeLists.txt | 2 + Externals/expr/CMakeLists.txt | 2 + Externals/expr/LICENSE | 21 + Externals/expr/include/expr.h | 921 ++++++++++++++++++ Externals/licenses.md | 2 + Source/Core/Core/CMakeLists.txt | 1 + Source/Core/Core/PowerPC/BreakPoints.cpp | 83 +- Source/Core/Core/PowerPC/BreakPoints.h | 3 +- .../DolphinQt/Debugger/BreakpointWidget.cpp | 7 +- .../DolphinQt/Debugger/BreakpointWidget.h | 2 +- .../Debugger/NewBreakpointDialog.cpp | 11 +- .../DolphinQt/Debugger/NewBreakpointDialog.h | 1 + Source/VSProps/Base.props | 1 + 13 files changed, 1045 insertions(+), 12 deletions(-) create mode 100644 Externals/expr/CMakeLists.txt create mode 100644 Externals/expr/LICENSE create mode 100644 Externals/expr/include/expr.h diff --git a/CMakeLists.txt b/CMakeLists.txt index b01b90fc5489..ca1b5f0a7349 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -812,6 +812,8 @@ endif() include_directories(Externals/picojson) +add_subdirectory(Externals/expr) + add_subdirectory(Externals/rangeset) ######################################## diff --git a/Externals/expr/CMakeLists.txt b/Externals/expr/CMakeLists.txt new file mode 100644 index 000000000000..4eb00fa0e954 --- /dev/null +++ b/Externals/expr/CMakeLists.txt @@ -0,0 +1,2 @@ +add_library(expr INTERFACE) +target_include_directories(expr INTERFACE include/) diff --git a/Externals/expr/LICENSE b/Externals/expr/LICENSE new file mode 100644 index 000000000000..44113db6d82a --- /dev/null +++ b/Externals/expr/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2016 Serge Zaitsev + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/Externals/expr/include/expr.h b/Externals/expr/include/expr.h new file mode 100644 index 000000000000..59ba4dbaadea --- /dev/null +++ b/Externals/expr/include/expr.h @@ -0,0 +1,921 @@ +#ifndef EXPR_H +#define EXPR_H + +#ifdef _MSC_VER +#pragma warning(push) +// Disable warning for zero-sized array: +#pragma warning(disable : 4200) +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +#include /* for isspace */ +#include +#include /* for pow */ +#include +#include +#include + +/* + * Simple expandable vector implementation + */ +static int vec_expand(char **buf, int *length, int *cap, int memsz) { + if (*length + 1 > *cap) { + void *ptr; + int n = (*cap == 0) ? 1 : *cap << 1; + ptr = realloc(*buf, n * memsz); + if (ptr == NULL) { + return -1; /* allocation failed */ + } + *buf = (char *)ptr; + *cap = n; + } + return 0; +} +#define vec(T) \ + struct { \ + T *buf; \ + int len; \ + int cap; \ + } +#define vec_init() \ + { NULL, 0, 0 } +#define vec_len(v) ((v)->len) +#define vec_unpack(v) \ + (char **)&(v)->buf, &(v)->len, &(v)->cap, sizeof(*(v)->buf) +#define vec_push(v, val) \ + vec_expand(vec_unpack(v)) ? -1 : ((v)->buf[(v)->len++] = (val), 0) +#define vec_nth(v, i) (v)->buf[i] +#define vec_peek(v) (v)->buf[(v)->len - 1] +#define vec_pop(v) (v)->buf[--(v)->len] +#define vec_free(v) (free((v)->buf), (v)->buf = NULL, (v)->len = (v)->cap = 0) +#define vec_foreach(v, var, iter) \ + if ((v)->len > 0) \ + for ((iter) = 0; (iter) < (v)->len && (((var) = (v)->buf[(iter)]), 1); \ + ++(iter)) + +/* + * Expression data types + */ +struct expr; +struct expr_func; + +enum expr_type { + OP_UNKNOWN, + OP_UNARY_MINUS, + OP_UNARY_LOGICAL_NOT, + OP_UNARY_BITWISE_NOT, + + OP_POWER, + OP_DIVIDE, + OP_MULTIPLY, + OP_REMAINDER, + + OP_PLUS, + OP_MINUS, + + OP_SHL, + OP_SHR, + + OP_LT, + OP_LE, + OP_GT, + OP_GE, + OP_EQ, + OP_NE, + + OP_BITWISE_AND, + OP_BITWISE_OR, + OP_BITWISE_XOR, + + OP_LOGICAL_AND, + OP_LOGICAL_OR, + + OP_ASSIGN, + OP_COMMA, + + OP_CONST, + OP_VAR, + OP_FUNC, +}; + +static int prec[] = {0, 1, 1, 1, 2, 2, 2, 2, 3, 3, 4, 4, 5, 5, + 5, 5, 5, 5, 6, 7, 8, 9, 10, 11, 12, 0, 0, 0}; + +typedef vec(struct expr) vec_expr_t; +typedef void (*exprfn_cleanup_t)(struct expr_func *f, void *context); +typedef double (*exprfn_t)(struct expr_func *f, vec_expr_t *args, void *context); + +struct expr { + enum expr_type type; + union { + struct { + double value; + } num; + struct { + double *value; + } var; + struct { + vec_expr_t args; + } op; + struct { + struct expr_func *f; + vec_expr_t args; + void *context; + } func; + } param; +}; + +#define expr_init() \ + { (enum expr_type)0 } + +struct expr_string { + const char *s; + int n; +}; +struct expr_arg { + int oslen; + int eslen; + vec_expr_t args; +}; + +typedef vec(struct expr_string) vec_str_t; +typedef vec(struct expr_arg) vec_arg_t; + +static int expr_is_unary(enum expr_type op) { + return op == OP_UNARY_MINUS || op == OP_UNARY_LOGICAL_NOT || + op == OP_UNARY_BITWISE_NOT; +} + +static int expr_is_binary(enum expr_type op) { + return !expr_is_unary(op) && op != OP_CONST && op != OP_VAR && + op != OP_FUNC && op != OP_UNKNOWN; +} + +static int expr_prec(enum expr_type a, enum expr_type b) { + int left = + expr_is_binary(a) && a != OP_ASSIGN && a != OP_POWER && a != OP_COMMA; + return (left && prec[a] >= prec[b]) || (prec[a] > prec[b]); +} + +#define isfirstvarchr(c) \ + (((unsigned char)c >= '@' && c != '^' && c != '|') || c == '$') +#define isvarchr(c) \ + (((unsigned char)c >= '@' && c != '^' && c != '|') || c == '$' || \ + c == '#' || (c >= '0' && c <= '9')) + +static struct { + const char *s; + const enum expr_type op; +} OPS[] = { + {"-u", OP_UNARY_MINUS}, + {"!u", OP_UNARY_LOGICAL_NOT}, + {"^u", OP_UNARY_BITWISE_NOT}, + {"**", OP_POWER}, + {"*", OP_MULTIPLY}, + {"/", OP_DIVIDE}, + {"%", OP_REMAINDER}, + {"+", OP_PLUS}, + {"-", OP_MINUS}, + {"<<", OP_SHL}, + {">>", OP_SHR}, + {"<", OP_LT}, + {"<=", OP_LE}, + {">", OP_GT}, + {">=", OP_GE}, + {"==", OP_EQ}, + {"!=", OP_NE}, + {"&", OP_BITWISE_AND}, + {"|", OP_BITWISE_OR}, + {"^", OP_BITWISE_XOR}, + {"&&", OP_LOGICAL_AND}, + {"||", OP_LOGICAL_OR}, + {"=", OP_ASSIGN}, + {",", OP_COMMA}, + + /* These are used by lexer and must be ignored by parser, so we put + them at the end */ + {"-", OP_UNARY_MINUS}, + {"!", OP_UNARY_LOGICAL_NOT}, + {"^", OP_UNARY_BITWISE_NOT}, +}; + +static enum expr_type expr_op(const char *s, size_t len, int unary) { + for (unsigned int i = 0; i < sizeof(OPS) / sizeof(OPS[0]); i++) { + if (strlen(OPS[i].s) == len && strncmp(OPS[i].s, s, len) == 0 && + (unary == -1 || expr_is_unary(OPS[i].op) == unary)) { + return OPS[i].op; + } + } + return OP_UNKNOWN; +} + +static double expr_parse_number(const char *s, size_t len) { + double num = 0; + unsigned int frac = 0; + unsigned int digits = 0; + for (unsigned int i = 0; i < len; i++) { + if (s[i] == '.' && frac == 0) { + frac++; + continue; + } + if (isdigit(s[i])) { + digits++; + if (frac > 0) { + frac++; + } + num = num * 10 + (s[i] - '0'); + } else { + return NAN; + } + } + while (frac > 1) { + num = num / 10; + frac--; + } + return (digits > 0 ? num : NAN); +} + +/* + * Functions + */ +struct expr_func { + const char *name; + exprfn_t f; + exprfn_cleanup_t cleanup; + size_t ctxsz; +}; + +static struct expr_func *expr_get_func(struct expr_func *funcs, const char *s, + size_t len) { + for (struct expr_func *f = funcs; f->name; f++) { + if (strlen(f->name) == len && strncmp(f->name, s, len) == 0) { + return f; + } + } + return NULL; +} + +/* + * Variables + */ +struct expr_var { + double value; + struct expr_var *next; + char name[]; +}; + +struct expr_var_list { + struct expr_var *head; +}; + +static struct expr_var *expr_get_var(struct expr_var_list *vars, const char *s, + size_t len) { + struct expr_var *v = NULL; + if (len == 0 || !isfirstvarchr(*s)) { + return NULL; + } + for (v = vars->head; v; v = v->next) { + if (strlen(v->name) == len && strncmp(v->name, s, len) == 0) { + return v; + } + } + v = (struct expr_var *)calloc(1, sizeof(struct expr_var) + len + 1); + if (v == NULL) { + return NULL; /* allocation failed */ + } + v->next = vars->head; + v->value = 0; + strncpy(v->name, s, len); + v->name[len] = '\0'; + vars->head = v; + return v; +} + +static int to_int(double x) { + if (isnan(x)) { + return 0; + } else if (isinf(x) != 0) { + return INT_MAX * isinf(x); + } else { + return (int)x; + } +} + +static double expr_eval(struct expr *e) { + double n; + switch (e->type) { + case OP_UNARY_MINUS: + return -(expr_eval(&e->param.op.args.buf[0])); + case OP_UNARY_LOGICAL_NOT: + return !(expr_eval(&e->param.op.args.buf[0])); + case OP_UNARY_BITWISE_NOT: + return ~(to_int(expr_eval(&e->param.op.args.buf[0]))); + case OP_POWER: + return pow(expr_eval(&e->param.op.args.buf[0]), + expr_eval(&e->param.op.args.buf[1])); + case OP_MULTIPLY: + return expr_eval(&e->param.op.args.buf[0]) * + expr_eval(&e->param.op.args.buf[1]); + case OP_DIVIDE: + return expr_eval(&e->param.op.args.buf[0]) / + expr_eval(&e->param.op.args.buf[1]); + case OP_REMAINDER: + return fmod(expr_eval(&e->param.op.args.buf[0]), + expr_eval(&e->param.op.args.buf[1])); + case OP_PLUS: + return expr_eval(&e->param.op.args.buf[0]) + + expr_eval(&e->param.op.args.buf[1]); + case OP_MINUS: + return expr_eval(&e->param.op.args.buf[0]) - + expr_eval(&e->param.op.args.buf[1]); + case OP_SHL: + return to_int(expr_eval(&e->param.op.args.buf[0])) + << to_int(expr_eval(&e->param.op.args.buf[1])); + case OP_SHR: + return to_int(expr_eval(&e->param.op.args.buf[0])) >> + to_int(expr_eval(&e->param.op.args.buf[1])); + case OP_LT: + return expr_eval(&e->param.op.args.buf[0]) < + expr_eval(&e->param.op.args.buf[1]); + case OP_LE: + return expr_eval(&e->param.op.args.buf[0]) <= + expr_eval(&e->param.op.args.buf[1]); + case OP_GT: + return expr_eval(&e->param.op.args.buf[0]) > + expr_eval(&e->param.op.args.buf[1]); + case OP_GE: + return expr_eval(&e->param.op.args.buf[0]) >= + expr_eval(&e->param.op.args.buf[1]); + case OP_EQ: + return expr_eval(&e->param.op.args.buf[0]) == + expr_eval(&e->param.op.args.buf[1]); + case OP_NE: + return expr_eval(&e->param.op.args.buf[0]) != + expr_eval(&e->param.op.args.buf[1]); + case OP_BITWISE_AND: + return to_int(expr_eval(&e->param.op.args.buf[0])) & + to_int(expr_eval(&e->param.op.args.buf[1])); + case OP_BITWISE_OR: + return to_int(expr_eval(&e->param.op.args.buf[0])) | + to_int(expr_eval(&e->param.op.args.buf[1])); + case OP_BITWISE_XOR: + return to_int(expr_eval(&e->param.op.args.buf[0])) ^ + to_int(expr_eval(&e->param.op.args.buf[1])); + case OP_LOGICAL_AND: + n = expr_eval(&e->param.op.args.buf[0]); + if (n != 0) { + n = expr_eval(&e->param.op.args.buf[1]); + if (n != 0) { + return n; + } + } + return 0; + case OP_LOGICAL_OR: + n = expr_eval(&e->param.op.args.buf[0]); + if (n != 0 && !isnan(n)) { + return n; + } else { + n = expr_eval(&e->param.op.args.buf[1]); + if (n != 0) { + return n; + } + } + return 0; + case OP_ASSIGN: + n = expr_eval(&e->param.op.args.buf[1]); + if (vec_nth(&e->param.op.args, 0).type == OP_VAR) { + *e->param.op.args.buf[0].param.var.value = n; + } + return n; + case OP_COMMA: + expr_eval(&e->param.op.args.buf[0]); + return expr_eval(&e->param.op.args.buf[1]); + case OP_CONST: + return e->param.num.value; + case OP_VAR: + return *e->param.var.value; + case OP_FUNC: + return e->param.func.f->f(e->param.func.f, &e->param.func.args, + e->param.func.context); + default: + return NAN; + } +} + +#define EXPR_TOP (1 << 0) +#define EXPR_TOPEN (1 << 1) +#define EXPR_TCLOSE (1 << 2) +#define EXPR_TNUMBER (1 << 3) +#define EXPR_TWORD (1 << 4) +#define EXPR_TDEFAULT (EXPR_TOPEN | EXPR_TNUMBER | EXPR_TWORD) + +#define EXPR_UNARY (1 << 5) +#define EXPR_COMMA (1 << 6) + +static int expr_next_token(const char *s, size_t len, int *flags) { + unsigned int i = 0; + if (len == 0) { + return 0; + } + char c = s[0]; + if (c == '#') { + for (; i < len && s[i] != '\n'; i++) + ; + return i; + } else if (c == '\n') { + for (; i < len && isspace(s[i]); i++) + ; + if (*flags & EXPR_TOP) { + if (i == len || s[i] == ')') { + *flags = *flags & (~EXPR_COMMA); + } else { + *flags = EXPR_TNUMBER | EXPR_TWORD | EXPR_TOPEN | EXPR_COMMA; + } + } + return i; + } else if (isspace(c)) { + while (i < len && isspace(s[i]) && s[i] != '\n') { + i++; + } + return i; + } else if (isdigit(c)) { + if ((*flags & EXPR_TNUMBER) == 0) { + return -1; // unexpected number + } + *flags = EXPR_TOP | EXPR_TCLOSE; + while ((c == '.' || isdigit(c)) && i < len) { + i++; + c = s[i]; + } + return i; + } else if (isfirstvarchr(c)) { + if ((*flags & EXPR_TWORD) == 0) { + return -2; // unexpected word + } + *flags = EXPR_TOP | EXPR_TOPEN | EXPR_TCLOSE; + while ((isvarchr(c)) && i < len) { + i++; + c = s[i]; + } + return i; + } else if (c == '(' || c == ')') { + if (c == '(' && (*flags & EXPR_TOPEN) != 0) { + *flags = EXPR_TNUMBER | EXPR_TWORD | EXPR_TOPEN | EXPR_TCLOSE; + } else if (c == ')' && (*flags & EXPR_TCLOSE) != 0) { + *flags = EXPR_TOP | EXPR_TCLOSE; + } else { + return -3; // unexpected parenthesis + } + return 1; + } else { + if ((*flags & EXPR_TOP) == 0) { + if (expr_op(&c, 1, 1) == OP_UNKNOWN) { + return -4; // missing expected operand + } + *flags = EXPR_TNUMBER | EXPR_TWORD | EXPR_TOPEN | EXPR_UNARY; + return 1; + } else { + int found = 0; + while (!isvarchr(c) && !isspace(c) && c != '(' && c != ')' && i < len) { + if (expr_op(s, i + 1, 0) != OP_UNKNOWN) { + found = 1; + } else if (found) { + break; + } + i++; + c = s[i]; + } + if (!found) { + return -5; // unknown operator + } + *flags = EXPR_TNUMBER | EXPR_TWORD | EXPR_TOPEN; + return i; + } + } +} + +#define EXPR_PAREN_ALLOWED 0 +#define EXPR_PAREN_EXPECTED 1 +#define EXPR_PAREN_FORBIDDEN 2 + +static int expr_bind(const char *s, size_t len, vec_expr_t *es) { + enum expr_type op = expr_op(s, len, -1); + if (op == OP_UNKNOWN) { + return -1; + } + + if (expr_is_unary(op)) { + if (vec_len(es) < 1) { + return -1; + } + struct expr arg = vec_pop(es); + struct expr unary = expr_init(); + unary.type = op; + vec_push(&unary.param.op.args, arg); + vec_push(es, unary); + } else { + if (vec_len(es) < 2) { + return -1; + } + struct expr b = vec_pop(es); + struct expr a = vec_pop(es); + struct expr binary = expr_init(); + binary.type = op; + if (op == OP_ASSIGN && a.type != OP_VAR) { + return -1; /* Bad assignment */ + } + vec_push(&binary.param.op.args, a); + vec_push(&binary.param.op.args, b); + vec_push(es, binary); + } + return 0; +} + +static struct expr expr_const(double value) { + struct expr e = expr_init(); + e.type = OP_CONST; + e.param.num.value = value; + return e; +} + +static struct expr expr_varref(struct expr_var *v) { + struct expr e = expr_init(); + e.type = OP_VAR; + e.param.var.value = &v->value; + return e; +} + +static struct expr expr_binary(enum expr_type type, struct expr a, + struct expr b) { + struct expr e = expr_init(); + e.type = type; + vec_push(&e.param.op.args, a); + vec_push(&e.param.op.args, b); + return e; +} + +static inline void expr_copy(struct expr *dst, struct expr *src) { + int i; + struct expr arg; + dst->type = src->type; + if (src->type == OP_FUNC) { + dst->param.func.f = src->param.func.f; + vec_foreach(&src->param.func.args, arg, i) { + struct expr tmp = expr_init(); + expr_copy(&tmp, &arg); + vec_push(&dst->param.func.args, tmp); + } + if (src->param.func.f->ctxsz > 0) { + dst->param.func.context = calloc(1, src->param.func.f->ctxsz); + } + } else if (src->type == OP_CONST) { + dst->param.num.value = src->param.num.value; + } else if (src->type == OP_VAR) { + dst->param.var.value = src->param.var.value; + } else { + vec_foreach(&src->param.op.args, arg, i) { + struct expr tmp = expr_init(); + expr_copy(&tmp, &arg); + vec_push(&dst->param.op.args, tmp); + } + } +} + +static void expr_destroy_args(struct expr *e); + +static struct expr *expr_create(const char *s, size_t len, + struct expr_var_list *vars, + struct expr_func *funcs) { + double num; + const char *id = NULL; + size_t idn = 0; + + struct expr *result = NULL; + + vec_expr_t es = vec_init(); + vec_str_t os = vec_init(); + vec_arg_t as = vec_init(); + + struct macro { + char *name; + vec_expr_t body; + }; + vec(struct macro) macros = vec_init(); + + int flags = EXPR_TDEFAULT; + int paren = EXPR_PAREN_ALLOWED; + for (;;) { + int n = expr_next_token(s, len, &flags); + if (n == 0) { + break; + } else if (n < 0) { + goto cleanup; + } + const char *tok = s; + s = s + n; + len = len - n; + if (*tok == '#') { + continue; + } + if (flags & EXPR_UNARY) { + if (n == 1) { + switch (*tok) { + case '-': + tok = "-u"; + break; + case '^': + tok = "^u"; + break; + case '!': + tok = "!u"; + break; + default: + goto cleanup; + } + n = 2; + } + } + if (*tok == '\n' && (flags & EXPR_COMMA)) { + flags = flags & (~EXPR_COMMA); + n = 1; + tok = ","; + } + if (isspace(*tok)) { + continue; + } + int paren_next = EXPR_PAREN_ALLOWED; + + if (idn > 0) { + struct expr_var *v; + if (n == 1 && *tok == '(') { + int i; + int has_macro = 0; + struct macro m; + vec_foreach(¯os, m, i) { + if (strlen(m.name) == idn && strncmp(m.name, id, idn) == 0) { + has_macro = 1; + break; + } + } + if ((idn == 1 && id[0] == '$') || has_macro || + expr_get_func(funcs, id, idn) != NULL) { + struct expr_string str = {id, (int)idn}; + vec_push(&os, str); + paren = EXPR_PAREN_EXPECTED; + } else { + goto cleanup; /* invalid function name */ + } + } else if ((v = expr_get_var(vars, id, idn)) != NULL) { + vec_push(&es, expr_varref(v)); + paren = EXPR_PAREN_FORBIDDEN; + } + id = NULL; + idn = 0; + } + + if (n == 1 && *tok == '(') { + if (paren == EXPR_PAREN_EXPECTED) { + struct expr_string str = {"{", 1}; + vec_push(&os, str); + struct expr_arg arg = {vec_len(&os), vec_len(&es), vec_init()}; + vec_push(&as, arg); + } else if (paren == EXPR_PAREN_ALLOWED) { + struct expr_string str = {"(", 1}; + vec_push(&os, str); + } else { + goto cleanup; // Bad call + } + } else if (paren == EXPR_PAREN_EXPECTED) { + goto cleanup; // Bad call + } else if (n == 1 && *tok == ')') { + int minlen = (vec_len(&as) > 0 ? vec_peek(&as).oslen : 0); + while (vec_len(&os) > minlen && *vec_peek(&os).s != '(' && + *vec_peek(&os).s != '{') { + struct expr_string str = vec_pop(&os); + if (expr_bind(str.s, str.n, &es) == -1) { + goto cleanup; + } + } + if (vec_len(&os) == 0) { + goto cleanup; // Bad parens + } + struct expr_string str = vec_pop(&os); + if (str.n == 1 && *str.s == '{') { + str = vec_pop(&os); + struct expr_arg arg = vec_pop(&as); + if (vec_len(&es) > arg.eslen) { + vec_push(&arg.args, vec_pop(&es)); + } + if (str.n == 1 && str.s[0] == '$') { + if (vec_len(&arg.args) < 1) { + vec_free(&arg.args); + goto cleanup; /* too few arguments for $() function */ + } + struct expr *u = &vec_nth(&arg.args, 0); + if (u->type != OP_VAR) { + vec_free(&arg.args); + goto cleanup; /* first argument is not a variable */ + } + for (struct expr_var *v = vars->head; v; v = v->next) { + if (&v->value == u->param.var.value) { + struct macro m = {v->name, arg.args}; + vec_push(¯os, m); + break; + } + } + vec_push(&es, expr_const(0)); + } else { + int i = 0; + int found = -1; + struct macro m; + vec_foreach(¯os, m, i) { + if (strlen(m.name) == (size_t)str.n && + strncmp(m.name, str.s, str.n) == 0) { + found = i; + } + } + if (found != -1) { + m = vec_nth(¯os, found); + struct expr root = expr_const(0); + struct expr *p = &root; + /* Assign macro parameters */ + for (int j = 0; j < vec_len(&arg.args); j++) { + char varname[12]; + snprintf(varname, sizeof(varname), "$%d", (j + 1)); + struct expr_var *v = expr_get_var(vars, varname, strlen(varname)); + struct expr ev = expr_varref(v); + struct expr assign = + expr_binary(OP_ASSIGN, ev, vec_nth(&arg.args, j)); + *p = expr_binary(OP_COMMA, assign, expr_const(0)); + p = &vec_nth(&p->param.op.args, 1); + } + /* Expand macro body */ + for (int j = 1; j < vec_len(&m.body); j++) { + if (j < vec_len(&m.body) - 1) { + *p = expr_binary(OP_COMMA, expr_const(0), expr_const(0)); + expr_copy(&vec_nth(&p->param.op.args, 0), &vec_nth(&m.body, j)); + } else { + expr_copy(p, &vec_nth(&m.body, j)); + } + p = &vec_nth(&p->param.op.args, 1); + } + vec_push(&es, root); + vec_free(&arg.args); + } else { + struct expr_func *f = expr_get_func(funcs, str.s, str.n); + struct expr bound_func = expr_init(); + bound_func.type = OP_FUNC; + bound_func.param.func.f = f; + bound_func.param.func.args = arg.args; + if (f->ctxsz > 0) { + void *p = calloc(1, f->ctxsz); + if (p == NULL) { + goto cleanup; /* allocation failed */ + } + bound_func.param.func.context = p; + } + vec_push(&es, bound_func); + } + } + } + paren_next = EXPR_PAREN_FORBIDDEN; + } else if (!isnan(num = expr_parse_number(tok, n))) { + vec_push(&es, expr_const(num)); + paren_next = EXPR_PAREN_FORBIDDEN; + } else if (expr_op(tok, n, -1) != OP_UNKNOWN) { + enum expr_type op = expr_op(tok, n, -1); + struct expr_string o2 = {NULL, 0}; + if (vec_len(&os) > 0) { + o2 = vec_peek(&os); + } + for (;;) { + if (n == 1 && *tok == ',' && vec_len(&os) > 0) { + struct expr_string str = vec_peek(&os); + if (str.n == 1 && *str.s == '{') { + struct expr e = vec_pop(&es); + vec_push(&vec_peek(&as).args, e); + break; + } + } + enum expr_type type2 = expr_op(o2.s, o2.n, -1); + if (!(type2 != OP_UNKNOWN && expr_prec(op, type2))) { + struct expr_string str = {tok, n}; + vec_push(&os, str); + break; + } + + if (expr_bind(o2.s, o2.n, &es) == -1) { + goto cleanup; + } + (void)vec_pop(&os); + if (vec_len(&os) > 0) { + o2 = vec_peek(&os); + } else { + o2.n = 0; + } + } + } else { + if (n > 0 && !isdigit(*tok)) { + /* Valid identifier, a variable or a function */ + id = tok; + idn = n; + } else { + goto cleanup; // Bad variable name, e.g. '2.3.4' or '4ever' + } + } + paren = paren_next; + } + + if (idn > 0) { + vec_push(&es, expr_varref(expr_get_var(vars, id, idn))); + } + + while (vec_len(&os) > 0) { + struct expr_string rest = vec_pop(&os); + if (rest.n == 1 && (*rest.s == '(' || *rest.s == ')')) { + goto cleanup; // Bad paren + } + if (expr_bind(rest.s, rest.n, &es) == -1) { + goto cleanup; + } + } + + result = (struct expr *)calloc(1, sizeof(struct expr)); + if (result != NULL) { + if (vec_len(&es) == 0) { + result->type = OP_CONST; + } else { + *result = vec_pop(&es); + } + } + + int i, j; + struct macro m; + struct expr e; + struct expr_arg a; +cleanup: + vec_foreach(¯os, m, i) { + struct expr e2; + vec_foreach(&m.body, e2, j) { expr_destroy_args(&e2); } + vec_free(&m.body); + } + vec_free(¯os); + + vec_foreach(&es, e, i) { expr_destroy_args(&e); } + vec_free(&es); + + vec_foreach(&as, a, i) { + vec_foreach(&a.args, e, j) { expr_destroy_args(&e); } + vec_free(&a.args); + } + vec_free(&as); + + /*vec_foreach(&os, o, i) {vec_free(&m.body);}*/ + vec_free(&os); + return result; +} + +static void expr_destroy_args(struct expr *e) { + int i; + struct expr arg; + if (e->type == OP_FUNC) { + vec_foreach(&e->param.func.args, arg, i) { expr_destroy_args(&arg); } + vec_free(&e->param.func.args); + if (e->param.func.context != NULL) { + if (e->param.func.f->cleanup != NULL) { + e->param.func.f->cleanup(e->param.func.f, e->param.func.context); + } + free(e->param.func.context); + } + } else if (e->type != OP_CONST && e->type != OP_VAR) { + vec_foreach(&e->param.op.args, arg, i) { expr_destroy_args(&arg); } + vec_free(&e->param.op.args); + } +} + +static void expr_destroy(struct expr *e, struct expr_var_list *vars) { + if (e != NULL) { + expr_destroy_args(e); + free(e); + } + if (vars != NULL) { + for (struct expr_var *v = vars->head; v;) { + struct expr_var *next = v->next; + free(v); + v = next; + } + } +} + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#ifdef _MSC_VER +#pragma warning(pop) +#endif + +#endif /* EXPR_H */ diff --git a/Externals/licenses.md b/Externals/licenses.md index f7b20041b9d9..beef359494a2 100644 --- a/Externals/licenses.md +++ b/Externals/licenses.md @@ -14,6 +14,8 @@ Dolphin includes or links code of the following third-party software projects: [MIT](https://github.com/discordapp/discord-rpc/blob/master/LICENSE) - [ENet](http://enet.bespin.org/): [MIT](http://enet.bespin.org/License.html) +- [expr](https://github.com/zserge/expr): + [MIT](https://github.com/zserge/expr/blob/master/LICENSE) - [GCEmu](http://sourceforge.net/projects/gcemu-project/): GPLv2+ - [gettext](https://www.gnu.org/software/gettext/): diff --git a/Source/Core/Core/CMakeLists.txt b/Source/Core/Core/CMakeLists.txt index de8fd8ac93ad..b46f96ce16ab 100644 --- a/Source/Core/Core/CMakeLists.txt +++ b/Source/Core/Core/CMakeLists.txt @@ -555,6 +555,7 @@ PUBLIC cubeb discio enet + expr inputcommon ${MBEDTLS_LIBRARIES} pugixml diff --git a/Source/Core/Core/PowerPC/BreakPoints.cpp b/Source/Core/Core/PowerPC/BreakPoints.cpp index bf1e1643d115..19d4d111475e 100644 --- a/Source/Core/Core/PowerPC/BreakPoints.cpp +++ b/Source/Core/Core/PowerPC/BreakPoints.cpp @@ -5,17 +5,92 @@ #include "Core/PowerPC/BreakPoints.h" #include +#include #include +#include +#include +#include +#include #include #include +#include #include +#include + #include "Common/CommonTypes.h" #include "Common/DebugInterface.h" #include "Common/Logging/Log.h" #include "Core/Core.h" #include "Core/PowerPC/JitInterface.h" #include "Core/PowerPC/MMU.h" +#include "Core/PowerPC/PowerPC.h" + +struct ExprDeleter +{ + void operator()(expr* expression) const { expr_destroy(expression, nullptr); } +}; + +using ExprPointer = std::unique_ptr; + +class ExprVarList +{ +public: + ExprVarList() = default; + ExprVarList(const ExprVarList&) = delete; + ExprVarList& operator=(const ExprVarList&) = delete; + + ExprVarList(ExprVarList&& other) noexcept : m_vars{std::exchange(other.m_vars, {})} {} + + ExprVarList& operator=(ExprVarList&& other) noexcept + { + std::swap(m_vars, other.m_vars); + return *this; + } + + ~ExprVarList() { expr_destroy(nullptr, &m_vars); } + + expr_var* GetHead() { return m_vars.head; } + expr_var_list* GetAddress() { return &m_vars; } + +private: + expr_var_list m_vars = {}; +}; + +static std::optional ParseGPR(const char* name) +{ + if (std::strlen(name) >= 2 && name[0] == 'r' && std::isdigit(name[1])) + { + char* end = nullptr; + int index = std::strtol(&name[1], &end, 10); + if (index < 32 && *end == '\0') + return index; + } + return {}; +} + +static double EvaluateExpression(const std::string& expression_string) +{ + ExprVarList vars; + ExprPointer expression{ + expr_create(expression_string.c_str(), expression_string.length(), vars.GetAddress(), nullptr)}; + if (!expression) + return false; + + for (auto* v = vars.GetHead(); v != nullptr; v = v->next) + { + auto index = ParseGPR(v->name); + if (index) + v->value = GPR(*index); + } + + return expr_eval(expression.get()); +} + +static bool EvaluateCondition(const std::string& condition) +{ + return condition.empty() || EvaluateExpression(condition) != 0; +} bool BreakPoints::IsAddressBreakPoint(u32 address) const { @@ -33,7 +108,7 @@ bool BreakPoints::IsTempBreakPoint(u32 address) const bool BreakPoints::IsBreakPointBreakOnHit(u32 address) const { return std::any_of(m_breakpoints.begin(), m_breakpoints.end(), [address](const auto& bp) { - return bp.address == address && bp.break_on_hit; + return bp.address == address && bp.break_on_hit && EvaluateCondition(bp.condition); }); } @@ -88,10 +163,11 @@ void BreakPoints::Add(const TBreakPoint& bp) void BreakPoints::Add(u32 address, bool temp) { - BreakPoints::Add(address, temp, true, false); + BreakPoints::Add(address, temp, true, false, {}); } -void BreakPoints::Add(u32 address, bool temp, bool break_on_hit, bool log_on_hit) +void BreakPoints::Add(u32 address, bool temp, bool break_on_hit, bool log_on_hit, + std::string condition) { // Only add new addresses if (IsAddressBreakPoint(address)) @@ -103,6 +179,7 @@ void BreakPoints::Add(u32 address, bool temp, bool break_on_hit, bool log_on_hit bp.break_on_hit = break_on_hit; bp.log_on_hit = log_on_hit; bp.address = address; + bp.condition = std::move(condition); m_breakpoints.push_back(bp); diff --git a/Source/Core/Core/PowerPC/BreakPoints.h b/Source/Core/Core/PowerPC/BreakPoints.h index 571427f47d16..fbc25e77745e 100644 --- a/Source/Core/Core/PowerPC/BreakPoints.h +++ b/Source/Core/Core/PowerPC/BreakPoints.h @@ -22,6 +22,7 @@ struct TBreakPoint bool is_temporary = false; bool log_on_hit = false; bool break_on_hit = false; + std::string condition; }; struct TMemCheck @@ -62,7 +63,7 @@ class BreakPoints bool IsBreakPointLogOnHit(u32 address) const; // Add BreakPoint - void Add(u32 address, bool temp, bool break_on_hit, bool log_on_hit); + void Add(u32 address, bool temp, bool break_on_hit, bool log_on_hit, std::string condition); void Add(u32 address, bool temp = false); void Add(const TBreakPoint& bp); diff --git a/Source/Core/DolphinQt/Debugger/BreakpointWidget.cpp b/Source/Core/DolphinQt/Debugger/BreakpointWidget.cpp index 8ba4f6144298..a0b4bc209663 100644 --- a/Source/Core/DolphinQt/Debugger/BreakpointWidget.cpp +++ b/Source/Core/DolphinQt/Debugger/BreakpointWidget.cpp @@ -316,12 +316,13 @@ void BreakpointWidget::OnSave() void BreakpointWidget::AddBP(u32 addr) { - AddBP(addr, false, true, true); + AddBP(addr, false, true, true, {}); } -void BreakpointWidget::AddBP(u32 addr, bool temp, bool break_on_hit, bool log_on_hit) +void BreakpointWidget::AddBP(u32 addr, bool temp, bool break_on_hit, bool log_on_hit, + const QString& condition) { - PowerPC::breakpoints.Add(addr, temp, break_on_hit, log_on_hit); + PowerPC::breakpoints.Add(addr, temp, break_on_hit, log_on_hit, condition.toStdString()); Update(); } diff --git a/Source/Core/DolphinQt/Debugger/BreakpointWidget.h b/Source/Core/DolphinQt/Debugger/BreakpointWidget.h index 570bb02b515e..b741e4e0c58d 100644 --- a/Source/Core/DolphinQt/Debugger/BreakpointWidget.h +++ b/Source/Core/DolphinQt/Debugger/BreakpointWidget.h @@ -22,7 +22,7 @@ class BreakpointWidget : public QDockWidget ~BreakpointWidget(); void AddBP(u32 addr); - void AddBP(u32 addr, bool temp, bool break_on_hit, bool log_on_hit); + void AddBP(u32 addr, bool temp, bool break_on_hit, bool log_on_hit, const QString& condition); void AddAddressMBP(u32 addr, bool on_read = true, bool on_write = true, bool do_log = true, bool do_break = true); void AddRangedMBP(u32 from, u32 to, bool do_read = true, bool do_write = true, bool do_log = true, diff --git a/Source/Core/DolphinQt/Debugger/NewBreakpointDialog.cpp b/Source/Core/DolphinQt/Debugger/NewBreakpointDialog.cpp index 4ad30016cd76..624ce55aa433 100644 --- a/Source/Core/DolphinQt/Debugger/NewBreakpointDialog.cpp +++ b/Source/Core/DolphinQt/Debugger/NewBreakpointDialog.cpp @@ -37,11 +37,14 @@ void NewBreakpointDialog::CreateWidgets() m_instruction_bp->setChecked(true); m_instruction_box = new QGroupBox; m_instruction_address = new QLineEdit; + m_instruction_condition = new QLineEdit; - auto* instruction_layout = new QHBoxLayout; + auto* instruction_layout = new QGridLayout; m_instruction_box->setLayout(instruction_layout); - instruction_layout->addWidget(new QLabel(tr("Address:"))); - instruction_layout->addWidget(m_instruction_address); + instruction_layout->addWidget(new QLabel(tr("Address:")), 0, 0); + instruction_layout->addWidget(m_instruction_address, 0, 1); + instruction_layout->addWidget(new QLabel(tr("Condition:")), 1, 0); + instruction_layout->addWidget(m_instruction_condition, 1, 1); // Memory BP m_memory_bp = new QRadioButton(tr("Memory Breakpoint")); @@ -165,7 +168,7 @@ void NewBreakpointDialog::accept() return; } - m_parent->AddBP(address, false, do_break, do_log); + m_parent->AddBP(address, false, do_break, do_log, m_instruction_condition->text()); } else { diff --git a/Source/Core/DolphinQt/Debugger/NewBreakpointDialog.h b/Source/Core/DolphinQt/Debugger/NewBreakpointDialog.h index d9d2958c2bed..646a58c4e810 100644 --- a/Source/Core/DolphinQt/Debugger/NewBreakpointDialog.h +++ b/Source/Core/DolphinQt/Debugger/NewBreakpointDialog.h @@ -35,6 +35,7 @@ class NewBreakpointDialog : public QDialog QRadioButton* m_instruction_bp; QGroupBox* m_instruction_box; QLineEdit* m_instruction_address; + QLineEdit* m_instruction_condition; // Memory BPs QRadioButton* m_memory_bp; diff --git a/Source/VSProps/Base.props b/Source/VSProps/Base.props index 804ed54fcc70..20800f1f4d31 100644 --- a/Source/VSProps/Base.props +++ b/Source/VSProps/Base.props @@ -26,6 +26,7 @@ $(ExternalsDir)discord-rpc\include;%(AdditionalIncludeDirectories) $(ExternalsDir)ed25519;%(AdditionalIncludeDirectories) $(ExternalsDir)enet\include;%(AdditionalIncludeDirectories) + $(ExternalsDir)expr\include;%(AdditionalIncludeDirectories) $(ExternalsDir)ffmpeg\include;%(AdditionalIncludeDirectories) $(ExternalsDir)fmt\include;%(AdditionalIncludeDirectories) $(ExternalsDir)GL;%(AdditionalIncludeDirectories)