1
1
const { worker } = require ( 'workerpool' )
2
2
const { VM } = require ( 'vm2' )
3
- const { inspect } = require ( 'util' )
3
+ const {
4
+ inspect,
5
+ types : { isUint8Array } ,
6
+ } = require ( 'node:util' )
4
7
const { Console } = console
5
- const { Writable } = require ( 'stream' )
8
+ const { Writable } = require ( 'node:stream' )
9
+ const { isEncoding, isBuffer } = Buffer
10
+ const bufferToString = Function . prototype . call . bind ( Buffer . prototype . toString )
11
+ const vmSetupScript = require ( 'node:fs' ) . readFileSync (
12
+ require ( 'node:path' ) . join ( __dirname , './setup.js' ) ,
13
+ 'utf8'
14
+ )
6
15
7
16
const errorToString = err => {
8
- if ( typeof err === 'object' && err instanceof Error ) {
17
+ if ( typeof err === 'object' && err instanceof Error )
9
18
return Error . prototype . toString . call ( err )
10
- }
11
19
return 'Thrown: ' + inspect ( err , { depth : null , maxArrayLength : null } )
12
20
}
13
21
14
22
const run = async code => {
15
23
const consoleOutput = [ ]
16
24
const outStream = Object . defineProperty ( new Writable ( ) , 'write' , {
17
- value : chunk => consoleOutput . push ( chunk ) ,
18
- } )
19
- const vm = new VM ( {
20
- sandbox : {
21
- Set,
22
- Map,
23
- Date,
24
- WeakSet,
25
- WeakMap,
26
- Buffer,
27
- ArrayBuffer,
28
- SharedArrayBuffer,
29
- Int8Array,
30
- Uint8Array,
31
- Uint8ClampedArray,
32
- Int16Array,
33
- Uint16Array,
34
- Int32Array,
35
- Uint32Array,
36
- Float32Array,
37
- Float64Array,
38
- BigInt64Array,
39
- BigUint64Array,
40
- Atomics,
41
- DataView,
42
- console : new Console ( {
43
- stdout : outStream ,
44
- stderr : outStream ,
45
- inspectOptions : { depth : null , maxArrayLength : null } ,
46
- } ) ,
25
+ value ( chunk , encoding , callback ) {
26
+ switch ( typeof encoding ) {
27
+ case 'function' :
28
+ callback = encoding
29
+ case 'undefined' :
30
+ encoding = 'utf8'
31
+ break
32
+ default :
33
+ if ( ! isEncoding ( encoding ) )
34
+ throw new TypeError ( `Unknown encoding '${ encoding } '` )
35
+ }
36
+ switch ( true ) {
37
+ case isUint8Array ( chunk ) :
38
+ chunk = Buffer . from ( chunk . buffer , chunk . byteOffset , chunk . byteLength )
39
+ case isBuffer ( chunk ) :
40
+ chunk = bufferToString ( chunk , encoding )
41
+ case typeof chunk === 'string' :
42
+ break
43
+ default :
44
+ throw new TypeError (
45
+ 'Invalid chunk type. The chunk must be a string, a Buffer or a Uint8Array'
46
+ )
47
+ }
48
+
49
+ consoleOutput . push ( chunk )
50
+
51
+ if ( typeof callback === 'function' ) callback ( )
47
52
} ,
48
53
} )
54
+ const vm = new VM ( )
49
55
50
- const { call } = Function . prototype
56
+ vm . freeze ( Atomics , 'Atomics' )
57
+ vm . freeze (
58
+ new Console ( {
59
+ stdout : outStream ,
60
+ stderr : outStream ,
61
+ inspectOptions : { depth : null , maxArrayLength : null } ,
62
+ } ) ,
63
+ 'console'
64
+ )
51
65
52
66
for ( const type of [ 'Number' , 'String' , 'Boolean' , 'Symbol' , 'BigInt' ] ) {
53
- const { prototype } = vm . run ( type )
54
- const valueOf = call . bind ( prototype . valueOf )
67
+ const prototype = vm . run ( type + '.prototype' )
68
+ const valueOf = Function . prototype . call . bind ( prototype . valueOf )
55
69
Object . defineProperty ( prototype , inspect . custom , {
56
70
value ( ) {
57
71
try {
@@ -62,28 +76,69 @@ const run = async code => {
62
76
} )
63
77
}
64
78
65
- const vmRegExpPrototype = vm . run ( 'RegExp' ) . prototype ,
66
- vmRegExpProtoToString = call . bind ( vmRegExpPrototype . toString )
67
- Object . defineProperty ( vmRegExpPrototype , inspect . custom , {
68
- value ( ) {
69
- try {
70
- return vmRegExpProtoToString ( this )
71
- } catch { }
72
- return this
73
- } ,
74
- } )
79
+ const proxify = vm . run ( vmSetupScript )
80
+
81
+ for ( const constructor of [
82
+ Set ,
83
+ Map ,
84
+ WeakSet ,
85
+ WeakMap ,
86
+ Buffer ,
87
+ ArrayBuffer ,
88
+ SharedArrayBuffer ,
89
+ Int8Array ,
90
+ Uint8Array ,
91
+ Uint8ClampedArray ,
92
+ Int16Array ,
93
+ Uint16Array ,
94
+ Int32Array ,
95
+ Uint32Array ,
96
+ Float32Array ,
97
+ Float64Array ,
98
+ BigInt64Array ,
99
+ BigUint64Array ,
100
+ DataView ,
101
+ ] ) {
102
+ // Mark the class constructor readonly
103
+ vm . readonly ( constructor )
104
+
105
+ // Mark the prototype and its properties readonly
106
+ for ( let o = constructor . prototype ; o ; o = Object . getPrototypeOf ( o ) ) {
107
+ vm . readonly ( o )
108
+ Reflect . ownKeys ( o ) . forEach ( k => {
109
+ try {
110
+ vm . readonly ( o [ k ] )
111
+ } catch {
112
+ // Ignore errors
113
+ }
114
+ } )
115
+ }
116
+
117
+ // Add prototype mapping
118
+ vm . _addProtoMapping ( constructor . prototype , constructor . prototype )
119
+
120
+ // Set class constructor to global
121
+ vm . sandbox [ constructor . name ] = constructor . prototype . constructor = proxify (
122
+ constructor ,
123
+ ( _ , args , newTarget ) => Reflect . construct ( constructor , args , newTarget ) ,
124
+ key => {
125
+ for ( let o = constructor ; o ; o = Reflect . getPrototypeOf ( o ) ) {
126
+ const getter = Reflect . getOwnPropertyDescriptor ( o , key ) ?. get
127
+ if ( getter ) return getter
128
+ }
129
+ }
130
+ )
131
+ }
75
132
76
- let result
77
133
try {
78
- result = await vm . run ( code )
134
+ const result = await vm . run ( code )
135
+ return [
136
+ consoleOutput . join ( '' ) . trim ( ) ,
137
+ inspect ( result , { depth : null , maxArrayLength : null } ) ,
138
+ ]
79
139
} catch ( ex ) {
80
140
return [ consoleOutput . join ( '' ) . trim ( ) , errorToString ( ex ) ]
81
141
}
82
-
83
- return [
84
- consoleOutput . join ( '' ) . trim ( ) ,
85
- inspect ( result , { depth : null , maxArrayLength : null } ) ,
86
- ]
87
142
}
88
143
89
144
worker ( { run } )
0 commit comments