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