github.com/klaytn/klaytn@v1.10.2/console/jsre/jsre.go (about)

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