gitee.com/liu-zhao234568/cntest@v1.0.0/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 "gitee.com/liu-zhao234568/cntest/common" 30 "github.com/dop251/goja" 31 ) 32 33 // JSRE is a JS runtime environment embedding the goja interpreter. 34 // It provides helper functions to load code from files, run code snippets 35 // and bind native go objects to JS. 36 // 37 // The runtime runs all code on a dedicated event loop and does not expose the underlying 38 // goja runtime directly. To use the runtime, call JSRE.Do. When binding a Go function, 39 // use the Call type to gain access to the runtime. 40 type JSRE struct { 41 assetPath string 42 output io.Writer 43 evalQueue chan *evalReq 44 stopEventLoop chan bool 45 closed chan struct{} 46 vm *goja.Runtime 47 } 48 49 // Call is the argument type of Go functions which are callable from JS. 50 type Call struct { 51 goja.FunctionCall 52 VM *goja.Runtime 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 goja.FunctionCall 61 } 62 63 // evalReq is a serialized vm execution request processed by runEventLoop. 64 type evalReq struct { 65 fn func(vm *goja.Runtime) 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 vm: goja.New(), 78 } 79 go re.runEventLoop() 80 re.Set("loadScript", MakeCallback(re.vm, re.loadScript)) 81 re.Set("inspect", re.prettyPrintJS) 82 return re 83 } 84 85 // randomSource returns a pseudo random value generator. 86 func randomSource() *rand.Rand { 87 bytes := make([]byte, 8) 88 seed := time.Now().UnixNano() 89 if _, err := crand.Read(bytes); err == nil { 90 seed = int64(binary.LittleEndian.Uint64(bytes)) 91 } 92 93 src := rand.NewSource(seed) 94 return rand.New(src) 95 } 96 97 // This function runs the main event loop from a goroutine that is started 98 // when JSRE is created. Use Stop() before exiting to properly stop it. 99 // The event loop processes vm access requests from the evalQueue in a 100 // serialized way and calls timer callback functions at the appropriate time. 101 102 // Exported functions always access the vm through the event queue. You can 103 // call the functions of the goja vm directly to circumvent the queue. These 104 // functions should be used if and only if running a routine that was already 105 // called from JS through an RPC call. 106 func (re *JSRE) runEventLoop() { 107 defer close(re.closed) 108 109 r := randomSource() 110 re.vm.SetRandSource(r.Float64) 111 112 registry := map[*jsTimer]*jsTimer{} 113 ready := make(chan *jsTimer) 114 115 newTimer := func(call goja.FunctionCall, interval bool) (*jsTimer, goja.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 return timer, re.vm.ToValue(timer) 132 } 133 134 setTimeout := func(call goja.FunctionCall) goja.Value { 135 _, value := newTimer(call, false) 136 return value 137 } 138 139 setInterval := func(call goja.FunctionCall) goja.Value { 140 _, value := newTimer(call, true) 141 return value 142 } 143 144 clearTimeout := func(call goja.FunctionCall) goja.Value { 145 timer := call.Argument(0).Export() 146 if timer, ok := timer.(*jsTimer); ok { 147 timer.timer.Stop() 148 delete(registry, timer) 149 } 150 return goja.Undefined() 151 } 152 re.vm.Set("_setTimeout", setTimeout) 153 re.vm.Set("_setInterval", setInterval) 154 re.vm.RunString(`var setTimeout = function(args) { 155 if (arguments.length < 1) { 156 throw TypeError("Failed to execute 'setTimeout': 1 argument required, but only 0 present."); 157 } 158 return _setTimeout.apply(this, arguments); 159 }`) 160 re.vm.RunString(`var setInterval = function(args) { 161 if (arguments.length < 1) { 162 throw TypeError("Failed to execute 'setInterval': 1 argument required, but only 0 present."); 163 } 164 return _setInterval.apply(this, arguments); 165 }`) 166 re.vm.Set("clearTimeout", clearTimeout) 167 re.vm.Set("clearInterval", clearTimeout) 168 169 var waitForCallbacks bool 170 171 loop: 172 for { 173 select { 174 case timer := <-ready: 175 // execute callback, remove/reschedule the timer 176 var arguments []interface{} 177 if len(timer.call.Arguments) > 2 { 178 tmp := timer.call.Arguments[2:] 179 arguments = make([]interface{}, 2+len(tmp)) 180 for i, value := range tmp { 181 arguments[i+2] = value 182 } 183 } else { 184 arguments = make([]interface{}, 1) 185 } 186 arguments[0] = timer.call.Arguments[0] 187 call, isFunc := goja.AssertFunction(timer.call.Arguments[0]) 188 if !isFunc { 189 panic(re.vm.ToValue("js error: timer/timeout callback is not a function")) 190 } 191 call(goja.Null(), timer.call.Arguments...) 192 193 _, inreg := registry[timer] // when clearInterval is called from within the callback don't reset it 194 if timer.interval && inreg { 195 timer.timer.Reset(timer.duration) 196 } else { 197 delete(registry, timer) 198 if waitForCallbacks && (len(registry) == 0) { 199 break loop 200 } 201 } 202 case req := <-re.evalQueue: 203 // run the code, send the result back 204 req.fn(re.vm) 205 close(req.done) 206 if waitForCallbacks && (len(registry) == 0) { 207 break loop 208 } 209 case waitForCallbacks = <-re.stopEventLoop: 210 if !waitForCallbacks || (len(registry) == 0) { 211 break loop 212 } 213 } 214 } 215 216 for _, timer := range registry { 217 timer.timer.Stop() 218 delete(registry, timer) 219 } 220 } 221 222 // Do executes the given function on the JS event loop. 223 func (re *JSRE) Do(fn func(*goja.Runtime)) { 224 done := make(chan bool) 225 req := &evalReq{fn, done} 226 re.evalQueue <- req 227 <-done 228 } 229 230 // stops the event loop before exit, optionally waits for all timers to expire 231 func (re *JSRE) Stop(waitForCallbacks bool) { 232 select { 233 case <-re.closed: 234 case re.stopEventLoop <- waitForCallbacks: 235 <-re.closed 236 } 237 } 238 239 // Exec(file) loads and runs the contents of a file 240 // if a relative path is given, the jsre's assetPath is used 241 func (re *JSRE) Exec(file string) error { 242 code, err := ioutil.ReadFile(common.AbsolutePath(re.assetPath, file)) 243 if err != nil { 244 return err 245 } 246 return re.Compile(file, string(code)) 247 } 248 249 // Run runs a piece of JS code. 250 func (re *JSRE) Run(code string) (v goja.Value, err error) { 251 re.Do(func(vm *goja.Runtime) { v, err = vm.RunString(code) }) 252 return v, err 253 } 254 255 // Set assigns value v to a variable in the JS environment. 256 func (re *JSRE) Set(ns string, v interface{}) (err error) { 257 re.Do(func(vm *goja.Runtime) { vm.Set(ns, v) }) 258 return err 259 } 260 261 // MakeCallback turns the given function into a function that's callable by JS. 262 func MakeCallback(vm *goja.Runtime, fn func(Call) (goja.Value, error)) goja.Value { 263 return vm.ToValue(func(call goja.FunctionCall) goja.Value { 264 result, err := fn(Call{call, vm}) 265 if err != nil { 266 panic(vm.NewGoError(err)) 267 } 268 return result 269 }) 270 } 271 272 // Evaluate executes code and pretty prints the result to the specified output stream. 273 func (re *JSRE) Evaluate(code string, w io.Writer) { 274 re.Do(func(vm *goja.Runtime) { 275 val, err := vm.RunString(code) 276 if err != nil { 277 prettyError(vm, err, w) 278 } else { 279 prettyPrint(vm, val, w) 280 } 281 fmt.Fprintln(w) 282 }) 283 } 284 285 // Compile compiles and then runs a piece of JS code. 286 func (re *JSRE) Compile(filename string, src string) (err error) { 287 re.Do(func(vm *goja.Runtime) { _, err = compileAndRun(vm, filename, src) }) 288 return err 289 } 290 291 // loadScript loads and executes a JS file. 292 func (re *JSRE) loadScript(call Call) (goja.Value, error) { 293 file := call.Argument(0).ToString().String() 294 file = common.AbsolutePath(re.assetPath, file) 295 source, err := ioutil.ReadFile(file) 296 if err != nil { 297 return nil, fmt.Errorf("Could not read file %s: %v", file, err) 298 } 299 value, err := compileAndRun(re.vm, file, string(source)) 300 if err != nil { 301 return nil, fmt.Errorf("Error while compiling or running script: %v", err) 302 } 303 return value, nil 304 } 305 306 func compileAndRun(vm *goja.Runtime, filename string, src string) (goja.Value, error) { 307 script, err := goja.Compile(filename, src, false) 308 if err != nil { 309 return goja.Null(), err 310 } 311 return vm.RunProgram(script) 312 }