github.com/ethereumproject/go-ethereum@v5.5.2+incompatible/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/ethereumproject/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.EnsurePathAbsoluteOrRelativeTo(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.EnsurePathAbsoluteOrRelativeTo(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 }