jsre.go 8.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336
  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. crand "crypto/rand"
  20. "encoding/binary"
  21. "fmt"
  22. "io"
  23. "io/ioutil"
  24. "math/rand"
  25. "time"
  26. "github.com/ethereum/go-ethereum/common"
  27. "github.com/ethereum/go-ethereum/internal/jsre/deps"
  28. "github.com/robertkrimen/otto"
  29. )
  30. var (
  31. BigNumber_JS = deps.MustAsset("bignumber.js")
  32. Web3_JS = deps.MustAsset("web3.js")
  33. )
  34. /*
  35. JSRE is a generic JS runtime environment embedding the otto JS interpreter.
  36. It provides some helper functions to
  37. - load code from files
  38. - run code snippets
  39. - require libraries
  40. - bind native go objects
  41. */
  42. type JSRE struct {
  43. assetPath string
  44. output io.Writer
  45. evalQueue chan *evalReq
  46. stopEventLoop chan bool
  47. closed chan struct{}
  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. // evalReq is a serialized vm execution request processed by runEventLoop.
  57. type evalReq struct {
  58. fn func(vm *otto.Otto)
  59. done chan bool
  60. }
  61. // runtime must be stopped with Stop() after use and cannot be used after stopping
  62. func New(assetPath string, output io.Writer) *JSRE {
  63. re := &JSRE{
  64. assetPath: assetPath,
  65. output: output,
  66. closed: make(chan struct{}),
  67. evalQueue: make(chan *evalReq),
  68. stopEventLoop: make(chan bool),
  69. }
  70. go re.runEventLoop()
  71. re.Set("loadScript", re.loadScript)
  72. re.Set("inspect", re.prettyPrintJS)
  73. return re
  74. }
  75. // randomSource returns a pseudo random value generator.
  76. func randomSource() *rand.Rand {
  77. bytes := make([]byte, 8)
  78. seed := time.Now().UnixNano()
  79. if _, err := crand.Read(bytes); err == nil {
  80. seed = int64(binary.LittleEndian.Uint64(bytes))
  81. }
  82. src := rand.NewSource(seed)
  83. return rand.New(src)
  84. }
  85. // This function runs the main event loop from a goroutine that is started
  86. // when JSRE is created. Use Stop() before exiting to properly stop it.
  87. // The event loop processes vm access requests from the evalQueue in a
  88. // serialized way and calls timer callback functions at the appropriate time.
  89. // Exported functions always access the vm through the event queue. You can
  90. // call the functions of the otto vm directly to circumvent the queue. These
  91. // functions should be used if and only if running a routine that was already
  92. // called from JS through an RPC call.
  93. func (re *JSRE) runEventLoop() {
  94. defer close(re.closed)
  95. vm := otto.New()
  96. r := randomSource()
  97. vm.SetRandomSource(r.Float64)
  98. registry := map[*jsTimer]*jsTimer{}
  99. ready := make(chan *jsTimer)
  100. newTimer := func(call otto.FunctionCall, interval bool) (*jsTimer, otto.Value) {
  101. delay, _ := call.Argument(1).ToInteger()
  102. if 0 >= delay {
  103. delay = 1
  104. }
  105. timer := &jsTimer{
  106. duration: time.Duration(delay) * time.Millisecond,
  107. call: call,
  108. interval: interval,
  109. }
  110. registry[timer] = timer
  111. timer.timer = time.AfterFunc(timer.duration, func() {
  112. ready <- timer
  113. })
  114. value, err := call.Otto.ToValue(timer)
  115. if err != nil {
  116. panic(err)
  117. }
  118. return timer, value
  119. }
  120. setTimeout := func(call otto.FunctionCall) otto.Value {
  121. _, value := newTimer(call, false)
  122. return value
  123. }
  124. setInterval := func(call otto.FunctionCall) otto.Value {
  125. _, value := newTimer(call, true)
  126. return value
  127. }
  128. clearTimeout := func(call otto.FunctionCall) otto.Value {
  129. timer, _ := call.Argument(0).Export()
  130. if timer, ok := timer.(*jsTimer); ok {
  131. timer.timer.Stop()
  132. delete(registry, timer)
  133. }
  134. return otto.UndefinedValue()
  135. }
  136. vm.Set("_setTimeout", setTimeout)
  137. vm.Set("_setInterval", setInterval)
  138. vm.Run(`var setTimeout = function(args) {
  139. if (arguments.length < 1) {
  140. throw TypeError("Failed to execute 'setTimeout': 1 argument required, but only 0 present.");
  141. }
  142. return _setTimeout.apply(this, arguments);
  143. }`)
  144. vm.Run(`var setInterval = function(args) {
  145. if (arguments.length < 1) {
  146. throw TypeError("Failed to execute 'setInterval': 1 argument required, but only 0 present.");
  147. }
  148. return _setInterval.apply(this, arguments);
  149. }`)
  150. vm.Set("clearTimeout", clearTimeout)
  151. vm.Set("clearInterval", clearTimeout)
  152. var waitForCallbacks bool
  153. loop:
  154. for {
  155. select {
  156. case timer := <-ready:
  157. // execute callback, remove/reschedule the timer
  158. var arguments []interface{}
  159. if len(timer.call.ArgumentList) > 2 {
  160. tmp := timer.call.ArgumentList[2:]
  161. arguments = make([]interface{}, 2+len(tmp))
  162. for i, value := range tmp {
  163. arguments[i+2] = value
  164. }
  165. } else {
  166. arguments = make([]interface{}, 1)
  167. }
  168. arguments[0] = timer.call.ArgumentList[0]
  169. _, err := vm.Call(`Function.call.call`, nil, arguments...)
  170. if err != nil {
  171. fmt.Println("js error:", err, arguments)
  172. }
  173. _, inreg := registry[timer] // when clearInterval is called from within the callback don't reset it
  174. if timer.interval && inreg {
  175. timer.timer.Reset(timer.duration)
  176. } else {
  177. delete(registry, timer)
  178. if waitForCallbacks && (len(registry) == 0) {
  179. break loop
  180. }
  181. }
  182. case req := <-re.evalQueue:
  183. // run the code, send the result back
  184. req.fn(vm)
  185. close(req.done)
  186. if waitForCallbacks && (len(registry) == 0) {
  187. break loop
  188. }
  189. case waitForCallbacks = <-re.stopEventLoop:
  190. if !waitForCallbacks || (len(registry) == 0) {
  191. break loop
  192. }
  193. }
  194. }
  195. for _, timer := range registry {
  196. timer.timer.Stop()
  197. delete(registry, timer)
  198. }
  199. }
  200. // Do executes the given function on the JS event loop.
  201. func (re *JSRE) Do(fn func(*otto.Otto)) {
  202. done := make(chan bool)
  203. req := &evalReq{fn, done}
  204. re.evalQueue <- req
  205. <-done
  206. }
  207. // stops the event loop before exit, optionally waits for all timers to expire
  208. func (re *JSRE) Stop(waitForCallbacks bool) {
  209. select {
  210. case <-re.closed:
  211. case re.stopEventLoop <- waitForCallbacks:
  212. <-re.closed
  213. }
  214. }
  215. // Exec(file) loads and runs the contents of a file
  216. // if a relative path is given, the jsre's assetPath is used
  217. func (re *JSRE) Exec(file string) error {
  218. code, err := ioutil.ReadFile(common.AbsolutePath(re.assetPath, file))
  219. if err != nil {
  220. return err
  221. }
  222. var script *otto.Script
  223. re.Do(func(vm *otto.Otto) {
  224. script, err = vm.Compile(file, code)
  225. if err != nil {
  226. return
  227. }
  228. _, err = vm.Run(script)
  229. })
  230. return err
  231. }
  232. // Bind assigns value v to a variable in the JS environment
  233. // This method is deprecated, use Set.
  234. func (re *JSRE) Bind(name string, v interface{}) error {
  235. return re.Set(name, v)
  236. }
  237. // Run runs a piece of JS code.
  238. func (re *JSRE) Run(code string) (v otto.Value, err error) {
  239. re.Do(func(vm *otto.Otto) { v, err = vm.Run(code) })
  240. return v, err
  241. }
  242. // Get returns the value of a variable in the JS environment.
  243. func (re *JSRE) Get(ns string) (v otto.Value, err error) {
  244. re.Do(func(vm *otto.Otto) { v, err = vm.Get(ns) })
  245. return v, err
  246. }
  247. // Set assigns value v to a variable in the JS environment.
  248. func (re *JSRE) Set(ns string, v interface{}) (err error) {
  249. re.Do(func(vm *otto.Otto) { err = vm.Set(ns, v) })
  250. return err
  251. }
  252. // loadScript executes a JS script from inside the currently executing JS code.
  253. func (re *JSRE) loadScript(call otto.FunctionCall) otto.Value {
  254. file, err := call.Argument(0).ToString()
  255. if err != nil {
  256. // TODO: throw exception
  257. return otto.FalseValue()
  258. }
  259. file = common.AbsolutePath(re.assetPath, file)
  260. source, err := ioutil.ReadFile(file)
  261. if err != nil {
  262. // TODO: throw exception
  263. return otto.FalseValue()
  264. }
  265. if _, err := compileAndRun(call.Otto, file, source); err != nil {
  266. // TODO: throw exception
  267. fmt.Println("err:", err)
  268. return otto.FalseValue()
  269. }
  270. // TODO: return evaluation result
  271. return otto.TrueValue()
  272. }
  273. // Evaluate executes code and pretty prints the result to the specified output
  274. // stream.
  275. func (re *JSRE) Evaluate(code string, w io.Writer) error {
  276. var fail error
  277. re.Do(func(vm *otto.Otto) {
  278. val, err := vm.Run(code)
  279. if err != nil {
  280. prettyError(vm, err, w)
  281. } else {
  282. prettyPrint(vm, val, w)
  283. }
  284. fmt.Fprintln(w)
  285. })
  286. return fail
  287. }
  288. // Compile compiles and then runs a piece of JS code.
  289. func (re *JSRE) Compile(filename string, src interface{}) (err error) {
  290. re.Do(func(vm *otto.Otto) { _, err = compileAndRun(vm, filename, src) })
  291. return err
  292. }
  293. func compileAndRun(vm *otto.Otto, filename string, src interface{}) (otto.Value, error) {
  294. script, err := vm.Compile(filename, src)
  295. if err != nil {
  296. return otto.Value{}, err
  297. }
  298. return vm.Run(script)
  299. }