Commit cf971327 authored by Philipp Stephani's avatar Philipp Stephani

Implement module assertions for users

Add a new command-line option '-module-assertions' that users can
enable developing or debugging a module.  If this option is present,
Emacs performs additional checks to verify that modules fulfill their
requirements.  These checks are expensive and crash Emacs if modules
are invalid, so disable them by default.

This is a command-line option instead of an ordinary variable because
changing it while Emacs is running would cause data structure

* src/emacs.c (main): New command line option '-module-assertions'.

* src/emacs-module.c (module_assert_main_thread)
(module_assert_runtime, module_assert_env, module_assert_value):
New functions to assert module requirements.
(syms_of_module): New uninterned variable 'module-runtimes'.
(init_module_assertions, in_main_thread, module_abort): New helper
(initialize_environment): Initialize value list.  If assertions are
enabled, use a heap-allocated environment object.
(finalize_environment): Add assertion that environment list is never
(finalize_runtime_unwind): Pop module runtime object stack.
(value_to_lisp): Assert that the value is valid.
(lisp_to_value): Record new value if assertions are enabled.
(mark_modules): Mark allocated object list.
(module_non_local_exit_check, module_non_local_exit_clear)
(module_non_local_exit_get, module_non_local_exit_signal)
(module_non_local_exit_throw): Assert thread and environment.
(module_get_environment): Assert thread and runtime.
(module_make_function, module_funcall, module_intern)
(module_funcall, module_make_integer, module_make_float)
(module_make_string, module_make_user_ptr, module_vec_get)
(funcall_module, Fmodule_load): Adapt callers.
(module_make_global_ref): If assertions are enabled, use the global
environment to store global values.
(module_free_global_ref): Remove value from global value list.

* test/ (EMACSOPT): Enable module assertions when testing

* test/data/emacs-module/mod-test.c (Fmod_test_invalid_store)
(Fmod_test_invalid_load): New functions to test module assertions.
(emacs_module_init): Bind the new functions.

* test/src/emacs-module-tests.el (mod-test-emacs): New constant for
the Emacs binary file.
(mod-test-file): New constant for the test module file name.
(module--test-assertions): New unit test.
parent b49dd3b0
......@@ -85,6 +85,12 @@ modern init systems such as systemd, which manage many of the traditional
aspects of daemon behavior themselves. '--bg-daemon' is now an alias
for '--daemon'.
** New option '--module-assertions'. If the user supplies this
option, Emacs will perform expensive correctness checks when dealing
with dynamic modules. This is intended for module authors that wish
to verify that their module conforms to the module requirements. The
option makes Emacs abort if a module-related assertion triggers.
** Emacs now supports 24-bit colors on capable text terminals
Terminal is automatically initialized to use 24-bit colors if the
This diff is collapsed.
......@@ -223,6 +223,7 @@ Initialization options:\n\
--fg-daemon[=NAME] start a (named) server in the foreground\n\
--debug-init enable Emacs Lisp debugger for init file\n\
--display, -d DISPLAY use X server DISPLAY\n\
--module-assertions assert behavior of dynamic modules\n\
--no-build-details do not add build details such as time stamps\n\
......@@ -1263,6 +1264,18 @@ Using an Emacs configured with --with-x-toolkit=lucid does not have this problem
build_details = ! argmatch (argv, argc, "-no-build-details",
"--no-build-details", 7, NULL, &skip_args);
bool module_assertions
= argmatch (argv, argc, "-module-assertions", "--module-assertions", 15,
NULL, &skip_args);
if (dumping && module_assertions)
fputs ("Module assertions are not supported during dumping\n", stderr);
exit (1);
init_module_assertions (module_assertions);
#ifdef HAVE_NS
ns_pool = ns_alloc_autorelease_pool ();
......@@ -1720,6 +1733,7 @@ static const struct standard_args standard_args[] =
{ "-nl", "--no-loadup", 70, 0 },
{ "-nsl", "--no-site-lisp", 65, 0 },
{ "-no-build-details", "--no-build-details", 63, 0 },
{ "-module-assertions", "--module-assertions", 62, 0 },
/* -d must come last before the options handled in startup.el. */
{ "-d", "--display", 60, 1 },
{ "-display", 0, 60, 1 },
......@@ -3943,6 +3943,7 @@ extern Lisp_Object make_user_ptr (void (*finalizer) (void *), void *p);
extern Lisp_Object funcall_module (Lisp_Object, ptrdiff_t, Lisp_Object *);
extern Lisp_Object module_function_arity (const struct Lisp_Module_Function *);
extern void mark_modules (void);
extern void init_module_assertions (bool);
extern void syms_of_module (void);
......@@ -68,7 +68,7 @@ EMACS_EXTRAOPT=
# Command line flags for Emacs.
# Apparently MSYS bash would convert "-L :" to "-L ;" anyway,
# but we might as well be explicit.
EMACSOPT = -batch --no-site-file --no-site-lisp -L "$(SEPCHAR)$(srcdir)" $(EMACS_EXTRAOPT)
EMACSOPT = -batch --no-site-file --no-site-lisp -module-assertions -L "$(SEPCHAR)$(srcdir)" $(EMACS_EXTRAOPT)
# Prevent any settings in the user environment causing problems.
......@@ -213,6 +213,28 @@ Fmod_test_vector_eq (emacs_env *env, ptrdiff_t nargs, emacs_value args[],
return env->intern (env, "t");
static emacs_value invalid_stored_value;
/* The next two functions perform a possibly-invalid operation: they
store a value in a static variable and load it. This causes
undefined behavior if the environment that the value was created
from is no longer live. The module assertions check for this
error. */
static emacs_value
Fmod_test_invalid_store (emacs_env *env, ptrdiff_t nargs, emacs_value *args,
void *data)
return invalid_stored_value = env->make_integer (env, 123);
static emacs_value
Fmod_test_invalid_load (emacs_env *env, ptrdiff_t nargs, emacs_value *args,
void *data)
return invalid_stored_value;
/* Lisp utilities for easier readability (simple wrappers). */
......@@ -260,6 +282,8 @@ emacs_module_init (struct emacs_runtime *ert)
DEFUN ("mod-test-userptr-get", Fmod_test_userptr_get, 1, 1, NULL, NULL);
DEFUN ("mod-test-vector-fill", Fmod_test_vector_fill, 2, 2, NULL, NULL);
DEFUN ("mod-test-vector-eq", Fmod_test_vector_eq, 2, 2, NULL, NULL);
DEFUN ("mod-test-invalid-store", Fmod_test_invalid_store, 0, 0, NULL, NULL);
DEFUN ("mod-test-invalid-load", Fmod_test_invalid_load, 0, 0, NULL, NULL);
#undef DEFUN
......@@ -19,9 +19,17 @@
(require 'ert)
(require 'mod-test
(expand-file-name "data/emacs-module/mod-test"
(defconst mod-test-emacs
(expand-file-name invocation-name invocation-directory)
"File name of the Emacs binary currently running.")
(defconst mod-test-file
"File name of the module test file."))
(require 'mod-test mod-test-file)
;; Basic tests.
......@@ -174,4 +182,30 @@ changes."
(should (equal (help-function-arglist #'mod-test-sum)
'(arg1 arg2))))
(ert-deftest module--test-assertions ()
"Check that -module-assertions work."
(skip-unless (file-executable-p mod-test-emacs))
;; This doesn’t yet cause undefined behavior.
(should (eq (mod-test-invalid-store) 123))
(should (equal (call-process mod-test-emacs nil t nil
"-batch" "-Q" "-module-assertions" "-eval"
(require 'mod-test ,mod-test-file)
;; Storing and reloading a local
;; value causes undefined
;; behavior, which should be
;; detected by the module
;; assertions.
;; FIXME: This string is probably different on
;; Windows and Linux.
"Abort trap: 6"))
(re-search-backward (rx bos "Emacs module assertion: "
"Emacs value not found in "
(+ digit) " values of "
(+ digit) " environments" ?\n eos))))
;;; emacs-module-tests.el ends here
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment