...
 
Commits (17)
......@@ -2,9 +2,8 @@ services:
- docker
stages:
- static_analysis
- source_check
- test
- build
- thorough_tests
- docs
......@@ -65,20 +64,25 @@ stages:
echo "*** See you soon ***";
echo "*********************************************";
'
#############################################
# Able to test despite of any Docker image changes
#############################################
fedora:
<<: *build
when: always
stage: test
image: claudioandre/spidermonkey:job-400.5 # temporarily pinned to old tag
stage: source_check
image: claudioandre/spidermonkey:job-400.5 # pinned on purpose
variables:
CC: gcc
DEV: devel
TEST: "check"
WARNINGS: "count"
#############################################
# Regular tests
#############################################
ubuntu_gcc:
<<: *build
stage: build
stage: test
image: claudioandre/spidermonkey:ubuntu.dev.gcc
variables:
CC: gcc
......@@ -89,7 +93,7 @@ ubuntu_gcc:
ubuntu_clang:
<<: *build
stage: build
stage: test
image: claudioandre/spidermonkey:ubuntu.dev.gcc
variables:
CC: clang
......@@ -100,7 +104,7 @@ ubuntu_clang:
fedora_clang:
<<: *build
stage: build
stage: test
image: claudioandre/spidermonkey:new-342.4 # temporarily pinned to old tag
variables:
CC: clang
......@@ -174,37 +178,42 @@ codequality:
only:
- master@GNOME/gjs
#############################################
# Static Analyzers
#############################################
cppcheck:
<<: *build
stage: static_analysis
stage: source_check
image: claudioandre/spidermonkey:fedora.static.analysis
variables:
CODECHECK: "CPPCHECK"
cpplint:
<<: *build
stage: static_analysis
stage: source_check
image: claudioandre/spidermonkey:fedora.static.analysis
variables:
CODECHECK: "CPPLINT"
eslint:
<<: *build
stage: static_analysis
stage: source_check
image: claudioandre/spidermonkey:fedora.static.analysis
variables:
CODECHECK: "ESLINT"
code_statistics:
<<: *build
stage: static_analysis
stage: source_check
image: claudioandre/spidermonkey:fedora.static.analysis
variables:
CODECHECK: "TOKEI"
only:
- master@GNOME/gjs
# Publish Code Coverage Report
#############################################
# Publish the Code Coverage Report
#############################################
pages:
stage: docs
dependencies:
......
......@@ -279,6 +279,7 @@ AM_TESTS_ENVIRONMENT = \
export G_FILENAME_ENCODING=latin1; \
export LSAN_OPTIONS="suppressions=$(abs_top_srcdir)/installed-tests/extra/lsan.supp"; \
export NO_AT_BRIDGE=1; \
export LC_ALL=C.UTF-8; \
$(COVERAGE_TESTS_ENVIRONMENT) \
$(XVFB_START) \
$(DBUS_SESSION_COMMAND) \
......
......@@ -70,6 +70,11 @@ struct ObjectInstance {
unsigned js_object_finalized : 1;
unsigned g_object_finalized : 1;
/* True if this object has visible JS state, and thus its lifecycle is
* managed using toggle references. False if this object just keeps a
* hard ref on the underlying GObject, and may be finalized at will. */
bool uses_toggle_ref : 1;
};
static std::stack<JS::PersistentRootedObject> object_init_list;
......@@ -85,6 +90,7 @@ extern struct JSClass gjs_object_instance_class;
GJS_DEFINE_PRIV_FROM_JS(ObjectInstance, gjs_object_instance_class)
static void disassociate_js_gobject (GObject *gobj);
static void ensure_uses_toggle_ref(JSContext *cx, ObjectInstance *priv);
typedef enum {
SOME_ERROR_OCCURRED = false,
......@@ -152,7 +158,7 @@ get_object_qdata(GObject *gobj)
auto priv = static_cast<ObjectInstance *>(g_object_get_qdata(gobj,
gjs_object_priv_quark()));
if (priv && G_UNLIKELY(priv->js_object_finalized)) {
if (priv && priv->uses_toggle_ref && G_UNLIKELY(priv->js_object_finalized)) {
g_critical("Object %p (a %s) resurfaced after the JS wrapper was finalized. "
"This is some library doing dubious memory management inside dispose()",
gobj, g_type_name(G_TYPE_FROM_INSTANCE(gobj)));
......@@ -430,6 +436,9 @@ set_g_param_from_prop(JSContext *context,
case SOME_ERROR_OCCURRED:
return false;
case NO_SUCH_G_PROPERTY:
/* We need to keep the wrapper alive in order not to lose custom
* "expando" properties */
ensure_uses_toggle_ref(context, priv);
return result.succeed();
case VALUE_WAS_SET:
default:
......@@ -496,14 +505,7 @@ object_instance_set_prop(JSContext *context,
bool ret = true;
bool g_param_was_set = false;
if (!gjs_get_string_id(context, id, &name))
return result.succeed(); /* not resolved, but no error */
priv = priv_from_js(context, obj);
gjs_debug_jsprop(GJS_DEBUG_GOBJECT,
"Set prop '%s' hook obj %p priv %p",
name.get(), obj.get(), priv);
if (priv == nullptr)
/* see the comment in object_instance_get_prop() on this */
return result.succeed();
......@@ -521,6 +523,18 @@ object_instance_set_prop(JSContext *context,
return result.succeed();
}
if (!gjs_get_string_id(context, id, &name)) {
/* We need to keep the wrapper alive in order not to lose custom
* "expando" properties. In this case if gjs_get_string_id() is false
* then a number or symbol property was probably set. */
ensure_uses_toggle_ref(context, priv);
return result.succeed(); /* not resolved, but no error */
}
gjs_debug_jsprop(GJS_DEBUG_GOBJECT,
"Set prop '%s' hook obj %p priv %p",
name.get(), obj.get(), priv);
ret = set_g_param_from_prop(context, priv, name, g_param_was_set, value_p, result);
if (g_param_was_set || !ret)
return ret;
......@@ -1126,7 +1140,10 @@ static void
release_native_object (ObjectInstance *priv)
{
priv->keep_alive.reset();
g_object_remove_toggle_ref(priv->gobj, wrapped_gobj_toggle_notify, NULL);
if (priv->uses_toggle_ref)
g_object_remove_toggle_ref(priv->gobj, wrapped_gobj_toggle_notify, nullptr);
else
g_object_unref(priv->gobj);
priv->gobj = NULL;
}
......@@ -1256,16 +1273,28 @@ associate_js_gobject (JSContext *context,
ObjectInstance *priv;
priv = priv_from_js(context, object);
priv->uses_toggle_ref = false;
priv->gobj = gobj;
g_assert(!priv->keep_alive.rooted());
set_object_qdata(gobj, priv);
priv->keep_alive = object;
ensure_weak_pointer_callback(context);
wrapped_gobject_list.insert(priv);
g_object_weak_ref(gobj, wrapped_gobj_dispose_notify, priv);
}
static void
ensure_uses_toggle_ref(JSContext *cx,
ObjectInstance *priv)
{
if (priv->uses_toggle_ref)
return;
g_assert(!priv->keep_alive.rooted());
/* OK, here is where things get complicated. We want the
* wrapped gobj to keep the JSObject* wrapper alive, because
......@@ -1278,8 +1307,14 @@ associate_js_gobject (JSContext *context,
* the wrapper to be garbage collected (and thus unref the
* wrappee).
*/
priv->keep_alive.root(context, object, gobj_no_longer_kept_alive_func, priv);
g_object_add_toggle_ref(gobj, wrapped_gobj_toggle_notify, NULL);
priv->uses_toggle_ref = true;
priv->keep_alive.switch_to_rooted(cx, gobj_no_longer_kept_alive_func, priv);
g_object_add_toggle_ref(priv->gobj, wrapped_gobj_toggle_notify, nullptr);
/* We now have both a ref and a toggle ref, we only want the toggle ref.
* This may immediately remove the GC root we just added, since refcount
* may drop to 1. */
g_object_unref(priv->gobj);
}
static void
......@@ -1322,11 +1357,16 @@ disassociate_js_gobject(GObject *gobj)
gobj, G_OBJECT_TYPE_NAME(gobj));
}
/* Fist, remove the wrapper pointer from the wrapped GObject */
set_object_qdata(gobj, nullptr);
/* Now release all the resources the current wrapper has */
invalidate_all_closures(priv);
release_native_object(priv);
/* Mark that a JS object once existed, but it doesn't any more */
priv->js_object_finalized = true;
priv->keep_alive = nullptr;
}
static void
......@@ -1383,6 +1423,7 @@ G_GNUC_END_IGNORE_DEPRECATIONS
* we're not actually using it, so just let it get collected. Avoiding
* this would require a non-trivial amount of work.
* */
ensure_uses_toggle_ref(context, other_priv);
object.set(other_priv->keep_alive);
g_object_unref(gobj); /* We already own a reference */
gobj = NULL;
......@@ -1410,11 +1451,6 @@ G_GNUC_END_IGNORE_DEPRECATIONS
if (priv->gobj == NULL)
associate_js_gobject(context, object, gobj);
/* We now have both a ref and a toggle ref, we only want the
* toggle ref. This may immediately remove the GC root
* we just added, since refcount may drop to 1.
*/
g_object_unref(gobj);
gjs_debug_lifecycle(GJS_DEBUG_GOBJECT, "JSObject created with GObject %p (%s)",
priv->gobj, G_OBJECT_TYPE_NAME(priv->gobj));
......@@ -1556,6 +1592,9 @@ object_instance_finalize(JSFreeOp *fop,
GJS_DEC_COUNTER(object);
priv->~ObjectInstance();
g_slice_free(ObjectInstance, priv);
/* Remove the ObjectInstance pointer from the JSObject */
JS_SetPrivate(obj, nullptr);
}
static JSObject *
......@@ -1683,6 +1722,8 @@ real_connect_func(JSContext *context,
return true;
}
ensure_uses_toggle_ref(context, priv);
if (argc != 2 || !argv[0].isString() || !JS::IsCallable(&argv[1].toObject())) {
gjs_throw(context, "connect() takes two args, the signal name and the callback");
return false;
......@@ -2123,9 +2164,6 @@ gjs_object_from_g_object(JSContext *context,
g_object_ref_sink(gobj);
associate_js_gobject(context, obj, gobj);
/* see the comment in init_object_instance() for this */
g_object_unref(gobj);
g_assert(priv->keep_alive == obj.get());
}
......@@ -2657,6 +2695,10 @@ gjs_object_custom_init(GTypeInstance *instance,
associate_js_gobject(context, object, G_OBJECT (instance));
/* Custom JS objects will most likely have visible state, so
* just do this from the start */
ensure_uses_toggle_ref(context, priv);
JS::RootedValue v(context);
if (!gjs_object_get_property(context, object,
GJS_STRING_INSTANCE_INIT, &v)) {
......@@ -3109,6 +3151,9 @@ gjs_object_associate_closure(JSContext *cx,
if (!priv)
return false;
if (priv->gobj)
ensure_uses_toggle_ref(cx, priv);
do_associate_closure(priv, closure);
return true;
}
......@@ -37,10 +37,6 @@
#include <windows.h>
#endif
#ifdef ENABLE_CAIRO
# include <cairo.h>
#endif
/* Implementations of locale-specific operations; these are used
* in the implementation of String.localeCompare(), Date.toLocaleDateString(),
* and so forth. We take the straight-forward approach of converting
......@@ -218,16 +214,6 @@ on_promise_unhandled_rejection(JSContext *cx,
std::move(stack));
}
static void
shutdown(void)
{
JS_ShutDown();
#ifdef ENABLE_CAIRO
cairo_debug_reset_static_data(); /* for valgrind reports */
#endif
}
#ifdef G_OS_WIN32
HMODULE gjs_dll;
static bool gjs_is_inited = false;
......@@ -245,7 +231,7 @@ LPVOID lpvReserved)
break;
case DLL_THREAD_DETACH:
shutdown();
JS_ShutDown ();
break;
default:
......@@ -265,7 +251,7 @@ public:
}
~GjsInit() {
shutdown();
JS_ShutDown();
}
operator bool() {
......
......@@ -313,8 +313,9 @@ gjs_build_string_array(JSContext *context,
g_error("Unable to reserve memory for vector");
for (i = 0; i < array_length; ++i) {
JS::ConstUTF8CharsZ chars(array_values[i], strlen(array_values[i]));
JS::RootedValue element(context,
JS::StringValue(JS_NewStringCopyZ(context, array_values[i])));
JS::StringValue(JS_NewStringCopyUTF8Z(context, chars)));
if (!elems.append(element))
g_error("Unable to append to vector");
}
......
......@@ -143,6 +143,29 @@
fun:cairo_show_text
}
# Data that Cairo keeps around for the process lifetime
# This could be freed by calling cairo_debug_reset_static_data(), but it's
# not a good idea to call that function in production, because certain versions
# of Cairo have bugs that cause it to fail assertions and crash.
{
cairo-static-data
Memcheck:Leak
match-leak-kinds: definite
fun:malloc
...
fun:FcPatternDuplicate
fun:_cairo_ft_font_face_create_for_pattern
fun:_cairo_ft_font_face_create_for_toy
fun:_cairo_toy_font_face_create_impl_face
fun:_cairo_toy_font_face_init
fun:cairo_toy_font_face_create
fun:_cairo_gstate_ensure_font_face
fun:_cairo_gstate_ensure_scaled_font
fun:_cairo_gstate_get_scaled_font
fun:_cairo_default_context_get_scaled_font
fun:cairo_show_text
}
# SpiderMonkey data races
# These are in SpiderMonkey's atomics / thread barrier stuff so presumably
......
......@@ -99,6 +99,12 @@ else
skip "System.exit() should still exit across an FFI boundary" "running under valgrind"
fi
# ensure the encoding of argv is being properly handled
$gjs -c 'imports.system.exit((ARGV[0] !== "Valentín") ? 1 : 0)' "Valentín"
report "Basic unicode encoding (accents, etc) should be functioning properly for ARGV and imports."
$gjs -c 'imports.system.exit((ARGV[0] !== "☭") ? 1 : 0)' "☭"
report "Unicode encoding for symbols should be functioning properly for ARGV and imports."
# gjs --help prints GJS help
$gjs --help >/dev/null
report "--help should succeed"
......
......@@ -101,6 +101,14 @@ function do_Compare_With_Upstream_Master(){
fi
}
function do_Check_Warnings(){
echo '-----------------------------------------'
cat compilation.log | grep "warning:" | awk '{total+=1}END{print "Total number of warnings: "total}'
echo '-----------------------------------------'
}
# ----------- Run the Tests -----------
if [[ -n "${BUILD_OPTS}" ]]; then
extra_opts="($BUILD_OPTS)"
......@@ -164,7 +172,7 @@ if [[ $1 == "GJS" ]]; then
echo "Autogen options: $ci_autogenargs"
eval ./autogen.sh "$ci_autogenargs"
make -sj
make -sj 2>&1 | tee compilation.log
if [[ $TEST == "distcheck" ]]; then
make -s distcheck
......@@ -174,6 +182,12 @@ if [[ $1 == "GJS" ]]; then
make -sj install
fi
if [[ $WARNINGS == "count" ]]; then
echo
echo '-- Warnings Report --'
do_Check_Warnings
fi
elif [[ $1 == "GJS_EXTRA" ]]; then
# Extra testing. It doesn't (re)build, just run the 'Installed Tests'
echo
......
......@@ -301,7 +301,6 @@ tree_graph_paths = {}
class style:
DIM = '\033[30m'
BOLD = '\033[1m'
ITALIC = '\033[3m'
UNDERLINE = '\033[4m'
......@@ -352,13 +351,6 @@ def get_node_label(graph, addr):
return label[:50]
def get_root_label(graph, root):
# This won't work on Windows.
#label = re.sub(r'0x[0-9a-f]{8}', '*', graph.root_labels[root])
label = graph.root_labels[root]
return '(ROOT) {}'.format(label)
def output_tree_graph(graph, data, base='', parent=''):
while data:
addr, children = data.popitem()
......@@ -375,9 +367,9 @@ def output_tree_graph(graph, data, base='', parent=''):
# Color/Style
if os.isatty(1):
if parent:
edge = style.DIM + edge + style.END
else:
edge = style.ITALIC + edge + style.END
else:
edge = style.BOLD + edge + style.END
orig = style.UNDERLINE + 'jsobj@' + addr + style.END
......