Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fatal error / exception handling #10

Merged
merged 8 commits into from
Dec 24, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 58 additions & 0 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,64 @@ To save space and be able to run JerryScript on an embedded device Sming compile
This means that the JavaScript files have to be compiled before landing on the device.
See the samples below to learn more.

Console messages
----------------

Two external functions are made available for scripts to use:

- ``print`` for normal console output (shown in AQUA)
- ``alert`` for messages in debug mode (show in RED)


VM integrity
------------

The Jerryscript VM may encounter situations where it cannot continue execution, such as memory allocation failure
or invalid bytecode.

Fatal errors cause engine execution to halt. It must be "re-booted" (in a virtual sense).
This requires application co-operation as scripts may need to be re-loaded, external functions re-registered, etc.

If intending to run unverified javascript it is especially important to guard all
calls into Jerryscript using the library exception handler:

.. code-block:: c++

JS_TRY()
{
// Call into jerryscript engine
}
JS_CATCH()
{
// Engine has failed
}

JS_TRY:
Place code here which sets up jerryscript values, etc. then calls into Jerryscript.
Data is allocated on local stack or in jerryscript engine.

DO NOT perform any system heap allocation here, such as creating or modifying `String`) objects.
If required, instantiate those above.

JS_CATCH:
Jerryscript ust be re-initialised before any further calls.

- De-allocate any globally held jerryscript values
- Call JS::cleanup()
- Call JS::init() and any other necessary initialisation

It is recommended to provide a single function for both
initial jerryscript initialisation and re-initialisation.

See the ``Event_Jsvm`` sample for a demonstratation.

Note that the VM uses own static heap (sized by :envvar:`JERRY_GLOBAL_HEAP_SIZE`) so main system heap is unaffected.

When :envvar:`JERRY_ENABLE_DEBUG` is enabled it may not be possible to recover because VM objects may be left with
invalid reference counts, for example, which will cause :cpp:func:`Jerryscript::cleanup` to fail.
Applications should generally be built with this setting disabled (the default).


Version
-------

Expand Down
7 changes: 4 additions & 3 deletions component.mk
Original file line number Diff line number Diff line change
Expand Up @@ -55,17 +55,17 @@ else
endif

#
COMPONENT_VARS += JERRY_MEM_STATS
COMPONENT_RELINK_VARS += JERRY_MEM_STATS
JERRY_MEM_STATS ?= 1
COMPONENT_CFLAGS += -DJERRY_MEM_STATS=$(JERRY_MEM_STATS)

#
COMPONENT_VARS += JERRY_ERROR_MESSAGES
COMPONENT_RELINK_VARS += JERRY_ERROR_MESSAGES
JERRY_ERROR_MESSAGES ?= 0
COMPONENT_CFLAGS += -DJERRY_ERROR_MESSAGES=$(JERRY_ERROR_MESSAGES)

#
COMPONENT_VARS += JERRY_PARSER
COMPONENT_RELINK_VARS += JERRY_PARSER
JERRY_PARSER ?= 0
COMPONENT_CFLAGS += -DJERRY_PARSER=$(JERRY_PARSER)

Expand Down Expand Up @@ -145,6 +145,7 @@ $(JERRY_TYPES_H):
$(Q) printf "//\n// Automatically generated: DO NOT EDIT\n//\n\n" > $@
$(call JerryGetTypes,JERRY_TYPE_,types,JERRY_VALUE_TYPE)
$(call JerryGetTypes,JERRY_ERROR_,types,JERRY_ERROR_TYPE)
$(call JerryGetTypes,ERR_,port,JERRY_FATAL_CODE)
$(call JerryGetTypes,JERRY_OBJECT_TYPE_,types)
$(call JerryGetTypes,JERRY_FUNCTION_TYPE_,types)
$(call JerryGetTypes,JERRY_FEATURE_,types)
Expand Down
2 changes: 1 addition & 1 deletion samples/Advanced_Jsvm/README.rst
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
Advanced_Jsvm
=============

.. highligh:: bash
.. highlight:: bash

To save space and be able to run JerryScript on an embedded device, this library builds without parser support.

Expand Down
37 changes: 28 additions & 9 deletions samples/Event_Jsvm/app/application.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@ Value addEventListener(const CallInfo& callInfo, Value& eventName, Callable& fun

JS_DEFINE_FUNCTION(addEventListener, 2)

void startJsvm();

/*
* Dispatch an event to all registered listeners
*
Expand All @@ -67,21 +69,38 @@ bool triggerEvent(const String& name, const JS::Object& params)
return false;
}

// Build the event object...
JS::Object event;
event["name"] = name;
event["params"] = params;
JS_TRY()
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mikee47 I am wondering isn't it possible to use try-catch directly in the JavaScript code and catch out-of-memory exception? We could expose a reset() JavaScript function that resets the VM and give the JS developers (end user) better control. On the C/C++ side we could check if the VM is in error state and restart the VM automatically if it stays in this error state for 8 seconds.

Copy link
Contributor Author

@mikee47 mikee47 Dec 22, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unfortunately fatal errors are not the same as javascript exceptions and cannot be caught in script. The engine is essentially halted. No attempt to even tidy up intermediate allocations so cleanup won't work. Try running the tests with JERRY_ENABLE_DEBUG=1 and see what happens!

A reset() function would be very easy to implement. However, depending on the use-case if we wanted multiple clients runninng together would we really want one of them to be able to pull the rug out from the others? Maybe, but that'll depend on usage.

{
// Build the event object...
JS::Object event;
event["name"] = name;
event["params"] = params;

auto context = JS::global();
for(auto& listener : events[name]) {
listener.call(context, event);
}
auto object = JS::global();
for(auto& listener : events[name]) {
listener.call(object, event);
}

return true;
return true;
}
JS_CATCH()
{
debug_i("%s", String(e).c_str());
debug_i("System heap %u", system_get_free_heap_size());
System.queueCallback(startJsvm);
return false;
}
}

/*
* Called at startup to initialise our jerryscript engine,
* Also called on fatal exceptiuon to re-initialise.
*/
void startJsvm()
{
// Release any allocated jerryscript values
events.clear();

JS::initialise();

auto context = JS::global();
Expand Down
8 changes: 8 additions & 0 deletions samples/Event_Jsvm/files/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,14 @@ var x;
function thirdListener(event) {
print('Third listener is a normal function!');
print('-------------' + (++x) + '------------------');

// Comment-out this line to see how program behaves with memory full exception!
return;

var y = [1, 2, 3, 4];
for(var x = 0; x < 10000; ++x) {
y[x] = 0;
}
}

/**
Expand Down
54 changes: 54 additions & 0 deletions src/Except.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
/****
* Sming Framework Project - Open Source framework for high efficiency native ESP8266 development.
* Created 2015 by Skurydin Alexey
* http://github.com/SmingHub/Sming
* All files of the Sming Core are provided under the LGPL v3 license.
*
* ErrorHandler.cpp
*
* @author: Dec 2021 - Mikee47 <mike@sillyhouse.net>
*
*/

#include "include/Jerryscript/VirtualMachine.h"
#include "include/Jerryscript/Except.h"
#include <debug_progmem.h>
#include <csetjmp>

String toString(Jerryscript::FatalCode code)
{
switch(code) {
#define XX(jt, t) \
case Jerryscript::FatalCode::t: \
return F(#t);
JERRY_FATAL_CODE_MAP(XX)
#undef XX
default:
return String(unsigned(code));
}
}

namespace Jerryscript
{
Except* Except::current;

void Except::raise(jerry_fatal_code_t code)
{
if(current == nullptr) {
debug_e("[JS] Fatal Error %u: calling abort()", code);
abort();
}
current->mCode = FatalCode(code);
longjmp(current->context, 1);
}

Except::operator String() const
{
String s = F("Exception #");
s += unsigned(mCode);
s += F(", ");
s += toString(mCode);
return s;
}

} // namespace Jerryscript
2 changes: 1 addition & 1 deletion src/VirtualMachine.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ void initialise(jerry_init_flag_t flags)

auto context = global();
context.registerFunction("print", printFunction);
#ifdef DEBUG
#if DEBUG_BUILD
context.registerFunction("alert", alertFunction);
#endif
}
Expand Down
1 change: 1 addition & 0 deletions src/include/Jerryscript.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
#include "Jerryscript/Task.h"
#include "Jerryscript/Debug.h"
#include "Jerryscript/Function.h"
#include "Jerryscript/Except.h"

namespace JS
{
Expand Down
61 changes: 61 additions & 0 deletions src/include/Jerryscript/Except.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
/****
* Sming Framework Project - Open Source framework for high efficiency native ESP8266 development.
* Created 2015 by Skurydin Alexey
* http://github.com/SmingHub/Sming
* All files of the Sming Core are provided under the LGPL v3 license.
*
* Except.h
*
* @author: Dec 2021 - Mikee47 <mike@sillyhouse.net>
*
*/

#include "include/Jerryscript/Types.h"
#include <csetjmp>

namespace Jerryscript
{
enum class FatalCode {
#define XX(jt, t) t = jt,
JERRY_FATAL_CODE_MAP(XX)
#undef XX
};

class Except
{
public:
Except() : prev(current)
{
current = this;
}

~Except()
{
current = prev;
}

[[noreturn]] static void raise(jerry_fatal_code_t code);

FatalCode code()
{
return mCode;
}

operator String() const;

std::jmp_buf context;

private:
static Except* current;
Except* prev;
FatalCode mCode{};
};

#define JS_TRY() \
::Jerryscript::Except e; \
if(setjmp(e.context) == 0)
#define JS_CATCH() else

} // namespace Jerryscript

String toString(Jerryscript::FatalCode code);
6 changes: 5 additions & 1 deletion src/include/Jerryscript/VirtualMachine.h
Original file line number Diff line number Diff line change
Expand Up @@ -54,11 +54,14 @@ namespace Snapshot
{
/**
* @name Load a snapshot into the virtual machine and execute it
* @{
*/

/**
* @brief Load from memory buffer
* @param snapshot Points to snapshot content
* @param snapshotSize Number of bytes in snapshot
* @retval Value Result from execution, or Error
* @{
*/
Value load(const uint32_t* snapshot, size_t snapshotSize);

Expand All @@ -81,6 +84,7 @@ inline Value loadFromFile(const String& fileName)
{
return load(fileGetContent(fileName));
}

/** @} */

} // namespace Snapshot
Expand Down
19 changes: 4 additions & 15 deletions src/jerry-port.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,23 +12,12 @@
#include <stdarg.h>
#include <sys/time.h>

#include "include/Jerryscript/Except.h"
#include <include/jerryscript-port.h>
#include <include/jerryscript-core.h>
#include <debug_progmem.h>
#include <Platform/RTC.h>

/**
* Provide console message implementation for the engine.
*/
void jerry_port_console(const char* format, /**< format string */
...) /**< parameters */
{
va_list args;
va_start(args, format);
m_vprintf(format, args);
va_end(args);
} /* jerry_port_console */

/**
* Provide log message implementation for the engine.
*/
Expand All @@ -42,7 +31,7 @@ void jerry_port_log(jerry_log_level_t level, /**< log level */
va_start(args, format);
m_vprintf(format, args);
va_end(args);
} /* jerry_port_log */
}

/**
* Default implementation of jerry_port_get_current_time.
Expand Down Expand Up @@ -71,8 +60,8 @@ double jerry_port_get_local_time_zone_adjustment(double unix_ms, /**< ms since u
*/
void jerry_port_fatal(jerry_fatal_code_t code)
{
abort();
} /* jerry_port_fatal */
Jerryscript::Except::raise(code);
}

/**
* Default module resolver.
Expand Down
Loading