duktape.cr v0.18.0

Evaluate JavaScript from Crystal!

Duktape.cr

GitHub version Build Status

Duktape.cr provides Crystal bindings to the Duktape javascript engine.

Installation

Duktape.cr is best installed using Shards.

Add this to your shard.yml:

name: example  # your project's name
version: 1.0.0 # your project's version

dependencies:
  duktape:
    github: jessedoyle/duktape.cr
    version: ~> 0.18.0

then execute:

shards install

Shards will automatically make the native library. You can make the library manually by calling make libduktape.

Usage

You must first create a Duktape context:

require "duktape"

sbx = Duktape::Sandbox.new

sbx.eval! <<-JS
  var birthYear = 1990;

  function calcAge(birthYear){
    var current = new Date();
    var year = current.getFullYear();
    return year - birthYear;
  }

  print("You are " + calcAge(birthYear) + " years old.");
JS

An overwhelming majority of the Duktape API has been implemented. You can call the API functions directly on a Duktape::Sandbox or Duktape::Context instance:

sbx = Duktape::Sandbox.new
sbx.push_global_object   # [ global ]
sbx.push_string "Math"   # [ global "Math" ]
sbx.get_prop -2          # [ global Math ]
sbx.push_string "PI"     # [ global Math "PI" ]
sbx.get_prop -2          # [ global Math PI ]
pi = sbx.get_number -1
puts "PI: #{pi}"         # => PI: 3.14159
sbx.pop_3

Eval vs Eval!

All of the evaluation API methods have a corresponding bang-method (!). The bang method calls will raise when a javascript error occurs, the non-bang methods will not raise on invalid javascript.

For example:

sbx = Duktape::Context.new
sbx.eval <<-JS
  var a =
JS

will not raise any errors, but will return a non-zero error code.

The following code:

sbx = Duktape::Context.new
sbx.eval! <<-JS
  __invalid();
JS

will raise Duktape::SyntaxError.

Sandbox vs Context

You should only execute untrusted javascript code from within a Duktape::Sandbox instance. A sandbox isolates code from insecure operations such as Duktape's internal require mechanism and the Duktape global javascript object.

Creating a Duktape::Context gives code access to internal Duktape properties:

ctx = Duktape::Context.new
ctx.eval! <<-JS
  print(Duktape.version);
JS

Setting a Timeout

Duktape::Sandbox instances may optionally take an execution timeout limit in milliseconds. This provides protection against infinite loops when executing untrusted code.

A Duktape::RangeError exception is raised when the following code executes for longer than specified:

sbx = Duktape::Sandbox.new 500 # 500ms execution time limit
sbx.eval! "while (true) {}"    # => RangeError

Duktape::Runtime

An alternative interface for evaluating JS code is available via the Duktape::Runtime class. This class provides a streamlined evaluation API (similar to ExecJS) that allows easier access to javascript values without the need to call many low-level Duktape API functions.

The entire Runtime API is as follows:

  • call(property, *args) - Call the property or function with the given arguments and return the result.
  • call([properties], *args) - Call the property that is nested within an array of string property names.
  • eval(source) - Evaluate the javascript source and return the last value.
  • exec(source) - Evaluate the javascript source and always return nil.

Duktape::Runtime instances can also be provided an initialization block when created.

Here's an example:

  require "duktape/runtime"

  # A Runtime (optionally) accepts an initialization block
  rt = Duktape::Runtime.new do |sbx|
    sbx.eval! <<-JS
      function test(a, b, c) { return a + b + c; }
    JS
  end

  rt.call("test", 3, 4, 5) # => 12.0 (same as test(3, 4, 5);)
  rt.call(["Math", "PI"])  # => 3.14159
  rt.eval("1 + 1")         # => 2.0
  rt.exec("1 + 1")         # => nil

Note that duktape/runtime is not loaded by the base duktape require, and may be used standalone if necessary (ie. replace your require "duktape" calls with require "duktape/runtime" if you want this functionality).

Calling Crystal Code from Javascript

Note: This functionality is considered experimental and syntax/functionality may change dramatically between releases.

It is possible to call Crystal code from your javascript:

  sbx = Duktape::Sandbox.new

  # Push a global function named "add_together"
  # that accepts two arguments.
  sbx.push_global_proc("add_together", 2) do |ptr|
    env = Duktape::Sandbox.new ptr

    # Get the two arguments
    # from the stack
    a = env.require_number 0
    b = env.require_number 1

    env.push_number a + b # Push the return value to the stack
    env.call_success      # call_success -> stack top is value returned
  end

  sbx.eval! "print(add_together(2, 3));" # => 5

The proc object that is pushed to the Duktape stack accepts a pointer to a Context instance. We must wrap this pointer by calling env = Duktape::Sandbox.new ptr. The proc must also return an Int32 status code - env.call_failure and env.call_success will provide the proper integer values.

Note: Because it is currently not possible to pass closures to C bindings in Crystal, one must be careful that any variables used in the proc must not be referenced or initialized outside the scope of the proc. This is why variable names such as env are used.

Exceptions

The following exceptions may be thrown at runtime and may be rescued normally:

  • Duktape::Error
  • Duktape::EvalError
  • Duktape::RangeError
  • Duktape::ReferenceError
  • Duktape::SyntaxError
  • Duktape::TypeError
  • Duktape::URIError

These exceptions all inherit from Duktape::Error, so it may be used as a catch-all for runtime errors.

The following exceptions represent errors internal to the Duktape engine and are generally not recoverable when thrown from a context:

  • Duktape::InternalError
  • Duktape::HeapError

These exceptions all inherit from Duktape::InternalError.

Contributing

I'll accept any pull requests that are well tested for bugs/features with Duktape.cr.

You should fork the main repo, create a feature branch, write tests and submit a pull request.

License

Duktape.cr is licensed under the MIT License. Please see LICENSE for details.

v0.18.0 - Sept 6, 2019

  • Update Duktape version to 2.4.0.
  • See the release notes for more info.
  • Add bindings for to_stacktrace, safe_to_stacktrace, push_bare_array, require_constructable, and require_constructor_call.
  • Allow C compiler flag overrides when compiling Duktape. Define the CFLAGS variable during shards install (i.e. CFLAGS=-O1 shards install).
  • No longer explicitly enable Duktape's Symbol builtin as it is now enabled by default.

v0.17.0 - June 6, 2019

  • Update ameba to the latest current version (0.10.0) as previous versions no longer compile in CI.
  • Relax the restriction on ameba to pull in newer minor versions.

v0.16.0 - Apr, 21 2019

  • Update ameba to the current latest version of v0.9.1.
  • Build specs with all warnings enabled in Crystal >= 0.28.0.
  • Fix a deprecation warning with Crystal 0.28.0 where integer division will return a float in future versions. Use Int#// to retain backwards compatibility.

v0.15.1 - Nov 7, 2018

  • Add ameba as a development dependency for static analysis.
  • Fix ameba lint exceptions consisting of unused variable definitions and block variables.

v0.15.0 - Aug 14, 2018

  • Update Duktape version to 2.3.0.
  • See the release notes for more info.
  • Fix a missing it block expectation in tests.
  • Add bindings for random, push_new_target, get_global_heaptr and put_global_heapptr.

v0.14.1 - May 13, 2018

  • Fix a type inference error on the Sandbox @timeout instance variable that occurs due to recent changes in Crystal master [#43]. Thanks @kostya!

v0.14.0 - Apr 30, 2018

  • Update Duktape to version 2.2.1.
  • See the release for more info.

v0.13.0 - Dec 28, 2017

  • Update Duktape to version 2.2.0, rebuilding all necessary configuration and header files.
  • [upstream change] LibDUK::Compile::* and LibDUK::BufObj::* constant values have been changed - remap these constants to their updated values.
  • [upstream change] LibDUK::Bool is now of type UInt32 (as opposed to Int32).
  • Add bindings for new public API methods: duk_pus_proxy, duk_seal, duk_freeze, duk_require_object, duk_is_constructable and duk_opt_xxx methods. The duk_opt methods work similar to duk_require_xxx, but allow a default value to be passed in that is used when there is no value at the given stack index.
  • Alias LibDUK::Number as Float64 for more simple type changes in the future.
  • Add the Duktape::API::Opt module to encapsulate binding wrapper code for the duk_opt methods implemented.
  • Run all code through the crystal 0.24.1 formatter.
  • See duktape releases for more info.

v0.12.1 - Nov 2, 2017

  • [bugfix] - Fix an unintended Duktape heap instantiation when creating a new Duktape::Context.
  • Run crystal tool format on all source code.

v0.12.0 - Oct 2017

  • [breaking change] All LibDUK hardcoded types are now enum values (i.e. LibDUK::TYPE_NULL becomes LibDUK::Type::Null). Where possible, methods accept both the original types as well as enumerated values.
  • [breaking change] Remove the UInt32 flags arguments from all Duktape::Context#compile methods.
  • [breaking change] Remove some bindings from LibDUK as they were removed upstream. See duktape releases for more info.
  • Update Duktape to v2.0.2.
  • Add Duktape::Builtin helpers that allow for modular extensions into a Duktape::Context instance.
  • Add builtins for console.log, alert and print.
  • Implement file operations natively in Crystal as they have been removed from Duktape.
  • The Duktape stack is no longer logged as a debug value when Duktape::InternalError is raised.
  • Alias Int32 as LibDUK::Index to allow for quicker changes to indexes in the future.

v0.11.0 - July 24, 2017

  • Fix compiler issues with Crystal 0.23.0 by making Duktape::Logger#log_color accept a Logger::Severity. [@kostya, #35]

v0.10.1 - Jan 31, 2017

  • Fix an incorrect type restriction that was causing compiler issues on recent Crystal versions.
  • Fix Sandbox timeout tests by no longer running a set number of iterations - instead infinite loop until timeout.

v0.10.0 - Nov 22, 2016

  • Update for Crystal 0.20.0. As shards now copies the entire shard directory into libs, we can move the ext directory to the shard root directory for simplicity.
  • Update makefile output paths to match new structure.
  • Resolve #25 by allowing a developer to pass a Duktape::Context instance when initializing a Duktape::Runtime. This allows the runtime to use the internal Duktape global object.

v0.9.1 - Sept 21, 2016

v0.9.0 - May 26, 2016

  • Update Duktape to v1.5.0. See release info.
  • Update to Crystal 0.17.4 syntax.
  • Format code using Crystal 0.17.4 formatter.
  • Add NamedTuple as a type that is allowed as parameter to call on a Duktape::Runtime instance. NamedTuples will be translated to a hash.
  • Optimize for speed (-O2) instead of size (-0s) when building the duktape library.
  • Use -Wpedantic as the compiler flag for warnings.

v0.8.2 - May 5, 2016

  • Update to Crystal 0.16.0 syntax.

v0.8.1 - Mar 23, 2016

  • Update to Crystal 0.14.2 syntax.
  • Refactor API::Eval code for readability.

v0.8.0 - Feb 4, 2016

  • (breaking change) JS errors are now mapped to their proper Crystal exceptions. i.e. JS SyntaxError becomes Duktape::SyntaxError.
  • (breaking change) Make all exception classes more consistent. Instances of Duktape::Error are all recoverable exceptions that are thrown by the engine at runtime (eg. Duktape::TypeError). Instances of Duktape::InternalError are generally non-recoverable for a given context (eg. Duktape::HeapError).
  • Added call_success, call_failure and return_undefined convenience methods that provide the appropriate integer status codes when returning from a native function call.
  • Added the push_global_proc method that simplifies pushing a named native function to the stack.
  • Duktape::Runtime instances may now accept a execution timeout value in milliseconds upon creation. [#15, @raydf].
  • Changed the name in the shard.yml from duktape.cr to duktape. This should have no effect on the installation process.

v0.7.0 - Jan 18, 2016

  • (breaking change) A monkeypatch to the Crystal Logger class was temporarily added to master to fix a bug in core Crystal (#1982). This patch has now been removed from the codebase. Crystal v0.10.1 or higher is a requirement for this library.
  • Duktape::Runtime instances now return Crystal arrays and hashes for corresponding JS arrays and objects.
  • Duktape::Runtime can now accept hashes and arrays as arguments for call. These will be translated into Javascript objects and pushed to the stack.
  • Updated Duktape version to v1.4.0. See release info.

v0.6.4 - Jan 2, 2016

  • Add the src/runtime.cr file so you can now properly require "./duktape/runtime" once shards does its thing.
  • Actually update the version in shard.yml (my mistake - sorry!).

v0.6.3 - Jan 2, 2016

NOTE - This release has issues, use 0.6.4 instead.

  • Rework the internal require order and duktape/base.
  • Add a Duktape::Runtime class that lessens the need for low-level API calls for many use-cases. This must be required using require "duktape/runtime".

v0.6.2 - November 30, 2015

  • Update Duktape version to v1.3.1. See release info.
  • More consistent exception classes in error.cr.
  • Removed a few unecessary method calls from spec files.
  • Adopt Crystal 0.9.1 code formatting.

v0.6.1 - September 21, 2015

  • Update to Crystal v0.8.0 syntax/compatibility.
  • Fix a potential use-after-free scenario that may occur when Context or Sandbox instances were garbage-collected by Crystal.
  • The Duktape heap is no longer destroyed when a Duktape::InternalError is thrown in Crystal. Instead, the heap will be destroyed automatically upon finalization.

v0.6.0 - September 14, 2015

  • Update Duktape to v1.3.0. This update does not break exisiting functionality. See release info.
  • Implement a timeout mechanism for Duktape::Sandbox instances. A timeout is not specified by default.

A timeout may be specified (in milliseconds) as such:

sbx = Duktape::Sandbox.new(500) # 500 millisecond execution limit

v0.5.1 - September 11, 2015

  • Add this CHANGLEOG.
  • Fix issue #1 by linking against standard math library.
  • Cleanup Makefile syntax.

v0.5.0 - September 8, 2015

  • Initial public release.
Github statistic:
  • 84
  • 10
  • 10
  • 6
  • 0
  • 14 days ago

License:

MIT License

Links: