jsre.go 8.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347
  1. package jsre
  2. import (
  3. "fmt"
  4. "io/ioutil"
  5. "sync"
  6. "time"
  7. "github.com/ethereum/go-ethereum/common"
  8. "github.com/robertkrimen/otto"
  9. )
  10. /*
  11. JSRE is a generic JS runtime environment embedding the otto JS interpreter.
  12. It provides some helper functions to
  13. - load code from files
  14. - run code snippets
  15. - require libraries
  16. - bind native go objects
  17. */
  18. type JSRE struct {
  19. assetPath string
  20. vm *otto.Otto
  21. evalQueue chan *evalReq
  22. stopEventLoop chan bool
  23. loopWg sync.WaitGroup
  24. }
  25. // jsTimer is a single timer instance with a callback function
  26. type jsTimer struct {
  27. timer *time.Timer
  28. duration time.Duration
  29. interval bool
  30. call otto.FunctionCall
  31. }
  32. // evalResult is a structure to store the result of any serialized vm execution
  33. type evalResult struct {
  34. result otto.Value
  35. err error
  36. }
  37. // evalReq is a serialized vm execution request put in evalQueue and processed by runEventLoop
  38. type evalReq struct {
  39. fn func(res *evalResult)
  40. done chan bool
  41. res evalResult
  42. }
  43. // runtime must be stopped with Stop() after use and cannot be used after stopping
  44. func New(assetPath string) *JSRE {
  45. re := &JSRE{
  46. assetPath: assetPath,
  47. vm: otto.New(),
  48. }
  49. // load prettyprint func definition
  50. re.vm.Run(pp_js)
  51. re.vm.Set("loadScript", re.loadScript)
  52. re.evalQueue = make(chan *evalReq)
  53. re.stopEventLoop = make(chan bool)
  54. re.loopWg.Add(1)
  55. go re.runEventLoop()
  56. return re
  57. }
  58. // this function runs a piece of JS code either in a serialized way (when useEQ is true) or instantly, circumventing the evalQueue
  59. func (self *JSRE) run(src interface{}, useEQ bool) (value otto.Value, err error) {
  60. if useEQ {
  61. done := make(chan bool)
  62. req := &evalReq{
  63. fn: func(res *evalResult) {
  64. res.result, res.err = self.vm.Run(src)
  65. },
  66. done: done,
  67. }
  68. self.evalQueue <- req
  69. <-done
  70. return req.res.result, req.res.err
  71. } else {
  72. return self.vm.Run(src)
  73. }
  74. }
  75. /*
  76. This function runs the main event loop from a goroutine that is started
  77. when JSRE is created. Use Stop() before exiting to properly stop it.
  78. The event loop processes vm access requests from the evalQueue in a
  79. serialized way and calls timer callback functions at the appropriate time.
  80. Exported functions always access the vm through the event queue. You can
  81. call the functions of the otto vm directly to circumvent the queue. These
  82. functions should be used if and only if running a routine that was already
  83. called from JS through an RPC call.
  84. */
  85. func (self *JSRE) runEventLoop() {
  86. registry := map[*jsTimer]*jsTimer{}
  87. ready := make(chan *jsTimer)
  88. newTimer := func(call otto.FunctionCall, interval bool) (*jsTimer, otto.Value) {
  89. delay, _ := call.Argument(1).ToInteger()
  90. if 0 >= delay {
  91. delay = 1
  92. }
  93. timer := &jsTimer{
  94. duration: time.Duration(delay) * time.Millisecond,
  95. call: call,
  96. interval: interval,
  97. }
  98. registry[timer] = timer
  99. timer.timer = time.AfterFunc(timer.duration, func() {
  100. ready <- timer
  101. })
  102. value, err := call.Otto.ToValue(timer)
  103. if err != nil {
  104. panic(err)
  105. }
  106. return timer, value
  107. }
  108. setTimeout := func(call otto.FunctionCall) otto.Value {
  109. _, value := newTimer(call, false)
  110. return value
  111. }
  112. setInterval := func(call otto.FunctionCall) otto.Value {
  113. _, value := newTimer(call, true)
  114. return value
  115. }
  116. clearTimeout := func(call otto.FunctionCall) otto.Value {
  117. timer, _ := call.Argument(0).Export()
  118. if timer, ok := timer.(*jsTimer); ok {
  119. timer.timer.Stop()
  120. delete(registry, timer)
  121. }
  122. return otto.UndefinedValue()
  123. }
  124. self.vm.Set("setTimeout", setTimeout)
  125. self.vm.Set("setInterval", setInterval)
  126. self.vm.Set("clearTimeout", clearTimeout)
  127. self.vm.Set("clearInterval", clearTimeout)
  128. var waitForCallbacks bool
  129. loop:
  130. for {
  131. select {
  132. case timer := <-ready:
  133. // execute callback, remove/reschedule the timer
  134. var arguments []interface{}
  135. if len(timer.call.ArgumentList) > 2 {
  136. tmp := timer.call.ArgumentList[2:]
  137. arguments = make([]interface{}, 2+len(tmp))
  138. for i, value := range tmp {
  139. arguments[i+2] = value
  140. }
  141. } else {
  142. arguments = make([]interface{}, 1)
  143. }
  144. arguments[0] = timer.call.ArgumentList[0]
  145. _, err := self.vm.Call(`Function.call.call`, nil, arguments...)
  146. if err != nil {
  147. fmt.Println("js error:", err, arguments)
  148. }
  149. if timer.interval {
  150. timer.timer.Reset(timer.duration)
  151. } else {
  152. delete(registry, timer)
  153. if waitForCallbacks && (len(registry) == 0) {
  154. break loop
  155. }
  156. }
  157. case evalReq := <-self.evalQueue:
  158. // run the code, send the result back
  159. evalReq.fn(&evalReq.res)
  160. close(evalReq.done)
  161. if waitForCallbacks && (len(registry) == 0) {
  162. break loop
  163. }
  164. case waitForCallbacks = <-self.stopEventLoop:
  165. if !waitForCallbacks || (len(registry) == 0) {
  166. break loop
  167. }
  168. }
  169. }
  170. for _, timer := range registry {
  171. timer.timer.Stop()
  172. delete(registry, timer)
  173. }
  174. self.loopWg.Done()
  175. }
  176. // stops the event loop before exit, optionally waits for all timers to expire
  177. func (self *JSRE) Stop(waitForCallbacks bool) {
  178. self.stopEventLoop <- waitForCallbacks
  179. self.loopWg.Wait()
  180. }
  181. // Exec(file) loads and runs the contents of a file
  182. // if a relative path is given, the jsre's assetPath is used
  183. func (self *JSRE) Exec(file string) error {
  184. return self.exec(common.AbsolutePath(self.assetPath, file), true)
  185. }
  186. // circumvents the eval queue, see runEventLoop
  187. func (self *JSRE) execWithoutEQ(file string) error {
  188. return self.exec(common.AbsolutePath(self.assetPath, file), false)
  189. }
  190. func (self *JSRE) exec(path string, useEQ bool) error {
  191. code, err := ioutil.ReadFile(path)
  192. if err != nil {
  193. return err
  194. }
  195. _, err = self.run(code, useEQ)
  196. return err
  197. }
  198. // assigns value v to a variable in the JS environment
  199. func (self *JSRE) Bind(name string, v interface{}) (err error) {
  200. self.Set(name, v)
  201. return
  202. }
  203. // runs a piece of JS code
  204. func (self *JSRE) Run(code string) (otto.Value, error) {
  205. return self.run(code, true)
  206. }
  207. // returns the value of a variable in the JS environment
  208. func (self *JSRE) Get(ns string) (otto.Value, error) {
  209. done := make(chan bool)
  210. req := &evalReq{
  211. fn: func(res *evalResult) {
  212. res.result, res.err = self.vm.Get(ns)
  213. },
  214. done: done,
  215. }
  216. self.evalQueue <- req
  217. <-done
  218. return req.res.result, req.res.err
  219. }
  220. // assigns value v to a variable in the JS environment
  221. func (self *JSRE) Set(ns string, v interface{}) error {
  222. done := make(chan bool)
  223. req := &evalReq{
  224. fn: func(res *evalResult) {
  225. res.err = self.vm.Set(ns, v)
  226. },
  227. done: done,
  228. }
  229. self.evalQueue <- req
  230. <-done
  231. return req.res.err
  232. }
  233. /*
  234. Executes a JS script from inside the currently executing JS code.
  235. Should only be called from inside an RPC routine.
  236. */
  237. func (self *JSRE) loadScript(call otto.FunctionCall) otto.Value {
  238. file, err := call.Argument(0).ToString()
  239. if err != nil {
  240. return otto.FalseValue()
  241. }
  242. if err := self.execWithoutEQ(file); err != nil { // loadScript is only called from inside js
  243. fmt.Println("err:", err)
  244. return otto.FalseValue()
  245. }
  246. return otto.TrueValue()
  247. }
  248. // uses the "prettyPrint" JS function to format a value
  249. func (self *JSRE) PrettyPrint(v interface{}) (val otto.Value, err error) {
  250. var method otto.Value
  251. v, err = self.ToValue(v)
  252. if err != nil {
  253. return
  254. }
  255. method, err = self.vm.Get("prettyPrint")
  256. if err != nil {
  257. return
  258. }
  259. return method.Call(method, v)
  260. }
  261. // creates an otto value from a go type (serialized version)
  262. func (self *JSRE) ToValue(v interface{}) (otto.Value, error) {
  263. done := make(chan bool)
  264. req := &evalReq{
  265. fn: func(res *evalResult) {
  266. res.result, res.err = self.vm.ToValue(v)
  267. },
  268. done: done,
  269. }
  270. self.evalQueue <- req
  271. <-done
  272. return req.res.result, req.res.err
  273. }
  274. // creates an otto value from a go type (non-serialized version)
  275. func (self *JSRE) ToVal(v interface{}) otto.Value {
  276. result, err := self.vm.ToValue(v)
  277. if err != nil {
  278. fmt.Println("Value unknown:", err)
  279. return otto.UndefinedValue()
  280. }
  281. return result
  282. }
  283. // evaluates JS function and returns result in a pretty printed string format
  284. func (self *JSRE) Eval(code string) (s string, err error) {
  285. var val otto.Value
  286. val, err = self.Run(code)
  287. if err != nil {
  288. return
  289. }
  290. val, err = self.PrettyPrint(val)
  291. if err != nil {
  292. return
  293. }
  294. return fmt.Sprintf("%v", val), nil
  295. }
  296. // compiles and then runs a piece of JS code
  297. func (self *JSRE) Compile(fn string, src interface{}) error {
  298. script, err := self.vm.Compile(fn, src)
  299. if err != nil {
  300. return err
  301. }
  302. self.run(script, true)
  303. return nil
  304. }