/
githubmirror
/
libxmlb
Обзор
Документация
Войти
/
githubmirror
/
libxmlb
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
src/xb-machine.c
2 444 строки
65 KB
Richard Hughes
trivial: Remove one layer of indirection in a hot path for a ~1% speedup
29 июн 2026, 19:17
29 июн 2026, 19:17
ebfaeaa
Код
Авторство
О чём код?
/* * Copyright 2018 Richard Hughes <richard@hughsie.com> * * SPDX-License-Identifier: LGPL-2.1-or-later */ #define G_LOG_DOMAIN "XbMachine" #include "config.h" #include <gio/gio.h> #include <string.h> #include "xb-machine-private.h" #include "xb-opcode-private.h" #include "xb-silo-private.h" #include "xb-stack-private.h" #include "xb-string-private.h" typedef struct { XbMachineDebugFlags debug_flags; GPtrArray *methods; /* of XbMachineMethodItem */ GPtrArray *operators; /* of XbMachineOperator */ GPtrArray *text_handlers; /* of XbMachineTextHandlerItem */ GHashTable *opcode_fixup; /* of str[XbMachineOpcodeFixupItem] */ GHashTable *opcode_tokens; /* of utf8 */ guint stack_size; } XbMachinePrivate; G_DEFINE_TYPE_WITH_PRIVATE(XbMachine, xb_machine, G_TYPE_OBJECT) #define GET_PRIVATE(o) (xb_machine_get_instance_private(o)) typedef struct { gchar *str; gsize strsz; gchar *name; } XbMachineOperator; typedef struct { XbMachineOpcodeFixupFunc fixup_cb; gpointer user_data; GDestroyNotify user_data_free; } XbMachineOpcodeFixupItem; typedef struct { XbMachineTextHandlerFunc handler_cb; gpointer user_data; GDestroyNotify user_data_free; } XbMachineTextHandlerItem; typedef struct { guint32 idx; gchar *name; guint n_opcodes; XbMachineMethodFunc method_cb; gpointer user_data; GDestroyNotify user_data_free; } XbMachineMethodItem; #define XB_MACHINE_STACK_LEVELS_MAX 20 typedef struct { XbOpcode ops[XB_MACHINE_STACK_LEVELS_MAX]; guint len; } XbOpcodeArray; static inline void xb_opcode_array_clear(XbOpcodeArray *self) { for (guint i = 0; i < self->len; i++) xb_opcode_clear(&self->ops[i]); } G_DEFINE_AUTO_CLEANUP_CLEAR_FUNC(XbOpcodeArray, xb_opcode_array_clear) /** * xb_machine_set_debug_flags: * @self: a #XbMachine * @flags: #XbMachineDebugFlags, e.g. %XB_MACHINE_DEBUG_FLAG_SHOW_STACK * * Sets the debug level of the virtual machine. * * Since: 0.1.1 **/ void xb_machine_set_debug_flags(XbMachine *self, XbMachineDebugFlags flags) { XbMachinePrivate *priv = GET_PRIVATE(self); g_return_if_fail(XB_IS_MACHINE(self)); priv->debug_flags = flags; } /** * xb_machine_add_operator: * @self: a #XbMachine * @str: operator string, e.g. `==` * @name: function name, e.g. `contains` * * Adds a new operator to the virtual machine. Operators can then be used * instead of explicit methods like `eq()`. * * You need to add a custom operator using xb_machine_add_operator() before * using xb_machine_parse(). Common operators like `<=` and `=` are built-in * and do not have to be added manually. * * Since: 0.1.1 **/ void xb_machine_add_operator(XbMachine *self, const gchar *str, const gchar *name) { XbMachineOperator *op; XbMachinePrivate *priv = GET_PRIVATE(self); g_return_if_fail(XB_IS_MACHINE(self)); g_return_if_fail(str != NULL); g_return_if_fail(name != NULL); op = g_slice_new0(XbMachineOperator); op->str = g_strdup(str); op->strsz = strlen(str); op->name = g_strdup(name); g_ptr_array_add(priv->operators, op); } /** * xb_machine_add_method: * @self: a #XbMachine * @name: function name, e.g. `contains` * @n_opcodes: minimum number of opcodes required on the stack * @method_cb: function to call * @user_data: user pointer to pass to @method_cb, or %NULL * @user_data_free: a function which gets called to free @user_data, or %NULL * * Adds a new function to the virtual machine. Registered functions can then be * used as methods. * * The @method_cb must not modify the stack it’s passed unless it’s going to * succeed. In particular, if a method call is not optimisable, it must not * modify the stack it’s passed. * * You need to add a custom function using xb_machine_add_method() before using * methods that may reference it, for example xb_machine_add_opcode_fixup(). * * Since: 0.1.1 **/ void xb_machine_add_method(XbMachine *self, const gchar *name, guint n_opcodes, XbMachineMethodFunc method_cb, gpointer user_data, GDestroyNotify user_data_free) { XbMachineMethodItem *item; XbMachinePrivate *priv = GET_PRIVATE(self); g_return_if_fail(XB_IS_MACHINE(self)); g_return_if_fail(name != NULL); g_return_if_fail(method_cb != NULL); item = g_slice_new0(XbMachineMethodItem); item->idx = priv->methods->len; item->name = g_strdup(name); item->n_opcodes = n_opcodes; item->method_cb = method_cb; item->user_data = user_data; item->user_data_free = user_data_free; g_ptr_array_add(priv->methods, item); } /** * xb_machine_add_opcode_fixup: * @self: a #XbMachine * @opcodes_sig: signature, e.g. `INTE,TEXT` * @fixup_cb: callback * @user_data: user pointer to pass to @fixup_cb * @user_data_free: a function which gets called to free @user_data, or %NULL * * Adds an opcode fixup. Fixups can be used to optimize the stack of opcodes or * to add support for a nonstandard feature, for instance supporting missing * attributes to functions. * * Since: 0.1.1 **/ void xb_machine_add_opcode_fixup(XbMachine *self, const gchar *opcodes_sig, XbMachineOpcodeFixupFunc fixup_cb, gpointer user_data, GDestroyNotify user_data_free) { XbMachineOpcodeFixupItem *item = g_slice_new0(XbMachineOpcodeFixupItem); XbMachinePrivate *priv = GET_PRIVATE(self); item->fixup_cb = fixup_cb; item->user_data = user_data; item->user_data_free = user_data_free; g_hash_table_insert(priv->opcode_fixup, g_strdup(opcodes_sig), item); } /** * xb_machine_add_text_handler: * @self: a #XbMachine * @handler_cb: callback * @user_data: user pointer to pass to @handler_cb * @user_data_free: a function which gets called to free @user_data, or %NULL * * Adds a text handler. This allows the virtual machine to support nonstandard * encoding or shorthand mnemonics for standard functions. * * Since: 0.1.1 **/ void xb_machine_add_text_handler(XbMachine *self, XbMachineTextHandlerFunc handler_cb, gpointer user_data, GDestroyNotify user_data_free) { XbMachineTextHandlerItem *item = g_slice_new0(XbMachineTextHandlerItem); XbMachinePrivate *priv = GET_PRIVATE(self); item->handler_cb = handler_cb; item->user_data = user_data; item->user_data_free = user_data_free; g_ptr_array_add(priv->text_handlers, item); } static XbMachineMethodItem * xb_machine_find_func(XbMachine *self, const gchar *func_name) { XbMachinePrivate *priv = GET_PRIVATE(self); for (guint i = 0; i < priv->methods->len; i++) { XbMachineMethodItem *item = g_ptr_array_index(priv->methods, i); if (g_strcmp0(item->name, func_name) == 0) return item; } return NULL; } /** * xb_machine_opcode_func_init: * @self: a #XbMachine * @opcode: (out caller-allocates): a stack allocated #XbOpcode to initialise * @func_name: function name, e.g. `eq` * * Initialises a stack allocated #XbOpcode for a registered function. * Some standard functions are registered by default, for instance `eq` or `ge`. * Other functions have to be added using xb_machine_add_method(). * * Returns: %TRUE if the function was found and the opcode initialised, %FALSE * otherwise * Since: 0.2.0 **/ gboolean xb_machine_opcode_func_init(XbMachine *self, XbOpcode *opcode, const gchar *func_name) { XbMachineMethodItem *item = xb_machine_find_func(self, func_name); if (item == NULL) return FALSE; xb_opcode_init(opcode, XB_OPCODE_KIND_FUNCTION, g_strdup(func_name), item->idx, g_free); return TRUE; } static gboolean xb_machine_parse_add_func(XbMachine *self, XbStack *opcodes, const gchar *func_name, guint8 level, GError **error) { XbOpcode *opcode; if (!xb_stack_push(opcodes, &opcode, error)) return FALSE; /* match opcode, which should always exist */ if (!xb_machine_opcode_func_init(self, opcode, func_name)) { if (error != NULL) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "built-in function not found: %s", func_name); } xb_stack_pop(opcodes, NULL, NULL); return FALSE; } xb_opcode_set_level(opcode, level); return TRUE; } static gboolean xb_machine_parse_add_text(XbMachine *self, XbStack *opcodes, const gchar *text, gssize text_len, guint8 level, GError **error) { XbMachinePrivate *priv = GET_PRIVATE(self); g_autofree gchar *str = NULL; guint64 val; /* NULL is perfectly valid */ if (text == NULL) { XbOpcode *opcode; if (!xb_stack_push(opcodes, &opcode, error)) return FALSE; xb_opcode_text_init_static(opcode, str); return TRUE; } /* never add empty literals */ if (text_len < 0) text_len = strlen(text); if (text_len == 0) return TRUE; /* do any additional handlers */ str = g_strndup(text, text_len); for (guint i = 0; i < priv->text_handlers->len; i++) { XbMachineTextHandlerItem *item = g_ptr_array_index(priv->text_handlers, i); gboolean handled = FALSE; guint opcodes_sz = xb_stack_get_size(opcodes); if (!item->handler_cb(self, opcodes, str, &handled, item->user_data, error)) return FALSE; if (handled) { /* ideally the XbMachineTextHandlerFunc would contain a `guint8 level` but * that is now public ABI. Just fixup the level for any added opcodes */ for (guint j = xb_stack_get_size(opcodes); j > opcodes_sz; j--) { XbOpcode *op_tmp = xb_stack_peek(opcodes, j - 1); xb_opcode_set_level(op_tmp, level); } return TRUE; } } /* quoted text */ if (text_len >= 2) { if (str[0] == '\'' && str[text_len - 1] == '\'') { g_autofree gchar *tmp = g_strndup(str + 1, text_len - 2); XbOpcode *opcode; if (!xb_stack_push(opcodes, &opcode, error)) return FALSE; xb_opcode_text_init_steal(opcode, g_steal_pointer(&tmp)); xb_opcode_set_level(opcode, level); return TRUE; } } /* indexed text */ if (text_len >= 3) { if (str[0] == '$' && str[1] == '\'' && str[text_len - 1] == '\'') { g_autofree gchar *tmp = g_strndup(str + 2, text_len - 3); XbOpcode *opcode; if (!xb_stack_push(opcodes, &opcode, error)) return FALSE; xb_opcode_init(opcode, XB_OPCODE_KIND_INDEXED_TEXT, g_steal_pointer(&tmp), XB_SILO_UNSET, g_free); xb_opcode_set_level(opcode, level); return TRUE; } } /* bind variables */ if (g_strcmp0(str, "?") == 0) { XbOpcode *opcode; if (!xb_stack_push(opcodes, &opcode, error)) return FALSE; xb_opcode_bind_init(opcode); xb_opcode_set_level(opcode, level); return TRUE; } /* check for plain integer */ if (g_ascii_string_to_unsigned(str, 10, 0, G_MAXUINT32, &val, NULL)) { XbOpcode *opcode; if (!xb_stack_push(opcodes, &opcode, error)) return FALSE; xb_opcode_integer_init(opcode, val); xb_opcode_set_level(opcode, level); return TRUE; } /* not supported */ if (error != NULL) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "cannot parse text or number `%s`", str); } return FALSE; } static gboolean xb_machine_parse_section(XbMachine *self, XbStack *opcodes, const gchar *text, gssize text_len, gboolean is_method, guint8 level, guint depth, GError **error) { XbMachinePrivate *priv = GET_PRIVATE(self); /* fall back for simplicity */ if (text_len < 0) text_len = strlen(text); if (text_len == 0) return TRUE; if (depth > XB_MACHINE_STACK_LEVELS_MAX) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "operator nesting deeper than supported"); return FALSE; } for (gssize i = 0; i < text_len; i++) { for (guint j = 0; j < priv->operators->len; j++) { XbMachineOperator *op = g_ptr_array_index(priv->operators, j); if (strncmp(text + i, op->str, op->strsz) != 0) continue; if (is_method) { XbOpcode *op_tail; const gchar *op_name = op->name; /* after then before */ if (!xb_machine_parse_section(self, opcodes, text + i + op->strsz, -1, is_method, level, depth + 1, error)) return FALSE; if (i > 0) { if (!xb_machine_parse_section(self, opcodes, text, i, FALSE, level, depth + 1, error)) return FALSE; } /* multiple "eq" sections are converted to "in" */ op_tail = xb_stack_peek_tail(opcodes); if (op_tail != NULL && _xb_opcode_get_level(op_tail) != level && g_strcmp0(op_name, "eq") == 0) op_name = "in"; if (!xb_machine_parse_add_func(self, opcodes, op_name, level, error)) return FALSE; } else { /* before then after */ if (i > 0) { if (!xb_machine_parse_section(self, opcodes, text, i, FALSE, level, depth + 1, error)) return FALSE; } if (!xb_machine_parse_section(self, opcodes, text + i + op->strsz, -1, is_method, level, depth + 1, error)) return FALSE; if (!xb_machine_parse_add_func(self, opcodes, op->name, level, error)) return FALSE; } return TRUE; } } /* nothing matched */ if (is_method) { g_autoptr(GError) error_local = NULL; if (!xb_machine_parse_add_text(self, opcodes, text, text_len, level, &error_local)) { if (priv->debug_flags & XB_MACHINE_DEBUG_FLAG_SHOW_PARSING) g_debug("Failed to add text %s, trying function", text); return xb_machine_parse_add_func(self, opcodes, text, level, error); } return TRUE; } return xb_machine_parse_add_text(self, opcodes, text, text_len, level, error); } static gboolean xb_machine_parse_sections(XbMachine *self, XbStack *opcodes, const gchar *text, gsize text_len, gboolean is_method, guint8 level, GError **error) { g_autofree gchar *tmp = NULL; if (text_len == 0) return TRUE; /* leading comma */ if (text[0] == ',') { tmp = g_strndup(text + 1, text_len - 1); } else { tmp = g_strndup(text, text_len); } for (gint i = text_len - 1; i >= 0; i--) { if (tmp[i] == ',') { tmp[i] = '\0'; if (is_method) { if (!xb_machine_parse_add_func(self, opcodes, tmp + i + 1, level, error)) return FALSE; is_method = FALSE; } else { if (!xb_machine_parse_section(self, opcodes, tmp + i + 1, -1, TRUE, level, 0, error)) return FALSE; } } } if (tmp[0] != '\0') { if (!xb_machine_parse_section(self, opcodes, tmp, -1, is_method, level, 0, error)) return FALSE; } return TRUE; } static gchar * xb_machine_get_opcodes_sig(XbMachine *self, XbStack *opcodes) { GString *str = g_string_new(NULL); for (guint i = 0; i < xb_stack_get_size(opcodes); i++) { XbOpcode *op = xb_stack_peek(opcodes, i); g_autofree gchar *sig = xb_opcode_get_sig(op); g_string_append_printf(str, "%s,", sig); } if (str->len > 0) g_string_truncate(str, str->len - 1); return g_string_free(str, FALSE); } /* @results *must* have enough space * @op is transfer full into this function */ static gboolean xb_machine_opcodes_optimize_fn(XbMachine *self, XbStack *opcodes, XbOpcode op, XbStack *results, GError **error) { XbMachineMethodItem *item; XbMachinePrivate *priv = GET_PRIVATE(self); g_autofree gchar *stack_str = NULL; g_autoptr(GError) error_local = NULL; g_auto(XbOpcode) op_result = XB_OPCODE_INIT(); g_auto(XbOpcode) op_owned = op; /* not a function */ if (_xb_opcode_get_kind(&op) != XB_OPCODE_KIND_FUNCTION) { XbOpcode *op_out; if (!xb_stack_push(results, &op_out, error)) return FALSE; *op_out = xb_opcode_steal(&op_owned); return TRUE; } /* get function, check if we have enough arguments */ item = g_ptr_array_index(priv->methods, _xb_opcode_get_val(&op)); if (item->n_opcodes > xb_stack_get_size(opcodes)) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "predicate invalid -- not enough args"); return FALSE; } /* run the method. it's only supposed to pop its arguments off the stack * if it can complete successfully */ if (!item->method_cb(self, opcodes, NULL, item->user_data, NULL, &error_local)) { XbOpcode *op_out; if (priv->debug_flags & XB_MACHINE_DEBUG_FLAG_SHOW_OPTIMIZER) { stack_str = xb_stack_to_string(opcodes); g_debug("ignoring optimized call to %s(%s): %s", item->name, stack_str, error_local->message); } if (!xb_stack_push(results, &op_out, error)) return FALSE; *op_out = xb_opcode_steal(&op_owned); return TRUE; } /* the method ran, add the result. the arguments have already been popped */ if (!xb_machine_stack_pop(self, opcodes, &op_result, error)) return FALSE; if (_xb_opcode_get_kind(&op_result) != XB_OPCODE_KIND_BOOLEAN || _xb_opcode_get_val(&op_result)) { XbOpcode *op_out; if (priv->debug_flags & XB_MACHINE_DEBUG_FLAG_SHOW_OPTIMIZER) { g_autofree gchar *tmp = xb_opcode_to_string(&op_result); g_debug("method ran, adding result %s", tmp); } if (!xb_stack_push(results, &op_out, error)) return FALSE; xb_opcode_set_level(&op_result, _xb_opcode_get_level(&op)); *op_out = xb_opcode_steal(&op_result); return TRUE; } /* the predicate will always evalulate to FALSE */ if (error != NULL) { stack_str = xb_stack_to_string(opcodes); g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "the predicate will always evalulate to FALSE: %s", stack_str); } return FALSE; } static gboolean xb_machine_opcodes_optimize(XbMachine *self, XbStack *opcodes, GError **error) { XbMachinePrivate *priv = GET_PRIVATE(self); g_autoptr(XbStack) results = xb_stack_new_inline(xb_stack_get_size(opcodes)); g_auto(XbOpcode) op = XB_OPCODE_INIT(); /* debug */ if (priv->debug_flags & XB_MACHINE_DEBUG_FLAG_SHOW_OPTIMIZER) { g_autofree gchar *str = xb_stack_to_string(opcodes); g_debug("before optimizing: %s", str); } /* process the stack in reverse order */ while (xb_machine_stack_pop(self, opcodes, &op, NULL)) { /* this takes ownership of @op */ if (!xb_machine_opcodes_optimize_fn(self, opcodes, xb_opcode_steal(&op), results, error)) return FALSE; } /* copy back the result into the opcodes stack (and reverse it) */ while (xb_stack_pop(results, &op, NULL)) { XbOpcode *op_out; if (!xb_stack_push(opcodes, &op_out, error)) return FALSE; *op_out = xb_opcode_steal(&op); } /* debug */ if (priv->debug_flags & XB_MACHINE_DEBUG_FLAG_SHOW_OPTIMIZER) { g_autofree gchar *str = xb_stack_to_string(opcodes); g_debug("after optimizing: %s", str); } return TRUE; } static gsize xb_machine_parse_text(XbMachine *self, XbStack *opcodes, const gchar *text, gsize text_len, guint8 level, GError **error) { XbMachinePrivate *priv = GET_PRIVATE(self); guint tail = 0; /* sanity check */ if (level > XB_MACHINE_STACK_LEVELS_MAX) { if (error != NULL) { g_autofree gchar *tmp = g_strndup(text, text_len); g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "nesting deeper than 20 levels supported: %s", tmp); } return G_MAXSIZE; } for (guint i = 0; i < text_len; i++) { if (priv->debug_flags & XB_MACHINE_DEBUG_FLAG_SHOW_PARSING) g_debug("LVL %u\t%u:\t\t%c", level, i, text[i]); if (text[i] == '(') { gsize j = 0; j = xb_machine_parse_text(self, opcodes, text + i + 1, text_len - i - 1, level + 1, error); if (j == G_MAXSIZE) return G_MAXSIZE; if (!xb_machine_parse_sections(self, opcodes, text + tail, i - tail, TRUE, level, error)) return G_MAXSIZE; i += j; tail = i + 1; continue; } if (text[i] == ')') { if (!xb_machine_parse_sections(self, opcodes, text + tail, i - tail, FALSE, level, error)) return G_MAXSIZE; return i + 1; } } if (level > 0) { if (error != NULL) { g_autofree gchar *tmp = g_strndup(text, text_len); g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "brackets did not match: %s", tmp); } return G_MAXSIZE; } if (!xb_machine_parse_sections(self, opcodes, text + tail, text_len - tail, FALSE, level, error)) return G_MAXSIZE; return 0; } /** * xb_machine_parse_full: * @self: a #XbMachine * @text: predicate to parse, e.g. `contains(text(),'xyx')` * @text_len: length of @text, or -1 if @text is `NUL` terminated * @flags: #XbMachineParseFlags, e.g. %XB_MACHINE_PARSE_FLAG_OPTIMIZE * @error: a #GError, or %NULL * * Parses an XPath predicate. Not all of XPath 1.0 or XPath 1.0 is supported, * and new functions and mnemonics can be added using xb_machine_add_method() * and xb_machine_add_text_handler(). * * Returns: (transfer full): opcodes, or %NULL on error * * Since: 0.1.4 **/ XbStack * xb_machine_parse_full(XbMachine *self, const gchar *text, gssize text_len, XbMachineParseFlags flags, GError **error) { XbMachineOpcodeFixupItem *item; XbMachinePrivate *priv = GET_PRIVATE(self); guint8 level = 0; g_autoptr(XbStack) opcodes = NULL; g_autofree gchar *opcodes_sig = NULL; g_return_val_if_fail(XB_IS_MACHINE(self), NULL); g_return_val_if_fail(text != NULL, NULL); g_return_val_if_fail(error == NULL || *error == NULL, NULL); /* assume NUL terminated */ if (text_len < 0) text_len = strlen(text); if (text_len == 0) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "string was zero size"); return NULL; } /* parse into opcodes */ opcodes = xb_stack_new(priv->stack_size); if (xb_machine_parse_text(self, opcodes, text, text_len, level, error) == G_MAXSIZE) return NULL; /* do any fixups */ opcodes_sig = xb_machine_get_opcodes_sig(self, opcodes); if (priv->debug_flags & XB_MACHINE_DEBUG_FLAG_SHOW_OPTIMIZER) g_debug("opcodes_sig=%s", opcodes_sig); item = g_hash_table_lookup(priv->opcode_fixup, opcodes_sig); if (item != NULL) { if (!item->fixup_cb(self, opcodes, item->user_data, error)) return NULL; } /* optimize */ if (flags & XB_MACHINE_PARSE_FLAG_OPTIMIZE) { for (guint i = 0; i < 10; i++) { guint oldsz = xb_stack_get_size(opcodes); /* Is the stack optimal already? */ if (oldsz == 1) break; if (!xb_machine_opcodes_optimize(self, opcodes, error)) return NULL; if (oldsz == xb_stack_get_size(opcodes)) break; } } /* success */ return g_steal_pointer(&opcodes); } /** * xb_machine_parse: * @self: a #XbMachine * @text: predicate to parse, e.g. `contains(text(),'xyx')` * @text_len: length of @text, or -1 if @text is `NUL` terminated * @error: a #GError, or %NULL * * Parses an XPath predicate. Not all of XPath 1.0 or XPath 1.0 is supported, * and new functions and mnemonics can be added using xb_machine_add_method() * and xb_machine_add_text_handler(). * * Returns: (transfer full): opcodes, or %NULL on error * * Since: 0.1.1 **/ XbStack * xb_machine_parse(XbMachine *self, const gchar *text, gssize text_len, GError **error) { return xb_machine_parse_full(self, text, text_len, XB_MACHINE_PARSE_FLAG_OPTIMIZE, error); } static void xb_machine_debug_show_stack(XbMachine *self, XbStack *stack) { g_autofree gchar *str = NULL; if (xb_stack_get_size(stack) == 0) { g_debug("stack is empty"); return; } str = xb_stack_to_string(stack); g_debug("stack: %s", str); } static gboolean xb_machine_run_func(XbMachine *self, XbStack *stack, XbOpcode *opcode, gpointer exec_data, GError **error) { XbMachinePrivate *priv = GET_PRIVATE(self); XbMachineMethodItem *item = g_ptr_array_index(priv->methods, _xb_opcode_get_val(opcode)); /* optional debugging */ if (priv->debug_flags & XB_MACHINE_DEBUG_FLAG_SHOW_STACK) { g_autofree gchar *str = xb_opcode_to_string(opcode); g_debug("running: %s", str); xb_machine_debug_show_stack(self, stack); } /* check we have enough stack elements */ if (item->n_opcodes > xb_stack_get_size(stack)) { if (error != NULL) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "function required %u arguments, stack only has %u", item->n_opcodes, xb_stack_get_size(stack)); } return FALSE; } if (!item->method_cb(self, stack, NULL, item->user_data, exec_data, error)) { g_prefix_error(error, "failed to call %s(): ", item->name); return FALSE; } return TRUE; } /** * xb_machine_run: * @self: a #XbMachine * @opcodes: a #XbStack of opcodes * @result: (out): return status after running @opcodes * @exec_data: per-run user data that is passed to all the #XbMachineMethodFunc functions * @error: a #GError, or %NULL * * Runs a set of opcodes on the virtual machine. * * It is safe to call this function from a different thread to the one that * created the #XbMachine. * * Returns: a new #XbOpcode, or %NULL * * Since: 0.1.1 * Deprecated: 0.3.0: Use xb_machine_run_with_bindings() instead. **/ gboolean xb_machine_run(XbMachine *self, XbStack *opcodes, gboolean *result, gpointer exec_data, GError **error) { return xb_machine_run_with_bindings(self, opcodes, NULL, result, exec_data, error); } /** * xb_machine_run_with_bindings: * @self: a #XbMachine * @opcodes: a #XbStack of opcodes * @bindings: (nullable) (transfer none): values bound to opcodes of type * %XB_OPCODE_KIND_BOUND_INTEGER or %XB_OPCODE_KIND_BOUND_TEXT, or %NULL if * the query doesn’t need any bound values * @result: (out): return status after running @opcodes * @exec_data: per-run user data that is passed to all the #XbMachineMethodFunc functions * @error: a #GError, or %NULL * * Runs a set of opcodes on the virtual machine, using the bound values given in * @bindings to substitute for bound opcodes. * * It is safe to call this function from a different thread to the one that * created the #XbMachine. * * Returns: a new #XbOpcode, or %NULL * * Since: 0.3.0 **/ gboolean xb_machine_run_with_bindings(XbMachine *self, XbStack *opcodes, XbValueBindings *bindings, gboolean *result, gpointer exec_data, GError **error) { XbMachinePrivate *priv = GET_PRIVATE(self); g_auto(XbOpcode) opcode_success = XB_OPCODE_INIT(); g_autoptr(XbStack) stack = NULL; guint opcodes_stack_size = xb_stack_get_size(opcodes); guint bound_opcode_idx = 0; g_return_val_if_fail(XB_IS_MACHINE(self), FALSE); g_return_val_if_fail(opcodes != NULL, FALSE); g_return_val_if_fail(result != NULL, FALSE); g_return_val_if_fail(error == NULL || *error == NULL, FALSE); /* process each opcode */ stack = xb_stack_new_inline(priv->stack_size); for (guint i = 0; i < opcodes_stack_size; i++) { XbOpcode *opcode = xb_stack_peek(opcodes, i); XbOpcodeKind kind = _xb_opcode_get_kind(opcode); /* replace post-0.3.0-style bound opcodes with their bound values */ if (bindings != NULL && (kind == XB_OPCODE_KIND_BOUND_TEXT || kind == XB_OPCODE_KIND_BOUND_INDEXED_TEXT || kind == XB_OPCODE_KIND_BOUND_INTEGER)) { XbOpcode *machine_opcode; if (!xb_stack_push(stack, &machine_opcode, error)) return FALSE; if (!xb_value_bindings_lookup_opcode(bindings, bound_opcode_idx++, machine_opcode)) { if (error != NULL) { g_autofree gchar *tmp1 = xb_stack_to_string(stack); g_autofree gchar *tmp2 = xb_stack_to_string(opcodes); g_set_error( error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "opcode was not bound at runtime, stack:%s, opcodes:%s", tmp1, tmp2); } return FALSE; } continue; } if (kind == XB_OPCODE_KIND_BOUND_UNSET) { if (error != NULL) { g_autofree gchar *tmp1 = xb_stack_to_string(stack); g_autofree gchar *tmp2 = xb_stack_to_string(opcodes); g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "opcode was not bound at runtime, stack:%s, opcodes:%s", tmp1, tmp2); } return FALSE; } /* process the stack */ if (kind == XB_OPCODE_KIND_FUNCTION) { if (!xb_machine_run_func(self, stack, opcode, exec_data, error)) return FALSE; continue; } /* add to stack; this uses a const copy of the input opcode, * so ownership of anything allocated on the heap remains with * the caller */ if (kind == XB_OPCODE_KIND_TEXT || kind == XB_OPCODE_KIND_BOOLEAN || kind == XB_OPCODE_KIND_INTEGER || kind == XB_OPCODE_KIND_INDEXED_TEXT || (bindings == NULL && (kind == XB_OPCODE_KIND_BOUND_TEXT || kind == XB_OPCODE_KIND_BOUND_INDEXED_TEXT || kind == XB_OPCODE_KIND_BOUND_INTEGER))) { XbOpcode *machine_opcode; if (!xb_stack_push(stack, &machine_opcode, error)) return FALSE; *machine_opcode = *opcode; machine_opcode->destroy_func = NULL; continue; } /* invalid */ if (error != NULL) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "opcode kind %u not recognised", kind); } return FALSE; } /* the stack should have one boolean left on the stack */ if (xb_stack_get_size(stack) != 1) { if (error != NULL) { g_autofree gchar *tmp = xb_stack_to_string(stack); g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "%u opcodes remain on the stack (%s)", xb_stack_get_size(stack), tmp); } return FALSE; } if (!xb_stack_pop(stack, &opcode_success, error)) return FALSE; if (_xb_opcode_get_kind(&opcode_success) != XB_OPCODE_KIND_BOOLEAN) { if (error != NULL) { g_autofree gchar *tmp = xb_stack_to_string(stack); g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "Expected boolean, got: %s", tmp); } return FALSE; } *result = _xb_opcode_get_val(&opcode_success); /* success */ return TRUE; } /** * xb_machine_stack_pop: * @self: a #XbMachine * @stack: a #XbStack * @opcode_out: (out caller-allocates) (optional): return location for the popped #XbOpcode * @error: a #GError, or %NULL * * Pops an opcode from the stack. * * Returns: %TRUE if popping succeeded, %FALSE if the stack was empty already * * Since: 0.2.0 **/ gboolean xb_machine_stack_pop(XbMachine *self, XbStack *stack, XbOpcode *opcode_out, GError **error) { XbMachinePrivate *priv = GET_PRIVATE(self); gboolean retval; if (priv->debug_flags & XB_MACHINE_DEBUG_FLAG_SHOW_STACK) { XbOpcode *opcode_peek = xb_stack_peek(stack, xb_stack_get_size(stack) - 1); if (opcode_peek != NULL) { g_autofree gchar *str = xb_opcode_to_string(opcode_peek); g_debug("popping: %s", str); } else { g_debug("not popping: stack empty"); } } retval = xb_stack_pop(stack, opcode_out, error); if (priv->debug_flags & XB_MACHINE_DEBUG_FLAG_SHOW_STACK) xb_machine_debug_show_stack(self, stack); return retval; } /** * xb_machine_stack_pop_two: (skip): **/ gboolean xb_machine_stack_pop_two(XbMachine *self, XbStack *stack, XbOpcode *opcode1_out, XbOpcode *opcode2_out, GError **error) { XbMachinePrivate *priv = GET_PRIVATE(self); gboolean retval; if (priv->debug_flags & XB_MACHINE_DEBUG_FLAG_SHOW_STACK) { XbOpcode *opcode_peek1 = xb_stack_peek(stack, xb_stack_get_size(stack) - 1); XbOpcode *opcode_peek2 = xb_stack_peek(stack, xb_stack_get_size(stack) - 2); if (opcode_peek1 != NULL && opcode_peek2 != NULL) { g_autofree gchar *str1 = xb_opcode_to_string(opcode_peek1); g_autofree gchar *str2 = xb_opcode_to_string(opcode_peek2); g_debug("popping1: %s", str1); g_debug("popping2: %s", str2); } else { g_debug("not popping: stack empty"); } } retval = xb_stack_pop_two(stack, opcode1_out, opcode2_out, error); if (priv->debug_flags & XB_MACHINE_DEBUG_FLAG_SHOW_STACK) xb_machine_debug_show_stack(self, stack); return retval; } /** * xb_machine_stack_push: * @self: a #XbMachine * @stack: a #XbStack * @opcode_out: (out) (nullable): return location for the new #XbOpcode * @error: return location for a #GError, or %NULL * * Pushes a new empty opcode onto the end of the stack. A pointer to the opcode * is returned in @opcode_out so that the caller can initialise it. * * If the stack reaches its maximum size, %G_IO_ERROR_NO_SPACE will be returned. * * Returns: %TRUE if a new empty opcode was returned, or %FALSE if the stack has * reached its maximum size * Since: 0.2.0 **/ gboolean xb_machine_stack_push(XbMachine *self, XbStack *stack, XbOpcode **opcode_out, GError **error) { XbMachinePrivate *priv = GET_PRIVATE(self); if (G_UNLIKELY(priv->debug_flags & XB_MACHINE_DEBUG_FLAG_SHOW_STACK)) { g_debug("pushing generic opcode"); } return xb_stack_push(stack, opcode_out, error); } /** * xb_machine_stack_push_text: * @self: a #XbMachine * @stack: a #XbStack * @str: text literal * @error: return location for a #GError, or %NULL * * Adds a text literal to the stack, copying @str. * * Errors are as for xb_machine_stack_push(). * * Returns: %TRUE on success, %FALSE otherwise * Since: 0.2.0 **/ gboolean xb_machine_stack_push_text(XbMachine *self, XbStack *stack, const gchar *str, GError **error) { XbMachinePrivate *priv = GET_PRIVATE(self); XbOpcode *opcode; if (G_UNLIKELY(priv->debug_flags & XB_MACHINE_DEBUG_FLAG_SHOW_STACK)) g_debug("pushing: %s", str); if (!xb_stack_push(stack, &opcode, error)) return FALSE; xb_opcode_text_init(opcode, str); if (G_UNLIKELY(priv->debug_flags & XB_MACHINE_DEBUG_FLAG_SHOW_STACK)) xb_machine_debug_show_stack(self, stack); return TRUE; } /** * xb_machine_stack_push_text_static: * @self: a #XbMachine * @stack: a #XbStack * @str: text literal * @error: return location for a #GError, or %NULL * * Adds static text literal to the stack. * * Errors are as for xb_machine_stack_push(). * * Returns: %TRUE on success, %FALSE otherwise * Since: 0.2.0 **/ gboolean xb_machine_stack_push_text_static(XbMachine *self, XbStack *stack, const gchar *str, GError **error) { XbOpcode *opcode; XbMachinePrivate *priv = GET_PRIVATE(self); if (G_UNLIKELY(priv->debug_flags & XB_MACHINE_DEBUG_FLAG_SHOW_STACK)) g_debug("pushing: %s", str); if (!xb_stack_push(stack, &opcode, error)) return FALSE; xb_opcode_text_init_static(opcode, str); if (priv->debug_flags & XB_MACHINE_DEBUG_FLAG_SHOW_STACK) xb_machine_debug_show_stack(self, stack); return TRUE; } /** * xb_machine_stack_push_text_steal: * @self: a #XbMachine * @stack: a #XbStack * @str: (transfer full): text literal * @error: return location for a #GError, or %NULL * * Adds a stolen text literal to the stack. * * Errors are as for xb_machine_stack_push(). * * Returns: %TRUE on success, %FALSE otherwise * Since: 0.2.0 **/ gboolean xb_machine_stack_push_text_steal(XbMachine *self, XbStack *stack, gchar *str, GError **error) { XbMachinePrivate *priv = GET_PRIVATE(self); XbOpcode *opcode; g_autofree gchar *str_stolen = g_steal_pointer(&str); if (G_UNLIKELY(priv->debug_flags & XB_MACHINE_DEBUG_FLAG_SHOW_STACK)) g_debug("pushing: %s", str_stolen); if (!xb_stack_push(stack, &opcode, error)) return FALSE; xb_opcode_text_init_steal(opcode, g_steal_pointer(&str_stolen)); if (priv->debug_flags & XB_MACHINE_DEBUG_FLAG_SHOW_STACK) xb_machine_debug_show_stack(self, stack); return TRUE; } /** * xb_machine_stack_push_integer: * @self: a #XbMachine * @stack: a #XbStack * @val: integer literal * @error: return location for a #GError, or %NULL * * Adds an integer literal to the stack. * * Errors are as for xb_machine_stack_push(). * * Returns: %TRUE on success, %FALSE otherwise * Since: 0.2.0 **/ gboolean xb_machine_stack_push_integer(XbMachine *self, XbStack *stack, guint32 val, GError **error) { XbMachinePrivate *priv = GET_PRIVATE(self); XbOpcode *opcode; if (G_UNLIKELY(priv->debug_flags & XB_MACHINE_DEBUG_FLAG_SHOW_STACK)) g_debug("pushing: %u", val); if (!xb_stack_push(stack, &opcode, error)) return FALSE; xb_opcode_integer_init(opcode, val); if (priv->debug_flags & XB_MACHINE_DEBUG_FLAG_SHOW_STACK) xb_machine_debug_show_stack(self, stack); return TRUE; } /** * xb_machine_set_stack_size: * @self: a #XbMachine * @stack_size: integer * * Sets the maximum stack size used for the machine. * * The stack size will be affective for new jobs started with xb_machine_run() * and xb_machine_parse(). * * Since: 0.1.3 **/ void xb_machine_set_stack_size(XbMachine *self, guint stack_size) { XbMachinePrivate *priv = GET_PRIVATE(self); g_return_if_fail(XB_IS_MACHINE(self)); g_return_if_fail(stack_size != 0); g_return_if_fail(stack_size < 5000); priv->stack_size = stack_size; } /** * xb_machine_get_stack_size: * @self: a #XbMachine * * Gets the maximum stack size used for the machine. * * Returns: integer * * Since: 0.1.3 **/ guint xb_machine_get_stack_size(XbMachine *self) { XbMachinePrivate *priv = GET_PRIVATE(self); g_return_val_if_fail(XB_IS_MACHINE(self), 0); return priv->stack_size; } static const gchar * xb_machine_intern_token(XbMachine *self, const gchar *str) { XbMachinePrivate *priv = GET_PRIVATE(self); const gchar *tmp; gchar *newstr; /* existing value */ tmp = g_hash_table_lookup(priv->opcode_tokens, str); if (tmp != NULL) return tmp; /* add as both key and value */ newstr = g_strdup(str); g_hash_table_add(priv->opcode_tokens, newstr); return newstr; } /* private */ void xb_machine_opcode_tokenize(XbMachine *self, XbOpcode *op) { const gchar *str; g_auto(GStrv) tokens = NULL; g_auto(GStrv) ascii_tokens = NULL; /* use the fast token path even if there are no valid tokens */ xb_opcode_add_flag(op, XB_OPCODE_FLAG_TOKENIZED); str = _xb_opcode_get_str(op); tokens = g_str_tokenize_and_fold(str, NULL, &ascii_tokens); for (guint i = 0; tokens[i] != NULL; i++) { if (!xb_string_token_valid(tokens[i])) continue; xb_opcode_append_token(op, xb_machine_intern_token(self, tokens[i])); } for (guint i = 0; ascii_tokens[i] != NULL; i++) { if (!xb_string_token_valid(ascii_tokens[i])) continue; xb_opcode_append_token(op, xb_machine_intern_token(self, ascii_tokens[i])); } } typedef gboolean (*OpcodeCheckFunc)(XbOpcode *op); static gboolean _xb_opcode_cmp_val_or_str(XbOpcode *op) { return xb_opcode_cmp_str(op) || _xb_opcode_cmp_int(op) || _xb_opcode_cmp_itx(op); } static gboolean xb_machine_check_one_arg(XbStack *stack, OpcodeCheckFunc f, GError **error) { XbOpcode *head; head = xb_stack_peek_tail(stack); if (head == NULL || !f(head)) { if (error != NULL) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "%s type not supported", (head != NULL) ? xb_opcode_kind_to_string(_xb_opcode_get_kind(head)) : "(null)"); } return FALSE; } return TRUE; } static gboolean xb_machine_check_two_args(XbStack *stack, OpcodeCheckFunc f1, OpcodeCheckFunc f2, GError **error) { XbOpcode *head1 = NULL; XbOpcode *head2 = NULL; guint stack_size = xb_stack_get_size(stack); if (stack_size >= 2) { head1 = xb_stack_peek(stack, stack_size - 1); head2 = xb_stack_peek(stack, stack_size - 2); } if (head1 == NULL || head2 == NULL || !f1(head1) || !f2(head2)) { if (error != NULL) { g_set_error( error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "%s:%s types not supported", (head1 != NULL) ? xb_opcode_kind_to_string(_xb_opcode_get_kind(head1)) : "(null)", (head2 != NULL) ? xb_opcode_kind_to_string(_xb_opcode_get_kind(head2)) : "(null)"); } return FALSE; } return TRUE; } static gboolean xb_machine_func_and_cb(XbMachine *self, XbStack *stack, gboolean *result, gpointer user_data, gpointer exec_data, GError **error) { g_auto(XbOpcode) op1 = XB_OPCODE_INIT(); g_auto(XbOpcode) op2 = XB_OPCODE_INIT(); if (!xb_machine_check_two_args(stack, _xb_opcode_cmp_int, _xb_opcode_cmp_int, error)) return FALSE; if (!xb_machine_stack_pop_two(self, stack, &op1, &op2, error)) return FALSE; /* INTE:INTE */ return xb_stack_push_bool(stack, _xb_opcode_get_val(&op1) && _xb_opcode_get_val(&op2), error); } static gboolean xb_machine_func_or_cb(XbMachine *self, XbStack *stack, gboolean *result, gpointer user_data, gpointer exec_data, GError **error) { g_auto(XbOpcode) op1 = XB_OPCODE_INIT(); g_auto(XbOpcode) op2 = XB_OPCODE_INIT(); if (!xb_machine_check_two_args(stack, _xb_opcode_cmp_int, _xb_opcode_cmp_int, error)) return FALSE; if (!xb_machine_stack_pop_two(self, stack, &op1, &op2, error)) return FALSE; /* INTE:INTE */ return xb_stack_push_bool(stack, _xb_opcode_get_val(&op1) || _xb_opcode_get_val(&op2), error); } static gboolean xb_machine_func_eq_cb(XbMachine *self, XbStack *stack, gboolean *result, gpointer user_data, gpointer exec_data, GError **error) { XbMachinePrivate *priv = GET_PRIVATE(self); g_auto(XbOpcode) op1 = XB_OPCODE_INIT(); g_auto(XbOpcode) op2 = XB_OPCODE_INIT(); if (!xb_machine_check_two_args(stack, _xb_opcode_cmp_val_or_str, _xb_opcode_cmp_val_or_str, error)) return FALSE; if (!xb_machine_stack_pop_two(self, stack, &op1, &op2, error)) return FALSE; /* INTE:INTE */ if ((_xb_opcode_cmp_int(&op1) && _xb_opcode_cmp_int(&op2)) || (_xb_opcode_cmp_itx(&op1) && _xb_opcode_cmp_itx(&op2))) return xb_stack_push_bool(stack, _xb_opcode_get_val(&op1) == _xb_opcode_get_val(&op2), error); /* TEXT:TEXT */ if (xb_opcode_cmp_str(&op1) && xb_opcode_cmp_str(&op2)) { if (priv->debug_flags & XB_MACHINE_DEBUG_FLAG_SHOW_SLOW_PATH) { g_autofree gchar *str1 = xb_opcode_to_string(&op1); g_autofree gchar *str2 = xb_opcode_to_string(&op2); g_debug("slow strcmp fallback of %s:%s", str1, str2); } return xb_stack_push_bool( stack, g_strcmp0(_xb_opcode_get_str(&op1), _xb_opcode_get_str(&op2)) == 0, error); } /* INTE:TEXT */ if (_xb_opcode_cmp_int(&op1) && xb_opcode_cmp_str(&op2)) { guint64 val = 0; if (_xb_opcode_get_str(&op2) == NULL) return xb_stack_push_bool(stack, FALSE, error); if (priv->debug_flags & XB_MACHINE_DEBUG_FLAG_SHOW_SLOW_PATH) { g_autofree gchar *str1 = xb_opcode_to_string(&op1); g_autofree gchar *str2 = xb_opcode_to_string(&op2); g_debug("slow atoi fallback of %s:%s", str1, str2); } if (!g_ascii_string_to_unsigned(_xb_opcode_get_str(&op2), 10, 0, G_MAXUINT32, &val, error)) { return FALSE; } return xb_stack_push_bool(stack, val == _xb_opcode_get_val(&op1), error); } /* TEXT:INTE */ if (xb_opcode_cmp_str(&op1) && _xb_opcode_cmp_int(&op2)) { guint64 val = 0; if (_xb_opcode_get_str(&op1) == NULL) return xb_stack_push_bool(stack, FALSE, error); if (priv->debug_flags & XB_MACHINE_DEBUG_FLAG_SHOW_SLOW_PATH) { g_autofree gchar *str1 = xb_opcode_to_string(&op1); g_autofree gchar *str2 = xb_opcode_to_string(&op2); g_debug("slow atoi fallback of %s:%s", str1, str2); } if (!g_ascii_string_to_unsigned(_xb_opcode_get_str(&op1), 10, 0, G_MAXUINT32, &val, error)) { return FALSE; } return xb_stack_push_bool(stack, val == _xb_opcode_get_val(&op2), error); } /* should have been checked above */ if (error != NULL) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "cannot compare %s and %s", xb_opcode_kind_to_string(_xb_opcode_get_kind(&op1)), xb_opcode_kind_to_string(_xb_opcode_get_kind(&op2))); } return FALSE; } static gboolean xb_machine_func_ne_cb(XbMachine *self, XbStack *stack, gboolean *result, gpointer user_data, gpointer exec_data, GError **error) { g_auto(XbOpcode) op1 = XB_OPCODE_INIT(); g_auto(XbOpcode) op2 = XB_OPCODE_INIT(); if (!xb_machine_check_two_args(stack, _xb_opcode_cmp_val_or_str, _xb_opcode_cmp_val_or_str, error)) return FALSE; if (!xb_machine_stack_pop_two(self, stack, &op1, &op2, error)) return FALSE; /* INTE:INTE */ if ((_xb_opcode_cmp_int(&op1) && _xb_opcode_cmp_int(&op2)) || (_xb_opcode_cmp_itx(&op1) && _xb_opcode_cmp_itx(&op2))) { return xb_stack_push_bool(stack, _xb_opcode_get_val(&op1) != _xb_opcode_get_val(&op2), error); } /* TEXT:TEXT */ if (xb_opcode_cmp_str(&op1) && xb_opcode_cmp_str(&op2)) { return xb_stack_push_bool( stack, g_strcmp0(_xb_opcode_get_str(&op1), _xb_opcode_get_str(&op2)) != 0, error); } /* INTE:TEXT */ if (_xb_opcode_cmp_int(&op1) && xb_opcode_cmp_str(&op2)) { guint64 val = 0; if (_xb_opcode_get_str(&op2) == NULL) return xb_stack_push_bool(stack, FALSE, error); if (!g_ascii_string_to_unsigned(_xb_opcode_get_str(&op2), 10, 0, G_MAXUINT32, &val, error)) { return FALSE; } return xb_stack_push_bool(stack, val != _xb_opcode_get_val(&op1), error); } /* TEXT:INTE */ if (xb_opcode_cmp_str(&op1) && _xb_opcode_cmp_int(&op2)) { guint64 val = 0; if (_xb_opcode_get_str(&op1) == NULL) return xb_stack_push_bool(stack, FALSE, error); if (!g_ascii_string_to_unsigned(_xb_opcode_get_str(&op1), 10, 0, G_MAXUINT32, &val, error)) { return FALSE; } return xb_stack_push_bool(stack, val != _xb_opcode_get_val(&op2), error); } /* should have been checked above */ if (error != NULL) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "cannot compare %s and %s", xb_opcode_kind_to_string(_xb_opcode_get_kind(&op1)), xb_opcode_kind_to_string(_xb_opcode_get_kind(&op2))); } return FALSE; } static gboolean xb_machine_func_lt_cb(XbMachine *self, XbStack *stack, gboolean *result, gpointer user_data, gpointer exec_data, GError **error) { g_auto(XbOpcode) op1 = XB_OPCODE_INIT(); g_auto(XbOpcode) op2 = XB_OPCODE_INIT(); if (!xb_machine_check_two_args(stack, _xb_opcode_cmp_val_or_str, _xb_opcode_cmp_val_or_str, error)) return FALSE; if (!xb_machine_stack_pop_two(self, stack, &op1, &op2, error)) return FALSE; /* INTE:INTE */ if (_xb_opcode_cmp_int(&op1) && _xb_opcode_cmp_int(&op2)) { return xb_stack_push_bool(stack, _xb_opcode_get_val(&op2) < _xb_opcode_get_val(&op1), error); } /* TEXT:TEXT */ if (xb_opcode_cmp_str(&op1) && xb_opcode_cmp_str(&op2)) { return xb_stack_push_bool( stack, g_strcmp0(_xb_opcode_get_str(&op2), _xb_opcode_get_str(&op1)) < 0, error); } /* INTE:TEXT */ if (_xb_opcode_cmp_int(&op1) && xb_opcode_cmp_str(&op2)) { guint64 val = 0; if (_xb_opcode_get_str(&op2) == NULL) return xb_stack_push_bool(stack, FALSE, error); if (!g_ascii_string_to_unsigned(_xb_opcode_get_str(&op2), 10, 0, G_MAXUINT32, &val, error)) { return FALSE; } return xb_stack_push_bool(stack, val < _xb_opcode_get_val(&op1), error); } /* TEXT:INTE */ if (xb_opcode_cmp_str(&op1) && _xb_opcode_cmp_int(&op2)) { guint64 val = 0; if (_xb_opcode_get_str(&op1) == NULL) return xb_stack_push_bool(stack, FALSE, error); if (!g_ascii_string_to_unsigned(_xb_opcode_get_str(&op1), 10, 0, G_MAXUINT32, &val, error)) { return FALSE; } return xb_stack_push_bool(stack, val < _xb_opcode_get_val(&op2), error); } /* should have been checked above */ if (error != NULL) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "cannot compare %s and %s", xb_opcode_kind_to_string(_xb_opcode_get_kind(&op1)), xb_opcode_kind_to_string(_xb_opcode_get_kind(&op2))); } return FALSE; } static gboolean xb_machine_func_gt_cb(XbMachine *self, XbStack *stack, gboolean *result, gpointer user_data, gpointer exec_data, GError **error) { g_auto(XbOpcode) op1 = XB_OPCODE_INIT(); g_auto(XbOpcode) op2 = XB_OPCODE_INIT(); if (!xb_machine_check_two_args(stack, _xb_opcode_cmp_val_or_str, _xb_opcode_cmp_val_or_str, error)) return FALSE; if (!xb_machine_stack_pop_two(self, stack, &op1, &op2, error)) return FALSE; /* INTE:INTE */ if (_xb_opcode_cmp_int(&op1) && _xb_opcode_cmp_int(&op2)) { return xb_stack_push_bool(stack, _xb_opcode_get_val(&op2) > _xb_opcode_get_val(&op1), error); } /* TEXT:TEXT */ if (xb_opcode_cmp_str(&op1) && xb_opcode_cmp_str(&op2)) { return xb_stack_push_bool( stack, g_strcmp0(_xb_opcode_get_str(&op2), _xb_opcode_get_str(&op1)) > 0, error); } /* INTE:TEXT */ if (_xb_opcode_cmp_int(&op1) && xb_opcode_cmp_str(&op2)) { guint64 val = 0; if (_xb_opcode_get_str(&op2) == NULL) return xb_stack_push_bool(stack, FALSE, error); if (!g_ascii_string_to_unsigned(_xb_opcode_get_str(&op2), 10, 0, G_MAXUINT32, &val, error)) { return FALSE; } return xb_stack_push_bool(stack, val > _xb_opcode_get_val(&op1), error); } /* TEXT:INTE */ if (xb_opcode_cmp_str(&op1) && _xb_opcode_cmp_int(&op2)) { guint64 val = 0; if (_xb_opcode_get_str(&op1) == NULL) return xb_stack_push_bool(stack, FALSE, error); if (!g_ascii_string_to_unsigned(_xb_opcode_get_str(&op1), 10, 0, G_MAXUINT32, &val, error)) { return FALSE; } return xb_stack_push_bool(stack, val > _xb_opcode_get_val(&op2), error); } /* should have been checked above */ if (error != NULL) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "cannot compare %s and %s", xb_opcode_kind_to_string(_xb_opcode_get_kind(&op1)), xb_opcode_kind_to_string(_xb_opcode_get_kind(&op2))); } return FALSE; } static gboolean xb_machine_func_le_cb(XbMachine *self, XbStack *stack, gboolean *result, gpointer user_data, gpointer exec_data, GError **error) { g_auto(XbOpcode) op1 = XB_OPCODE_INIT(); g_auto(XbOpcode) op2 = XB_OPCODE_INIT(); if (!xb_machine_check_two_args(stack, _xb_opcode_cmp_val_or_str, _xb_opcode_cmp_val_or_str, error)) return FALSE; if (!xb_machine_stack_pop_two(self, stack, &op1, &op2, error)) return FALSE; /* INTE:INTE */ if (_xb_opcode_cmp_int(&op1) && _xb_opcode_cmp_int(&op2)) { return xb_stack_push_bool(stack, _xb_opcode_get_val(&op2) <= _xb_opcode_get_val(&op1), error); } /* TEXT:TEXT */ if (xb_opcode_cmp_str(&op1) && xb_opcode_cmp_str(&op2)) { return xb_stack_push_bool( stack, g_strcmp0(_xb_opcode_get_str(&op2), _xb_opcode_get_str(&op1)) <= 0, error); return TRUE; } /* INTE:TEXT */ if (_xb_opcode_cmp_int(&op1) && xb_opcode_cmp_str(&op2)) { guint64 val = 0; if (_xb_opcode_get_str(&op2) == NULL) return xb_stack_push_bool(stack, FALSE, error); if (!g_ascii_string_to_unsigned(_xb_opcode_get_str(&op2), 10, 0, G_MAXUINT32, &val, error)) { return FALSE; } return xb_stack_push_bool(stack, val <= _xb_opcode_get_val(&op1), error); } /* TEXT:INTE */ if (xb_opcode_cmp_str(&op1) && _xb_opcode_cmp_int(&op2)) { guint64 val = 0; if (_xb_opcode_get_str(&op1) == NULL) return xb_stack_push_bool(stack, FALSE, error); if (!g_ascii_string_to_unsigned(_xb_opcode_get_str(&op1), 10, 0, G_MAXUINT32, &val, error)) { return FALSE; } return xb_stack_push_bool(stack, val <= _xb_opcode_get_val(&op2), error); } /* should have been checked above */ if (error != NULL) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "cannot compare %s and %s", xb_opcode_kind_to_string(_xb_opcode_get_kind(&op1)), xb_opcode_kind_to_string(_xb_opcode_get_kind(&op2))); } return FALSE; } static gboolean xb_machine_func_lower_cb(XbMachine *self, XbStack *stack, gboolean *result, gpointer user_data, gpointer exec_data, GError **error) { g_auto(XbOpcode) op = XB_OPCODE_INIT(); if (!xb_machine_check_one_arg(stack, xb_opcode_cmp_str, error)) return FALSE; if (!xb_machine_stack_pop(self, stack, &op, error)) return FALSE; /* TEXT */ if (_xb_opcode_get_str(&op) == NULL) return xb_machine_stack_push_text_static(self, stack, NULL, error); return xb_machine_stack_push_text_steal(self, stack, g_utf8_strdown(_xb_opcode_get_str(&op), -1), error); } static gboolean xb_machine_func_upper_cb(XbMachine *self, XbStack *stack, gboolean *result, gpointer user_data, gpointer exec_data, GError **error) { g_auto(XbOpcode) op = XB_OPCODE_INIT(); if (!xb_machine_check_one_arg(stack, xb_opcode_cmp_str, error)) return FALSE; if (!xb_machine_stack_pop(self, stack, &op, error)) return FALSE; /* TEXT */ if (_xb_opcode_get_str(&op) == NULL) return xb_machine_stack_push_text_static(self, stack, NULL, error); return xb_machine_stack_push_text_steal(self, stack, g_utf8_strup(_xb_opcode_get_str(&op), -1), error); } static gboolean xb_machine_func_not_cb(XbMachine *self, XbStack *stack, gboolean *result, gpointer user_data, gpointer exec_data, GError **error) { g_auto(XbOpcode) op = XB_OPCODE_INIT(); if (!xb_machine_check_one_arg(stack, _xb_opcode_cmp_val_or_str, error)) return FALSE; if (!xb_machine_stack_pop(self, stack, &op, error)) return FALSE; /* TEXT */ if (xb_opcode_cmp_str(&op)) return xb_stack_push_bool(stack, _xb_opcode_get_str(&op) == NULL, error); /* INTE */ if (_xb_opcode_cmp_int(&op)) return xb_stack_push_bool(stack, _xb_opcode_get_val(&op) == 0, error); /* should have been checked above */ if (error != NULL) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "cannot invert %s", xb_opcode_kind_to_string(_xb_opcode_get_kind(&op))); } return FALSE; } static gboolean xb_machine_func_ge_cb(XbMachine *self, XbStack *stack, gboolean *result, gpointer user_data, gpointer exec_data, GError **error) { g_auto(XbOpcode) op1 = XB_OPCODE_INIT(); g_auto(XbOpcode) op2 = XB_OPCODE_INIT(); if (!xb_machine_check_two_args(stack, _xb_opcode_cmp_val_or_str, _xb_opcode_cmp_val_or_str, error)) return FALSE; if (!xb_machine_stack_pop_two(self, stack, &op1, &op2, error)) return FALSE; /* TEXT:TEXT */ if (xb_opcode_cmp_str(&op1) && xb_opcode_cmp_str(&op2)) { return xb_stack_push_bool( stack, g_strcmp0(_xb_opcode_get_str(&op2), _xb_opcode_get_str(&op1)) >= 0, error); } /* INTE:INTE */ if (_xb_opcode_cmp_int(&op1) && _xb_opcode_cmp_int(&op2)) { return xb_stack_push_bool(stack, _xb_opcode_get_val(&op2) >= _xb_opcode_get_val(&op1), error); } /* INTE:TEXT */ if (_xb_opcode_cmp_int(&op1) && xb_opcode_cmp_str(&op2)) { guint64 val = 0; if (_xb_opcode_get_str(&op2) == NULL) return xb_stack_push_bool(stack, FALSE, error); if (!g_ascii_string_to_unsigned(_xb_opcode_get_str(&op2), 10, 0, G_MAXUINT32, &val, error)) { return FALSE; } return xb_stack_push_bool(stack, val >= _xb_opcode_get_val(&op1), error); } /* TEXT:INTE */ if (xb_opcode_cmp_str(&op1) && _xb_opcode_cmp_int(&op2)) { guint64 val = 0; if (_xb_opcode_get_str(&op1) == NULL) return xb_stack_push_bool(stack, FALSE, error); if (!g_ascii_string_to_unsigned(_xb_opcode_get_str(&op1), 10, 0, G_MAXUINT32, &val, error)) { return FALSE; } return xb_stack_push_bool(stack, val >= _xb_opcode_get_val(&op2), error); } /* should have been checked above */ if (error != NULL) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "cannot compare %s and %s", xb_opcode_kind_to_string(_xb_opcode_get_kind(&op1)), xb_opcode_kind_to_string(_xb_opcode_get_kind(&op2))); } return FALSE; } static gboolean xb_machine_func_contains_cb(XbMachine *self, XbStack *stack, gboolean *result, gpointer user_data, gpointer exec_data, GError **error) { g_auto(XbOpcode) op1 = XB_OPCODE_INIT(); g_auto(XbOpcode) op2 = XB_OPCODE_INIT(); if (!xb_machine_check_two_args(stack, xb_opcode_cmp_str, xb_opcode_cmp_str, error)) return FALSE; if (!xb_machine_stack_pop_two(self, stack, &op1, &op2, error)) return FALSE; /* TEXT:TEXT */ return xb_stack_push_bool( stack, xb_string_contains(_xb_opcode_get_str(&op2), _xb_opcode_get_str(&op1)), error); } static gboolean xb_machine_func_starts_with_cb(XbMachine *self, XbStack *stack, gboolean *result, gpointer user_data, gpointer exec_data, GError **error) { g_auto(XbOpcode) op1 = XB_OPCODE_INIT(); g_auto(XbOpcode) op2 = XB_OPCODE_INIT(); if (!xb_machine_check_two_args(stack, xb_opcode_cmp_str, xb_opcode_cmp_str, error)) return FALSE; if (!xb_machine_stack_pop_two(self, stack, &op1, &op2, error)) return FALSE; /* TEXT:TEXT */ if (_xb_opcode_get_str(&op1) == NULL || _xb_opcode_get_str(&op2) == NULL) return xb_stack_push_bool(stack, FALSE, error); return xb_stack_push_bool( stack, g_str_has_prefix(_xb_opcode_get_str(&op2), _xb_opcode_get_str(&op1)), error); } static gboolean xb_machine_func_ends_with_cb(XbMachine *self, XbStack *stack, gboolean *result, gpointer user_data, gpointer exec_data, GError **error) { g_auto(XbOpcode) op1 = XB_OPCODE_INIT(); g_auto(XbOpcode) op2 = XB_OPCODE_INIT(); if (!xb_machine_check_two_args(stack, xb_opcode_cmp_str, xb_opcode_cmp_str, error)) return FALSE; if (!xb_machine_stack_pop_two(self, stack, &op1, &op2, error)) return FALSE; /* TEXT:TEXT */ if (_xb_opcode_get_str(&op1) == NULL || _xb_opcode_get_str(&op2) == NULL) return xb_stack_push_bool(stack, FALSE, error); return xb_stack_push_bool( stack, g_str_has_suffix(_xb_opcode_get_str(&op2), _xb_opcode_get_str(&op1)), error); } static gboolean xb_machine_func_number_cb(XbMachine *self, XbStack *stack, gboolean *result, gpointer user_data, gpointer exec_data, GError **error) { guint64 val = 0; g_auto(XbOpcode) op = XB_OPCODE_INIT(); if (!xb_machine_check_one_arg(stack, xb_opcode_cmp_str, error)) return FALSE; if (!xb_machine_stack_pop(self, stack, &op, error)) return FALSE; /* TEXT */ if (_xb_opcode_get_str(&op) == NULL) return xb_stack_push_bool(stack, FALSE, error); if (!g_ascii_string_to_unsigned(_xb_opcode_get_str(&op), 10, 0, G_MAXUINT32, &val, error)) { return FALSE; } return xb_machine_stack_push_integer(self, stack, val, error); } static gboolean xb_machine_func_strlen_cb(XbMachine *self, XbStack *stack, gboolean *result, gpointer user_data, gpointer exec_data, GError **error) { g_auto(XbOpcode) op = XB_OPCODE_INIT(); if (!xb_machine_check_one_arg(stack, xb_opcode_cmp_str, error)) return FALSE; if (!xb_machine_stack_pop(self, stack, &op, error)) return FALSE; /* TEXT */ if (_xb_opcode_get_str(&op) == NULL) return xb_stack_push_bool(stack, FALSE, error); return xb_machine_stack_push_integer(self, stack, strlen(_xb_opcode_get_str(&op)), error); } static gboolean xb_machine_func_string_cb(XbMachine *self, XbStack *stack, gboolean *result, gpointer user_data, gpointer exec_data, GError **error) { gchar *tmp; g_auto(XbOpcode) op = XB_OPCODE_INIT(); if (!xb_machine_check_one_arg(stack, _xb_opcode_cmp_int, error)) return FALSE; if (!xb_machine_stack_pop(self, stack, &op, error)) return FALSE; /* INTE */ tmp = g_strdup_printf("%" G_GUINT32_FORMAT, _xb_opcode_get_val(&op)); return xb_machine_stack_push_text_steal(self, stack, tmp, error); } static gboolean xb_machine_func_in_cb(XbMachine *self, XbStack *stack, gboolean *result, gpointer user_data, gpointer exec_data, GError **error) { XbOpcode *op_needle; const gchar *haystack[XB_MACHINE_STACK_LEVELS_MAX + 1] = {NULL}; g_auto(XbOpcode) op = XB_OPCODE_INIT(); g_auto(XbOpcodeArray) haystack_ops = {.len = 0}; guint8 level = G_MAXUINT8; guint nr_args = 0; /* get the size of the haystack, ensuring we only have strings */ if (xb_stack_get_size(stack) == 0) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "not enough arguments for in()"); return FALSE; } for (guint i = xb_stack_get_size(stack) - 1; i > 0; i--) { XbOpcode *op_tmp = xb_stack_peek(stack, i); /* this is a hack as we do not get the current @level */ if (level != G_MAXUINT8) { if (_xb_opcode_get_level(op_tmp) != level) break; } else { level = _xb_opcode_get_level(op_tmp); } if (!xb_opcode_cmp_str(op_tmp)) { if (error != NULL) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "%s type not supported", xb_opcode_kind_to_string(_xb_opcode_get_kind(op_tmp))); } return FALSE; } nr_args++; } /* sanity check */ if (nr_args > XB_MACHINE_STACK_LEVELS_MAX) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "too many arguments for in(): %u", nr_args); return FALSE; } /* ensure the needle is also a string */ op_needle = xb_stack_peek(stack, xb_stack_get_size(stack) - (nr_args + 1)); if (!xb_opcode_cmp_str(op_needle)) { if (error != NULL) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "%s type not supported", xb_opcode_kind_to_string(_xb_opcode_get_kind(op_needle))); } return FALSE; } /* build the haystack */ for (guint i = 0; i < nr_args; i++) { if (!xb_machine_stack_pop(self, stack, &haystack_ops.ops[i], error)) return FALSE; haystack_ops.len++; haystack[i] = _xb_opcode_get_str(&haystack_ops.ops[i]); } /* get the needle */ if (!xb_machine_stack_pop(self, stack, &op, error)) return FALSE; /* a NULL needle (e.g. from a missing attribute) can never match */ if (_xb_opcode_get_str(&op) == NULL) return xb_stack_push_bool(stack, FALSE, error); /* found */ for (guint i = 0; i < nr_args; i++) { if (g_strcmp0(haystack[i], _xb_opcode_get_str(&op)) == 0) return xb_stack_push_bool(stack, TRUE, error); } return xb_stack_push_bool(stack, FALSE, error); } static void xb_machine_opcode_fixup_free(XbMachineOpcodeFixupItem *item) { if (item->user_data_free != NULL) item->user_data_free(item->user_data); g_slice_free(XbMachineOpcodeFixupItem, item); } static void xb_machine_func_free(XbMachineMethodItem *item) { if (item->user_data_free != NULL) item->user_data_free(item->user_data); g_free(item->name); g_slice_free(XbMachineMethodItem, item); } static void xb_machine_text_handler_free(XbMachineTextHandlerItem *item) { if (item->user_data_free != NULL) item->user_data_free(item->user_data); g_slice_free(XbMachineTextHandlerItem, item); } static void xb_machine_operator_free(XbMachineOperator *op) { g_free(op->str); g_free(op->name); g_slice_free(XbMachineOperator, op); } static void xb_machine_init(XbMachine *self) { XbMachinePrivate *priv = GET_PRIVATE(self); priv->stack_size = 10; priv->methods = g_ptr_array_new_with_free_func((GDestroyNotify)xb_machine_func_free); priv->operators = g_ptr_array_new_with_free_func((GDestroyNotify)xb_machine_operator_free); priv->text_handlers = g_ptr_array_new_with_free_func((GDestroyNotify)xb_machine_text_handler_free); priv->opcode_fixup = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, (GDestroyNotify)xb_machine_opcode_fixup_free); priv->opcode_tokens = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL); /* built-in functions */ xb_machine_add_method(self, "and", 2, xb_machine_func_and_cb, NULL, NULL); xb_machine_add_method(self, "or", 2, xb_machine_func_or_cb, NULL, NULL); xb_machine_add_method(self, "eq", 2, xb_machine_func_eq_cb, NULL, NULL); xb_machine_add_method(self, "ne", 2, xb_machine_func_ne_cb, NULL, NULL); xb_machine_add_method(self, "lt", 2, xb_machine_func_lt_cb, NULL, NULL); xb_machine_add_method(self, "gt", 2, xb_machine_func_gt_cb, NULL, NULL); xb_machine_add_method(self, "le", 2, xb_machine_func_le_cb, NULL, NULL); xb_machine_add_method(self, "ge", 2, xb_machine_func_ge_cb, NULL, NULL); xb_machine_add_method(self, "not", 1, xb_machine_func_not_cb, NULL, NULL); xb_machine_add_method(self, "lower-case", 1, xb_machine_func_lower_cb, NULL, NULL); xb_machine_add_method(self, "upper-case", 1, xb_machine_func_upper_cb, NULL, NULL); xb_machine_add_method(self, "contains", 2, xb_machine_func_contains_cb, NULL, NULL); xb_machine_add_method(self, "starts-with", 2, xb_machine_func_starts_with_cb, NULL, NULL); xb_machine_add_method(self, "ends-with", 2, xb_machine_func_ends_with_cb, NULL, NULL); xb_machine_add_method(self, "string", 1, xb_machine_func_string_cb, NULL, NULL); xb_machine_add_method(self, "number", 1, xb_machine_func_number_cb, NULL, NULL); xb_machine_add_method(self, "string-length", 1, xb_machine_func_strlen_cb, NULL, NULL); xb_machine_add_method(self, "in", 0, xb_machine_func_in_cb, NULL, NULL); /* built-in operators */ xb_machine_add_operator(self, " and ", "and"); xb_machine_add_operator(self, " or ", "or"); xb_machine_add_operator(self, "&&", "and"); xb_machine_add_operator(self, "||", "or"); xb_machine_add_operator(self, "!=", "ne"); xb_machine_add_operator(self, "<=", "le"); xb_machine_add_operator(self, ">=", "ge"); xb_machine_add_operator(self, "==", "eq"); xb_machine_add_operator(self, "=", "eq"); xb_machine_add_operator(self, ">", "gt"); xb_machine_add_operator(self, "<", "lt"); } static void xb_machine_finalize(GObject *obj) { XbMachine *self = XB_MACHINE(obj); XbMachinePrivate *priv = GET_PRIVATE(self); g_ptr_array_unref(priv->methods); g_ptr_array_unref(priv->operators); g_ptr_array_unref(priv->text_handlers); g_hash_table_unref(priv->opcode_fixup); g_hash_table_unref(priv->opcode_tokens); G_OBJECT_CLASS(xb_machine_parent_class)->finalize(obj); } static void xb_machine_class_init(XbMachineClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS(klass); object_class->finalize = xb_machine_finalize; } /** * xb_machine_new: * * Creates a new virtual machine. * * Returns: a new #XbMachine * * Since: 0.1.1 **/ XbMachine * xb_machine_new(void) { return g_object_new(XB_TYPE_MACHINE, NULL); }