[gjs/wip/coverage-2: 2/9] Add GjsReflectedScript
- From: Jasper St. Pierre <jstpierre src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gjs/wip/coverage-2: 2/9] Add GjsReflectedScript
- Date: Wed, 15 Jan 2014 23:01:17 +0000 (UTC)
commit 560138a7c39f000d3b16e43a6a7929eeb5a2f1bd
Author: Sam Spilsbury <smspillaz gmail com>
Date: Wed Jan 1 15:25:07 2014 -0800
Add GjsReflectedScript
GjsReflectedScript contains some useful information about a
script, including an array with all available function names,
array of all branches and an array of all executable lines.
https://bugzilla.gnome.org/show_bug.cgi?id=721246
Makefile-insttest.am | 1 +
Makefile-test.am | 3 +-
Makefile.am | 3 +
gjs/reflected-script-private.h | 56 +++
gjs/reflected-script.cpp | 764 +++++++++++++++++++++++++++++++++
gjs/reflected-script.h | 93 ++++
installed-tests/js/testInfoReflect.js | 528 +++++++++++++++++++++++
modules/infoReflect.js | 338 +++++++++++++++
modules/modules.gresource.xml.in | 1 +
test/gjs-test-reflected-script.cpp | 419 ++++++++++++++++++
test/gjs-tests-add-funcs.h | 25 ++
test/gjs-tests.cpp | 4 +
12 files changed, 2234 insertions(+), 1 deletions(-)
---
diff --git a/Makefile-insttest.am b/Makefile-insttest.am
index c1ea941..f736537 100644
--- a/Makefile-insttest.am
+++ b/Makefile-insttest.am
@@ -109,6 +109,7 @@ dist_jstests_DATA += \
installed-tests/js/testGObjectClass.js \
installed-tests/js/testJS1_8.js \
installed-tests/js/testLang.js \
+ installed-tests/js/testInfoReflect.js \
installed-tests/js/testLocale.js \
installed-tests/js/testMainloop.js \
installed-tests/js/testMetaClass.js \
diff --git a/Makefile-test.am b/Makefile-test.am
index 27b2204..895ebfc 100644
--- a/Makefile-test.am
+++ b/Makefile-test.am
@@ -22,7 +22,8 @@ gjs_tests_LDADD = \
$(GJSTESTS_LIBS)
gjs_tests_SOURCES = \
- test/gjs-tests.cpp
+ test/gjs-tests.cpp \
+ test/gjs-test-reflected-script.cpp
check-local: gjs-tests
@test -z "${TEST_PROGS}" || ${GTESTER} --verbose ${TEST_PROGS} ${TEST_PROGS_OPTIONS}
diff --git a/Makefile.am b/Makefile.am
index 5059966..3028ead 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -36,6 +36,7 @@ nobase_gjs_module_include_HEADERS = \
gjs/type-module.h \
gjs/mem.h \
gjs/native.h \
+ gjs/reflected-script.h \
gi/ns.h \
gi/object.h \
gi/foreign.h \
@@ -55,6 +56,7 @@ nobase_gjs_module_include_HEADERS = \
noinst_HEADERS += \
gjs/jsapi-private.h \
+ gjs/reflected-script-private.h \
gi/proxyutils.h \
util/crash.h \
util/hash-x32.h \
@@ -106,6 +108,7 @@ libgjs_la_SOURCES = \
gjs/importer.cpp \
gjs/gi.h \
gjs/gi.cpp \
+ gjs/reflected-script.cpp \
gjs/jsapi-private.cpp \
gjs/jsapi-util.cpp \
gjs/jsapi-dynamic-class.cpp \
diff --git a/gjs/reflected-script-private.h b/gjs/reflected-script-private.h
new file mode 100644
index 0000000..444beb2
--- /dev/null
+++ b/gjs/reflected-script-private.h
@@ -0,0 +1,56 @@
+/*
+ * Copyright © 2014 Endless Mobile, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * Authored By: Sam Spilsbury <sam endlessm com>
+ */
+#ifndef GJS_REFLECTED_SCRIPT_PRIVATE_H
+#define GJS_REFLECTED_SCRIPT_PRIVATE_H
+
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+typedef struct _GjsReflectedScriptBranchInfo {
+ unsigned int branch_point;
+ GArray *branch_alternatives;
+} GjsReflectedScriptBranchInfo;
+
+GjsReflectedScriptBranchInfo *
+gjs_reflected_script_branch_info_new(unsigned int branch_point,
+ GArray *alternatives);
+
+void
+gjs_reflected_script_branch_info_destroy(gpointer info);
+
+typedef struct _GjsReflectedScriptFunctionInfo {
+ unsigned int n_params;
+ unsigned int line_number;
+ char *name;
+} GjsReflectedScriptFunctionInfo;
+
+/* This function takes ownership of the string */
+GjsReflectedScriptFunctionInfo *
+gjs_reflected_script_function_info_new(char *name,
+ unsigned int line_number,
+ unsigned int n_params);
+
+void
+gjs_reflected_script_function_info_destroy(gpointer info);
+
+G_END_DECLS
+
+#endif
diff --git a/gjs/reflected-script.cpp b/gjs/reflected-script.cpp
new file mode 100644
index 0000000..336145c
--- /dev/null
+++ b/gjs/reflected-script.cpp
@@ -0,0 +1,764 @@
+/*
+ * Copyright © 2014 Endless Mobile, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * Authored By: Sam Spilsbury <sam endlessm com>
+ */
+#include <glib-object.h>
+
+#include <gio/gio.h>
+
+#include <gjs/gjs.h>
+#include <gjs/jsapi-util.h>
+#include <gjs/compat.h>
+#include <gjs/gjs-module.h>
+
+#include <gjs/reflected-script.h>
+#include "reflected-script-private.h"
+
+struct _GjsReflectedScriptPrivate {
+ /* External context where the reflection happens. We take
+ * a reference to it so that we can still do evaluation even
+ * if the original owner of the context goes away
+ *
+ * One important precondition is that the context needs to
+ * be "initialized" to a state that we expect it to be in,
+ * eg, we need to have loaded the infoReflect.js script
+ * and set up some global variables. If it hasn't then
+ * this will trigger an assertion */
+ GjsContext *reflection_context;
+
+ char *script_filename;
+
+ /* Array of strings, null-terminated */
+ GArray *all_function_names;
+
+ /* Array of GjsReflectedScriptBranchInfo */
+ GArray *all_branches;
+
+ /* Sorted array of unsigned int */
+ GArray *all_expression_lines;
+
+ /* Number of lines */
+ unsigned int n_lines;
+
+ /* A flag which indicates whether or not reflection
+ * data has been gathered for this script yet. Reflect.parse
+ * can be a super-expensive operation for large scripts so
+ * we should perform it on-demand when we actually need to */
+ gboolean reflection_performed;
+};
+
+G_DEFINE_TYPE_WITH_PRIVATE(GjsReflectedScript,
+ gjs_reflected_script,
+ G_TYPE_OBJECT)
+
+enum {
+ PROP_0,
+ PROP_SCRIPT_FILENAME,
+ PROP_REFLECTION_CONTEXT,
+ PROP_N
+};
+
+static GParamSpec *properties[PROP_N];
+
+static void ensure_script_reflected(GjsReflectedScript *script);
+
+GjsReflectedScriptBranchInfo *
+gjs_reflected_script_branch_info_new(unsigned int branch_point,
+ GArray *alternatives)
+{
+ GjsReflectedScriptBranchInfo *info = g_new0(GjsReflectedScriptBranchInfo, 1);
+ info->branch_point = branch_point;
+ info->branch_alternatives = alternatives;
+ return info;
+}
+
+void
+gjs_reflected_script_branch_info_destroy(gpointer info_data)
+{
+ GjsReflectedScriptBranchInfo *info = (GjsReflectedScriptBranchInfo *) info_data;
+ g_array_free(info->branch_alternatives, TRUE);
+ g_free(info);
+}
+
+unsigned int
+gjs_reflected_script_branch_info_get_branch_point(const GjsReflectedScriptBranchInfo *info)
+{
+ return info->branch_point;
+}
+
+const unsigned int *
+gjs_reflected_script_branch_info_get_branch_alternatives(const GjsReflectedScriptBranchInfo *info,
+ unsigned int *n)
+{
+ g_return_val_if_fail(n, NULL);
+
+ *n = info->branch_alternatives->len;
+ return (unsigned int *) info->branch_alternatives->data;
+}
+
+GjsReflectedScriptFunctionInfo *
+gjs_reflected_script_function_info_new(char *name,
+ unsigned int line_number,
+ unsigned int n_params)
+{
+ GjsReflectedScriptFunctionInfo *info = (GjsReflectedScriptFunctionInfo *)
g_new0(GjsReflectedScriptFunctionInfo, 1);
+ info->name = name;
+ info->line_number = line_number;
+ info->n_params = n_params;
+
+ return info;
+}
+
+unsigned int
+gjs_reflected_script_function_info_get_line_number(const GjsReflectedScriptFunctionInfo *info)
+{
+ return info->line_number;
+}
+
+unsigned int
+gjs_reflected_script_function_info_get_n_params(const GjsReflectedScriptFunctionInfo *info)
+{
+ return info->n_params;
+}
+
+const char *
+gjs_reflected_script_function_info_get_name(const GjsReflectedScriptFunctionInfo *info)
+{
+ return info->name;
+}
+
+void
+gjs_reflected_script_function_info_destroy(gpointer info_data)
+{
+ GjsReflectedScriptFunctionInfo *info = (GjsReflectedScriptFunctionInfo *) info_data;
+
+ if (info->name)
+ g_free(info->name);
+
+ g_free(info);
+}
+
+
+const GjsReflectedScriptBranchInfo **
+gjs_reflected_script_get_branches(GjsReflectedScript *script)
+{
+ g_return_val_if_fail(script, NULL);
+
+ GjsReflectedScriptPrivate *priv = (GjsReflectedScriptPrivate *)
gjs_reflected_script_get_instance_private(script);
+
+ ensure_script_reflected(script);
+
+ g_assert(priv->all_branches);
+ return (const GjsReflectedScriptBranchInfo **) priv->all_branches->data;\
+}
+
+const GjsReflectedScriptFunctionInfo **
+gjs_reflected_script_get_functions(GjsReflectedScript *script)
+{
+ g_return_val_if_fail(script, NULL);
+
+ GjsReflectedScriptPrivate *priv = (GjsReflectedScriptPrivate *)
gjs_reflected_script_get_instance_private(script);
+
+ ensure_script_reflected(script);
+
+ g_assert(priv->all_function_names);
+ return (const GjsReflectedScriptFunctionInfo **) priv->all_function_names->data;
+}
+
+const unsigned int *
+gjs_reflected_script_get_expression_lines(GjsReflectedScript *script,
+ unsigned int *n)
+{
+ g_return_val_if_fail(script, NULL);
+ g_return_val_if_fail(n, NULL);
+
+ g_return_val_if_fail(n, NULL);
+
+ GjsReflectedScriptPrivate *priv = (GjsReflectedScriptPrivate *)
gjs_reflected_script_get_instance_private(script);
+
+ ensure_script_reflected(script);
+
+ g_assert(priv->all_expression_lines);
+ if (priv->all_expression_lines) {
+ *n = priv->all_expression_lines->len;
+ return (const unsigned int *) priv->all_expression_lines->data;
+ }
+}
+
+typedef gboolean (*ConvertAndInsertJSVal) (GArray *array,
+ JSContext *context,
+ jsval *element);
+
+static gboolean
+get_array_from_js_value(JSContext *context,
+ jsval *value,
+ size_t array_element_size,
+ GDestroyNotify element_clear_func,
+ ConvertAndInsertJSVal inserter,
+ GArray **out_array)
+{
+ g_return_val_if_fail(out_array != NULL, FALSE);
+ g_return_val_if_fail(*out_array == NULL, FALSE);
+
+ JSObject *js_array = JSVAL_TO_OBJECT(*value);
+
+ if (!JS_IsArrayObject(context, js_array)) {
+ g_critical("Returned object from is not an array");
+ return FALSE;
+ }
+
+ /* We're not preallocating any space here at the moment until
+ * we have some profiling data that suggests a good size to
+ * preallocate to.
+ *
+ * TODO: Preallocating would be nice. */
+ GArray *script_functions_array = g_array_new(TRUE, TRUE, array_element_size);
+ u_int32_t script_funtions_array_len;
+
+ if (element_clear_func)
+ g_array_set_clear_func(script_functions_array, element_clear_func);
+
+ if (JS_GetArrayLength(context, js_array, &script_funtions_array_len)) {
+ u_int32_t i = 0;
+ for (; i < script_funtions_array_len; ++i) {
+ jsval element;
+ if (!JS_GetElement(context, js_array, i, &element)) {
+ g_array_unref(script_functions_array);
+ gjs_throw(context, "Failed to get function names array element %i", i);
+ return FALSE;
+ }
+
+ if (!(inserter(script_functions_array, context, &element))) {
+ g_array_unref(script_functions_array);
+ gjs_throw(context, "Failed to convert array element %i", i);
+ return FALSE;
+ }
+ }
+ }
+
+ *out_array = script_functions_array;
+
+ return TRUE;
+}
+
+static GArray *
+call_js_function_for_array_return(JSContext *context,
+ JSObject *object,
+ size_t array_element_size,
+ GDestroyNotify element_clear_func,
+ ConvertAndInsertJSVal inserter,
+ const char *function_name,
+ jsval *ast)
+{
+ GArray *array = NULL;
+ jsval rval;
+ if (!JS_CallFunctionName(context, object, function_name, 1, ast, &rval)) {
+ gjs_log_exception(context);
+ return NULL;
+ }
+
+ if (!get_array_from_js_value(context,
+ &rval,
+ array_element_size,
+ element_clear_func,
+ inserter,
+ &array)) {
+ gjs_log_exception(context);
+ return NULL;
+ }
+
+ return array;
+}
+
+static void
+clear_reflected_script_function_info(gpointer info_location)
+{
+ GjsReflectedScriptFunctionInfo **info_ptr = (GjsReflectedScriptFunctionInfo **) info_location;
+
+ gjs_reflected_script_function_info_destroy(*info_ptr);
+}
+
+static gboolean
+convert_and_insert_function_decl(GArray *array,
+ JSContext *context,
+ jsval *element)
+{
+ JSObject *object = JSVAL_TO_OBJECT(*element);
+
+ if (!object) {
+ gjs_throw(context, "Converting element to object failed");
+ return FALSE;
+ }
+
+ jsval line_number_property_value;
+ if (!JS_GetProperty(context, object, "line", &line_number_property_value) ||
+ !JSVAL_IS_INT(line_number_property_value)) {
+ gjs_throw(context, "Failed to get line property for function object");
+ return FALSE;
+ }
+
+ unsigned int line_number = JSVAL_TO_INT(line_number_property_value);
+
+ jsval n_params_property_value;
+ if (!JS_GetProperty(context, object, "n_params", &n_params_property_value) ||
+ !JSVAL_IS_INT(n_params_property_value)) {
+ gjs_throw(context, "Failed to get n_params property for function object");
+ return FALSE;
+ }
+
+ unsigned int n_params = JSVAL_TO_INT(n_params_property_value);
+
+ jsval function_name_property_value;
+
+ if (!JS_GetProperty(context, object, "name", &function_name_property_value)) {
+ gjs_throw(context, "Failed to get name property for function object");
+ return FALSE;
+ }
+
+ char *utf8_string;
+
+ if (JSVAL_IS_STRING(function_name_property_value)) {
+ if (!gjs_string_to_utf8(context,
+ function_name_property_value,
+ &utf8_string)) {
+ gjs_throw(context, "Failed to convert function_name to string");
+ return FALSE;
+ }
+ } else if (JSVAL_IS_NULL(function_name_property_value)) {
+ utf8_string = NULL;
+ } else {
+ gjs_throw(context, "Unexpected type for function_name");
+ return FALSE;
+ }
+
+
+ GjsReflectedScriptFunctionInfo *info =
+ gjs_reflected_script_function_info_new(utf8_string,
+ line_number,
+ n_params);
+
+ g_array_append_val(array, info);
+
+ return TRUE;
+}
+
+static GArray *
+get_script_functions_from_reflection(JSContext *context,
+ JSObject *global,
+ jsval *ast)
+{
+ return call_js_function_for_array_return(context,
+ global,
+ sizeof(GjsReflectedScriptFunctionInfo *),
+ clear_reflected_script_function_info,
+ convert_and_insert_function_decl,
+ "functionsForAST",
+ ast);
+}
+
+static gboolean
+convert_and_insert_unsigned_int(GArray *array,
+ JSContext *context,
+ jsval *element)
+{
+ if (!JSVAL_IS_INT(*element)) {
+ g_critical("Array element is not an integer");
+ return FALSE;
+ }
+
+ unsigned int element_integer = JSVAL_TO_INT(*element);
+ g_array_append_val(array, element_integer);
+ return TRUE;
+}
+
+static int
+uint_compare(gconstpointer left,
+ gconstpointer right)
+{
+ unsigned int *left_int = (unsigned int *) left;
+ unsigned int *right_int = (unsigned int *) right;
+
+ return *left_int - *right_int;
+}
+
+static GArray *
+get_all_lines_with_executable_expressions_from_reflection(JSContext *context,
+ JSObject *global,
+ jsval *ast)
+{
+ GArray *all_expressions = call_js_function_for_array_return(context,
+ global,
+ sizeof(unsigned int),
+ NULL,
+ convert_and_insert_unsigned_int,
+ "expressionLinesForAST",
+ ast);
+
+ /* Sort, just to be sure */
+ g_array_sort(all_expressions, uint_compare);
+ return all_expressions;
+}
+
+static void
+gjs_reflected_script_branch_info_clear(gpointer branch_info_location)
+{
+ GjsReflectedScriptBranchInfo **info_ptr = (GjsReflectedScriptBranchInfo **) branch_info_location;
+ gjs_reflected_script_branch_info_destroy(*info_ptr);
+}
+
+static gboolean
+convert_and_insert_branch_info(GArray *array,
+ JSContext *context,
+ jsval *element)
+{
+ if (!JSVAL_IS_OBJECT(*element)) {
+ gjs_throw(context, "Array element is not an object");
+ return FALSE;
+ }
+
+ JSObject *object = JSVAL_TO_OBJECT(*element);
+
+ if (!object) {
+ gjs_throw(context, "Converting element to object failed");
+ return FALSE;
+ }
+
+ jsval branch_point_value;
+ int32_t branch_point;
+
+ if (!JS_GetProperty(context, object, "point", &branch_point_value) ||
+ !JSVAL_IS_INT(branch_point_value)) {
+ gjs_throw(context, "Failed to get point property from element");
+ return FALSE;
+ }
+
+ branch_point = JSVAL_TO_INT(branch_point_value);
+
+ jsval branch_exits_value;
+ GArray *branch_exists_array = NULL;
+
+ if (!JS_GetProperty(context, object, "exits", &branch_exits_value) ||
+ !JSVAL_IS_OBJECT(branch_exits_value)) {
+ gjs_throw(context, "Failed to get exits property from element");
+ return FALSE;
+ }
+
+ if (!get_array_from_js_value(context,
+ &branch_exits_value,
+ sizeof(unsigned int),
+ NULL,
+ convert_and_insert_unsigned_int,
+ &branch_exists_array)) {
+ /* Already logged the exception, no need to do anything here */
+ return FALSE;
+ }
+
+ GjsReflectedScriptBranchInfo *info = gjs_reflected_script_branch_info_new(branch_point,
+ branch_exists_array);
+
+ g_array_append_val(array, info);
+
+ return TRUE;
+}
+
+static GArray *
+get_script_branches_from_reflection(JSContext *context,
+ JSObject *global,
+ jsval *ast)
+{
+ return call_js_function_for_array_return(context,
+ global,
+ sizeof(GjsReflectedScriptBranchInfo *),
+ gjs_reflected_script_branch_info_clear,
+ convert_and_insert_branch_info,
+ "branchesForAST",
+ ast);
+}
+
+static unsigned int
+count_lines_in_script(const char *data)
+{
+ int lines = 1;
+ for (; *data; ++data)
+ if (*data == '\n')
+ ++lines;
+ return lines;
+}
+
+static JSString *
+load_script_for_reflection(GjsContext *context,
+ const char *filename,
+ int *start_line_number,
+ unsigned int *script_n_lines)
+{
+ JSContext *js_context = (JSContext *) gjs_context_get_native_context(context);
+ GFile *script_file = g_file_new_for_commandline_arg(filename);
+
+ if (!g_file_query_exists(script_file, NULL)) {
+ g_object_unref(script_file);
+ gjs_throw(js_context, "Script: %s does not exist!", filename);
+ return NULL;
+ }
+
+ char *original_script_contents;
+ gsize script_contents_len;
+ if (!g_file_load_contents(script_file,
+ NULL,
+ &original_script_contents,
+ &script_contents_len,
+ NULL,
+ NULL)) {
+ g_object_unref(script_file);
+ gjs_throw(js_context, "Failed to get script contents for %s", filename);
+ return FALSE;
+ }
+
+ /* Number of lines in the script must be based on the original script contents
+ * as we get line numbers relative to the starting line there */
+ *script_n_lines = count_lines_in_script(original_script_contents);
+
+ /* gjs_script_unix_shebang will modify start_line_number if necessary */
+ const char *modified_script_contents =
+ gjs_strip_unix_shebang(original_script_contents,
+ (gssize *) &script_contents_len,
+ start_line_number);
+
+ JSString *str = JS_NewStringCopyZ(js_context, modified_script_contents);
+
+ g_free(original_script_contents);
+ g_object_unref(script_file);
+
+ return str;
+}
+
+static gboolean
+perform_reflection_within_compartment(GjsContext *internal_context,
+ GjsReflectedScript *script)
+{
+ GjsReflectedScriptPrivate *priv = (GjsReflectedScriptPrivate *)
gjs_reflected_script_get_instance_private(script);
+ JSContext *js_context = (JSContext *) gjs_context_get_native_context(internal_context);
+ JSObject *global = JS_GetGlobalObject(js_context);
+
+ JSAutoCompartment ac(js_context, global);
+
+ int start_line_number = 1;
+ unsigned int script_n_lines;
+
+ JSString *str = load_script_for_reflection(internal_context,
+ priv->script_filename,
+ &start_line_number,
+ &script_n_lines);
+
+ if (!str)
+ return FALSE;
+
+ jsval reflect_object_value;
+ if (!JS_GetProperty(js_context, global, "Reflect", &reflect_object_value) ||
+ !JSVAL_IS_OBJECT(reflect_object_value)) {
+ gjs_throw(js_context, "'Reflect' object not found in context");
+ return FALSE;
+ }
+
+ JSObject *reflect_object = JSVAL_TO_OBJECT(reflect_object_value);
+ JSObject *reflect_options_object = JS_NewObject(js_context, NULL, NULL, NULL);
+
+ jsval loc_value = BOOLEAN_TO_JSVAL(JS_TRUE);
+ jsval line_value = INT_TO_JSVAL(start_line_number);
+
+ JS_SetProperty(js_context, reflect_options_object, "loc", &loc_value);
+ JS_SetProperty(js_context, reflect_options_object, "line", &line_value);
+
+ jsval parseArgv[] = {
+ STRING_TO_JSVAL(str),
+ OBJECT_TO_JSVAL(reflect_options_object)
+ };
+ jsval ast_value;
+
+ if (!JS_CallFunctionName(js_context, reflect_object, "parse", 2, parseArgv, &ast_value)) {
+ gjs_throw(js_context, "Failed to call Reflect.parse");
+ return FALSE;
+ }
+
+ priv->all_function_names = get_script_functions_from_reflection(js_context,
+ global,
+ &ast_value);
+ priv->all_branches = get_script_branches_from_reflection(js_context,
+ global,
+ &ast_value);
+ priv->all_expression_lines = get_all_lines_with_executable_expressions_from_reflection(js_context,
+ global,
+ &ast_value);
+ priv->n_lines = script_n_lines;
+
+ return TRUE;
+}
+
+static void
+ensure_script_reflected(GjsReflectedScript *script)
+{
+ GjsReflectedScriptPrivate *priv = (GjsReflectedScriptPrivate *)
gjs_reflected_script_get_instance_private(script);
+
+ if (priv->reflection_performed)
+ return;
+
+ if (!perform_reflection_within_compartment(priv->reflection_context, script)) {
+ g_warning("Reflecting script %s failed", priv->script_filename);
+ /* If the reflection failed, we should make sure that the the reflection
+ * details have sane defaults */
+ priv->all_branches = g_array_new(TRUE, TRUE, sizeof(GjsReflectedScriptBranchInfo));
+ priv->all_function_names = g_array_new(TRUE, TRUE, sizeof(GjsReflectedScriptFunctionInfo));
+ priv->all_expression_lines = g_array_new(TRUE, TRUE, sizeof(unsigned int));
+ priv->n_lines = 0;
+ }
+
+ priv->reflection_performed = TRUE;
+}
+
+GjsContext *
+gjs_reflected_script_create_reflection_context()
+{
+ static const char *resource_path = "resource:///org/gnome/gjs/modules/infoReflect.js";
+
+ GjsContext *context = gjs_context_new();
+
+ if (!gjs_context_eval_file(context,
+ resource_path,
+ NULL,
+ NULL)) {
+ g_object_unref(context);
+ return NULL;
+ }
+
+ gjs_context_pop();
+
+ return context;
+}
+
+unsigned int
+gjs_reflected_script_get_n_lines(GjsReflectedScript *script)
+{
+ g_return_val_if_fail(script, 0);
+
+ GjsReflectedScriptPrivate *priv = (GjsReflectedScriptPrivate *)
gjs_reflected_script_get_instance_private(script);
+
+ ensure_script_reflected(script);
+
+ return priv->n_lines;
+}
+
+static void
+gjs_reflected_script_init(GjsReflectedScript *script)
+{
+ GjsReflectedScriptPrivate *priv = (GjsReflectedScriptPrivate *)
gjs_reflected_script_get_instance_private(script);
+
+ priv->all_branches = NULL;
+ priv->all_function_names = NULL;
+ priv->all_expression_lines = NULL;
+}
+
+static void
+unref_array_if_nonnull(GArray *array)
+{
+ if (array)
+ g_array_unref(array);
+}
+
+static void
+gjs_reflected_script_dispose(GObject *object)
+{
+ GjsReflectedScript *script = GJS_REFLECTED_SCRIPT(object);
+ GjsReflectedScriptPrivate *priv = (GjsReflectedScriptPrivate *)
gjs_reflected_script_get_instance_private(script);
+
+ g_clear_object(&priv->reflection_context);
+
+ G_OBJECT_CLASS(gjs_reflected_script_parent_class)->dispose(object);
+}
+
+static void
+gjs_reflected_script_finalize(GObject *object)
+{
+ GjsReflectedScript *script = GJS_REFLECTED_SCRIPT(object);
+ GjsReflectedScriptPrivate *priv = (GjsReflectedScriptPrivate *)
gjs_reflected_script_get_instance_private(script);
+
+ unref_array_if_nonnull(priv->all_branches);
+ unref_array_if_nonnull(priv->all_function_names);
+ unref_array_if_nonnull(priv->all_expression_lines);
+
+ g_free(priv->script_filename);
+
+ G_OBJECT_CLASS(gjs_reflected_script_parent_class)->finalize(object);
+}
+
+static void
+gjs_reflected_script_set_property(GObject *object,
+ unsigned int prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GjsReflectedScript *script = GJS_REFLECTED_SCRIPT(object);
+ GjsReflectedScriptPrivate *priv = (GjsReflectedScriptPrivate *)
gjs_reflected_script_get_instance_private(script);
+
+ switch (prop_id) {
+ case PROP_SCRIPT_FILENAME:
+ priv->script_filename = g_value_dup_string(value);
+ break;
+ case PROP_REFLECTION_CONTEXT:
+ priv->reflection_context = (GjsContext *) g_value_dup_object(value);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
+ }
+}
+
+static void
+gjs_reflected_script_class_init(GjsReflectedScriptClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS(klass);
+
+ object_class->set_property = gjs_reflected_script_set_property;
+ object_class->dispose = gjs_reflected_script_dispose;
+ object_class->finalize = gjs_reflected_script_finalize;
+
+ properties[PROP_0] = NULL;
+ properties[PROP_SCRIPT_FILENAME] = g_param_spec_string("filename",
+ "Script Filename",
+ "Valid path to script",
+ NULL,
+ (GParamFlags) (G_PARAM_CONSTRUCT_ONLY |
G_PARAM_WRITABLE));
+ properties[PROP_REFLECTION_CONTEXT] = g_param_spec_object("reflection-context",
+ "Reflection Context",
+ "Context to perform reflection in. "
+ "This must have been initialized with
infoReflect.js loaded ",
+ GJS_TYPE_CONTEXT,
+ (GParamFlags) (G_PARAM_CONSTRUCT_ONLY |
G_PARAM_WRITABLE));
+ g_object_class_install_properties(object_class,
+ PROP_N,
+ properties);
+}
+
+GjsReflectedScript *
+gjs_reflected_script_new(const char *filename,
+ GjsContext *reflection_context)
+{
+ GjsReflectedScript *script =
+ GJS_REFLECTED_SCRIPT(g_object_new(GJS_TYPE_REFLECTED_SCRIPT,
+ "filename", filename,
+ "reflection-context", reflection_context,
+ NULL));
+ return script;
+}
diff --git a/gjs/reflected-script.h b/gjs/reflected-script.h
new file mode 100644
index 0000000..9e2ea17
--- /dev/null
+++ b/gjs/reflected-script.h
@@ -0,0 +1,93 @@
+/*
+ * Copyright © 20134Endless Mobile, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * Authored By: Sam Spilsbury <sam endlessm com>
+ */
+#ifndef GJS_REFLECTED_SCRIPT_H
+#define GJS_REFLECTED_SCRIPT_H
+
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+#define GJS_TYPE_REFLECTED_SCRIPT gjs_reflected_script_get_type()
+
+#define GJS_REFLECTED_SCRIPT(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST((obj), \
+ GJS_TYPE_REFLECTED_SCRIPT, GjsReflectedScript))
+
+#define GJS_REFLECTED_SCRIPT_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST((klass), \
+ GJS_TYPE_REFLECTED_SCRIPT, GjsReflectedScriptClass))
+
+#define GJS_IS_REFLECTED_SCRIPT(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE ((obj), \
+ GJS_TYPE_REFLECTED_SCRIPT))
+
+#define GJS_IS_REFLECTED_SCRIPT_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE ((klass), \
+ GJS_TYPE_REFLECTED_SCRIPT))
+
+#define GJS_REFLECTED_SCRIPT_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), \
+ GJS_TYPE_REFLECTED_SCRIPT, GjsReflectedScriptClass))
+
+typedef struct _GjsReflectedScript GjsReflectedScript;
+typedef struct _GjsReflectedScriptClass GjsReflectedScriptClass;
+typedef struct _GjsReflectedScriptPrivate GjsReflectedScriptPrivate;
+
+typedef struct _GjsReflectedScriptBranchInfo GjsReflectedScriptBranchInfo;
+typedef struct _GjsReflectedScriptFunctionInfo GjsReflectedScriptFunctionInfo;
+
+typedef struct _GjsContext GjsContext;
+
+struct _GjsReflectedScriptClass {
+ GObjectClass parent_class;
+};
+
+struct _GjsReflectedScript {
+ GObject parent;
+};
+
+unsigned int gjs_reflected_script_branch_info_get_branch_point(const GjsReflectedScriptBranchInfo *info);
+const unsigned int * gjs_reflected_script_branch_info_get_branch_alternatives(const
GjsReflectedScriptBranchInfo *info,
+ unsigned int
*n);
+
+unsigned int gjs_reflected_script_function_info_get_line_number(const GjsReflectedScriptFunctionInfo *info);
+unsigned int gjs_reflected_script_function_info_get_n_params(const GjsReflectedScriptFunctionInfo *info);
+const char * gjs_reflected_script_function_info_get_name(const GjsReflectedScriptFunctionInfo *info);
+
+const GjsReflectedScriptFunctionInfo ** gjs_reflected_script_get_functions(GjsReflectedScript *script);
+const unsigned int * gjs_reflected_script_get_expression_lines(GjsReflectedScript *script,
+ unsigned int *n_executable_lines);
+const GjsReflectedScriptBranchInfo ** gjs_reflected_script_get_branches(GjsReflectedScript *script);
+unsigned int gjs_reflected_script_get_n_lines(GjsReflectedScript *script);
+
+GType gjs_reflected_script_get_type(void);
+
+GjsReflectedScript * gjs_reflected_script_new(const char *filename,
+ GjsContext *reflection_context);
+
+/* Creates a "reflection context" that can be passed to the constructor of
+ * gjs_reflected_script_new. This context will have the script
+ * containing the functions which permit reflection pre-defined and can
+ * be shared across all reflections */
+GjsContext * gjs_reflected_script_create_reflection_context();
+
+G_END_DECLS
+
+#endif
diff --git a/installed-tests/js/testInfoReflect.js b/installed-tests/js/testInfoReflect.js
new file mode 100644
index 0000000..9deceda
--- /dev/null
+++ b/installed-tests/js/testInfoReflect.js
@@ -0,0 +1,528 @@
+const JSUnit = imports.jsUnit;
+const InfoReflect = imports.infoReflect;
+
+function parseScriptForExpressionLines(script) {
+ const ast = Reflect.parse(script);
+ return InfoReflect.expressionLinesForAST(ast);
+}
+
+function assertArrayEquals(actual, expected, assertion) {
+ if (actual.length != expected.length)
+ throw "Arrays not equal length. Actual array was " +
+ actual.length + " and Expected array was " +
+ expected.length;
+
+ for (let i = 0; i < actual.length; i++) {
+ assertion(expected[i], actual[i]);
+ }
+}
+
+function testExpressionLinesFoundForAssignmentExpressionSides() {
+ let foundLinesOnBothExpressionSides =
+ parseScriptForExpressionLines("var x;\n" +
+ "x = (function() {\n" +
+ " return 10;\n" +
+ "})();\n");
+ assertArrayEquals(foundLinesOnBothExpressionSides,
+ [1, 2, 3],
+ JSUnit.assertEquals);
+}
+
+
+function testExpressionLinesFoundForLinesInsideFunctions() {
+ let foundLinesInsideNamedFunction =
+ parseScriptForExpressionLines("function f(a, b) {\n" +
+ " let x = a;\n" +
+ " let y = b;\n" +
+ " return x + y;\n" +
+ "}\n" +
+ "\n" +
+ "var z = f(1, 2);\n");
+ assertArrayEquals(foundLinesInsideNamedFunction,
+ [2, 3, 4, 7],
+ JSUnit.assertEquals);
+}
+
+function testExpressionLinesFoundForLinesInsideAnonymousFunctions() {
+ let foundLinesInsideAnonymousFunction =
+ parseScriptForExpressionLines("var z = (function f(a, b) {\n" +
+ " let x = a;\n" +
+ " let y = b;\n" +
+ " return x + y;\n" +
+ " })();\n");
+ assertArrayEquals(foundLinesInsideAnonymousFunction,
+ [1, 2, 3, 4],
+ JSUnit.assertEquals);
+}
+
+function testExpressionLinesFoundForBodyOfFunctionProperty() {
+ let foundLinesInsideFunctionProperty =
+ parseScriptForExpressionLines("var o = {\n" +
+ " foo: function () {\n" +
+ " let x = a;\n" +
+ " }\n" +
+ "};\n");
+ assertArrayEquals(foundLinesInsideFunctionProperty,
+ [1, 2, 3],
+ JSUnit.assertEquals);
+}
+
+function testExpressionLinesFoundForCallArgsOfFunctionProperty() {
+ let foundLinesInsideCallArgs =
+ parseScriptForExpressionLines("function f(a) {\n" +
+ "}\n" +
+ "f({\n" +
+ " foo: function() {\n" +
+ " let x = a;\n" +
+ " }\n" +
+ "});\n");
+ assertArrayEquals(foundLinesInsideCallArgs,
+ [1, 3, 4, 5],
+ JSUnit.assertEquals);
+}
+
+function testExpressionLinesFoundForMultilineCallArgs() {
+ let foundLinesInsideMultilineCallArgs =
+ parseScriptForExpressionLines("function f(a, b, c) {\n" +
+ "}\n" +
+ "f(1,\n" +
+ " 2,\n" +
+ " 3);\n");
+ assertArrayEquals(foundLinesInsideMultilineCallArgs,
+ [1, 3, 4, 5],
+ JSUnit.assertEquals);
+}
+
+function testExpressionLinesFoundForNewCallWithObject() {
+ let foundLinesInsideObjectCallArg =
+ parseScriptForExpressionLines("function f(o) {\n" +
+ "}\n" +
+ "let obj = {\n" +
+ " Name: new f({ a: 1,\n" +
+ " b: 2,\n" +
+ " c: 3\n" +
+ " })\n" +
+ "}\n");
+ assertArrayEquals(foundLinesInsideObjectCallArg,
+ [1, 3, 4, 5, 6],
+ JSUnit.assertEquals);
+}
+
+function testExpressionLinesFoundForWhileLoop() {
+ let foundLinesInsideWhileLoop =
+ parseScriptForExpressionLines("var a = 0;\n" +
+ "while (a < 1) {\n" +
+ " let x = 0;\n" +
+ " let y = 1;\n" +
+ " a++;" +
+ "\n" +
+ "}\n");
+ assertArrayEquals(foundLinesInsideWhileLoop,
+ [1, 2, 3, 4, 5],
+ JSUnit.assertEquals);
+}
+
+function testExpressionLinesFoundForTryCatchFinally() {
+ let foundLinesInsideTryCatchFinally =
+ parseScriptForExpressionLines("var a = 0;\n" +
+ "try {\n" +
+ " a++;\n" +
+ "} catch (e) {\n" +
+ " a++;\n" +
+ "} finally {\n" +
+ " a++;\n" +
+ "}\n");
+ assertArrayEquals(foundLinesInsideTryCatchFinally,
+ [1, 2, 3, 4, 5, 7],
+ JSUnit.assertEquals);
+}
+
+function testExpressionLinesFoundForCaseStatements() {
+ let foundLinesInsideCaseStatements =
+ parseScriptForExpressionLines("var a = 0;\n" +
+ "switch (a) {\n" +
+ "case 1:\n" +
+ " a++;\n" +
+ " break;\n" +
+ "case 2:\n" +
+ " a++;\n" +
+ " break;\n" +
+ "}\n");
+ assertArrayEquals(foundLinesInsideCaseStatements,
+ [1, 2, 4, 5, 7, 8],
+ JSUnit.assertEquals);
+}
+
+function testExpressionLinesFoundForLoop() {
+ let foundLinesInsideLoop =
+ parseScriptForExpressionLines("for (let i = 0; i < 1; i++) {\n" +
+ " let x = 0;\n" +
+ " let y = 1;\n" +
+ "\n" +
+ "}\n");
+ assertArrayEquals(foundLinesInsideLoop,
+ [1, 2, 3],
+ JSUnit.assertEquals);
+}
+
+function testExpressionLinesFoundForIfExits() {
+ let foundLinesInsideIfExits =
+ parseScriptForExpressionLines("if (1 > 0) {\n" +
+ " let i = 0;\n" +
+ "} else {\n" +
+ " let j = 1;\n" +
+ "}\n");
+ assertArrayEquals(foundLinesInsideIfExits,
+ [1, 2, 4],
+ JSUnit.assertEquals);
+}
+
+function testExpressionLinesFoundForFirstLineOfMultilineIfTests() {
+ let foundLinesInsideMultilineIfTest =
+ parseScriptForExpressionLines("if (1 > 0 &&\n" +
+ " 2 > 0 &&\n" +
+ " 3 > 0){\n" +
+ " let a = 3;\n" +
+ "}\n");
+ assertArrayEquals(foundLinesInsideMultilineIfTest,
+ [1, 4],
+ JSUnit.assertEquals);
+}
+
+function testExpressionLinesFoundForObjectPropertyLiterals() {
+ let foundLinesInsideObjectPropertyLiterals =
+ parseScriptForExpressionLines("var a = {\n" +
+ " Name: 'foo',\n" +
+ " Ex: 'bar'\n" +
+ "};\n");
+ assertArrayEquals(foundLinesInsideObjectPropertyLiterals,
+ [1, 2, 3],
+ JSUnit.assertEquals);
+}
+
+function testExpressionLinesFoundForObjectPropertyFunction() {
+ let foundLinesInsideObjectPropertyFunction =
+ parseScriptForExpressionLines("var a = {\n" +
+ " Name: function() {},\n" +
+ "};\n");
+ assertArrayEquals(foundLinesInsideObjectPropertyFunction,
+ [1, 2],
+ JSUnit.assertEquals);
+}
+
+function testExpressionLinesFoundForObjectPropertyFunction() {
+ let foundLinesInsideObjectPropertyFunction =
+ parseScriptForExpressionLines("var a = {\n" +
+ " Name: function() {},\n" +
+ "};\n");
+ assertArrayEquals(foundLinesInsideObjectPropertyFunction,
+ [1, 2],
+ JSUnit.assertEquals);
+}
+
+function testExpressionLinesFoundForObjectPropertyObjectExpression() {
+ let foundLinesInsideObjectPropertyObjectExpression =
+ parseScriptForExpressionLines("var a = {\n" +
+ " Name: {},\n" +
+ "};\n");
+ assertArrayEquals(foundLinesInsideObjectPropertyObjectExpression,
+ [1, 2],
+ JSUnit.assertEquals);
+}
+
+function testExpressionLinesFoundForObjectPropertyArrayExpression() {
+ let foundLinesInsideObjectPropertyObjectExpression =
+ parseScriptForExpressionLines("var a = {\n" +
+ " Name: [],\n" +
+ "};\n");
+ assertArrayEquals(foundLinesInsideObjectPropertyObjectExpression,
+ [1, 2],
+ JSUnit.assertEquals);
+}
+
+function testExpressionLinesFoundForObjectArgsToReturn() {
+ let foundLinesInsideObjectArgsToReturn =
+ parseScriptForExpressionLines("var a = {\n" +
+ " Name: {},\n" +
+ "};\n");
+ assertArrayEquals(foundLinesInsideObjectArgsToReturn,
+ [1, 2],
+ JSUnit.assertEquals);
+}
+
+function testExpressionLinesFoundForObjectArgsToThrow() {
+ let foundLinesInsideObjectArgsToThrow =
+ parseScriptForExpressionLines("function f() {\n" +
+ " throw {\n" +
+ " a: 1,\n" +
+ " b: 2\n" +
+ " }\n" +
+ "}\n");
+ assertArrayEquals(foundLinesInsideObjectArgsToThrow,
+ [2, 3, 4],
+ JSUnit.assertEquals);
+}
+
+
+function parseScriptForFunctionNames(script) {
+ const ast = Reflect.parse(script);
+ return InfoReflect.functionsForAST(ast);
+}
+
+function functionDeclarationsEqual(actual, expected) {
+ JSUnit.assertEquals(expected.name, actual.name);
+ JSUnit.assertEquals(expected.line, actual.line);
+ JSUnit.assertEquals(expected.n_params, actual.n_params);
+}
+
+function testFunctionsFoundForDeclarations() {
+ let foundFunctionDeclarations =
+ parseScriptForFunctionNames("function f1() {}\n" +
+ "function f2() {}\n" +
+ "function f3() {}\n");
+ assertArrayEquals(foundFunctionDeclarations,
+ [
+ { name: "f1", line: 1, n_params: 0 },
+ { name: "f2", line: 2, n_params: 0 },
+ { name: "f3", line: 3, n_params: 0 }
+ ],
+ functionDeclarationsEqual);
+}
+
+function testFunctionsFoundForNestedFunctions() {
+ let foundFunctions =
+ parseScriptForFunctionNames("function f1() {\n" +
+ " let f2 = function() {\n" +
+ " let f3 = function() {\n" +
+ " }\n" +
+ " }\n" +
+ "}\n");
+ assertArrayEquals(foundFunctions,
+ [
+ { name: "f1", line: 1, n_params: 0 },
+ { name: null, line: 2, n_params: 0 },
+ { name: null, line: 3, n_params: 0 }
+ ],
+ functionDeclarationsEqual);
+}
+
+function testFunctionsFoundOnSameLineButDifferentiatedOnArgs() {
+ /* Note the lack of newlines. This is all on
+ * one line */
+ let foundFunctionsOnSameLine =
+ parseScriptForFunctionNames("function f1() {" +
+ " return (function (a) {" +
+ " return function (a, b) {}" +
+ " });" +
+ "}");
+ assertArrayEquals(foundFunctionsOnSameLine,
+ [
+ { name: "f1", line: 1, n_params: 0 },
+ { name: null, line: 1, n_params: 1 },
+ { name: null, line: 1, n_params: 2 }
+ ],
+ functionDeclarationsEqual);
+}
+
+function parseScriptForBranches(script) {
+ const ast = Reflect.parse(script);
+ return InfoReflect.branchesForAST(ast);
+}
+
+function branchInfoEqual(actual, expected) {
+ JSUnit.assertEquals(expected.point, actual.point);
+ assertArrayEquals(expected.exits, actual.exits, JSUnit.assertEquals);
+}
+
+function testBothBranchExitsFoundForSimpleBranch() {
+ let foundBranchExitsForSimpleBranch =
+ parseScriptForBranches("if (1) {\n" +
+ " let a = 1;\n" +
+ "} else {\n" +
+ " let b = 2;\n" +
+ "}\n");
+ assertArrayEquals(foundBranchExitsForSimpleBranch,
+ [
+ { point: 1, exits: [2, 4] }
+ ],
+ branchInfoEqual);
+}
+
+function testSingleExitFoundForBranchWithOneConsequent() {
+ let foundBranchExitsForSingleConsequentBranch =
+ parseScriptForBranches("if (1) {\n" +
+ " let a = 1.0;\n" +
+ "}\n");
+ assertArrayEquals(foundBranchExitsForSingleConsequentBranch,
+ [
+ { point: 1, exits: [2] }
+ ],
+ branchInfoEqual);
+}
+
+function testMultipleBranchesFoundForNestedIfElseBranches() {
+ let foundBranchesForNestedIfElseBranches =
+ parseScriptForBranches("if (1) {\n" +
+ " let a = 1.0;\n" +
+ "} else if (2) {\n" +
+ " let b = 2.0;\n" +
+ "} else if (3) {\n" +
+ " let c = 3.0;\n" +
+ "} else {\n" +
+ " let d = 4.0;\n" +
+ "}\n");
+ assertArrayEquals(foundBranchesForNestedIfElseBranches,
+ [
+ /* the 'else if' line is actually an
+ * exit for the first branch */
+ { point: 1, exits: [2, 3] },
+ { point: 3, exits: [4, 5] },
+ /* 'else' by itself is not executable,
+ * it is the block it contains whcih
+ * is */
+ { point: 5, exits: [6, 8] }
+ ],
+ branchInfoEqual);
+}
+
+
+function testSimpleTwoExitBranchWithoutBlocks() {
+ let foundBranches =
+ parseScriptForBranches("let a, b;\n" +
+ "if (1)\n" +
+ " a = 1.0\n" +
+ "else\n" +
+ " b = 2.0\n" +
+ "\n");
+ assertArrayEquals(foundBranches,
+ [
+ { point: 2, exits: [3, 5] }
+ ],
+ branchInfoEqual);
+}
+
+function testNoBranchFoundIfConsequentWasEmpty() {
+ let foundBranches =
+ parseScriptForBranches("let a, b;\n" +
+ "if (1);\n");
+ assertArrayEquals(foundBranches,
+ [],
+ branchInfoEqual);
+}
+
+function testSingleExitFoundIfOnlyAlternateExitDefined() {
+ let foundBranchesForOnlyAlternateDefinition =
+ parseScriptForBranches("let a, b;\n" +
+ "if (1);\n" +
+ "else\n" +
+ " a++;\n");
+ assertArrayEquals(foundBranchesForOnlyAlternateDefinition,
+ [
+ { point: 2, exits: [4] }
+ ],
+ branchInfoEqual);
+}
+
+function testImplicitBranchFoundForWhileStatement() {
+ let foundBranchesForWhileStatement =
+ parseScriptForBranches("while (1) {\n" +
+ " let a = 1;\n" +
+ "}\n" +
+ "let b = 2;");
+ assertArrayEquals(foundBranchesForWhileStatement,
+ [
+ { point: 1, exits: [2] }
+ ],
+ branchInfoEqual);
+}
+
+function testImplicitBranchFoundForDoWhileStatement() {
+ let foundBranchesForDoWhileStatement =
+ parseScriptForBranches("do {\n" +
+ " let a = 1;\n" +
+ "} while (1)\n" +
+ "let b = 2;");
+ assertArrayEquals(foundBranchesForDoWhileStatement,
+ [
+ { point: 1, exits: [2] }
+ ],
+ branchInfoEqual);
+}
+
+function testImplicitBranchFoundForDoWhileStatement() {
+ let foundBranchesForDoWhileStatement =
+ parseScriptForBranches("do {\n" +
+ " let a = 1;\n" +
+ "} while (1)\n" +
+ "let b = 2;");
+ assertArrayEquals(foundBranchesForDoWhileStatement,
+ [
+ /* For do-while loops the branch-point is
+ * at the 'do' condition and not the
+ * 'while' */
+ { point: 1, exits: [2] }
+ ],
+ branchInfoEqual);
+}
+
+function testAllExitsFoundForCaseStatements() {
+ let foundExitsInCaseStatement =
+ parseScriptForBranches("let a = 1;\n" +
+ "switch (1) {\n" +
+ "case '1':\n" +
+ " a++;\n" +
+ " break;\n" +
+ "case '2':\n" +
+ " a++\n" +
+ " break;\n" +
+ "default:\n" +
+ " a++\n" +
+ " break;\n" +
+ "}\n");
+ assertArrayEquals(foundExitsInCaseStatement,
+ [
+ /* There are three potential exits here */
+ { point: 2, exits: [4, 7, 10] }
+ ],
+ branchInfoEqual);
+}
+
+function testAllExitsFoundForFallthroughCaseStatements() {
+ let foundExitsInCaseStatement =
+ parseScriptForBranches("let a = 1;\n" +
+ "switch (1) {\n" +
+ "case '1':\n" +
+ "case 'a':\n" +
+ "case 'b':\n" +
+ " a++;\n" +
+ " break;\n" +
+ "case '2':\n" +
+ " a++\n" +
+ " break;\n" +
+ "default:\n" +
+ " a++\n" +
+ " break;\n" +
+ "}\n");
+ assertArrayEquals(foundExitsInCaseStatement,
+ [
+ /* There are three potential exits here */
+ { point: 2, exits: [6, 9, 12] }
+ ],
+ branchInfoEqual);
+}
+
+function testAllNoExitsFoundForCaseStatementsWithNoopLabels() {
+ let foundExitsInCaseStatement =
+ parseScriptForBranches("let a = 1;\n" +
+ "switch (1) {\n" +
+ "case '1':\n" +
+ "case '2':\n" +
+ "default:\n" +
+ "}\n");
+ assertArrayEquals(foundExitsInCaseStatement,
+ [],
+ branchInfoEqual);
+}
+
+
+JSUnit.gjstestRun(this, JSUnit.setUp, JSUnit.tearDown);
diff --git a/modules/infoReflect.js b/modules/infoReflect.js
new file mode 100644
index 0000000..b1d5cab
--- /dev/null
+++ b/modules/infoReflect.js
@@ -0,0 +1,338 @@
+/*
+ * Copyright (c) 2014 Endless Mobile, Inc.
+ *
+ * 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.
+ *
+ * Authored By: Sam Spilsbury <sam endlessm com>
+ */
+
+function getSubNodesForNode(node) {
+ let subNodes = [];
+ switch (node.type) {
+ /* These statements have a single body */
+ case 'LabelledStatement':
+ case 'WithStatement':
+ case 'LetStatement':
+ case 'ForInStatement':
+ case 'ForOfStatement':
+ case 'FunctionDeclaration':
+ case 'FunctionExpression':
+ case 'ArrowExpression':
+ case 'CatchClause':
+ subNodes.push(node.body);
+ break;
+ case 'WhileStatement':
+ case 'DoWhileStatement':
+ subNodes.push(node.body);
+ subNodes.push(node.test);
+ break;
+ case 'ForStatement':
+ if (node.init !== null)
+ subNodes.push(node.init);
+ if (node.test !== null)
+ subNodes.push(node.test);
+ if (node.update !== null)
+ subNodes.push(node.update);
+
+ subNodes.push(node.body);
+ break;
+ case 'BlockStatement':
+ Array.prototype.push.apply(subNodes, node.body);
+ break;
+ case 'ThrowStatement':
+ case 'ReturnStatement':
+ if (node.argument !== null)
+ subNodes.push(node.argument);
+ break;
+ case 'ExpressionStatement':
+ subNodes.push(node.expression);
+ break;
+ case 'AssignmentExpression':
+ subNodes.push(node.left, node.right);
+ break;
+ case 'ObjectExpression':
+ node.properties.forEach(function(prop) {
+ subNodes.push(prop.value);
+ });
+ break;
+ /* It is very possible that there might be something
+ * interesting in the function arguments, so we need to
+ * walk them too */
+ case 'NewExpression':
+ case 'CallExpression':
+ Array.prototype.push.apply(subNodes, node.arguments);
+ subNodes.push(node.callee);
+ break;
+ /* These statements might have multiple different bodies
+ * depending on whether or not they were entered */
+ case 'IfStatement':
+ subNodes = [node.test, node.consequent];
+ if (node.alternate !== null)
+ subNodes.push(node.alternate);
+ break;
+ case 'TryStatement':
+ subNodes = [node.block];
+ if (node.handler !== null)
+ subNodes.push(node.handler);
+ if (node.finalizer !== null)
+ subNodes.push(node.finalizer);
+ break;
+ case 'SwitchStatement':
+ for (let caseClause of node.cases) {
+ caseClause.consequent.forEach(function(expression) {
+ subNodes.push(expression);
+ });
+ }
+
+ break;
+ /* Variable declarations might be initialized to
+ * some expression, so traverse the tree and see if
+ * we can get into the expression */
+ case 'VariableDeclaration':
+ node.declarations.forEach(function (declarator) {
+ if (declarator.init !== null)
+ subNodes.push(declarator.init);
+ });
+
+ break;
+ }
+
+ return subNodes;
+}
+
+function collectForSubNodes(subNodes, collector) {
+ let result = [];
+ if (subNodes !== undefined &&
+ subNodes.length > 0) {
+
+ subNodes.forEach(function(node) {
+ let subResult = collector(node);
+ if (subResult !== undefined)
+ Array.prototype.push.apply(result, subResult);
+
+ Array.prototype.push.apply(result,
+ collectForSubNodes(getSubNodesForNode(node),
+ collector));
+ });
+ }
+
+ return result;
+}
+
+/* Unfortunately, the Reflect API doesn't give us enough information to
+ * uniquely identify a function. A function might be anonymous, in which
+ * case the JS engine uses some heurisitics to get a unique string identifier
+ * but that isn't available to us here.
+ *
+ * There's also the edge-case where functions with the same name might be
+ * defined within the same scope, or multiple anonymous functions might
+ * be defined on the same line. In that case, it will look like we entered
+ * the same function multiple times since we can't get column information
+ * from the engine-side.
+ *
+ * For instance:
+ *
+ * 1. function f() {
+ * function f() {
+ * }
+ * }
+ *
+ * 2. let a = function() { function(a, b) {} };
+ *
+ * 3. let a = function() { function () {} }
+ *
+ * We can work-around case 1 by using the line numbers to get a unique identifier.
+ * We can work-around case 2 by using the arguments length to get a unique identifier
+ * We can't work-around case 3. The best thing we can do is warn that coverage
+ * reports might be inaccurate as a result */
+function functionsForNode(node) {
+ let functionNames = [];
+ switch (node.type) {
+ case 'FunctionDeclaration':
+ case 'FunctionExpression':
+ if (node.id !== null) {
+ functionNames.push({ name: node.id.name,
+ line: node.loc.start.line,
+ n_params: node.params.length });
+ }
+ /* If the function wasn't found, we just push a name
+ * that looks like 'function:lineno' to signify that
+ * this was an anonymous function. If the coverage tool
+ * enters a function with no name (but a line number)
+ * then it can probably use this information to
+ * figure out which function it was */
+ else {
+ functionNames.push({ name: null,
+ line: node.loc.start.line,
+ n_params: node.params.length });
+ }
+ }
+
+ return functionNames;
+}
+
+function functionsForAST(ast) {
+ return collectForSubNodes(ast.body, functionsForNode);
+}
+
+/* If a branch' consequent is a block statement, there's
+ * a chance that it could start on the same line, although
+ * that's not where execution really starts. If it is
+ * a block statement then handle the case and go
+ * to the first line where execution starts */
+function getBranchExitStartLine(branchBodyNode) {
+ switch (branchBodyNode.type) {
+ case 'BlockStatement':
+ /* Hit a block statement, but nothing inside, can never
+ * be executed, tell the upper level to move on to the next
+ * statement */
+ if (branchBodyNode.body.length === 0)
+ return -1;
+
+ /* Handle the case where we have nested block statements
+ * that never actually get to executable code by handling
+ * all statements within a block */
+ for (let statement of branchBodyNode.body) {
+ let startLine = getBranchExitStartLine(statement);
+ if (startLine !== -1)
+ return startLine;
+ }
+
+ /* Couldn't find an executable line inside this block */
+ return -1;
+
+ case 'SwitchCase':
+ /* Hit a switch, but nothing inside, can never
+ * be executed, tell the upper level to move on to the next
+ * statement */
+ if (branchBodyNode.consequent.length === 0)
+ return -1;
+
+ /* Handle the case where we have nested block statements
+ * that never actually get to executable code by handling
+ * all statements within a block */
+ for (let statement of branchBodyNode.consequent) {
+ let startLine = getBranchExitStartLine(statement);
+ if (startLine !== -1) {
+ return startLine;
+ }
+ }
+
+ /* Couldn't find an executable line inside this block */
+ return -1;
+ /* These types of statements are never executable */
+ case 'EmptyStatement':
+ case 'LabelledStatement':
+ return -1;
+ default:
+ break;
+ }
+
+ return branchBodyNode.loc.start.line;
+}
+
+function branchesForNode(node) {
+ let branches = [];
+
+ let branchExitNodes = [];
+ switch (node.type) {
+ case 'IfStatement':
+ branchExitNodes.push(node.consequent);
+ if (node.alternate !== null)
+ branchExitNodes.push(node.alternate);
+ break;
+ case 'WhileStatement':
+ case 'DoWhileStatement':
+ branchExitNodes.push(node.body);
+ break;
+ case 'SwitchStatement':
+
+ /* The case clauses by themselves are never executable
+ * so find the actual exits */
+ Array.prototype.push.apply(branchExitNodes, node.cases);
+ break;
+ default:
+ break;
+ }
+
+ let branchExitStartLines = branchExitNodes.map(getBranchExitStartLine);
+ branchExitStartLines = branchExitStartLines.filter(function(line) {
+ return line !== -1;
+ });
+
+ /* Branch must have at least one exit */
+ if (branchExitStartLines.length) {
+ branches.push({ point: node.loc.start.line,
+ exits: branchExitStartLines });
+ }
+
+ return branches;
+}
+
+function branchesForAST(ast) {
+ return collectForSubNodes(ast.body, branchesForNode);
+}
+
+function expressionLinesForNode(statement) {
+ let expressionLines = [];
+
+ let expressionNodeTypes = ['Expression',
+ 'Declaration',
+ 'Statement',
+ 'Clause',
+ 'Literal',
+ 'Identifier'];
+
+ if (expressionNodeTypes.some(function(type) {
+ return statement.type.indexOf(type) !== -1;
+ })) {
+
+ /* These expressions aren't executable on their own */
+ switch (statement.type) {
+ case 'FunctionDeclaration':
+ case 'LiteralExpression':
+ break;
+ /* Perplexingly, an empty block statement is actually executable,
+ * push it if it is */
+ case 'BlockStatement':
+ if (statement.body.length !== 0)
+ break;
+ expressionLines.push(statement.loc.start.line);
+ break;
+ default:
+ expressionLines.push(statement.loc.start.line);
+ break;
+ }
+ }
+
+ return expressionLines;
+}
+
+function deduplicate(list) {
+ return list.filter(function(elem, pos, self) {
+ return self.indexOf(elem) === pos;
+ });
+}
+
+function expressionLinesForAST(ast) {
+ let allExpressions = collectForSubNodes(ast.body, expressionLinesForNode);
+ allExpressions = deduplicate(allExpressions);
+
+ return allExpressions;
+}
diff --git a/modules/modules.gresource.xml.in b/modules/modules.gresource.xml.in
index 8fccdfd..01436bd 100644
--- a/modules/modules.gresource.xml.in
+++ b/modules/modules.gresource.xml.in
@@ -14,6 +14,7 @@
<file>modules/gettext.js</file>
<file>modules/lang.js</file>
<file>modules/mainloop.js</file>
+ <file>modules/infoReflect.js</file>
<file>modules/jsUnit.js</file>
<file>modules/signals.js</file>
<file>modules/format.js</file>
diff --git a/test/gjs-test-reflected-script.cpp b/test/gjs-test-reflected-script.cpp
new file mode 100644
index 0000000..de3ec1e
--- /dev/null
+++ b/test/gjs-test-reflected-script.cpp
@@ -0,0 +1,419 @@
+/*
+ * Copyright © 2014 Endless Mobile, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * Authored By: Sam Spilsbury <sam endlessm com>
+ */
+
+#include <errno.h>
+#include <string.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <glib.h>
+#include <gio/gio.h>
+#include <gjs/gjs.h>
+#include <gjs/reflected-script.h>
+
+typedef struct _GjsReflectedScriptTestFixture {
+ char *temporary_js_script_filename;
+ int temporary_js_script_open_handle;
+ GjsContext *reflection_context;
+} GjsReflectedScriptTestFixture;
+
+static void
+gjs_reflected_test_fixture_set_up(gpointer fixture_data,
+ gconstpointer test_data)
+{
+ GjsReflectedScriptTestFixture *fixture = (GjsReflectedScriptTestFixture *) fixture_data;
+ char *current_dir = g_get_current_dir();
+ fixture->temporary_js_script_open_handle = g_file_open_tmp("mock-js-XXXXXXX.js",
+ &fixture->temporary_js_script_filename,
+ NULL);
+ fixture->reflection_context = gjs_reflected_script_create_reflection_context();
+
+ g_free(current_dir);
+}
+
+static void
+gjs_reflected_test_fixture_tear_down(gpointer fixture_data,
+ gconstpointer test_data)
+{
+ GjsReflectedScriptTestFixture *fixture = (GjsReflectedScriptTestFixture *) fixture_data;
+ unlink(fixture->temporary_js_script_filename);
+ g_free(fixture->temporary_js_script_filename);
+ close(fixture->temporary_js_script_open_handle);
+
+ g_object_unref(fixture->reflection_context);
+}
+
+static void
+test_reflect_creation_and_destruction(gpointer fixture_data,
+ gconstpointer user_data)
+{
+ GjsReflectedScriptTestFixture *fixture = (GjsReflectedScriptTestFixture *) fixture_data;
+
+ const char mock_script[] = "var a = 1;\n";
+
+ if (write(fixture->temporary_js_script_open_handle, mock_script, strlen(mock_script) * sizeof(char)) ==
-1)
+ g_error("Failed to write to test script");
+
+ GjsReflectedScript *script = gjs_reflected_script_new(fixture->temporary_js_script_filename,
+ fixture->reflection_context);
+ g_object_unref(script);
+}
+
+static gboolean
+integer_arrays_equal(const unsigned int *actual,
+ unsigned int actual_n,
+ const unsigned int *expected,
+ unsigned int expected_n)
+{
+ if (actual_n != expected_n)
+ return FALSE;
+
+ unsigned int i;
+
+ for (i = 0; i < actual_n; ++i)
+ if (actual[i] != expected[i])
+ return FALSE;
+
+ return TRUE;
+}
+
+static GjsReflectedScript *
+get_reflected_script_for(const char *script,
+ int file,
+ const char *filename,
+ GjsContext *reflection_context)
+{
+ if (write(file, script, strlen(script) * sizeof(char)) == -1)
+ g_error("Failed to write to test script");
+
+ GjsReflectedScript *reflected_script =
+ gjs_reflected_script_new(filename, reflection_context);
+ return reflected_script;
+}
+
+static void
+test_reflect_get_all_executable_expression_lines(gpointer fixture_data,
+ gconstpointer user_data)
+{
+ GjsReflectedScriptTestFixture *fixture = (GjsReflectedScriptTestFixture *) fixture_data;
+
+ const char mock_script[] =
+ "var a = 1.0;\n"
+ "var b = 2.0;\n"
+ "var c = 3.0;\n";
+
+ GjsReflectedScript *script = get_reflected_script_for(mock_script,
+ fixture->temporary_js_script_open_handle,
+ fixture->temporary_js_script_filename,
+ fixture->reflection_context);
+ unsigned int n_executable_lines = 0;
+ const unsigned int *executable_lines = gjs_reflected_script_get_expression_lines(script,
+ &n_executable_lines);
+
+ const unsigned int expected_executable_lines[] = {
+ 1, 2, 3
+ };
+ const unsigned int n_expected_executable_lines = G_N_ELEMENTS(expected_executable_lines);
+
+ g_assert(integer_arrays_equal(executable_lines,
+ n_executable_lines,
+ expected_executable_lines,
+ n_expected_executable_lines));
+
+ g_object_unref(script);
+}
+
+typedef struct _GjsReflectedScriptExpectedBranch {
+ unsigned int point;
+ unsigned int alternatives[256];
+ unsigned int n_alternatives;
+} ExpectedBranch;
+
+static gboolean
+branch_info_equal(ExpectedBranch *expected,
+ const GjsReflectedScriptBranchInfo *branch)
+{
+ if (gjs_reflected_script_branch_info_get_branch_point(branch) != expected->point)
+ return FALSE;
+
+ unsigned int n_actual_alternatives = 0;
+ const unsigned int *actual_alternatives =
gjs_reflected_script_branch_info_get_branch_alternatives(branch,
+
&n_actual_alternatives);
+ unsigned int n_expected_alternatives = expected->n_alternatives;
+ const unsigned int *expected_alternatives = expected->alternatives;
+
+ return integer_arrays_equal(actual_alternatives,
+ n_actual_alternatives,
+ expected_alternatives,
+ n_expected_alternatives);
+}
+
+static gboolean
+has_elements_in_branch_array_in_order (ExpectedBranch *expected,
+ const GjsReflectedScriptBranchInfo **branches,
+ unsigned int n_expected)
+{
+ const GjsReflectedScriptBranchInfo **branch_iterator = branches;
+ unsigned int i;
+
+ for (i = 0; i < n_expected; ++i, ++branch_iterator) {
+ /* Size mismatch */
+ if (!(*branch_iterator))
+ return FALSE;
+
+ if (!branch_info_equal(&expected[i],
+ *branch_iterator))
+ return FALSE;
+ }
+
+ /* Size mismatch - there are still remaining branches */
+ if (*branch_iterator)
+ return FALSE;
+
+ return TRUE;
+}
+
+static void
+test_reflect_finds_branches(gpointer fixture_data,
+ gconstpointer user_data)
+{
+ GjsReflectedScriptTestFixture *fixture = (GjsReflectedScriptTestFixture *) fixture_data;
+
+ const char mock_script[] =
+ "let a, b;\n"
+ "if (1)\n"
+ " a = 1.0\n"
+ "else\n"
+ " b = 2.0\n"
+ "\n";
+
+ GjsReflectedScript *script = get_reflected_script_for(mock_script,
+ fixture->temporary_js_script_open_handle,
+ fixture->temporary_js_script_filename,
+ fixture->reflection_context);
+ const GjsReflectedScriptBranchInfo **branches =
+ gjs_reflected_script_get_branches(script);
+
+ ExpectedBranch expected[] = {
+ {
+ 2,
+ { 3, 5 },
+ 2
+ }
+ };
+
+ g_assert(has_elements_in_branch_array_in_order(expected,
+ branches,
+ G_N_ELEMENTS(expected)));
+
+ g_object_unref(script);
+}
+
+typedef struct _ExpectedReflectedFunction {
+ unsigned int line;
+ unsigned int n_params;
+ const char *name;
+} ExpectedReflectedFunction;
+
+static gboolean
+function_info_equal(const ExpectedReflectedFunction *expected,
+ const GjsReflectedScriptFunctionInfo *actual)
+{
+ const gchar *actual_name = gjs_reflected_script_function_info_get_name(actual);
+ unsigned int actual_line = gjs_reflected_script_function_info_get_line_number(actual);
+ unsigned int actual_n_params = gjs_reflected_script_function_info_get_n_params(actual);
+
+ if ((actual_name && !expected->name) ||
+ (!actual_name && expected->name))
+ return FALSE;
+
+ if (g_strcmp0(actual_name, expected->name) != 0)
+ return FALSE;
+
+ return actual_line == expected->line &&
+ actual_n_params == expected->n_params;
+}
+
+static gboolean
+has_elements_in_function_array_in_order(const GjsReflectedScriptFunctionInfo **functions,
+ const ExpectedReflectedFunction *expected,
+ unsigned int n_expected)
+{
+ const GjsReflectedScriptFunctionInfo **function_iterator = functions;
+ unsigned int i;
+
+ for (i = 0; i < n_expected; ++i, ++function_iterator) {
+ /* Size mismatch */
+ if (!(*function_iterator))
+ return FALSE;
+
+ if (!function_info_equal(&expected[i],
+ *function_iterator))
+ return FALSE;
+ }
+
+ /* Size mismatch - there are still remaining functions */
+ if (*function_iterator)
+ return FALSE;
+
+ return TRUE;
+}
+
+static void
+test_reflect_finds_functions(gpointer fixture_data,
+ gconstpointer user_data)
+{
+ GjsReflectedScriptTestFixture *fixture = (GjsReflectedScriptTestFixture *) fixture_data;
+
+ const char mock_script[] =
+ "function f1() {}\n"
+ "function f2() {}\n"
+ "function f3() {}\n";
+
+ GjsReflectedScript *script = get_reflected_script_for(mock_script,
+ fixture->temporary_js_script_open_handle,
+ fixture->temporary_js_script_filename,
+ fixture->reflection_context);
+ const GjsReflectedScriptFunctionInfo **functions =
+ gjs_reflected_script_get_functions(script);
+
+ ExpectedReflectedFunction expected[] = {
+ { 1, 0, "f1" },
+ { 2, 0, "f2" },
+ { 3, 0, "f3" }
+ };
+
+ g_assert(has_elements_in_function_array_in_order(functions,
+ expected,
+ G_N_ELEMENTS(expected)));
+
+ g_object_unref(script);
+}
+
+static void
+test_reflect_get_n_lines(gpointer fixture_data,
+ gconstpointer user_data)
+{
+ GjsReflectedScriptTestFixture *fixture = (GjsReflectedScriptTestFixture *) fixture_data;
+
+ const char mock_script[] =
+ "function f1() {}\n"
+ "function f2() {}\n"
+ "function f3() {}\n";
+
+ GjsReflectedScript *script = get_reflected_script_for(mock_script,
+ fixture->temporary_js_script_open_handle,
+ fixture->temporary_js_script_filename,
+ fixture->reflection_context);
+ unsigned int n_lines = gjs_reflected_script_get_n_lines(script);
+
+ g_assert(n_lines == 4);
+
+ g_object_unref(script);
+}
+
+static void
+silence_log_handler(const char *log_domain,
+ GLogLevelFlags flags,
+ const char *message,
+ gpointer user_data)
+{
+}
+
+static void
+test_reflect_on_nonexistent_script_returns_empty(gpointer fixture_data,
+ gconstpointer user_data)
+{
+ GjsReflectedScriptTestFixture *fixture =
+ (GjsReflectedScriptTestFixture *) fixture_data;
+ GjsReflectedScript *script = gjs_reflected_script_new("doesnotexist://does_not_exist",
+ fixture->reflection_context);
+
+ /* Make the handler shut up so that we don't get an assertion on
+ * raised warnings from bad scripts */
+ GLogLevelFlags old_flags = g_log_set_always_fatal((GLogLevelFlags) G_LOG_LEVEL_ERROR);
+ GLogFunc old_handler = g_log_set_default_handler(silence_log_handler, NULL);
+
+ const GjsReflectedScriptFunctionInfo **functions =
+ gjs_reflected_script_get_functions(script);
+ const GjsReflectedScriptBranchInfo **branches =
+ gjs_reflected_script_get_branches(script);
+ unsigned int n_expression_lines;
+ const unsigned int *lines =
+ gjs_reflected_script_get_expression_lines(script, &n_expression_lines);
+ unsigned int n_lines = gjs_reflected_script_get_n_lines(script);
+
+ g_assert(*functions == NULL);
+ g_assert(*branches == NULL);
+ g_assert(*lines == 0);
+ g_assert(n_lines == 0);
+ g_assert(n_expression_lines == 0);
+
+ g_log_set_default_handler(old_handler, NULL);
+ g_log_set_always_fatal(old_flags);
+}
+
+typedef struct _TestFixture
+{
+ GTestFixtureFunc set_up;
+ GTestFixtureFunc tear_down;
+ size_t test_fixture_size;
+} TestFixture;
+
+static void
+add_test_for_fixture(const char *name,
+ TestFixture *fixture,
+ GTestFixtureFunc test_func)
+{
+ g_test_add_vtable(name,
+ fixture->test_fixture_size,
+ NULL,
+ fixture->set_up,
+ test_func,
+ fixture->tear_down);
+}
+
+void
+gjs_test_add_tests_for_reflected_script(void)
+{
+ TestFixture reflected_script_default_fixture = {
+ gjs_reflected_test_fixture_set_up,
+ gjs_reflected_test_fixture_tear_down,
+ sizeof(GjsReflectedScriptTestFixture)
+ };
+
+ add_test_for_fixture("/gjs/reflected_script/construction",
+ &reflected_script_default_fixture,
+ test_reflect_creation_and_destruction);
+ add_test_for_fixture("/gjs/reflected_script/all_are_expression_lines",
+ &reflected_script_default_fixture,
+ test_reflect_get_all_executable_expression_lines);
+ add_test_for_fixture("/gjs/reflected_script/finds_branches",
+ &reflected_script_default_fixture,
+ test_reflect_finds_branches);
+ add_test_for_fixture("/gjs/reflected_script/finds_functions",
+ &reflected_script_default_fixture,
+ test_reflect_finds_functions);
+ add_test_for_fixture("/gjs/reflected_script/n_lines",
+ &reflected_script_default_fixture,
+ test_reflect_get_n_lines);
+ add_test_for_fixture("/gjs/reflected/script/nonexistent",
+ &reflected_script_default_fixture,
+ test_reflect_on_nonexistent_script_returns_empty);
+}
diff --git a/test/gjs-tests-add-funcs.h b/test/gjs-tests-add-funcs.h
new file mode 100644
index 0000000..e872f1c
--- /dev/null
+++ b/test/gjs-tests-add-funcs.h
@@ -0,0 +1,25 @@
+/*
+ * Copyright © 2013 Endless Mobile, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * Authored By: Sam Spilsbury <sam endlessm com>
+ */
+#ifndef GJS_TESTS_ADD_FUNCS_H
+#define GJS_TESTS_ADD_FUNCS_H
+
+void gjs_test_add_tests_for_reflected_script ();
+
+#endif
diff --git a/test/gjs-tests.cpp b/test/gjs-tests.cpp
index b3a02fe..faf75f7 100644
--- a/test/gjs-tests.cpp
+++ b/test/gjs-tests.cpp
@@ -28,6 +28,8 @@
#include <util/glib.h>
#include <util/crash.h>
+#include "gjs-tests-add-funcs.h"
+
typedef struct _GjsUnitTestFixture GjsUnitTestFixture;
struct _GjsUnitTestFixture {
@@ -396,6 +398,8 @@ main(int argc,
g_test_add_func("/util/glib/strv/concat/null", gjstest_test_func_util_glib_strv_concat_null);
g_test_add_func("/util/glib/strv/concat/pointers", gjstest_test_func_util_glib_strv_concat_pointers);
+ gjs_test_add_tests_for_reflected_script();
+
g_test_run();
return 0;
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]