github.com/sberex/go-sberex@v1.8.2-0.20181113200658-ed96ac38f7d7/internal/jsre/jsre.go (about)

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