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 }