github.com/aigarnetwork/aigar@v0.0.0-20191115204914-d59a6eb70f8e/internal/jsre/jsre.go (about)

     1  //  Copyright 2018 The go-ethereum Authors
     2  //  Copyright 2019 The go-aigar Authors
     3  //  This file is part of the go-aigar library.
     4  //
     5  //  The go-aigar library is free software: you can redistribute it and/or modify
     6  //  it under the terms of the GNU Lesser General Public License as published by
     7  //  the Free Software Foundation, either version 3 of the License, or
     8  //  (at your option) any later version.
     9  //
    10  //  The go-aigar library is distributed in the hope that it will be useful,
    11  //  but WITHOUT ANY WARRANTY; without even the implied warranty of
    12  //  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    13  //  GNU Lesser General Public License for more details.
    14  //
    15  //  You should have received a copy of the GNU Lesser General Public License
    16  //  along with the go-aigar library. If not, see <http://www.gnu.org/licenses/>.
    17  
    18  // Package jsre provides execution environment for JavaScript.
    19  package jsre
    20  
    21  import (
    22  	crand "crypto/rand"
    23  	"encoding/binary"
    24  	"fmt"
    25  	"io"
    26  	"io/ioutil"
    27  	"math/rand"
    28  	"time"
    29  
    30  	"github.com/AigarNetwork/aigar/common"
    31  	"github.com/AigarNetwork/aigar/internal/jsre/deps"
    32  	"github.com/robertkrimen/otto"
    33  )
    34  
    35  var (
    36  	BignumberJs = deps.MustAsset("bignumber.js")
    37  	Web3Js      = deps.MustAsset("web3.js")
    38  )
    39  
    40  /*
    41  JSRE is a generic JS runtime environment embedding the otto JS interpreter.
    42  It provides some helper functions to
    43  - load code from files
    44  - run code snippets
    45  - require libraries
    46  - bind native go objects
    47  */
    48  type JSRE struct {
    49  	assetPath     string
    50  	output        io.Writer
    51  	evalQueue     chan *evalReq
    52  	stopEventLoop chan bool
    53  	closed        chan struct{}
    54  }
    55  
    56  // jsTimer is a single timer instance with a callback function
    57  type jsTimer struct {
    58  	timer    *time.Timer
    59  	duration time.Duration
    60  	interval bool
    61  	call     otto.FunctionCall
    62  }
    63  
    64  // evalReq is a serialized vm execution request processed by runEventLoop.
    65  type evalReq struct {
    66  	fn   func(vm *otto.Otto)
    67  	done chan bool
    68  }
    69  
    70  // runtime must be stopped with Stop() after use and cannot be used after stopping
    71  func New(assetPath string, output io.Writer) *JSRE {
    72  	re := &JSRE{
    73  		assetPath:     assetPath,
    74  		output:        output,
    75  		closed:        make(chan struct{}),
    76  		evalQueue:     make(chan *evalReq),
    77  		stopEventLoop: make(chan bool),
    78  	}
    79  	go re.runEventLoop()
    80  	re.Set("loadScript", re.loadScript)
    81  	re.Set("inspect", re.prettyPrintJS)
    82  	return re
    83  }
    84  
    85  // randomSource returns a pseudo random value generator.
    86  func randomSource() *rand.Rand {
    87  	bytes := make([]byte, 8)
    88  	seed := time.Now().UnixNano()
    89  	if _, err := crand.Read(bytes); err == nil {
    90  		seed = int64(binary.LittleEndian.Uint64(bytes))
    91  	}
    92  
    93  	src := rand.NewSource(seed)
    94  	return rand.New(src)
    95  }
    96  
    97  // This function runs the main event loop from a goroutine that is started
    98  // when JSRE is created. Use Stop() before exiting to properly stop it.
    99  // The event loop processes vm access requests from the evalQueue in a
   100  // serialized way and calls timer callback functions at the appropriate time.
   101  
   102  // Exported functions always access the vm through the event queue. You can
   103  // call the functions of the otto vm directly to circumvent the queue. These
   104  // functions should be used if and only if running a routine that was already
   105  // called from JS through an RPC call.
   106  func (re *JSRE) runEventLoop() {
   107  	defer close(re.closed)
   108  
   109  	vm := otto.New()
   110  	r := randomSource()
   111  	vm.SetRandomSource(r.Float64)
   112  
   113  	registry := map[*jsTimer]*jsTimer{}
   114  	ready := make(chan *jsTimer)
   115  
   116  	newTimer := func(call otto.FunctionCall, interval bool) (*jsTimer, otto.Value) {
   117  		delay, _ := call.Argument(1).ToInteger()
   118  		if 0 >= delay {
   119  			delay = 1
   120  		}
   121  		timer := &jsTimer{
   122  			duration: time.Duration(delay) * time.Millisecond,
   123  			call:     call,
   124  			interval: interval,
   125  		}
   126  		registry[timer] = timer
   127  
   128  		timer.timer = time.AfterFunc(timer.duration, func() {
   129  			ready <- timer
   130  		})
   131  
   132  		value, err := call.Otto.ToValue(timer)
   133  		if err != nil {
   134  			panic(err)
   135  		}
   136  		return timer, value
   137  	}
   138  
   139  	setTimeout := func(call otto.FunctionCall) otto.Value {
   140  		_, value := newTimer(call, false)
   141  		return value
   142  	}
   143  
   144  	setInterval := func(call otto.FunctionCall) otto.Value {
   145  		_, value := newTimer(call, true)
   146  		return value
   147  	}
   148  
   149  	clearTimeout := func(call otto.FunctionCall) otto.Value {
   150  		timer, _ := call.Argument(0).Export()
   151  		if timer, ok := timer.(*jsTimer); ok {
   152  			timer.timer.Stop()
   153  			delete(registry, timer)
   154  		}
   155  		return otto.UndefinedValue()
   156  	}
   157  	vm.Set("_setTimeout", setTimeout)
   158  	vm.Set("_setInterval", setInterval)
   159  	vm.Run(`var setTimeout = function(args) {
   160  		if (arguments.length < 1) {
   161  			throw TypeError("Failed to execute 'setTimeout': 1 argument required, but only 0 present.");
   162  		}
   163  		return _setTimeout.apply(this, arguments);
   164  	}`)
   165  	vm.Run(`var setInterval = function(args) {
   166  		if (arguments.length < 1) {
   167  			throw TypeError("Failed to execute 'setInterval': 1 argument required, but only 0 present.");
   168  		}
   169  		return _setInterval.apply(this, arguments);
   170  	}`)
   171  	vm.Set("clearTimeout", clearTimeout)
   172  	vm.Set("clearInterval", clearTimeout)
   173  
   174  	var waitForCallbacks bool
   175  
   176  loop:
   177  	for {
   178  		select {
   179  		case timer := <-ready:
   180  			// execute callback, remove/reschedule the timer
   181  			var arguments []interface{}
   182  			if len(timer.call.ArgumentList) > 2 {
   183  				tmp := timer.call.ArgumentList[2:]
   184  				arguments = make([]interface{}, 2+len(tmp))
   185  				for i, value := range tmp {
   186  					arguments[i+2] = value
   187  				}
   188  			} else {
   189  				arguments = make([]interface{}, 1)
   190  			}
   191  			arguments[0] = timer.call.ArgumentList[0]
   192  			_, err := vm.Call(`Function.call.call`, nil, arguments...)
   193  			if err != nil {
   194  				fmt.Println("js error:", err, arguments)
   195  			}
   196  
   197  			_, inreg := registry[timer] // when clearInterval is called from within the callback don't reset it
   198  			if timer.interval && inreg {
   199  				timer.timer.Reset(timer.duration)
   200  			} else {
   201  				delete(registry, timer)
   202  				if waitForCallbacks && (len(registry) == 0) {
   203  					break loop
   204  				}
   205  			}
   206  		case req := <-re.evalQueue:
   207  			// run the code, send the result back
   208  			req.fn(vm)
   209  			close(req.done)
   210  			if waitForCallbacks && (len(registry) == 0) {
   211  				break loop
   212  			}
   213  		case waitForCallbacks = <-re.stopEventLoop:
   214  			if !waitForCallbacks || (len(registry) == 0) {
   215  				break loop
   216  			}
   217  		}
   218  	}
   219  
   220  	for _, timer := range registry {
   221  		timer.timer.Stop()
   222  		delete(registry, timer)
   223  	}
   224  }
   225  
   226  // Do executes the given function on the JS event loop.
   227  func (re *JSRE) Do(fn func(*otto.Otto)) {
   228  	done := make(chan bool)
   229  	req := &evalReq{fn, done}
   230  	re.evalQueue <- req
   231  	<-done
   232  }
   233  
   234  // stops the event loop before exit, optionally waits for all timers to expire
   235  func (re *JSRE) Stop(waitForCallbacks bool) {
   236  	select {
   237  	case <-re.closed:
   238  	case re.stopEventLoop <- waitForCallbacks:
   239  		<-re.closed
   240  	}
   241  }
   242  
   243  // Exec(file) loads and runs the contents of a file
   244  // if a relative path is given, the jsre's assetPath is used
   245  func (re *JSRE) Exec(file string) error {
   246  	code, err := ioutil.ReadFile(common.AbsolutePath(re.assetPath, file))
   247  	if err != nil {
   248  		return err
   249  	}
   250  	var script *otto.Script
   251  	re.Do(func(vm *otto.Otto) {
   252  		script, err = vm.Compile(file, code)
   253  		if err != nil {
   254  			return
   255  		}
   256  		_, err = vm.Run(script)
   257  	})
   258  	return err
   259  }
   260  
   261  // Bind assigns value v to a variable in the JS environment
   262  // This method is deprecated, use Set.
   263  func (re *JSRE) Bind(name string, v interface{}) error {
   264  	return re.Set(name, v)
   265  }
   266  
   267  // Run runs a piece of JS code.
   268  func (re *JSRE) Run(code string) (v otto.Value, err error) {
   269  	re.Do(func(vm *otto.Otto) { v, err = vm.Run(code) })
   270  	return v, err
   271  }
   272  
   273  // Get returns the value of a variable in the JS environment.
   274  func (re *JSRE) Get(ns string) (v otto.Value, err error) {
   275  	re.Do(func(vm *otto.Otto) { v, err = vm.Get(ns) })
   276  	return v, err
   277  }
   278  
   279  // Set assigns value v to a variable in the JS environment.
   280  func (re *JSRE) Set(ns string, v interface{}) (err error) {
   281  	re.Do(func(vm *otto.Otto) { err = vm.Set(ns, v) })
   282  	return err
   283  }
   284  
   285  // loadScript executes a JS script from inside the currently executing JS code.
   286  func (re *JSRE) loadScript(call otto.FunctionCall) otto.Value {
   287  	file, err := call.Argument(0).ToString()
   288  	if err != nil {
   289  		// TODO: throw exception
   290  		return otto.FalseValue()
   291  	}
   292  	file = common.AbsolutePath(re.assetPath, file)
   293  	source, err := ioutil.ReadFile(file)
   294  	if err != nil {
   295  		// TODO: throw exception
   296  		return otto.FalseValue()
   297  	}
   298  	if _, err := compileAndRun(call.Otto, file, source); err != nil {
   299  		// TODO: throw exception
   300  		fmt.Println("err:", err)
   301  		return otto.FalseValue()
   302  	}
   303  	// TODO: return evaluation result
   304  	return otto.TrueValue()
   305  }
   306  
   307  // Evaluate executes code and pretty prints the result to the specified output
   308  // stream.
   309  func (re *JSRE) Evaluate(code string, w io.Writer) error {
   310  	var fail error
   311  
   312  	re.Do(func(vm *otto.Otto) {
   313  		val, err := vm.Run(code)
   314  		if err != nil {
   315  			prettyError(vm, err, w)
   316  		} else {
   317  			prettyPrint(vm, val, w)
   318  		}
   319  		fmt.Fprintln(w)
   320  	})
   321  	return fail
   322  }
   323  
   324  // Compile compiles and then runs a piece of JS code.
   325  func (re *JSRE) Compile(filename string, src interface{}) (err error) {
   326  	re.Do(func(vm *otto.Otto) { _, err = compileAndRun(vm, filename, src) })
   327  	return err
   328  }
   329  
   330  func compileAndRun(vm *otto.Otto, filename string, src interface{}) (otto.Value, error) {
   331  	script, err := vm.Compile(filename, src)
   332  	if err != nil {
   333  		return otto.Value{}, err
   334  	}
   335  	return vm.Run(script)
   336  }