jsre.go 8.96 KB
Newer Older
1
// Copyright 2015 The go-ethereum Authors
2
// This file is part of the go-ethereum library.
3
//
4
// The go-ethereum library is free software: you can redistribute it and/or modify
5 6 7 8
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
9
// The go-ethereum library is distributed in the hope that it will be useful,
10
// but WITHOUT ANY WARRANTY; without even the implied warranty of
11
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 13 14
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
15
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
16

17
// Package jsre provides execution environment for JavaScript.
zelig's avatar
zelig committed
18 19 20
package jsre

import (
21 22
	crand "crypto/rand"
	"encoding/binary"
zelig's avatar
zelig committed
23
	"fmt"
24
	"io"
zelig's avatar
zelig committed
25
	"io/ioutil"
26
	"math/rand"
27
	"time"
28

zelig's avatar
zelig committed
29
	"github.com/ethereum/go-ethereum/common"
30
	"github.com/ethereum/go-ethereum/internal/jsre/deps"
31
	"github.com/robertkrimen/otto"
zelig's avatar
zelig committed
32 33
)

34 35 36 37 38
var (
	BigNumber_JS = deps.MustAsset("bignumber.js")
	Web3_JS      = deps.MustAsset("web3.js")
)

zelig's avatar
zelig committed
39 40 41 42 43 44 45 46 47
/*
JSRE is a generic JS runtime environment embedding the otto JS interpreter.
It provides some helper functions to
- load code from files
- run code snippets
- require libraries
- bind native go objects
*/
type JSRE struct {
48
	assetPath     string
49
	output        io.Writer
50 51
	evalQueue     chan *evalReq
	stopEventLoop chan bool
52
	closed        chan struct{}
53 54 55 56 57 58 59 60
}

// jsTimer is a single timer instance with a callback function
type jsTimer struct {
	timer    *time.Timer
	duration time.Duration
	interval bool
	call     otto.FunctionCall
zelig's avatar
zelig committed
61 62
}

63
// evalReq is a serialized vm execution request processed by runEventLoop.
64
type evalReq struct {
65
	fn   func(vm *otto.Otto)
66 67 68 69
	done chan bool
}

// runtime must be stopped with Stop() after use and cannot be used after stopping
70
func New(assetPath string, output io.Writer) *JSRE {
zelig's avatar
zelig committed
71
	re := &JSRE{
72
		assetPath:     assetPath,
73
		output:        output,
74
		closed:        make(chan struct{}),
75 76
		evalQueue:     make(chan *evalReq),
		stopEventLoop: make(chan bool),
zelig's avatar
zelig committed
77
	}
78
	go re.runEventLoop()
79
	re.Set("loadScript", re.loadScript)
80
	re.Set("inspect", re.prettyPrintJS)
zelig's avatar
zelig committed
81 82 83
	return re
}

84 85 86 87 88 89 90 91 92 93 94 95
// randomSource returns a pseudo random value generator.
func randomSource() *rand.Rand {
	bytes := make([]byte, 8)
	seed := time.Now().UnixNano()
	if _, err := crand.Read(bytes); err == nil {
		seed = int64(binary.LittleEndian.Uint64(bytes))
	}

	src := rand.NewSource(seed)
	return rand.New(src)
}

96 97 98 99
// This function runs the main event loop from a goroutine that is started
// when JSRE is created. Use Stop() before exiting to properly stop it.
// The event loop processes vm access requests from the evalQueue in a
// serialized way and calls timer callback functions at the appropriate time.
100

101 102 103 104
// Exported functions always access the vm through the event queue. You can
// call the functions of the otto vm directly to circumvent the queue. These
// functions should be used if and only if running a routine that was already
// called from JS through an RPC call.
105
func (self *JSRE) runEventLoop() {
106 107
	defer close(self.closed)

108
	vm := otto.New()
109 110 111
	r := randomSource()
	vm.SetRandomSource(r.Float64)

112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155
	registry := map[*jsTimer]*jsTimer{}
	ready := make(chan *jsTimer)

	newTimer := func(call otto.FunctionCall, interval bool) (*jsTimer, otto.Value) {
		delay, _ := call.Argument(1).ToInteger()
		if 0 >= delay {
			delay = 1
		}
		timer := &jsTimer{
			duration: time.Duration(delay) * time.Millisecond,
			call:     call,
			interval: interval,
		}
		registry[timer] = timer

		timer.timer = time.AfterFunc(timer.duration, func() {
			ready <- timer
		})

		value, err := call.Otto.ToValue(timer)
		if err != nil {
			panic(err)
		}
		return timer, value
	}

	setTimeout := func(call otto.FunctionCall) otto.Value {
		_, value := newTimer(call, false)
		return value
	}

	setInterval := func(call otto.FunctionCall) otto.Value {
		_, value := newTimer(call, true)
		return value
	}

	clearTimeout := func(call otto.FunctionCall) otto.Value {
		timer, _ := call.Argument(0).Export()
		if timer, ok := timer.(*jsTimer); ok {
			timer.timer.Stop()
			delete(registry, timer)
		}
		return otto.UndefinedValue()
	}
156 157 158 159 160 161 162 163 164 165 166 167 168 169
	vm.Set("_setTimeout", setTimeout)
	vm.Set("_setInterval", setInterval)
	vm.Run(`var setTimeout = function(args) {
		if (arguments.length < 1) {
			throw TypeError("Failed to execute 'setTimeout': 1 argument required, but only 0 present.");
		}
		return _setTimeout.apply(this, arguments);
	}`)
	vm.Run(`var setInterval = function(args) {
		if (arguments.length < 1) {
			throw TypeError("Failed to execute 'setInterval': 1 argument required, but only 0 present.");
		}
		return _setInterval.apply(this, arguments);
	}`)
170 171
	vm.Set("clearTimeout", clearTimeout)
	vm.Set("clearInterval", clearTimeout)
172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190

	var waitForCallbacks bool

loop:
	for {
		select {
		case timer := <-ready:
			// execute callback, remove/reschedule the timer
			var arguments []interface{}
			if len(timer.call.ArgumentList) > 2 {
				tmp := timer.call.ArgumentList[2:]
				arguments = make([]interface{}, 2+len(tmp))
				for i, value := range tmp {
					arguments[i+2] = value
				}
			} else {
				arguments = make([]interface{}, 1)
			}
			arguments[0] = timer.call.ArgumentList[0]
191
			_, err := vm.Call(`Function.call.call`, nil, arguments...)
192
			if err != nil {
193
				fmt.Println("js error:", err, arguments)
194
			}
195

196 197
			_, inreg := registry[timer] // when clearInterval is called from within the callback don't reset it
			if timer.interval && inreg {
198 199 200 201 202 203 204
				timer.timer.Reset(timer.duration)
			} else {
				delete(registry, timer)
				if waitForCallbacks && (len(registry) == 0) {
					break loop
				}
			}
205
		case req := <-self.evalQueue:
206
			// run the code, send the result back
207 208
			req.fn(vm)
			close(req.done)
209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224
			if waitForCallbacks && (len(registry) == 0) {
				break loop
			}
		case waitForCallbacks = <-self.stopEventLoop:
			if !waitForCallbacks || (len(registry) == 0) {
				break loop
			}
		}
	}

	for _, timer := range registry {
		timer.timer.Stop()
		delete(registry, timer)
	}
}

Felix Lange's avatar
Felix Lange committed
225 226
// Do executes the given function on the JS event loop.
func (self *JSRE) Do(fn func(*otto.Otto)) {
227 228 229 230 231 232
	done := make(chan bool)
	req := &evalReq{fn, done}
	self.evalQueue <- req
	<-done
}

233 234
// stops the event loop before exit, optionally waits for all timers to expire
func (self *JSRE) Stop(waitForCallbacks bool) {
235 236 237 238 239
	select {
	case <-self.closed:
	case self.stopEventLoop <- waitForCallbacks:
		<-self.closed
	}
240 241
}

zelig's avatar
zelig committed
242 243 244
// Exec(file) loads and runs the contents of a file
// if a relative path is given, the jsre's assetPath is used
func (self *JSRE) Exec(file string) error {
245
	code, err := ioutil.ReadFile(common.AbsolutePath(self.assetPath, file))
zelig's avatar
zelig committed
246 247 248
	if err != nil {
		return err
	}
249 250 251 252 253 254 255 256
	var script *otto.Script
	self.Do(func(vm *otto.Otto) {
		script, err = vm.Compile(file, code)
		if err != nil {
			return
		}
		_, err = vm.Run(script)
	})
zelig's avatar
zelig committed
257 258 259
	return err
}

260 261 262 263
// Bind assigns value v to a variable in the JS environment
// This method is deprecated, use Set.
func (self *JSRE) Bind(name string, v interface{}) error {
	return self.Set(name, v)
zelig's avatar
zelig committed
264 265
}

266 267
// Run runs a piece of JS code.
func (self *JSRE) Run(code string) (v otto.Value, err error) {
Felix Lange's avatar
Felix Lange committed
268
	self.Do(func(vm *otto.Otto) { v, err = vm.Run(code) })
269
	return v, err
zelig's avatar
zelig committed
270 271
}

272 273
// Get returns the value of a variable in the JS environment.
func (self *JSRE) Get(ns string) (v otto.Value, err error) {
Felix Lange's avatar
Felix Lange committed
274
	self.Do(func(vm *otto.Otto) { v, err = vm.Get(ns) })
275
	return v, err
zelig's avatar
zelig committed
276 277
}

278 279
// Set assigns value v to a variable in the JS environment.
func (self *JSRE) Set(ns string, v interface{}) (err error) {
Felix Lange's avatar
Felix Lange committed
280
	self.Do(func(vm *otto.Otto) { err = vm.Set(ns, v) })
281
	return err
zelig's avatar
zelig committed
282 283
}

284
// loadScript executes a JS script from inside the currently executing JS code.
zelig's avatar
zelig committed
285 286 287
func (self *JSRE) loadScript(call otto.FunctionCall) otto.Value {
	file, err := call.Argument(0).ToString()
	if err != nil {
288 289 290 291 292 293 294
		// TODO: throw exception
		return otto.FalseValue()
	}
	file = common.AbsolutePath(self.assetPath, file)
	source, err := ioutil.ReadFile(file)
	if err != nil {
		// TODO: throw exception
zelig's avatar
zelig committed
295 296
		return otto.FalseValue()
	}
297 298
	if _, err := compileAndRun(call.Otto, file, source); err != nil {
		// TODO: throw exception
zelig's avatar
zelig committed
299 300 301
		fmt.Println("err:", err)
		return otto.FalseValue()
	}
302
	// TODO: return evaluation result
zelig's avatar
zelig committed
303 304 305
	return otto.TrueValue()
}

306 307 308 309 310
// Evaluate executes code and pretty prints the result to the specified output
// stream.
func (self *JSRE) Evaluate(code string, w io.Writer) error {
	var fail error

Felix Lange's avatar
Felix Lange committed
311
	self.Do(func(vm *otto.Otto) {
312
		val, err := vm.Run(code)
313
		if err != nil {
314
			prettyError(vm, err, w)
315 316
		} else {
			prettyPrint(vm, val, w)
317
		}
318
		fmt.Fprintln(w)
319
	})
320
	return fail
zelig's avatar
zelig committed
321
}
322

323 324
// Compile compiles and then runs a piece of JS code.
func (self *JSRE) Compile(filename string, src interface{}) (err error) {
Felix Lange's avatar
Felix Lange committed
325
	self.Do(func(vm *otto.Otto) { _, err = compileAndRun(vm, filename, src) })
326 327 328 329 330
	return err
}

func compileAndRun(vm *otto.Otto, filename string, src interface{}) (otto.Value, error) {
	script, err := vm.Compile(filename, src)
331
	if err != nil {
332
		return otto.Value{}, err
333
	}
334
	return vm.Run(script)
335
}