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 }