jsre.go 8.0 KB

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