Compare commits
13 commits
| Author | SHA1 | Date | |
|---|---|---|---|
| f88d632767 | |||
| 819a6dc1b6 | |||
| 3ef1a8e806 | |||
| a0afae092a | |||
| c990de5ad6 | |||
| 743a1cc623 | |||
| 0531387498 | |||
| 26fb773b0b | |||
| 3570c7a55b | |||
| 5c08cd4acc | |||
| c4142cce60 | |||
| de77e9e6b9 | |||
| 6fa3e64b8d |
11 changed files with 1242 additions and 101 deletions
76
Cargo.lock
generated
76
Cargo.lock
generated
|
|
@ -150,7 +150,7 @@ dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"swc_macros_common",
|
"swc_macros_common",
|
||||||
"syn 2.0.18",
|
"syn 2.0.37",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -180,6 +180,12 @@ version = "0.13.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8"
|
checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "base64"
|
||||||
|
version = "0.21.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9ba43ea6f343b788c8764558649e08df62f86c6ef251fdaeb1ffd010a9ae50a2"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "better_scoped_tls"
|
name = "better_scoped_tls"
|
||||||
version = "0.1.1"
|
version = "0.1.1"
|
||||||
|
|
@ -295,7 +301,7 @@ checksum = "fdde5c9cd29ebd706ce1b35600920a33550e402fc998a2e53ad3b42c3c47a192"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.18",
|
"syn 2.0.37",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -304,6 +310,12 @@ version = "1.4.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610"
|
checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bytes"
|
||||||
|
version = "1.5.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "calloop"
|
name = "calloop"
|
||||||
version = "0.10.5"
|
version = "0.10.5"
|
||||||
|
|
@ -521,7 +533,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "577ec3850834c2578eb44afa9250f9a807f8497664e6e2aaae19cea0aac2fe3b"
|
checksum = "577ec3850834c2578eb44afa9250f9a807f8497664e6e2aaae19cea0aac2fe3b"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"base64",
|
"base64 0.13.1",
|
||||||
"deno_media_type",
|
"deno_media_type",
|
||||||
"dprint-swc-ext",
|
"dprint-swc-ext",
|
||||||
"serde",
|
"serde",
|
||||||
|
|
@ -716,7 +728,7 @@ checksum = "1a5c6c585bc94aaf2c7b51dd4c2ba22680844aba4c687be581871a6f518c5742"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.18",
|
"syn 2.0.37",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -749,7 +761,7 @@ dependencies = [
|
||||||
"pmutil",
|
"pmutil",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"swc_macros_common",
|
"swc_macros_common",
|
||||||
"syn 2.0.18",
|
"syn 2.0.37",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -1034,7 +1046,7 @@ dependencies = [
|
||||||
"pmutil",
|
"pmutil",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.18",
|
"syn 2.0.37",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -1536,7 +1548,9 @@ name = "oden"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
|
"base64 0.21.4",
|
||||||
"bytemuck",
|
"bytemuck",
|
||||||
|
"bytes",
|
||||||
"deno_ast",
|
"deno_ast",
|
||||||
"env_logger",
|
"env_logger",
|
||||||
"fontdue",
|
"fontdue",
|
||||||
|
|
@ -1546,7 +1560,9 @@ dependencies = [
|
||||||
"notify",
|
"notify",
|
||||||
"oden-js",
|
"oden-js",
|
||||||
"pollster",
|
"pollster",
|
||||||
|
"sha-1",
|
||||||
"sourcemap 7.0.0",
|
"sourcemap 7.0.0",
|
||||||
|
"thiserror",
|
||||||
"tracy-client",
|
"tracy-client",
|
||||||
"wgpu",
|
"wgpu",
|
||||||
"winit",
|
"winit",
|
||||||
|
|
@ -1712,7 +1728,7 @@ checksum = "52a40bc70c2c58040d2d8b167ba9a5ff59fc9dab7ad44771cfde3dcfde7a09c6"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.18",
|
"syn 2.0.37",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -1764,9 +1780,9 @@ checksum = "dc375e1527247fe1a97d8b7156678dfe7c1af2fc075c9a4db3690ecd2a148068"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "proc-macro2"
|
name = "proc-macro2"
|
||||||
version = "1.0.59"
|
version = "1.0.67"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "6aeca18b86b413c660b781aa319e4e2648a3e6f9eadc9b47e9038e6fe9f3451b"
|
checksum = "3d433d9f1a3e8c1263d9456598b16fec66f4acc9a74dacffd35c7bb09b3a1328"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"unicode-ident",
|
"unicode-ident",
|
||||||
]
|
]
|
||||||
|
|
@ -1788,9 +1804,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "quote"
|
name = "quote"
|
||||||
version = "1.0.28"
|
version = "1.0.33"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "1b9ab9c7eadfd8df19006f1cf1a4aed13540ed5cbc047010ece5826e10825488"
|
checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
]
|
]
|
||||||
|
|
@ -2006,7 +2022,7 @@ checksum = "d9735b638ccc51c28bf6914d90a2e9725b377144fc612c49a611fddd1b631d68"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.18",
|
"syn 2.0.37",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -2211,7 +2227,7 @@ dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"swc_macros_common",
|
"swc_macros_common",
|
||||||
"syn 2.0.18",
|
"syn 2.0.37",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -2277,7 +2293,7 @@ dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"swc_macros_common",
|
"swc_macros_common",
|
||||||
"syn 2.0.18",
|
"syn 2.0.37",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -2326,7 +2342,7 @@ dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"swc_macros_common",
|
"swc_macros_common",
|
||||||
"syn 2.0.18",
|
"syn 2.0.37",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -2409,7 +2425,7 @@ dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"swc_macros_common",
|
"swc_macros_common",
|
||||||
"syn 2.0.18",
|
"syn 2.0.37",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -2438,7 +2454,7 @@ version = "0.178.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "675b5c755b0448268830e85e59429095d3423c0ce4a850b209c6f0eeab069f63"
|
checksum = "675b5c755b0448268830e85e59429095d3423c0ce4a850b209c6f0eeab069f63"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"base64",
|
"base64 0.13.1",
|
||||||
"dashmap",
|
"dashmap",
|
||||||
"indexmap",
|
"indexmap",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
|
|
@ -2513,7 +2529,7 @@ dependencies = [
|
||||||
"pmutil",
|
"pmutil",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.18",
|
"syn 2.0.37",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -2525,7 +2541,7 @@ dependencies = [
|
||||||
"pmutil",
|
"pmutil",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.18",
|
"syn 2.0.37",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -2549,7 +2565,7 @@ dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"swc_macros_common",
|
"swc_macros_common",
|
||||||
"syn 2.0.18",
|
"syn 2.0.37",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -2565,9 +2581,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "syn"
|
name = "syn"
|
||||||
version = "2.0.18"
|
version = "2.0.37"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "32d41677bcbe24c20c52e7c70b0d8db04134c5d1066bf98662e2871ad200ea3e"
|
checksum = "7303ef2c05cd654186cb250d29049a24840ca25d2747c25c0381c8d9e2f582e8"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
|
|
@ -2594,22 +2610,22 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "thiserror"
|
name = "thiserror"
|
||||||
version = "1.0.40"
|
version = "1.0.49"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "978c9a314bd8dc99be594bc3c175faaa9794be04a5a5e153caba6915336cebac"
|
checksum = "1177e8c6d7ede7afde3585fd2513e611227efd6481bd78d2e82ba1ce16557ed4"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"thiserror-impl",
|
"thiserror-impl",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "thiserror-impl"
|
name = "thiserror-impl"
|
||||||
version = "1.0.40"
|
version = "1.0.49"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f9456a42c5b0d803c8cd86e73dd7cc9edd429499f37a3550d286d5e86720569f"
|
checksum = "10712f02019e9288794769fba95cd6847df9874d49d871d062172f9dd41bc4cc"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.18",
|
"syn 2.0.37",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -2699,7 +2715,7 @@ checksum = "5f4f31f56159e98206da9efd823404b79b6ef3143b4a7ab76e67b1751b25a4ab"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.18",
|
"syn 2.0.37",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
@ -2907,7 +2923,7 @@ dependencies = [
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.18",
|
"syn 2.0.37",
|
||||||
"wasm-bindgen-shared",
|
"wasm-bindgen-shared",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
@ -2941,7 +2957,7 @@ checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
"syn 2.0.18",
|
"syn 2.0.37",
|
||||||
"wasm-bindgen-backend",
|
"wasm-bindgen-backend",
|
||||||
"wasm-bindgen-shared",
|
"wasm-bindgen-shared",
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,9 @@ tracing = ["tracy-client/enable"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
anyhow = "1.0"
|
anyhow = "1.0"
|
||||||
|
base64 = "0.21.4"
|
||||||
bytemuck = { version = "1.13", features = ["derive"] }
|
bytemuck = { version = "1.13", features = ["derive"] }
|
||||||
|
bytes = "1.5.0"
|
||||||
deno_ast = { version = "0.29.3", features = ["transpiling", "typescript"] }
|
deno_ast = { version = "0.29.3", features = ["transpiling", "typescript"] }
|
||||||
env_logger = "0.10"
|
env_logger = "0.10"
|
||||||
fontdue = "0.7.3"
|
fontdue = "0.7.3"
|
||||||
|
|
@ -20,7 +22,9 @@ lru = "0.11.0"
|
||||||
notify = "6"
|
notify = "6"
|
||||||
oden-js = { path = "oden-js" }
|
oden-js = { path = "oden-js" }
|
||||||
pollster = "0.3"
|
pollster = "0.3"
|
||||||
|
sha-1 = "0.10.0"
|
||||||
sourcemap = "7.0.0"
|
sourcemap = "7.0.0"
|
||||||
|
thiserror = "1.0.49"
|
||||||
tracy-client = { version = "0.15.2", default-features = false }
|
tracy-client = { version = "0.15.2", default-features = false }
|
||||||
wgpu = "0.17"
|
wgpu = "0.17"
|
||||||
winit = "0.28"
|
winit = "0.28"
|
||||||
|
|
|
||||||
|
|
@ -132,7 +132,7 @@ else
|
||||||
LDEXPORT=-rdynamic
|
LDEXPORT=-rdynamic
|
||||||
endif
|
endif
|
||||||
|
|
||||||
PROGS=qjs$(EXE) qjsc$(EXE) run-test262
|
PROGS=qjs$(EXE) qjsc$(EXE) qjsd$(EXE) run-test262
|
||||||
ifneq ($(CROSS_PREFIX),)
|
ifneq ($(CROSS_PREFIX),)
|
||||||
QJSC_CC=gcc
|
QJSC_CC=gcc
|
||||||
QJSC=./host-qjsc
|
QJSC=./host-qjsc
|
||||||
|
|
@ -193,6 +193,15 @@ qjs-debug$(EXE): $(patsubst %.o, %.debug.o, $(QJS_OBJS))
|
||||||
qjsc$(EXE): $(OBJDIR)/qjsc.o $(QJS_LIB_OBJS)
|
qjsc$(EXE): $(OBJDIR)/qjsc.o $(QJS_LIB_OBJS)
|
||||||
$(CC) $(LDFLAGS) -o $@ $^ $(LIBS)
|
$(CC) $(LDFLAGS) -o $@ $^ $(LIBS)
|
||||||
|
|
||||||
|
qjsc-debug$(EXE): $(OBJDIR)/qjsc.debug.o $(patsubst %.o, %.debug.o, $(QJS_LIB_OBJS))
|
||||||
|
$(CC) $(LDFLAGS) -o $@ $^ $(LIBS)
|
||||||
|
|
||||||
|
qjsd$(EXE): $(OBJDIR)/qjsd.o $(QJS_LIB_OBJS)
|
||||||
|
$(CC) $(LDFLAGS) -o $@ $^ $(LIBS)
|
||||||
|
|
||||||
|
qjsd-debug$(EXE): $(OBJDIR)/qjsd.debug.o $(patsubst %.o, %.debug.o, $(QJS_LIB_OBJS))
|
||||||
|
$(CC) $(LDFLAGS) -o $@ $^ $(LIBS)
|
||||||
|
|
||||||
ifneq ($(CROSS_PREFIX),)
|
ifneq ($(CROSS_PREFIX),)
|
||||||
|
|
||||||
$(QJSC): $(OBJDIR)/qjsc.host.o \
|
$(QJSC): $(OBJDIR)/qjsc.host.o \
|
||||||
|
|
@ -294,7 +303,7 @@ clean:
|
||||||
rm -f *.a *.o *.d *~ unicode_gen regexp_test $(PROGS)
|
rm -f *.a *.o *.d *~ unicode_gen regexp_test $(PROGS)
|
||||||
rm -f hello.c test_fib.c
|
rm -f hello.c test_fib.c
|
||||||
rm -f examples/*.so tests/*.so
|
rm -f examples/*.so tests/*.so
|
||||||
rm -rf $(OBJDIR)/ *.dSYM/ qjs-debug
|
rm -rf $(OBJDIR)/ *.dSYM/ qjs-debug qjsc-debug
|
||||||
rm -rf run-test262-debug run-test262-32
|
rm -rf run-test262-debug run-test262-32
|
||||||
|
|
||||||
install: all
|
install: all
|
||||||
|
|
|
||||||
|
|
@ -260,6 +260,7 @@ DEF(is_undefined_or_null, 1, 1, 1, none)
|
||||||
DEF( mul_pow10, 1, 2, 1, none)
|
DEF( mul_pow10, 1, 2, 1, none)
|
||||||
DEF( math_mod, 1, 2, 1, none)
|
DEF( math_mod, 1, 2, 1, none)
|
||||||
#endif
|
#endif
|
||||||
|
DEF( breakpoint, 1, 0, 1, none)
|
||||||
/* must be the last non short and non temporary opcode */
|
/* must be the last non short and non temporary opcode */
|
||||||
DEF( nop, 1, 0, 0, none)
|
DEF( nop, 1, 0, 0, none)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -289,6 +289,9 @@ struct JSRuntime {
|
||||||
JSModuleLoaderFunc *module_loader_func;
|
JSModuleLoaderFunc *module_loader_func;
|
||||||
void *module_loader_opaque;
|
void *module_loader_opaque;
|
||||||
|
|
||||||
|
JSMapSourceFunc *map_source_func;
|
||||||
|
void *map_source_opaque;
|
||||||
|
|
||||||
BOOL can_block : 8; /* TRUE if Atomics.wait can block */
|
BOOL can_block : 8; /* TRUE if Atomics.wait can block */
|
||||||
/* used to allocate, free and clone SharedArrayBuffers */
|
/* used to allocate, free and clone SharedArrayBuffers */
|
||||||
JSSharedArrayBufferFunctions sab_funcs;
|
JSSharedArrayBufferFunctions sab_funcs;
|
||||||
|
|
@ -306,8 +309,12 @@ struct JSRuntime {
|
||||||
uint32_t operator_count;
|
uint32_t operator_count;
|
||||||
#endif
|
#endif
|
||||||
void *user_opaque;
|
void *user_opaque;
|
||||||
JSMapSourceFunc *map_source_func;
|
|
||||||
void *map_source_opaque;
|
struct list_head breakpoint_list;
|
||||||
|
JSDebugCallbackFunc *debug_callback;
|
||||||
|
void *debug_opaque;
|
||||||
|
int stepping_pc;
|
||||||
|
uint8_t stepping_byte;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct JSClass {
|
struct JSClass {
|
||||||
|
|
@ -997,6 +1004,25 @@ enum OPCodeEnum {
|
||||||
OP_TEMP_END,
|
OP_TEMP_END,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
typedef struct JSBreakpoint {
|
||||||
|
JSRuntime *rt;
|
||||||
|
JSAtom file;
|
||||||
|
int line;
|
||||||
|
|
||||||
|
struct list_head link;
|
||||||
|
|
||||||
|
int ref_count;
|
||||||
|
JSFunctionBytecode *function;
|
||||||
|
int code_offset;
|
||||||
|
uint8_t replaced_byte;
|
||||||
|
JS_BOOL enabled;
|
||||||
|
} JSBreakpoint;
|
||||||
|
|
||||||
|
typedef struct JSDebugContext {
|
||||||
|
JSContext *ctx;
|
||||||
|
JSStackFrame *sf;
|
||||||
|
} JSDebugContext;
|
||||||
|
|
||||||
static int JS_InitAtoms(JSRuntime *rt);
|
static int JS_InitAtoms(JSRuntime *rt);
|
||||||
static JSAtom __JS_NewAtomInit(JSRuntime *rt, const char *str, int len,
|
static JSAtom __JS_NewAtomInit(JSRuntime *rt, const char *str, int len,
|
||||||
int atom_type);
|
int atom_type);
|
||||||
|
|
@ -1253,6 +1279,9 @@ static JSValue JS_InstantiateFunctionListItem2(JSContext *ctx, JSObject *p,
|
||||||
JSAtom atom, void *opaque);
|
JSAtom atom, void *opaque);
|
||||||
void JS_SetUncatchableError(JSContext *ctx, JSValueConst val, BOOL flag);
|
void JS_SetUncatchableError(JSContext *ctx, JSValueConst val, BOOL flag);
|
||||||
|
|
||||||
|
static JSBreakpoint *find_existing_breakpoint(JSContext *ctx, JSFunctionBytecode *func, int pc);
|
||||||
|
static int js_handle_breakpoint(JSContext *ctx, JSFunctionBytecode *b, int code_offset);
|
||||||
|
|
||||||
static const JSClassExoticMethods js_arguments_exotic_methods;
|
static const JSClassExoticMethods js_arguments_exotic_methods;
|
||||||
static const JSClassExoticMethods js_string_exotic_methods;
|
static const JSClassExoticMethods js_string_exotic_methods;
|
||||||
static const JSClassExoticMethods js_proxy_exotic_methods;
|
static const JSClassExoticMethods js_proxy_exotic_methods;
|
||||||
|
|
@ -1636,6 +1665,8 @@ JSRuntime *JS_NewRuntime2(const JSMallocFunctions *mf, void *opaque)
|
||||||
#endif
|
#endif
|
||||||
init_list_head(&rt->job_list);
|
init_list_head(&rt->job_list);
|
||||||
|
|
||||||
|
init_list_head(&rt->breakpoint_list);
|
||||||
|
|
||||||
if (JS_InitAtoms(rt))
|
if (JS_InitAtoms(rt))
|
||||||
goto fail;
|
goto fail;
|
||||||
|
|
||||||
|
|
@ -6469,6 +6500,50 @@ static const char *get_func_name(JSContext *ctx, JSValueConst func)
|
||||||
/* only taken into account if filename is provided */
|
/* only taken into account if filename is provided */
|
||||||
#define JS_BACKTRACE_FLAG_SINGLE_LEVEL (1 << 1)
|
#define JS_BACKTRACE_FLAG_SINGLE_LEVEL (1 << 1)
|
||||||
|
|
||||||
|
static void get_frame_debug_position(JSContext *ctx, JSStackFrame *sf,
|
||||||
|
const char **function, const char **file,
|
||||||
|
int *line, BOOL *backtrace_barrier)
|
||||||
|
{
|
||||||
|
JSObject *p;
|
||||||
|
|
||||||
|
*backtrace_barrier = FALSE;
|
||||||
|
*function = get_func_name(ctx, sf->cur_func);
|
||||||
|
|
||||||
|
p = JS_VALUE_GET_OBJ(sf->cur_func);
|
||||||
|
if (js_class_has_bytecode(p->class_id)) {
|
||||||
|
JSFunctionBytecode *b;
|
||||||
|
int line_num1;
|
||||||
|
|
||||||
|
b = p->u.func.function_bytecode;
|
||||||
|
*backtrace_barrier = b->backtrace_barrier;
|
||||||
|
if (b->has_debug) {
|
||||||
|
line_num1 = find_line_num(ctx, b, sf->cur_pc - b->byte_code_buf - 1);
|
||||||
|
|
||||||
|
JSAtom fname = b->debug.filename;
|
||||||
|
if (ctx->rt->map_source_func) {
|
||||||
|
ctx->rt->map_source_func(
|
||||||
|
ctx,
|
||||||
|
ctx->rt->map_source_opaque,
|
||||||
|
fname, line_num1,
|
||||||
|
&fname, &line_num1);
|
||||||
|
}
|
||||||
|
|
||||||
|
*file = JS_AtomToCString(ctx, fname);
|
||||||
|
*line = line_num1;
|
||||||
|
|
||||||
|
if (ctx->rt->map_source_func) {
|
||||||
|
JS_FreeAtomRT(ctx->rt, fname);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
*file = NULL;
|
||||||
|
*line = -1;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
*file = js_strdup(ctx, "native");
|
||||||
|
*line = -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/* if filename != NULL, an additional level is added with the filename
|
/* if filename != NULL, an additional level is added with the filename
|
||||||
and line number information (used for parse error). */
|
and line number information (used for parse error). */
|
||||||
static void build_backtrace(JSContext *ctx, JSValueConst error_obj,
|
static void build_backtrace(JSContext *ctx, JSValueConst error_obj,
|
||||||
|
|
@ -6479,8 +6554,9 @@ static void build_backtrace(JSContext *ctx, JSValueConst error_obj,
|
||||||
JSValue str;
|
JSValue str;
|
||||||
DynBuf dbuf;
|
DynBuf dbuf;
|
||||||
const char *func_name_str;
|
const char *func_name_str;
|
||||||
|
const char *file_name_str;
|
||||||
|
int line_num1;
|
||||||
const char *str1;
|
const char *str1;
|
||||||
JSObject *p;
|
|
||||||
BOOL backtrace_barrier;
|
BOOL backtrace_barrier;
|
||||||
|
|
||||||
js_dbuf_init(ctx, &dbuf);
|
js_dbuf_init(ctx, &dbuf);
|
||||||
|
|
@ -6502,7 +6578,9 @@ static void build_backtrace(JSContext *ctx, JSValueConst error_obj,
|
||||||
backtrace_flags &= ~JS_BACKTRACE_FLAG_SKIP_FIRST_LEVEL;
|
backtrace_flags &= ~JS_BACKTRACE_FLAG_SKIP_FIRST_LEVEL;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
func_name_str = get_func_name(ctx, sf->cur_func);
|
|
||||||
|
get_frame_debug_position(ctx, sf, &func_name_str, &file_name_str,
|
||||||
|
&line_num1, &backtrace_barrier);
|
||||||
if (!func_name_str || func_name_str[0] == '\0')
|
if (!func_name_str || func_name_str[0] == '\0')
|
||||||
str1 = "<anonymous>";
|
str1 = "<anonymous>";
|
||||||
else
|
else
|
||||||
|
|
@ -6510,43 +6588,15 @@ static void build_backtrace(JSContext *ctx, JSValueConst error_obj,
|
||||||
dbuf_printf(&dbuf, " at %s", str1);
|
dbuf_printf(&dbuf, " at %s", str1);
|
||||||
JS_FreeCString(ctx, func_name_str);
|
JS_FreeCString(ctx, func_name_str);
|
||||||
|
|
||||||
p = JS_VALUE_GET_OBJ(sf->cur_func);
|
if (file_name_str && file_name_str[0]) {
|
||||||
backtrace_barrier = FALSE;
|
dbuf_printf(&dbuf, " (%s", file_name_str);
|
||||||
if (js_class_has_bytecode(p->class_id)) {
|
if (line_num1 >= 0) {
|
||||||
JSFunctionBytecode *b;
|
|
||||||
const char *atom_str;
|
|
||||||
int line_num1;
|
|
||||||
|
|
||||||
b = p->u.func.function_bytecode;
|
|
||||||
backtrace_barrier = b->backtrace_barrier;
|
|
||||||
if (b->has_debug) {
|
|
||||||
line_num1 = find_line_num(ctx, b,
|
|
||||||
sf->cur_pc - b->byte_code_buf - 1);
|
|
||||||
|
|
||||||
JSAtom file = b->debug.filename;
|
|
||||||
if (ctx->rt->map_source_func) {
|
|
||||||
ctx->rt->map_source_func(
|
|
||||||
ctx,
|
|
||||||
ctx->rt->map_source_opaque,
|
|
||||||
file, line_num1,
|
|
||||||
&file, &line_num1);
|
|
||||||
}
|
|
||||||
|
|
||||||
atom_str = JS_AtomToCString(ctx, file);
|
|
||||||
dbuf_printf(&dbuf, " (%s",
|
|
||||||
atom_str ? atom_str : "<null>");
|
|
||||||
JS_FreeCString(ctx, atom_str);
|
|
||||||
if (line_num1 != -1)
|
|
||||||
dbuf_printf(&dbuf, ":%d", line_num1);
|
dbuf_printf(&dbuf, ":%d", line_num1);
|
||||||
|
}
|
||||||
dbuf_putc(&dbuf, ')');
|
dbuf_putc(&dbuf, ')');
|
||||||
|
}
|
||||||
|
JS_FreeCString(ctx, file_name_str);
|
||||||
|
|
||||||
if (ctx->rt->map_source_func) {
|
|
||||||
JS_FreeAtomRT(ctx->rt, file);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
dbuf_printf(&dbuf, " (native)");
|
|
||||||
}
|
|
||||||
dbuf_putc(&dbuf, '\n');
|
dbuf_putc(&dbuf, '\n');
|
||||||
/* stop backtrace if JS_EVAL_FLAG_BACKTRACE_BARRIER was used */
|
/* stop backtrace if JS_EVAL_FLAG_BACKTRACE_BARRIER was used */
|
||||||
if (backtrace_barrier)
|
if (backtrace_barrier)
|
||||||
|
|
@ -16227,7 +16277,11 @@ static JSValue JS_CallInternal(JSContext *caller_ctx, JSValueConst func_obj,
|
||||||
size_t alloca_size;
|
size_t alloca_size;
|
||||||
|
|
||||||
#if !DIRECT_DISPATCH
|
#if !DIRECT_DISPATCH
|
||||||
#define SWITCH(pc) switch (opcode = *pc++)
|
#define SWITCH(pc) \
|
||||||
|
opcode = *pc++; \
|
||||||
|
resume_breakpoint: \
|
||||||
|
switch (opcode)
|
||||||
|
#define RESUME_BREAKPOINT(op) opcode = op; goto resume_breakpoint;
|
||||||
#define CASE(op) case op
|
#define CASE(op) case op
|
||||||
#define DEFAULT default
|
#define DEFAULT default
|
||||||
#define BREAK break
|
#define BREAK break
|
||||||
|
|
@ -16243,6 +16297,7 @@ static JSValue JS_CallInternal(JSContext *caller_ctx, JSValueConst func_obj,
|
||||||
[ OP_COUNT ... 255 ] = &&case_default
|
[ OP_COUNT ... 255 ] = &&case_default
|
||||||
};
|
};
|
||||||
#define SWITCH(pc) goto *dispatch_table[opcode = *pc++];
|
#define SWITCH(pc) goto *dispatch_table[opcode = *pc++];
|
||||||
|
#define RESUME_BREAKPOINT(op) opcode = op; goto *dispatch_table[opcode];
|
||||||
#define CASE(op) case_ ## op
|
#define CASE(op) case_ ## op
|
||||||
#define DEFAULT case_default
|
#define DEFAULT case_default
|
||||||
#define BREAK SWITCH(pc)
|
#define BREAK SWITCH(pc)
|
||||||
|
|
@ -18667,6 +18722,13 @@ static JSValue JS_CallInternal(JSContext *caller_ctx, JSValueConst func_obj,
|
||||||
JS_FreeValue(ctx, sp[-1]);
|
JS_FreeValue(ctx, sp[-1]);
|
||||||
sp[-1] = JS_FALSE;
|
sp[-1] = JS_FALSE;
|
||||||
BREAK;
|
BREAK;
|
||||||
|
CASE(OP_breakpoint):
|
||||||
|
{
|
||||||
|
int code_offset = (int)(pc - b->byte_code_buf - 1);
|
||||||
|
int next_op = js_handle_breakpoint(ctx, b, code_offset);
|
||||||
|
RESUME_BREAKPOINT(next_op);
|
||||||
|
}
|
||||||
|
BREAK;
|
||||||
CASE(OP_invalid):
|
CASE(OP_invalid):
|
||||||
DEFAULT:
|
DEFAULT:
|
||||||
JS_ThrowInternalError(ctx, "invalid opcode: pc=%u opcode=0x%02x",
|
JS_ThrowInternalError(ctx, "invalid opcode: pc=%u opcode=0x%02x",
|
||||||
|
|
@ -31142,6 +31204,7 @@ static __exception int resolve_variables(JSContext *ctx, JSFunctionDef *s)
|
||||||
case OP_nop:
|
case OP_nop:
|
||||||
/* remove erased code */
|
/* remove erased code */
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case OP_set_class_name:
|
case OP_set_class_name:
|
||||||
/* only used during parsing */
|
/* only used during parsing */
|
||||||
break;
|
break;
|
||||||
|
|
@ -33717,6 +33780,24 @@ static JSValue __JS_EvalInternal(JSContext *ctx, JSValueConst this_obj,
|
||||||
goto fail1;
|
goto fail1;
|
||||||
fun_obj = JS_DupValue(ctx, JS_MKPTR(JS_TAG_MODULE, m));
|
fun_obj = JS_DupValue(ctx, JS_MKPTR(JS_TAG_MODULE, m));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (ctx->rt->debug_callback) {
|
||||||
|
JSDebugContext dctx;
|
||||||
|
dctx.ctx = ctx;
|
||||||
|
dctx.sf = sf;
|
||||||
|
|
||||||
|
JSDebugEvent evt;
|
||||||
|
evt.reason = JS_BREAK_CODE_LOADED;
|
||||||
|
evt.u.loaded.filename = filename;
|
||||||
|
|
||||||
|
/* TODO: Handle the step/resume/what-have-you. Step from here will
|
||||||
|
be complicated because the function we need to step into will be
|
||||||
|
the one we just parsed, kinda- unless it's a module? Even then
|
||||||
|
it's not clear where the break will go if we're loading a
|
||||||
|
module... yikes. */
|
||||||
|
/* resume = */ctx->rt->debug_callback(&dctx, &evt, ctx->rt->debug_opaque);
|
||||||
|
}
|
||||||
|
|
||||||
if (flags & JS_EVAL_FLAG_COMPILE_ONLY) {
|
if (flags & JS_EVAL_FLAG_COMPILE_ONLY) {
|
||||||
ret_val = fun_obj;
|
ret_val = fun_obj;
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -54086,3 +54167,220 @@ void JS_SetSourceMapFunc(JSRuntime *rt, JSMapSourceFunc *map_func, void *opaque)
|
||||||
rt->map_source_func = map_func;
|
rt->map_source_func = map_func;
|
||||||
rt->map_source_opaque = opaque;
|
rt->map_source_opaque = opaque;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Debugger */
|
||||||
|
|
||||||
|
void JS_SetDebugCallbackFunc(JSRuntime *rt, JSDebugCallbackFunc *func, void *opaque)
|
||||||
|
{
|
||||||
|
rt->debug_callback = func;
|
||||||
|
rt->debug_opaque = opaque;
|
||||||
|
}
|
||||||
|
|
||||||
|
static BOOL find_pc_offset(JSFunctionBytecode *b, int target_line, int *break_pc, int *actual_line)
|
||||||
|
{
|
||||||
|
const uint8_t *p_end, *p;
|
||||||
|
int new_line_num, line_num, new_pc, pc, v, ret;
|
||||||
|
unsigned int op;
|
||||||
|
|
||||||
|
if (!b->has_debug || !b->debug.pc2line_buf) {
|
||||||
|
/* function was stripped */
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
if (target_line < b->debug.line_num) {
|
||||||
|
/* this line is before this function even starts */
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
p = b->debug.pc2line_buf;
|
||||||
|
p_end = p + b->debug.pc2line_len;
|
||||||
|
pc = 0;
|
||||||
|
line_num = b->debug.line_num;
|
||||||
|
while (p < p_end) {
|
||||||
|
op = *p++;
|
||||||
|
if (op == 0) {
|
||||||
|
uint32_t val;
|
||||||
|
ret = get_leb128(&val, p, p_end);
|
||||||
|
if (ret < 0)
|
||||||
|
goto fail;
|
||||||
|
new_pc = pc + val;
|
||||||
|
p += ret;
|
||||||
|
ret = get_sleb128(&v, p, p_end);
|
||||||
|
if (ret < 0) {
|
||||||
|
fail:
|
||||||
|
/* should never happen */
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
p += ret;
|
||||||
|
new_line_num = line_num + v;
|
||||||
|
} else {
|
||||||
|
op -= PC2LINE_OP_FIRST;
|
||||||
|
new_pc = pc + (op / PC2LINE_RANGE);
|
||||||
|
new_line_num = line_num + (op % PC2LINE_RANGE) + PC2LINE_BASE;
|
||||||
|
}
|
||||||
|
if (new_line_num > target_line) {
|
||||||
|
*break_pc = pc;
|
||||||
|
*actual_line = line_num;
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
line_num = new_line_num;
|
||||||
|
pc = new_pc;
|
||||||
|
}
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
static JSBreakpoint *find_existing_breakpoint(JSContext *ctx, JSFunctionBytecode *func, int pc)
|
||||||
|
{
|
||||||
|
struct list_head *el;
|
||||||
|
list_for_each(el, &ctx->rt->breakpoint_list) {
|
||||||
|
JSBreakpoint *bp = list_entry(el, JSBreakpoint, link);
|
||||||
|
if (bp->function == func && bp->code_offset == pc) {
|
||||||
|
return JS_DupBreakpoint(bp);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int js_handle_breakpoint(JSContext *ctx, JSFunctionBytecode *b, int code_offset)
|
||||||
|
{
|
||||||
|
int next_op;
|
||||||
|
/* JSResumeMode resume = JS_RESUME_MODE_CONTINUE; */
|
||||||
|
JSRuntime *rt = ctx->rt;
|
||||||
|
JSBreakpoint *bp = find_existing_breakpoint(ctx, b, code_offset);
|
||||||
|
if (bp) {
|
||||||
|
if (bp->enabled) {
|
||||||
|
if (rt->debug_callback) {
|
||||||
|
JSDebugContext dctx;
|
||||||
|
dctx.ctx = ctx;
|
||||||
|
dctx.sf = ctx->rt->current_stack_frame;
|
||||||
|
|
||||||
|
JSDebugEvent evt;
|
||||||
|
evt.reason = JS_BREAK_BREAKPOINT;
|
||||||
|
evt.u.breakpoint = bp;
|
||||||
|
|
||||||
|
/* resume = */rt->debug_callback(&dctx, &evt, rt->debug_opaque);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
next_op = bp->replaced_byte;
|
||||||
|
} else {
|
||||||
|
assert(code_offset == rt->stepping_pc);
|
||||||
|
rt->stepping_pc = -1;
|
||||||
|
next_op = rt->stepping_byte;
|
||||||
|
b->byte_code_buf[code_offset] = rt->stepping_byte;
|
||||||
|
|
||||||
|
if (rt->debug_callback) {
|
||||||
|
JSDebugContext dctx;
|
||||||
|
dctx.ctx = ctx;
|
||||||
|
dctx.sf = ctx->rt->current_stack_frame;
|
||||||
|
|
||||||
|
JSDebugEvent evt;
|
||||||
|
evt.reason = JS_BREAK_STEP;
|
||||||
|
|
||||||
|
/* resume = */rt->debug_callback(&dctx, &evt, rt->debug_opaque);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return next_op;
|
||||||
|
}
|
||||||
|
|
||||||
|
JSContext *JS_DebugGetContext(JSDebugContext *ctx)
|
||||||
|
{
|
||||||
|
return ctx->ctx;
|
||||||
|
}
|
||||||
|
|
||||||
|
JSDebugFrame *JS_DebugGetFrame(JSDebugContext *ctx) {
|
||||||
|
return (JSDebugFrame *)(ctx->sf);
|
||||||
|
}
|
||||||
|
|
||||||
|
JSDebugFrame *JS_DebugGetPreviousFrame(JSDebugContext *ctx, JSDebugFrame *frame) {
|
||||||
|
JSStackFrame *sf = (JSStackFrame *)frame;
|
||||||
|
if (!sf)
|
||||||
|
return NULL;
|
||||||
|
return (JSDebugFrame *)(sf->prev_frame);
|
||||||
|
}
|
||||||
|
|
||||||
|
void JS_DebugGetFrameSourcePosition(JSDebugContext *dctx, JSDebugFrame *frame,
|
||||||
|
const char **function, const char **file,
|
||||||
|
int *line) {
|
||||||
|
JSStackFrame *sf = (JSStackFrame *)frame;
|
||||||
|
JSContext *ctx = dctx->ctx;
|
||||||
|
BOOL backtrace_barrier;
|
||||||
|
|
||||||
|
get_frame_debug_position(ctx, sf, function, file, line, &backtrace_barrier);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
JSBreakpoint *JS_SetBreakpoint(JSContext *ctx, JSAtom file, int line)
|
||||||
|
{
|
||||||
|
struct list_head *el;
|
||||||
|
list_for_each(el, &ctx->rt->gc_obj_list) {
|
||||||
|
JSGCObjectHeader *gp = list_entry(el, JSGCObjectHeader, link);
|
||||||
|
if (gp->gc_obj_type == JS_GC_OBJ_TYPE_FUNCTION_BYTECODE) {
|
||||||
|
JSFunctionBytecode *func = (JSFunctionBytecode *)gp;
|
||||||
|
if (func->debug.filename == file) {
|
||||||
|
int break_pc, actual_line;
|
||||||
|
if (find_pc_offset(func, line, &break_pc, &actual_line)) {
|
||||||
|
uint8_t code = func->byte_code_buf[break_pc];
|
||||||
|
if (code == OP_breakpoint) {
|
||||||
|
return find_existing_breakpoint(ctx, func, break_pc);
|
||||||
|
}
|
||||||
|
|
||||||
|
JSBreakpoint *breakpoint = js_mallocz(ctx, sizeof(JSBreakpoint));
|
||||||
|
if (!breakpoint) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
breakpoint->ref_count = 1;
|
||||||
|
|
||||||
|
breakpoint->rt = ctx->rt;
|
||||||
|
breakpoint->file = JS_DupAtom(ctx, file);
|
||||||
|
breakpoint->line = actual_line;
|
||||||
|
|
||||||
|
list_add_tail(&breakpoint->link, &ctx->rt->breakpoint_list);
|
||||||
|
breakpoint->function = func;
|
||||||
|
func->header.ref_count++;
|
||||||
|
|
||||||
|
breakpoint->code_offset = break_pc;
|
||||||
|
breakpoint->enabled = TRUE;
|
||||||
|
breakpoint->replaced_byte = code;
|
||||||
|
func->byte_code_buf[break_pc] = OP_breakpoint;
|
||||||
|
|
||||||
|
return breakpoint;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
JSBreakpoint *JS_DupBreakpoint(JSBreakpoint *breakpoint)
|
||||||
|
{
|
||||||
|
breakpoint->ref_count += 1;
|
||||||
|
return breakpoint;
|
||||||
|
}
|
||||||
|
|
||||||
|
void JS_DeleteBreakpoint(JSBreakpoint *breakpoint)
|
||||||
|
{
|
||||||
|
breakpoint->ref_count -= 1;
|
||||||
|
if (breakpoint->ref_count == 0) {
|
||||||
|
breakpoint->function->byte_code_buf[breakpoint->code_offset] = breakpoint->replaced_byte;
|
||||||
|
list_del(&breakpoint->link);
|
||||||
|
JS_FreeValueRT(breakpoint->rt, JS_MKPTR(JS_TAG_FUNCTION_BYTECODE, breakpoint->function));
|
||||||
|
JS_FreeAtomRT(breakpoint->rt, breakpoint->file);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
JS_BOOL JS_GetBreakpointEnabled(JSBreakpoint *breakpoint)
|
||||||
|
{
|
||||||
|
return breakpoint->enabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
void JS_SetBreakpointEnabled(JSBreakpoint *breakpoint, JS_BOOL enabled)
|
||||||
|
{
|
||||||
|
breakpoint->enabled = enabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
void JS_GetBreakpointLocation(JSBreakpoint *breakpoint, JSAtom *file, int *line)
|
||||||
|
{
|
||||||
|
*file = JS_DupAtomRT(breakpoint->rt, breakpoint->file);
|
||||||
|
*line = breakpoint->line;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1050,6 +1050,56 @@ typedef void JSMapSourceFunc(JSContext *ctx, void *opaque, JSAtom file, int line
|
||||||
|
|
||||||
void JS_SetSourceMapFunc(JSRuntime *rt, JSMapSourceFunc *map_func, void *opaque);
|
void JS_SetSourceMapFunc(JSRuntime *rt, JSMapSourceFunc *map_func, void *opaque);
|
||||||
|
|
||||||
|
/* Debugger support */
|
||||||
|
|
||||||
|
typedef struct JSDebugContext JSDebugContext;
|
||||||
|
typedef struct JSDebugFrame JSDebugFrame;
|
||||||
|
|
||||||
|
JSContext *JS_DebugGetContext(JSDebugContext *ctx);
|
||||||
|
|
||||||
|
JSDebugFrame *JS_DebugGetFrame(JSDebugContext *ctx);
|
||||||
|
JSDebugFrame *JS_DebugGetPreviousFrame(JSDebugContext *ctx, JSDebugFrame *frame);
|
||||||
|
void JS_DebugGetFrameSourcePosition(JSDebugContext *ctx, JSDebugFrame *frame,
|
||||||
|
const char **function, const char **file,
|
||||||
|
int *line);
|
||||||
|
|
||||||
|
typedef enum JSResumeMode {
|
||||||
|
JS_RESUME_MODE_CONTINUE,
|
||||||
|
JS_RESUME_MODE_STEP_OVER,
|
||||||
|
JS_RESUME_MODE_STEP_INTO,
|
||||||
|
} JSResumeMode;
|
||||||
|
|
||||||
|
typedef enum JSBreakReason {
|
||||||
|
JS_BREAK_CODE_LOADED,
|
||||||
|
JS_BREAK_BREAKPOINT,
|
||||||
|
JS_BREAK_STEP,
|
||||||
|
} JSBreakReason;
|
||||||
|
|
||||||
|
typedef struct JSBreakpoint JSBreakpoint;
|
||||||
|
|
||||||
|
typedef struct JSDebugEvent {
|
||||||
|
JSBreakReason reason;
|
||||||
|
union {
|
||||||
|
struct {
|
||||||
|
const char *filename;
|
||||||
|
} loaded;
|
||||||
|
JSBreakpoint *breakpoint;
|
||||||
|
} u;
|
||||||
|
} JSDebugEvent;
|
||||||
|
|
||||||
|
typedef JSResumeMode JSDebugCallbackFunc(JSDebugContext *ctx,
|
||||||
|
JSDebugEvent *event,
|
||||||
|
void *opaque);
|
||||||
|
void JS_SetDebugCallbackFunc(JSRuntime *rt, JSDebugCallbackFunc *bp_func,
|
||||||
|
void *opqaue);
|
||||||
|
|
||||||
|
JSBreakpoint *JS_SetBreakpoint(JSContext *ctx, JSAtom file, int line);
|
||||||
|
JSBreakpoint *JS_DupBreakpoint(JSBreakpoint *breakpoint);
|
||||||
|
void JS_DeleteBreakpoint(JSBreakpoint *breakpoint);
|
||||||
|
JS_BOOL JS_GetBreakpointEnabled(JSBreakpoint *breakpoint);
|
||||||
|
void JS_SetBreakpointEnabled(JSBreakpoint *breakpoint, JS_BOOL enabled);
|
||||||
|
void JS_GetBreakpointLocation(JSBreakpoint *breakpoint, JSAtom *file, int *line);
|
||||||
|
|
||||||
#undef js_unlikely
|
#undef js_unlikely
|
||||||
#undef js_force_inline
|
#undef js_force_inline
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
callback::new_fn, conversion::RustFunction, module::Module, Atom, ClassID, Error, Promise,
|
callback::new_fn, conversion::RustFunction, module::Module, Atom, ClassID, Error, Promise,
|
||||||
Result, Runtime, Value, ValueRef, ValueResult,
|
Result, Runtime, TryIntoValue, Value, ValueRef, ValueResult,
|
||||||
};
|
};
|
||||||
use bitflags::bitflags;
|
use bitflags::bitflags;
|
||||||
use oden_js_sys as sys;
|
use oden_js_sys as sys;
|
||||||
|
|
@ -177,6 +177,21 @@ impl ContextRef {
|
||||||
self.check_exception(unsafe { sys::JS_NewObject(self.ctx) })
|
self.check_exception(unsafe { sys::JS_NewObject(self.ctx) })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Construct a new value of type object with the specified properties.
|
||||||
|
pub fn new_object_props<K, V, const N: usize>(&self, props: [(K, V); N]) -> ValueResult
|
||||||
|
where
|
||||||
|
K: AsRef<str>,
|
||||||
|
V: TryIntoValue,
|
||||||
|
{
|
||||||
|
let mut obj = self.new_object()?;
|
||||||
|
for (k, v) in props.into_iter() {
|
||||||
|
let k: &str = k.as_ref();
|
||||||
|
let v = v.try_into_value(self)?;
|
||||||
|
obj.set_property(self, k, &v)?;
|
||||||
|
}
|
||||||
|
Ok(obj)
|
||||||
|
}
|
||||||
|
|
||||||
/// Construct a new value from a boolean.
|
/// Construct a new value from a boolean.
|
||||||
pub fn new_bool<T>(&self, value: T) -> ValueResult
|
pub fn new_bool<T>(&self, value: T) -> ValueResult
|
||||||
where
|
where
|
||||||
|
|
@ -249,6 +264,19 @@ impl ContextRef {
|
||||||
self.check_exception(unsafe { sys::JS_NewArray(self.ctx) })
|
self.check_exception(unsafe { sys::JS_NewArray(self.ctx) })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Construct a new array value with the given contents
|
||||||
|
pub fn new_array_values<K, V, const N: usize>(&self, values: [V; N]) -> ValueResult
|
||||||
|
where
|
||||||
|
V: TryIntoValue,
|
||||||
|
{
|
||||||
|
let mut arr = self.new_array()?;
|
||||||
|
for (i, v) in values.into_iter().enumerate() {
|
||||||
|
let v = v.try_into_value(self)?;
|
||||||
|
arr.set_index(self, i.try_into().unwrap(), &v)?;
|
||||||
|
}
|
||||||
|
Ok(arr)
|
||||||
|
}
|
||||||
|
|
||||||
/// Construct a new value from a string.
|
/// Construct a new value from a string.
|
||||||
pub fn new_string(&self, value: &str) -> ValueResult {
|
pub fn new_string(&self, value: &str) -> ValueResult {
|
||||||
let c_value = CString::new(value)?;
|
let c_value = CString::new(value)?;
|
||||||
|
|
@ -413,6 +441,31 @@ impl ContextRef {
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Parse the specified JSON as data. `filename` is used for reporting
|
||||||
|
/// errors.
|
||||||
|
pub fn parse_json(&self, data: &str, filename: &str) -> Result<Value> {
|
||||||
|
let c_data = CString::new(data)?;
|
||||||
|
let c_filename = CString::new(filename)?;
|
||||||
|
|
||||||
|
self.check_exception(unsafe {
|
||||||
|
sys::JS_ParseJSON(self.ctx, c_data.as_ptr(), data.len(), c_filename.as_ptr())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Convert the value to a JSON string.
|
||||||
|
pub fn json_stringify(&self, value: &ValueRef) -> Result<Value> {
|
||||||
|
self.check_exception(unsafe {
|
||||||
|
let undef = sys::JSValue {
|
||||||
|
u: sys::JSValueUnion {
|
||||||
|
ptr: std::ptr::null_mut(),
|
||||||
|
},
|
||||||
|
tag: sys::JS_TAG_UNDEFINED as i64,
|
||||||
|
};
|
||||||
|
|
||||||
|
sys::JS_JSONStringify(self.ctx, value.val, undef, undef)
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
|
|
||||||
|
|
@ -481,6 +481,12 @@ impl Clone for Value {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl AsRef<ValueRef> for Value {
|
||||||
|
fn as_ref(&self) -> &ValueRef {
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
|
||||||
|
|
@ -1376,6 +1376,8 @@ fn main_thread(event_loop: EventLoopProxy<OdenEvent>, state: State, reciever: Re
|
||||||
let mut script = script::ScriptContext::new(None, script_reload_send.clone())
|
let mut script = script::ScriptContext::new(None, script_reload_send.clone())
|
||||||
.expect("Unable to create initial script context");
|
.expect("Unable to create initial script context");
|
||||||
|
|
||||||
|
let _debugger = script::debugger::start_debugger();
|
||||||
|
|
||||||
const SPF: f64 = 1.0 / 60.0;
|
const SPF: f64 = 1.0 / 60.0;
|
||||||
loop {
|
loop {
|
||||||
frame_mark();
|
frame_mark();
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@ use std::time::Instant;
|
||||||
use tracy_client::span;
|
use tracy_client::span;
|
||||||
use winit::event::*;
|
use winit::event::*;
|
||||||
|
|
||||||
|
pub mod debugger;
|
||||||
pub mod graphics;
|
pub mod graphics;
|
||||||
mod input;
|
mod input;
|
||||||
mod io;
|
mod io;
|
||||||
|
|
|
||||||
701
src/script/debugger.rs
Normal file
701
src/script/debugger.rs
Normal file
|
|
@ -0,0 +1,701 @@
|
||||||
|
use base64::Engine;
|
||||||
|
use bytes::buf::BufMut;
|
||||||
|
use oden_js::{Context, ContextRef, Runtime, ValueRef};
|
||||||
|
use sha1::{Digest, Sha1};
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use std::io;
|
||||||
|
use std::io::{BufRead, BufReader, Read, Write};
|
||||||
|
use std::net::{TcpListener, TcpStream};
|
||||||
|
use std::sync::{
|
||||||
|
mpsc::{channel, Receiver, Sender},
|
||||||
|
Arc, Mutex,
|
||||||
|
};
|
||||||
|
use std::thread;
|
||||||
|
use thiserror::Error;
|
||||||
|
|
||||||
|
pub enum DebuggerMethod {
|
||||||
|
UnknownMethod,
|
||||||
|
DebuggerEnable,
|
||||||
|
DebuggerSetAsyncCallStackDepth,
|
||||||
|
DebuggerSetBlackboxPatterns,
|
||||||
|
DebuggerSetPauseOnExceptions,
|
||||||
|
ProfilerEnable,
|
||||||
|
RuntimeEvaluate,
|
||||||
|
RuntimeEnable,
|
||||||
|
RuntimeRunIfWaitingForDebugger,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DebuggerMethod {
|
||||||
|
pub fn from_request(method: &str, _params: &ValueRef) -> Result<Self> {
|
||||||
|
let evt = match method {
|
||||||
|
"Profiler.enable" => DebuggerMethod::ProfilerEnable,
|
||||||
|
"Runtime.enable" => DebuggerMethod::RuntimeEnable,
|
||||||
|
"Runtime.evaluate" => DebuggerMethod::RuntimeEvaluate,
|
||||||
|
"Runtime.runIfWaitingForDebugger" => DebuggerMethod::RuntimeRunIfWaitingForDebugger,
|
||||||
|
"Debugger.enable" => DebuggerMethod::DebuggerEnable,
|
||||||
|
"Debugger.setAsyncCallStackDepth" => DebuggerMethod::DebuggerSetAsyncCallStackDepth,
|
||||||
|
"Debugger.setBlackboxPatterns" => DebuggerMethod::DebuggerSetBlackboxPatterns,
|
||||||
|
"Debugger.setPauseOnExceptions" => DebuggerMethod::DebuggerSetPauseOnExceptions,
|
||||||
|
_ => {
|
||||||
|
eprintln!("Unsupported method: {method}");
|
||||||
|
DebuggerMethod::UnknownMethod
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(evt)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct DebuggerMessage {
|
||||||
|
pub id: String,
|
||||||
|
pub session_id: Option<String>,
|
||||||
|
pub method: DebuggerMethod,
|
||||||
|
pub response: Websocket,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DebuggerMessage {
|
||||||
|
pub fn from_message(
|
||||||
|
context: &ContextRef,
|
||||||
|
message: &ValueRef,
|
||||||
|
response: Websocket,
|
||||||
|
) -> Result<Self> {
|
||||||
|
let session_id = {
|
||||||
|
let session_id = message.get_property(&context, "sessionId")?;
|
||||||
|
if session_id.is_undefined() {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(session_id.to_string(&context)?)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let id = {
|
||||||
|
let id = message.get_property(context, "id")?;
|
||||||
|
id.to_string(&context)?
|
||||||
|
};
|
||||||
|
let method = {
|
||||||
|
let method = message.get_property(&context, "method")?;
|
||||||
|
method.to_string(&context)?
|
||||||
|
};
|
||||||
|
let params = message.get_property(&context, "params")?;
|
||||||
|
|
||||||
|
let method = DebuggerMethod::from_request(&method, ¶ms)?;
|
||||||
|
Ok(DebuggerMessage {
|
||||||
|
id,
|
||||||
|
session_id,
|
||||||
|
method,
|
||||||
|
response,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub enum DebuggerEvent {
|
||||||
|
Error {
|
||||||
|
id: String,
|
||||||
|
code: i32,
|
||||||
|
message: String,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Debugger {
|
||||||
|
_thread: thread::JoinHandle<()>,
|
||||||
|
messages: Receiver<DebuggerMessage>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub fn start_debugger() -> Debugger {
|
||||||
|
let (send_message, receive_message) = channel();
|
||||||
|
let thread = thread::spawn(move || {
|
||||||
|
if let Err(e) = debugger_listener(send_message) {
|
||||||
|
eprintln!("Error in debug listener: {:?}", e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Debugger {
|
||||||
|
_thread: thread,
|
||||||
|
messages: receive_message,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
impl Debugger {
|
||||||
|
pub fn handle_debugger_messages(&self, _context: &Context) {
|
||||||
|
while let Ok(msg) = self.messages.try_recv() {
|
||||||
|
let event = match msg.method {
|
||||||
|
// TODO
|
||||||
|
_ => DebuggerEvent::Error {
|
||||||
|
id: msg.id.clone(),
|
||||||
|
code: -32601,
|
||||||
|
message: "Unsupported method".to_string(),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
// msg.response.send_json_response(context, context.new_string(msg.id)?
|
||||||
|
// let _ = msg.response.send(event);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn send_debug_event(&self) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn debugger_listener(send: Sender<DebuggerMessage>) -> Result<()> {
|
||||||
|
let listener = TcpListener::bind("127.0.0.1:9229")?;
|
||||||
|
let port = listener.local_addr()?.port();
|
||||||
|
println!("Debugger listening on http://127.0.0.1:{port}");
|
||||||
|
|
||||||
|
for stream in listener.incoming() {
|
||||||
|
let stream = match stream {
|
||||||
|
Ok(s) => s,
|
||||||
|
Err(e) => {
|
||||||
|
eprintln!("Error accepting incoming connection: {:?}", e);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
DebuggerConnection::start(stream, send.clone());
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
struct DebuggerConnection {
|
||||||
|
stream: TcpStream,
|
||||||
|
send: Sender<DebuggerMessage>,
|
||||||
|
event_send: Sender<DebuggerEvent>,
|
||||||
|
events: Receiver<DebuggerEvent>,
|
||||||
|
context: Context,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DebuggerConnection {
|
||||||
|
fn start(stream: TcpStream, send: Sender<DebuggerMessage>) -> Sender<DebuggerEvent> {
|
||||||
|
let (event_send, event_receive) = channel();
|
||||||
|
let result = event_send.clone();
|
||||||
|
thread::spawn(move || {
|
||||||
|
let mut connection = DebuggerConnection {
|
||||||
|
stream,
|
||||||
|
send,
|
||||||
|
event_send,
|
||||||
|
events: event_receive,
|
||||||
|
context: Context::new(Runtime::new()),
|
||||||
|
};
|
||||||
|
loop {
|
||||||
|
match connection.handle_connection() {
|
||||||
|
Ok(keep_open) => {
|
||||||
|
if !keep_open {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
eprintln!("Closing connection: {e}");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
result
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Dumb HTTP stuff
|
||||||
|
// ============================================================================
|
||||||
|
fn handle_connection(&mut self) -> Result<bool> {
|
||||||
|
let port = self.stream.local_addr()?.port();
|
||||||
|
let id = "dd5cfe85-f0b1-4241-a643-8ed81e436188";
|
||||||
|
|
||||||
|
let mut buffer = String::new();
|
||||||
|
let mut buf_reader = BufReader::new(&mut self.stream);
|
||||||
|
while buffer.trim().is_empty() {
|
||||||
|
buf_reader.read_line(&mut buffer)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
let parts: Vec<_> = buffer.trim().split(" ").collect();
|
||||||
|
if parts.len() != 3 {
|
||||||
|
eprintln!("Invalid request line: {buffer}");
|
||||||
|
self.write_http_error(400, "Invalid request")?;
|
||||||
|
return Ok(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
let method = parts[0];
|
||||||
|
let path = parts[1];
|
||||||
|
eprintln!("Debugger: {method} {path}");
|
||||||
|
|
||||||
|
let mut headers = HashMap::new();
|
||||||
|
let mut header_line_buffer = String::new();
|
||||||
|
loop {
|
||||||
|
header_line_buffer.clear();
|
||||||
|
buf_reader.read_line(&mut header_line_buffer)?;
|
||||||
|
let header_line = header_line_buffer.trim();
|
||||||
|
if header_line.is_empty() {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
let sep_idx = match header_line.find(":") {
|
||||||
|
Some(idx) => idx,
|
||||||
|
None => {
|
||||||
|
self.write_http_error(400, "Invalid request")?;
|
||||||
|
return Ok(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let header = &header_line[0..sep_idx];
|
||||||
|
let value = &header_line[sep_idx + 1..];
|
||||||
|
// eprintln!("HEADER: {header} {value}");
|
||||||
|
headers.insert(header.trim().to_lowercase(), value.trim().to_string());
|
||||||
|
}
|
||||||
|
|
||||||
|
if method == "GET" {
|
||||||
|
if path == "/json/version" {
|
||||||
|
// NOTE: Deno spits out a V8 version here but we're not running v8.
|
||||||
|
self.write_http_ok(
|
||||||
|
"application/json",
|
||||||
|
r#"
|
||||||
|
{
|
||||||
|
"Browser": "Oden/0.0.1",
|
||||||
|
"Protocol-Version": "1.3"
|
||||||
|
}"#
|
||||||
|
.as_bytes(),
|
||||||
|
)?;
|
||||||
|
} else if path == "/json" || path == "/json/list" {
|
||||||
|
// TODO: title should reflect the game being loaded
|
||||||
|
// TODO: url should go to the main .ts file
|
||||||
|
// TODO: faciconUrl?
|
||||||
|
let result = format!(
|
||||||
|
r#"
|
||||||
|
[{{
|
||||||
|
"description": "oden",
|
||||||
|
"devtoolsFrontendUrl": "devtools://devtools/bundled/js_app.html?ws=127.0.0.1:9229/ws/01b9bd23-8810-43ed-86f7-5feef5d120fa&experiments=true&v8only=true",
|
||||||
|
"id": "{id}",
|
||||||
|
"title": "oden game",
|
||||||
|
"url": "oden://game",
|
||||||
|
"webSocketDebuggerUrl": "ws://127.0.0.1:{port}/ws/{id}"
|
||||||
|
}}]
|
||||||
|
"#
|
||||||
|
);
|
||||||
|
self.write_http_ok("application/json", result.as_bytes())?;
|
||||||
|
} else if path == "/ws/dd5cfe85-f0b1-4241-a643-8ed81e436188" {
|
||||||
|
// I don't feel like making a new guid for each thing.
|
||||||
|
// Websocket upgrade and then debugger messages.
|
||||||
|
|
||||||
|
let mut key = match headers.get("sec-websocket-key") {
|
||||||
|
Some(v) => v.clone(),
|
||||||
|
None => {
|
||||||
|
self.write_http_error(400, "Invalid request")?;
|
||||||
|
return Ok(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
key.push_str("258EAFA5-E914-47DA-95CA-C5AB0DC85B11");
|
||||||
|
let mut hasher = Sha1::new();
|
||||||
|
hasher.update(key.as_bytes());
|
||||||
|
let result = base64::engine::general_purpose::STANDARD.encode(hasher.finalize());
|
||||||
|
let response = format!(
|
||||||
|
"HTTP/1.1 101 Switching Protocols\r\n\
|
||||||
|
Upgrade: websocket\r\n\
|
||||||
|
Connection: Upgrade\r\n\
|
||||||
|
Sec-Websocket-Accept: {result}\r\n\
|
||||||
|
\r\n"
|
||||||
|
);
|
||||||
|
self.stream.write_all(response.as_bytes())?;
|
||||||
|
|
||||||
|
self.handle_websocket_connection()?;
|
||||||
|
return Ok(false);
|
||||||
|
} else {
|
||||||
|
self.write_http_error(404, "Not Found")?;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
self.write_http_error(404, "Not Found")?;
|
||||||
|
}
|
||||||
|
Ok(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write_http_response(
|
||||||
|
&mut self,
|
||||||
|
code: u16,
|
||||||
|
phrase: &str,
|
||||||
|
content_type: &str,
|
||||||
|
data: &[u8],
|
||||||
|
) -> Result<()> {
|
||||||
|
let length = data.len();
|
||||||
|
let buffer = format!(
|
||||||
|
"HTTP/1.1 {code} {phrase}\r\n\
|
||||||
|
content-length: {length}\r\n\
|
||||||
|
content-type: {content_type}\r\n\
|
||||||
|
\r\n"
|
||||||
|
);
|
||||||
|
self.stream.write_all(buffer.as_bytes())?;
|
||||||
|
self.stream.write_all(data)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write_http_error(&mut self, code: u16, phrase: &str) -> Result<()> {
|
||||||
|
self.write_http_response(code, phrase, "text/plain", phrase.as_bytes())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write_http_ok(&mut self, content_type: &str, data: &[u8]) -> Result<()> {
|
||||||
|
self.write_http_response(200, "OK", content_type, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_websocket_connection(&mut self) -> Result<()> {
|
||||||
|
let websocket = Websocket::new(&self.stream)?;
|
||||||
|
let mut payload: Vec<u8> = Vec::new();
|
||||||
|
loop {
|
||||||
|
let op = websocket.read_websocket_message(&mut payload)?;
|
||||||
|
if let WebsocketOp::Close = op {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
let text = String::from_utf8_lossy(&payload);
|
||||||
|
eprintln!("Received: {text}");
|
||||||
|
|
||||||
|
let message = match self.context.parse_json(&text, "<debugger message>") {
|
||||||
|
Ok(v) => v,
|
||||||
|
Err(e) => {
|
||||||
|
eprintln!("error parsing json {e}");
|
||||||
|
websocket.send_websocket_close(1007, "invalid json")?;
|
||||||
|
return Err("invalid json".into());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let message =
|
||||||
|
DebuggerMessage::from_message(&self.context, &message, websocket.clone())?;
|
||||||
|
let _ = self.send.send(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Dumb Websocket stuff
|
||||||
|
// ============================================================================
|
||||||
|
#[derive(Clone)]
|
||||||
|
struct Websocket {
|
||||||
|
// TODO: Atomic close
|
||||||
|
send: Arc<Mutex<TcpStream>>,
|
||||||
|
receive: Arc<Mutex<TcpStream>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Websocket {
|
||||||
|
pub fn new(stream: &TcpStream) -> Result<Websocket> {
|
||||||
|
let send = stream.try_clone()?;
|
||||||
|
let receive = stream.try_clone()?;
|
||||||
|
Ok(Websocket {
|
||||||
|
send: Arc::new(Mutex::new(send)),
|
||||||
|
receive: Arc::new(Mutex::new(receive)),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn read_websocket_message(&self, payload: &mut Vec<u8>) -> Result<WebsocketOp> {
|
||||||
|
// BLARG: https://datatracker.ietf.org/doc/html/rfc6455
|
||||||
|
payload.clear();
|
||||||
|
|
||||||
|
let mut stream = self.receive.lock().unwrap();
|
||||||
|
let mut opcode: WebsocketOp = WebsocketOp::Continue;
|
||||||
|
loop {
|
||||||
|
let frame = WebsocketFrameHeader::read(&mut stream)?;
|
||||||
|
eprintln!("FRAME: {:?}", frame);
|
||||||
|
let mask = match frame.mask {
|
||||||
|
Some(m) => m,
|
||||||
|
None => {
|
||||||
|
eprintln!("Client sent unmasked frame");
|
||||||
|
self.send_websocket_close(1002, "client sent unmasked frame")?;
|
||||||
|
return Err(DebuggerError::WebsocketError);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// OK what are we dealing with here? Check the opcode before we decide
|
||||||
|
// where to read the data.
|
||||||
|
if frame.opcode.is_control() {
|
||||||
|
// This is a control frame, handle it on its own.
|
||||||
|
if frame.length > 125 {
|
||||||
|
eprintln!("Control frame too big ({})", frame.length);
|
||||||
|
self.send_websocket_close(1002, "control frame too big")?;
|
||||||
|
return Err(DebuggerError::WebsocketError);
|
||||||
|
}
|
||||||
|
let mut buffer: [u8; 125] = [0; 125];
|
||||||
|
stream.read_exact(&mut buffer[0..frame.length])?;
|
||||||
|
|
||||||
|
if frame.opcode == WebsocketOp::Close {
|
||||||
|
self.send_websocket_message(frame.opcode, &buffer[..frame.length])?;
|
||||||
|
return Ok(WebsocketOp::Close);
|
||||||
|
} else if frame.opcode == WebsocketOp::Ping {
|
||||||
|
self.send_websocket_message(WebsocketOp::Pong, &buffer[..frame.length])?;
|
||||||
|
} else if frame.opcode == WebsocketOp::Pong {
|
||||||
|
// got a pong
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// This is a user data frame, it goes into `payload`.
|
||||||
|
let last_tail = payload.len();
|
||||||
|
if last_tail + frame.length > 1 * 1024 * 1024 {
|
||||||
|
eprintln!("Message too big ({})", frame.length);
|
||||||
|
self.send_websocket_close(1009, "message too big")?;
|
||||||
|
return Err(DebuggerError::WebsocketError);
|
||||||
|
}
|
||||||
|
payload.resize(last_tail + frame.length, 0);
|
||||||
|
let slice = payload.as_mut_slice();
|
||||||
|
let mut slice = &mut slice[last_tail..];
|
||||||
|
// eprintln!("Reading {} bytes", slice.len());
|
||||||
|
stream.read_exact(&mut slice)?;
|
||||||
|
for i in 0..frame.length {
|
||||||
|
slice[i] = slice[i] ^ mask[i % 4];
|
||||||
|
}
|
||||||
|
// eprintln!("Done");
|
||||||
|
|
||||||
|
if opcode == WebsocketOp::Continue {
|
||||||
|
opcode = frame.opcode;
|
||||||
|
}
|
||||||
|
|
||||||
|
if frame.fin {
|
||||||
|
// Dispatch.
|
||||||
|
// eprintln!("Dispatching: {:?} {}", opcode, payload.len());
|
||||||
|
return Ok(opcode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn send_websocket_message(&self, opcode: WebsocketOp, payload: &[u8]) -> Result<()> {
|
||||||
|
let mut stream = self.send.lock().unwrap();
|
||||||
|
WebsocketFrameHeader {
|
||||||
|
fin: true,
|
||||||
|
opcode,
|
||||||
|
length: payload.len(),
|
||||||
|
mask: None,
|
||||||
|
}
|
||||||
|
.write(&mut stream)?;
|
||||||
|
stream.write_all(payload)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn send_websocket_close(&self, close_code: u16, reason: &str) -> Result<()> {
|
||||||
|
if reason.len() > 123 {
|
||||||
|
return Err("reason doesn't fit in close packet".into());
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut body: [u8; 125] = [0; 125];
|
||||||
|
let mut dst = &mut body[..];
|
||||||
|
dst.put_u16(close_code);
|
||||||
|
dst.put_slice(reason.as_bytes());
|
||||||
|
let remaining = dst.remaining_mut();
|
||||||
|
|
||||||
|
let body_len = body.len() - remaining;
|
||||||
|
self.send_websocket_message(WebsocketOp::Close, &body[0..body_len])
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn send_json_event(
|
||||||
|
&self,
|
||||||
|
context: &ContextRef,
|
||||||
|
event: &str,
|
||||||
|
params: &ValueRef,
|
||||||
|
) -> Result<()> {
|
||||||
|
self.send_json_message(
|
||||||
|
&context,
|
||||||
|
context
|
||||||
|
.new_object_props([
|
||||||
|
("method", context.new_string(event)?.as_ref()),
|
||||||
|
("params", params),
|
||||||
|
])?
|
||||||
|
.as_ref(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn send_json_error(
|
||||||
|
&self,
|
||||||
|
context: &ContextRef,
|
||||||
|
id: &ValueRef,
|
||||||
|
code: i32,
|
||||||
|
msg: &str,
|
||||||
|
) -> Result<()> {
|
||||||
|
self.send_json_message(
|
||||||
|
&context,
|
||||||
|
context
|
||||||
|
.new_object_props([
|
||||||
|
("id", id),
|
||||||
|
(
|
||||||
|
"error",
|
||||||
|
context
|
||||||
|
.new_object_props([
|
||||||
|
("code", context.new_i32(code)?),
|
||||||
|
("message", context.new_string(msg)?),
|
||||||
|
])?
|
||||||
|
.as_ref(),
|
||||||
|
),
|
||||||
|
])?
|
||||||
|
.as_ref(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn send_json_response(
|
||||||
|
&self,
|
||||||
|
context: &ContextRef,
|
||||||
|
id: &ValueRef,
|
||||||
|
result: &ValueRef,
|
||||||
|
) -> Result<()> {
|
||||||
|
let response = context.new_object_props([("id", id), ("result", result)])?;
|
||||||
|
self.send_json_message(&context, &response)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn send_json_message(&self, context: &ContextRef, value: &ValueRef) -> Result<()> {
|
||||||
|
let txt = context.json_stringify(&value)?.to_string(&context)?;
|
||||||
|
eprintln!("Send: {txt}");
|
||||||
|
self.send_websocket_message(WebsocketOp::Text, txt.as_bytes())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct WebsocketFrameHeader {
|
||||||
|
pub fin: bool,
|
||||||
|
pub opcode: WebsocketOp,
|
||||||
|
pub length: usize,
|
||||||
|
pub mask: Option<[u8; 4]>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl WebsocketFrameHeader {
|
||||||
|
fn read(stream: &mut TcpStream) -> Result<WebsocketFrameHeader> {
|
||||||
|
let mut buffer: [u8; 2] = [0, 0];
|
||||||
|
stream.read_exact(&mut buffer)?;
|
||||||
|
if (buffer[0] & 0x70) != 0 {
|
||||||
|
eprintln!("Frame has non-zero RSV* ({:x})", buffer[0]);
|
||||||
|
return Err("unknown extensions".into());
|
||||||
|
}
|
||||||
|
let has_mask = (buffer[1] & 0x80) != 0;
|
||||||
|
|
||||||
|
let fin = (buffer[0] & 0x80) != 0;
|
||||||
|
let opcode = buffer[0] & 0x0F;
|
||||||
|
let mut length: u64 = (buffer[1] & !0x80).into();
|
||||||
|
// eprintln!("Decoded length {} from {}", length, buffer[1]);
|
||||||
|
if length == 126 {
|
||||||
|
let mut len_buffer: [u8; 2] = [0, 0];
|
||||||
|
stream.read_exact(&mut len_buffer)?;
|
||||||
|
length = u16::from_be_bytes(len_buffer).into();
|
||||||
|
} else if length == 127 {
|
||||||
|
let mut len_buffer: [u8; 8] = [0, 0, 0, 0, 0, 0, 0, 0];
|
||||||
|
stream.read_exact(&mut len_buffer)?;
|
||||||
|
length = u64::from_be_bytes(len_buffer);
|
||||||
|
}
|
||||||
|
if length > 1 * 1024 * 1024 {
|
||||||
|
eprintln!("Frame too big ({length})");
|
||||||
|
return Err(io::Error::new(io::ErrorKind::InvalidData, "frame too big").into());
|
||||||
|
}
|
||||||
|
|
||||||
|
let mask = if has_mask {
|
||||||
|
let mut mask: [u8; 4] = [0, 0, 0, 0];
|
||||||
|
stream.read_exact(&mut mask)?;
|
||||||
|
Some(mask)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
|
let length: usize = length.try_into().unwrap();
|
||||||
|
Ok(WebsocketFrameHeader {
|
||||||
|
fin,
|
||||||
|
opcode: opcode.try_into()?,
|
||||||
|
length,
|
||||||
|
mask,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write(&self, stream: &mut TcpStream) -> Result<()> {
|
||||||
|
let mut header: [u8; 16] = [0; 16];
|
||||||
|
let mut dst = &mut header[..];
|
||||||
|
|
||||||
|
let byte: u8 = self.opcode.into();
|
||||||
|
dst.put_u8(byte | if self.fin { 0x80 } else { 0x00 });
|
||||||
|
|
||||||
|
let mask = if self.mask.is_some() { 0x80 } else { 0x00 };
|
||||||
|
if self.length < 126 {
|
||||||
|
let len: u8 = self.length.try_into().unwrap();
|
||||||
|
dst.put_u8(len | mask);
|
||||||
|
} else if self.length <= u16::max_value().into() {
|
||||||
|
dst.put_u8(126 | mask);
|
||||||
|
dst.put_u16(self.length.try_into().unwrap());
|
||||||
|
} else {
|
||||||
|
dst.put_u8(127 | mask);
|
||||||
|
dst.put_u64(self.length.try_into().unwrap());
|
||||||
|
}
|
||||||
|
if let Some(bytes) = self.mask {
|
||||||
|
dst.put_slice(&bytes);
|
||||||
|
}
|
||||||
|
let remaining = dst.remaining_mut();
|
||||||
|
|
||||||
|
let header_len = header.len() - remaining;
|
||||||
|
stream.write_all(&header[0..header_len])?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, PartialEq, Eq, Debug, Hash)]
|
||||||
|
enum WebsocketOp {
|
||||||
|
Continue,
|
||||||
|
Text,
|
||||||
|
Binary,
|
||||||
|
Close,
|
||||||
|
Ping,
|
||||||
|
Pong,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl WebsocketOp {
|
||||||
|
fn is_control(&self) -> bool {
|
||||||
|
let byte: u8 = (*self).into();
|
||||||
|
byte & 0x08 != 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<u8> for WebsocketOp {
|
||||||
|
type Error = DebuggerError;
|
||||||
|
|
||||||
|
fn try_from(value: u8) -> Result<Self> {
|
||||||
|
match value {
|
||||||
|
0x0 => Ok(WebsocketOp::Continue),
|
||||||
|
0x1 => Ok(WebsocketOp::Text),
|
||||||
|
0x2 => Ok(WebsocketOp::Binary),
|
||||||
|
0x8 => Ok(WebsocketOp::Close),
|
||||||
|
0x9 => Ok(WebsocketOp::Ping),
|
||||||
|
0xA => Ok(WebsocketOp::Pong),
|
||||||
|
_ => Err(format!("unknown opcode {value}").into()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<WebsocketOp> for u8 {
|
||||||
|
fn from(value: WebsocketOp) -> Self {
|
||||||
|
match value {
|
||||||
|
WebsocketOp::Continue => 0x0,
|
||||||
|
WebsocketOp::Text => 0x1,
|
||||||
|
WebsocketOp::Binary => 0x2,
|
||||||
|
WebsocketOp::Close => 0x8,
|
||||||
|
WebsocketOp::Ping => 0x9,
|
||||||
|
WebsocketOp::Pong => 0xA,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Error)]
|
||||||
|
pub enum DebuggerError {
|
||||||
|
#[error("an unknown error has occurred: {0}")]
|
||||||
|
MiscError(String),
|
||||||
|
#[error("a javascript error has occurred: {0}")]
|
||||||
|
JsError(oden_js::Error),
|
||||||
|
#[error("an io error has occurred: {0}")]
|
||||||
|
IoError(io::Error),
|
||||||
|
#[error("the websocket encountered a protocol error")]
|
||||||
|
WebsocketError,
|
||||||
|
}
|
||||||
|
|
||||||
|
type Result<T> = core::result::Result<T, DebuggerError>;
|
||||||
|
|
||||||
|
impl From<&str> for DebuggerError {
|
||||||
|
fn from(value: &str) -> Self {
|
||||||
|
DebuggerError::MiscError(value.to_string())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<String> for DebuggerError {
|
||||||
|
fn from(value: String) -> Self {
|
||||||
|
DebuggerError::MiscError(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<oden_js::Error> for DebuggerError {
|
||||||
|
fn from(value: oden_js::Error) -> Self {
|
||||||
|
DebuggerError::JsError(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<io::Error> for DebuggerError {
|
||||||
|
fn from(value: io::Error) -> Self {
|
||||||
|
DebuggerError::IoError(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue