github.com/ur-technology/go-ur@v1.5.5/internal/jsre/jsre.go (about)

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