mirror of
https://github.com/Samsung/escargot.git
synced 2026-06-22 10:01:50 +00:00
Compare commits
205 commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bab3a57975 | ||
|
|
181019c0c3 | ||
|
|
c37e2b4851 | ||
|
|
2dee22f5c7 | ||
|
|
5e3b91b052 | ||
|
|
e7221f4211 | ||
|
|
b30b63fc63 | ||
|
|
60b1202a72 | ||
|
|
0a2fcaaf5e | ||
|
|
09f0a10bba | ||
|
|
7e2b3292fd | ||
|
|
8fd141b29c | ||
|
|
c80623fc00 | ||
|
|
92ee65bc0c | ||
|
|
07cdae7850 | ||
|
|
ef525f337f | ||
|
|
29fdbc741f | ||
|
|
ebe3761308 | ||
|
|
c423a4bfa0 | ||
|
|
4b40f92aba | ||
|
|
779f6bedf5 | ||
|
|
d581b27af6 | ||
|
|
3b43994a7d | ||
|
|
299a7ff451 | ||
|
|
36f5fb5836 | ||
|
|
d6aae0777f | ||
|
|
590345cc62 | ||
|
|
c02c6595be | ||
|
|
166fa7c66b | ||
|
|
3cf7d60b43 | ||
|
|
2bbd27caac | ||
|
|
78e5e333b9 | ||
|
|
22bedcec9e | ||
|
|
aa727d22a1 | ||
|
|
121d2fefca | ||
|
|
2e9a6393b9 | ||
|
|
685a71c3d1 | ||
|
|
78576a5af9 | ||
|
|
c8588c323c | ||
|
|
2cc649c97e | ||
|
|
98f54274d1 | ||
|
|
0a79d6c1f7 | ||
|
|
634fe864d7 | ||
|
|
475149426f | ||
|
|
7683468efb | ||
|
|
48eb4b6af9 | ||
|
|
e9833cd791 | ||
|
|
769e86e32a | ||
|
|
ad3844437e | ||
|
|
633fe63795 | ||
|
|
e52f0ce0cf | ||
|
|
2624608567 | ||
|
|
0ac274d1fc | ||
|
|
ab7a13e089 | ||
|
|
f25f05faca | ||
|
|
a7f9695bf3 | ||
|
|
50215a5ce8 | ||
|
|
13e3a62312 | ||
|
|
2156cfa5b8 | ||
|
|
b4f2b24e4a | ||
|
|
97e8115ab1 | ||
|
|
989e6922b6 | ||
|
|
6ebbd22c06 | ||
|
|
069fba1151 | ||
|
|
9819a8de49 | ||
|
|
f41ec3426b | ||
|
|
1b36b95006 | ||
|
|
c69b8ada67 | ||
|
|
f6e0b04be4 | ||
|
|
4d4cded5be | ||
|
|
5c16ae5d84 | ||
|
|
12a37ed4d1 | ||
|
|
fb8b241655 | ||
|
|
339a5d1838 | ||
|
|
bb00312798 | ||
|
|
c9f13d0730 | ||
|
|
e35a8cb14d | ||
|
|
af4c67a706 | ||
|
|
c90e358e2f | ||
|
|
17bdb07cc6 | ||
|
|
eeea83ef3e | ||
|
|
d50bb8897a | ||
|
|
a3abf7e40a | ||
|
|
32281e3d22 | ||
|
|
ea826db76a | ||
|
|
7bd328b5df | ||
|
|
20c7641b16 | ||
|
|
e44005a1d2 | ||
|
|
a130b108a5 | ||
|
|
fb2ad1eb04 | ||
|
|
32e9a72156 | ||
|
|
2a3447dc1c | ||
|
|
3b242a2e6e | ||
|
|
95036c339e | ||
|
|
50f1d7a2f1 | ||
|
|
3f07b5696c | ||
|
|
a138441b98 | ||
|
|
a474595528 | ||
|
|
75394413ac | ||
|
|
1ab58f5470 | ||
|
|
a622791f6e | ||
|
|
142cf01ad4 | ||
|
|
c231374c38 | ||
|
|
ec0d3f1c61 | ||
|
|
982f15c83d | ||
|
|
23b88e0d3d | ||
|
|
94abd9142c | ||
|
|
bf4d55e27a | ||
|
|
f5ae276846 | ||
|
|
ef4b1ef414 | ||
|
|
5537c312dc | ||
|
|
b66f1f6678 | ||
|
|
d49aece60c | ||
|
|
be331e04c3 | ||
|
|
c7a1b4154b | ||
|
|
e74404b9a2 | ||
|
|
8aae441360 | ||
|
|
c54390bf2e | ||
|
|
c599abdc60 | ||
|
|
8f24498310 | ||
|
|
7bb1e520a5 | ||
|
|
82924e7db5 | ||
|
|
f541c5c63f | ||
|
|
2f42f070f3 | ||
|
|
5c64d63fd1 | ||
|
|
2b7a5657ef | ||
|
|
345a295cba | ||
|
|
ce0d795280 | ||
|
|
4e589b1a52 | ||
|
|
15d86aeeb9 | ||
|
|
71706514f4 | ||
|
|
ee64383623 | ||
|
|
e6417b8ed8 | ||
|
|
7667784d79 | ||
|
|
b1dd07f05c | ||
|
|
2325f6fc64 | ||
|
|
1175bb303b | ||
|
|
d06a31a1a2 | ||
|
|
369278e640 | ||
|
|
7dd0c1821e | ||
|
|
33d32009da | ||
|
|
c3841a7176 | ||
|
|
0a2eec0e85 | ||
|
|
75cdb470ba | ||
|
|
45313ea2ce | ||
|
|
66e105e9f8 | ||
|
|
4a4f8a6d7e | ||
|
|
ef5ef0b9b4 | ||
|
|
18d3f010a0 | ||
|
|
297162133e | ||
|
|
ba05eaec99 | ||
|
|
5bbfa73dcd | ||
|
|
5240b9cbec | ||
|
|
72172f6c47 | ||
|
|
d017677d54 | ||
|
|
98de57bc8a | ||
|
|
fdac7ae1c3 | ||
|
|
dccf2f9256 | ||
|
|
83c9e9a50a | ||
|
|
0c41944316 | ||
|
|
3844dced3f | ||
|
|
0249b5efb5 | ||
|
|
1c3248ce35 | ||
|
|
ef617652dc | ||
|
|
bb3c62e2cc | ||
|
|
acd242f7df | ||
|
|
5618ae6f7b | ||
|
|
dc7640a152 | ||
|
|
5e5eb5d631 | ||
|
|
fa3432f1d6 | ||
|
|
9d634be004 | ||
|
|
cb01142c4c | ||
|
|
f9ca29d5cb | ||
|
|
c3c3bca85e | ||
|
|
600fa1a906 | ||
|
|
b9041e60b7 | ||
|
|
70e0721082 | ||
|
|
36e4562e68 | ||
|
|
77a3b8554f | ||
|
|
77741e8d13 | ||
|
|
4f0a1cff00 | ||
|
|
9e414f3933 | ||
|
|
641e3813c4 | ||
|
|
8b39a2c3ff | ||
|
|
8e5636c198 | ||
|
|
5b74588aa1 | ||
|
|
141797871f | ||
|
|
90d8da7fe8 | ||
|
|
d7c2db8f3f | ||
|
|
84a305785a | ||
|
|
293729c869 | ||
|
|
40e54ebead | ||
|
|
8d140c3c0f | ||
|
|
7135cbaefe | ||
|
|
34c2f0a20e | ||
|
|
5711241b99 | ||
|
|
96beab3416 | ||
|
|
06e356f15a | ||
|
|
6175024ffc | ||
|
|
3c1ddaaa50 | ||
|
|
52bbc7a9bc | ||
|
|
fc47134b6e | ||
|
|
2482f40fe2 | ||
|
|
25a5bf17c3 | ||
|
|
6cfdea8169 |
218 changed files with 113192 additions and 26261 deletions
39
.github/workflows/analysis-actions.yml
vendored
39
.github/workflows/analysis-actions.yml
vendored
|
|
@ -4,19 +4,23 @@ on:
|
|||
schedule:
|
||||
# trigger on every monday, wednesday and friday
|
||||
- cron: '30 22 * * 1,3,5'
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
coverity-scan:
|
||||
if: github.repository == 'Samsung/escargot'
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
- name: Install Packages
|
||||
- name: Build ICU(64)
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y ninja-build
|
||||
git clone --depth 1 --single-branch -b release-78.1 https://github.com/unicode-org/icu.git $GITHUB_WORKSPACE/icu64-build/
|
||||
cd $GITHUB_WORKSPACE/icu64-build/icu4c/source
|
||||
LDFLAGS="-Wl,-rpath=$GITHUB_WORKSPACE/icu64/lib/" ./runConfigureICU Linux/gcc --prefix="$GITHUB_WORKSPACE/icu64/"
|
||||
make -j8
|
||||
make install
|
||||
- name: Download Coverity Tool
|
||||
env:
|
||||
TOKEN: ${{ secrets.COVERITY_SCAN_TOKEN }}
|
||||
|
|
@ -26,10 +30,10 @@ jobs:
|
|||
tar xzf cov-analysis-linux64.tar.gz --strip 1 -C cov-analysis-linux64
|
||||
- name: Build
|
||||
env:
|
||||
BUILD_OPTIONS: -DESCARGOT_MODE=release -DESCARGOT_THREADING=ON -DESCARGOT_TCO=ON -DESCARGOT_CODE_CACHE=ON -DESCARGOT_WASM=ON -DESCARGOT_TEMPORAL=ON -DESCARGOT_TEST=ON -DESCARGOT_OUTPUT=shell -GNinja
|
||||
BUILD_OPTIONS: -DESCARGOT_MODE=release -DESCARGOT_THREADING=ON -DESCARGOT_TCO=ON -DESCARGOT_CODE_CACHE=ON -DESCARGOT_WASM=ON -DESCARGOT_TEMPORAL=ON -DESCARGOT_TEST=ON -DESCARGOT_SHADOWREALM=ON -DESCARGOT_OUTPUT=shell -GNinja
|
||||
run: |
|
||||
export PATH=$GITHUB_WORKSPACE/cov-analysis-linux64/bin:$PATH
|
||||
cmake -DCMAKE_POLICY_VERSION_MINIMUM=3.5 -H. -Bout/coverity_scan $BUILD_OPTIONS
|
||||
LDFLAGS="-L$GITHUB_WORKSPACE/icu64/lib/ -Wl,-rpath=$GITHUB_WORKSPACE/icu64/lib/" PKG_CONFIG_PATH="$GITHUB_WORKSPACE/icu64/lib/pkgconfig/" cmake -DCMAKE_POLICY_VERSION_MINIMUM=3.5 -H. -Bout/coverity_scan $BUILD_OPTIONS
|
||||
cov-build --dir cov-int ninja -Cout/coverity_scan
|
||||
- name: Submit
|
||||
env:
|
||||
|
|
@ -41,28 +45,26 @@ jobs:
|
|||
--form token=$TOKEN \
|
||||
--form email=$NOTI_MAIL \
|
||||
--form file=@escargot.tgz \
|
||||
--form version="4.0.0" \
|
||||
--form version="4.3.0" \
|
||||
--form description="escargot coverity scan" \
|
||||
https://scan.coverity.com/builds?project=Samsung%2Fescargot
|
||||
|
||||
coverage-scan:
|
||||
if: github.repository == 'Samsung/escargot'
|
||||
# ubuntu version fixed
|
||||
runs-on: ubuntu-22.04
|
||||
runs-on: [self-hosted, linux, x64, test]
|
||||
timeout-minutes: 600
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: true
|
||||
- name: Install Packages
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y pypy ninja-build libicu-dev gcovr
|
||||
- name: Build x64
|
||||
env:
|
||||
BUILD_OPTIONS: -DESCARGOT_MODE=release -DESCARGOT_THREADING=ON -DESCARGOT_TCO=ON -DESCARGOT_COVERAGE=ON -DESCARGOT_TEMPORAL=ON -DESCARGOT_TEST=ON -DESCARGOT_OUTPUT=shell -GNinja
|
||||
BUILD_OPTIONS: -DESCARGOT_MODE=release -DESCARGOT_THREADING=ON -DESCARGOT_TCO=ON -DESCARGOT_COVERAGE=ON -DESCARGOT_TEMPORAL=ON -DESCARGOT_TEST=ON -DESCARGOT_SHADOWREALM=ON -DESCARGOT_OUTPUT=shell -GNinja
|
||||
run: |
|
||||
cmake -DCMAKE_POLICY_VERSION_MINIMUM=3.5 -H. -Bout/coverage $BUILD_OPTIONS
|
||||
ninja -Cout/coverage
|
||||
LDFLAGS=" -L/usr/icu78-64/lib/ -Wl,-rpath=/usr/icu78-64/lib/" PKG_CONFIG_PATH="/usr/icu78-64/lib/pkgconfig/" cmake -DCMAKE_POLICY_VERSION_MINIMUM=3.5 -H. -Bout/coverage64 -DESCARGOT_ARCH=x64 $BUILD_OPTIONS
|
||||
ninja -Cout/coverage64
|
||||
LDFLAGS=" -L/usr/icu78-32/lib/ -Wl,-rpath=/usr/icu78-32/lib/" PKG_CONFIG_PATH="/usr/icu78-32/lib/pkgconfig/" cmake -DCMAKE_POLICY_VERSION_MINIMUM=3.5 -H. -Bout/coverage32 -DESCARGOT_ARCH=x86 $BUILD_OPTIONS
|
||||
ninja -Cout/coverage32
|
||||
- name: Run test262 and collect coverage data
|
||||
# test262 is unstable in actions env, but coverage data will be accumulated
|
||||
continue-on-error: true
|
||||
|
|
@ -71,10 +73,11 @@ jobs:
|
|||
sudo locale-gen en_US.UTF-8
|
||||
export LANG=en_US.UTF-8
|
||||
locale
|
||||
tools/run-tests.py --arch=x86_64 --engine="$GITHUB_WORKSPACE/out/coverage/escargot" new-es regression-tests test262
|
||||
LD_LIBRARY_PATH=/usr/icu78-64/lib/ GC_FREE_SPACE_DIVISOR=1 tools/run-tests.py --arch=x86_64 --engine="$GITHUB_WORKSPACE/out/coverage64/escargot" new-es regression-tests test262 octane chakracore sunspider-js modifiedVendorTest jsc-stress v8 spidermonkey intl jetstream-only-cdjs
|
||||
LD_LIBRARY_PATH=/usr/icu78-32/lib/ GC_FREE_SPACE_DIVISOR=1 tools/run-tests.py --arch=x86 --engine="$GITHUB_WORKSPACE/out/coverage32/escargot" new-es regression-tests test262 octane chakracore sunspider-js modifiedVendorTest jsc-stress v8 spidermonkey intl jetstream-only-cdjs
|
||||
- name: Generate coverage report
|
||||
run: |
|
||||
gcovr --gcov-ignore-parse-errors --exclude-unreachable-branches --exclude-throw-branches --exclude '.*third_party/' --exclude '.*api/' -r . --xml coverage.xml
|
||||
gcovr --gcov-ignore-parse-errors --exclude-unreachable-branches --exclude-throw-branches --exclude '.*third_party/' --exclude '.*shell/' --exclude '.*api/' -r . --xml coverage.xml
|
||||
- name: Upload coverage reports to Codecov
|
||||
uses: codecov/codecov-action@v4
|
||||
with:
|
||||
|
|
|
|||
2
.github/workflows/android-release.yml
vendored
2
.github/workflows/android-release.yml
vendored
|
|
@ -38,7 +38,7 @@ jobs:
|
|||
if-no-files-found: error
|
||||
|
||||
build-android-on-macos:
|
||||
runs-on: macos-13
|
||||
runs-on: macos-15
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
|
|
|
|||
110
.github/workflows/code-review.yml
vendored
Normal file
110
.github/workflows/code-review.yml
vendored
Normal file
|
|
@ -0,0 +1,110 @@
|
|||
name: Review Bot (PR)
|
||||
|
||||
on:
|
||||
pull_request_target:
|
||||
types: [opened, synchronize, reopened]
|
||||
branches: [ "master" ]
|
||||
|
||||
concurrency:
|
||||
group: review-${{ github.event.pull_request.number }}
|
||||
cancel-in-progress: true
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: write
|
||||
|
||||
jobs:
|
||||
review:
|
||||
if: github.repository == 'Samsung/escargot'
|
||||
runs-on: [self-hosted, escargot-review]
|
||||
|
||||
steps:
|
||||
- name: Compute diff range (incremental on synchronize)
|
||||
id: shas
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
const action = context.payload.action;
|
||||
const pr = context.payload.pull_request.number;
|
||||
const baseSha = context.payload.pull_request.base.sha;
|
||||
const headSha = context.payload.pull_request.head.sha;
|
||||
|
||||
let base = baseSha;
|
||||
let skip = false;
|
||||
if (action === 'synchronize') {
|
||||
const before = context.payload.before;
|
||||
if (before) {
|
||||
base = before;
|
||||
} else {
|
||||
skip = true;
|
||||
}
|
||||
}
|
||||
|
||||
core.setOutput('base', base);
|
||||
core.setOutput('head', headSha);
|
||||
core.setOutput('pr', pr);
|
||||
core.setOutput('skip', String(skip));
|
||||
- name: Skip review (no before on synchronize)
|
||||
if: ${{ steps.shas.outputs.skip == 'true' }}
|
||||
run: |
|
||||
echo "[Review Bot] Skipping review: 'before' SHA missing on synchronize event."
|
||||
- name: Call review server
|
||||
id: call
|
||||
if: ${{ steps.shas.outputs.skip != 'true' }}
|
||||
env:
|
||||
REVIEW_SERVER: ${{ secrets.REVIEW_SERVER }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
REVIEW_SERVER="${REVIEW_SERVER}"
|
||||
BASE_SHA="${{ steps.shas.outputs.base }}"
|
||||
HEAD_SHA="${{ steps.shas.outputs.head }}"
|
||||
PR_NUMBER="${{ steps.shas.outputs.pr }}"
|
||||
|
||||
curl -sS --fail-with-body --show-error \
|
||||
--connect-timeout 10 --max-time 7200 \
|
||||
-X POST "${REVIEW_SERVER}/review" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "{\"base_sha\":\"${BASE_SHA}\",\"head_sha\":\"${HEAD_SHA}\",\"pull_request_number\":${PR_NUMBER}}" \
|
||||
-o review.json
|
||||
|
||||
echo "==== review.json ===="
|
||||
cat review.json
|
||||
|
||||
- name: Post review comments
|
||||
if: ${{ steps.shas.outputs.skip != 'true' }}
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
const fs = require('fs');
|
||||
const pr = context.payload.pull_request.number;
|
||||
let raw;
|
||||
try {
|
||||
raw = fs.readFileSync('review.json', 'utf8');
|
||||
} catch (e) {
|
||||
core.warning(`review.json not found: ${e}`);
|
||||
return;
|
||||
}
|
||||
let data;
|
||||
try {
|
||||
data = JSON.parse(raw);
|
||||
} catch (e) {
|
||||
core.warning(`Failed to parse review.json as JSON: ${e}`);
|
||||
return;
|
||||
}
|
||||
const comments = Array.isArray(data.comments) ? data.comments : [];
|
||||
|
||||
for (const c of comments) {
|
||||
await github.request('POST /repos/{owner}/{repo}/pulls/{pull_number}/comments', {
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
pull_number: pr,
|
||||
body: c.body,
|
||||
commit_id: c.commit_id,
|
||||
path: c.path,
|
||||
line: c.line,
|
||||
side: c.side,
|
||||
headers: { 'accept': 'application/vnd.github+json' }
|
||||
});
|
||||
|
||||
await new Promise(r => setTimeout(r, 200));
|
||||
}
|
||||
279
.github/workflows/es-actions.yml
vendored
279
.github/workflows/es-actions.yml
vendored
|
|
@ -27,7 +27,7 @@ jobs:
|
|||
run: tools/check_tidy.py
|
||||
|
||||
build-on-macos:
|
||||
runs-on: macos-13
|
||||
runs-on: macos-15
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
|
|
@ -38,17 +38,17 @@ jobs:
|
|||
brew install ninja icu4c
|
||||
- name: Build x64
|
||||
env:
|
||||
BUILD_OPTIONS: -DESCARGOT_WASM=ON -DESCARGOT_TEMPORAL=ON -DESCARGOT_TCO=ON -DESCARGOT_TEST=ON -DESCARGOT_OUTPUT=shell -GNinja
|
||||
BUILD_OPTIONS: -DESCARGOT_WASM=ON -DESCARGOT_TEMPORAL=ON -DESCARGOT_TCO=ON -DESCARGOT_TEST=ON -DESCARGOT_SHADOWREALM=ON -DESCARGOT_OUTPUT=shell -GNinja
|
||||
run: |
|
||||
# check cpu
|
||||
sysctl -a | grep machdep.cpu
|
||||
# add icu path to pkg_config_path
|
||||
export PKG_CONFIG_PATH="$(brew --prefix icu4c)/lib/pkgconfig"
|
||||
echo $PKG_CONFIG_PATH
|
||||
cmake -H. -Bout/debug/ -DESCARGOT_MODE=debug $BUILD_OPTIONS
|
||||
cmake -DCMAKE_POLICY_VERSION_MINIMUM=3.10 -H. -Bout/debug/ -DESCARGOT_MODE=debug $BUILD_OPTIONS
|
||||
ninja -Cout/debug/
|
||||
$RUNNER --engine="./out/debug/escargot" new-es
|
||||
cmake -H. -Bout/release/ -DESCARGOT_MODE=release $BUILD_OPTIONS
|
||||
cmake -DCMAKE_POLICY_VERSION_MINIMUM=3.10 -H. -Bout/release/ -DESCARGOT_MODE=release $BUILD_OPTIONS
|
||||
ninja -Cout/release/
|
||||
cp test/octane/*.js .
|
||||
./out/release/escargot run.js
|
||||
|
|
@ -65,23 +65,23 @@ jobs:
|
|||
brew install ninja icu4c
|
||||
- name: Build arm64
|
||||
env:
|
||||
BUILD_OPTIONS: -DESCARGOT_WASM=ON -DESCARGOT_TEMPORAL=ON -DESCARGOT_TCO=ON -DESCARGOT_TEST=ON -DESCARGOT_OUTPUT=shell -GNinja
|
||||
BUILD_OPTIONS: -DESCARGOT_WASM=ON -DESCARGOT_TEMPORAL=ON -DESCARGOT_TCO=ON -DESCARGOT_TEST=ON -DESCARGOT_SHADOWREALM=ON -DESCARGOT_OUTPUT=shell -GNinja
|
||||
run: |
|
||||
# check cpu
|
||||
sysctl -a | grep machdep.cpu
|
||||
# add icu path to pkg_config_path
|
||||
export PKG_CONFIG_PATH="$(brew --prefix icu4c)/lib/pkgconfig"
|
||||
echo $PKG_CONFIG_PATH
|
||||
cmake -DCMAKE_POLICY_VERSION_MINIMUM=3.5 -H. -Bout/debug/ -DESCARGOT_MODE=debug $BUILD_OPTIONS
|
||||
cmake -DCMAKE_POLICY_VERSION_MINIMUM=3.10 -H. -Bout/debug/ -DESCARGOT_MODE=debug $BUILD_OPTIONS
|
||||
ninja -Cout/debug/
|
||||
$RUNNER --engine="./out/debug/escargot" new-es
|
||||
cmake -DCMAKE_POLICY_VERSION_MINIMUM=3.5 -H. -Bout/release/ -DESCARGOT_MODE=release $BUILD_OPTIONS
|
||||
cmake -DCMAKE_POLICY_VERSION_MINIMUM=3.10 -H. -Bout/release/ -DESCARGOT_MODE=release $BUILD_OPTIONS
|
||||
ninja -Cout/release/
|
||||
cp test/octane/*.js .
|
||||
./out/release/escargot run.js
|
||||
|
||||
build-test-on-android:
|
||||
runs-on: macos-13
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
arch: [x86, x86_64]
|
||||
|
|
@ -90,9 +90,11 @@ jobs:
|
|||
- uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: true
|
||||
- name: Install Packages
|
||||
- name: Enable KVM
|
||||
run: |
|
||||
brew update
|
||||
echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules
|
||||
sudo udevadm control --reload-rules
|
||||
sudo udevadm trigger --name-match=kvm
|
||||
- name: Set up JDK
|
||||
uses: actions/setup-java@v4.1.0
|
||||
with:
|
||||
|
|
@ -110,29 +112,19 @@ jobs:
|
|||
disable-animations: true
|
||||
script: cd build/android/;./gradlew connectedDebugAndroidTest -DESCARGOT_BUILD_TLS_ACCESS_BY_PTHREAD_KEY=ON
|
||||
|
||||
build-by-clang:
|
||||
runs-on: ubuntu-latest
|
||||
build-test-tizen:
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
- name: Install Packages
|
||||
- name: prepare deb sources for GBS
|
||||
run: echo "deb [trusted=yes] http://download.tizen.org/tools/latest-release/Ubuntu_24.04/ /" | sudo tee /etc/apt/sources.list.d/tizen.list
|
||||
- name: install GBS
|
||||
run: sudo apt-get update && sudo apt-get install -y gbs
|
||||
- name: build
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y ninja-build gcc-multilib g++-multilib libicu-dev
|
||||
- name: Build x86
|
||||
env:
|
||||
BUILD_OPTIONS: -DCMAKE_SYSTEM_NAME=Linux -DCMAKE_SYSTEM_PROCESSOR=x86 -DESCARGOT_MODE=debug -DESCARGOT_WASM=ON -DESCARGOT_TEMPORAL=ON -DESCARGOT_TCO=ON -DESCARGOT_TLS_ACCESS_BY_ADDRESS=ON -DESCARGOT_TEST=ON -DESCARGOT_OUTPUT=shell -GNinja
|
||||
run: |
|
||||
CC=clang CXX=clang++ cmake -DCMAKE_POLICY_VERSION_MINIMUM=3.5 -H. -Bout/clang/x86 $BUILD_OPTIONS
|
||||
ninja -Cout/clang/x86
|
||||
- name: Build x64
|
||||
env:
|
||||
BUILD_OPTIONS: -DESCARGOT_MODE=debug -DESCARGOT_WASM=ON -DESCARGOT_TEMPORAL=ON -DESCARGOT_TCO=ON -DESCARGOT_TLS_ACCESS_BY_ADDRESS=ON -DESCARGOT_TEST=ON -DESCARGOT_OUTPUT=shell -GNinja
|
||||
run: |
|
||||
CC=clang CXX=clang++ cmake -DCMAKE_POLICY_VERSION_MINIMUM=3.5 -H. -Bout/clang/x64 $BUILD_OPTIONS
|
||||
ninja -Cout/clang/x64
|
||||
$RUNNER --engine="./out/clang/x64/escargot" new-es
|
||||
gbs -c .github/workflows/gbs.conf build -A armv7l -P profile.tizen --incremental --define "enable_shell 1"
|
||||
|
||||
test-on-windows-clang-cl:
|
||||
runs-on: windows-2022
|
||||
|
|
@ -166,10 +158,24 @@ jobs:
|
|||
- uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: '3.11'
|
||||
- name: Install msvc redist package
|
||||
- name: Download and Install Visual C++ Redistributable
|
||||
shell: powershell
|
||||
run: |
|
||||
(new-object System.Net.WebClient).DownloadFile('https://github.com/abbodi1406/vcredist/releases/download/v0.73.0/VisualCppRedist_AIO_x86_x64.exe','VisualCppRedist_AIO_x86_x64.exe')
|
||||
.\VisualCppRedist_AIO_x86_x64.exe /y
|
||||
$vcRedistUrl64 = "https://aka.ms/vs/17/release/vc_redist.x64.exe" # Or the appropriate URL for your target architecture/version
|
||||
$vcRedistPath64 = "$env:TEMP\vc_redist.x64.exe"
|
||||
$vcRedistUrl32 = "https://aka.ms/vs/17/release/vc_redist.x86.exe" # Or the appropriate URL for your target architecture/version
|
||||
$vcRedistPath32 = "$env:TEMP\vc_redist.x86.exe"
|
||||
|
||||
Write-Host "Downloading Visual C++ Redistributable from $vcRedistUrl64"
|
||||
Invoke-WebRequest -Uri $vcRedistUrl64 -OutFile $vcRedistPath64
|
||||
|
||||
Write-Host "Downloading Visual C++ Redistributable from $vcRedistUrl32"
|
||||
Invoke-WebRequest -Uri $vcRedistUrl32 -OutFile $vcRedistPath32
|
||||
|
||||
Write-Host "Installing Visual C++ Redistributable silently"
|
||||
Start-Process -FilePath $vcRedistPath64 -ArgumentList "/install /quiet /norestart" -Wait
|
||||
Start-Process -FilePath $vcRedistPath32 -ArgumentList "/install /quiet /norestart" -Wait
|
||||
Write-Host "Visual C++ Redistributable installation complete."
|
||||
- uses: ilammy/msvc-dev-cmd@v1.13.0
|
||||
with:
|
||||
arch: ${{ matrix.arch.cpu }}
|
||||
|
|
@ -180,7 +186,7 @@ jobs:
|
|||
platform: ${{ matrix.arch.cpu }}
|
||||
- name: Build ${{ matrix.arch.cpu }} Release
|
||||
run: |
|
||||
CMake -DCMAKE_SYSTEM_NAME=Windows -DCMAKE_SYSTEM_VERSION:STRING="10.0" -DCMAKE_SYSTEM_PROCESSOR=${{ matrix.arch.cpu }} -Bout/ -DESCARGOT_OUTPUT=shell -DESCARGOT_LIBICU_SUPPORT=ON -DESCARGOT_THREADING=ON -DESCARGOT_TCO=ON -DESCARGOT_TEST=ON -G Ninja -DCMAKE_C_COMPILER=clang-cl -DCMAKE_CXX_COMPILER=clang-cl -DCMAKE_BUILD_TYPE=release -DCMAKE_C_FLAGS="${{ matrix.arch.flag }}" -DCMAKE_CXX_FLAGS="${{ matrix.arch.flag }}"
|
||||
CMake -DCMAKE_SYSTEM_NAME=Windows -DCMAKE_SYSTEM_VERSION:STRING="10.0" -DCMAKE_SYSTEM_PROCESSOR=${{ matrix.arch.cpu }} -Bout/ -DESCARGOT_OUTPUT=shell -DESCARGOT_LIBICU_SUPPORT=ON -DESCARGOT_THREADING=ON -DESCARGOT_TCO=ON -DESCARGOT_TEST=ON -DESCARGOT_SHADOWREALM=ON -G Ninja -DCMAKE_C_COMPILER=clang-cl -DCMAKE_CXX_COMPILER=clang-cl -DCMAKE_BUILD_TYPE=release -DCMAKE_C_FLAGS="${{ matrix.arch.flag }}" -DCMAKE_CXX_FLAGS="${{ matrix.arch.flag }}"
|
||||
CMake --build out/ --config Release
|
||||
- name: Run octane
|
||||
run: |
|
||||
|
|
@ -223,17 +229,31 @@ jobs:
|
|||
- uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: '3.11'
|
||||
- name: Install msvc redist package
|
||||
- name: Download and Install Visual C++ Redistributable
|
||||
shell: powershell
|
||||
run: |
|
||||
(new-object System.Net.WebClient).DownloadFile('https://github.com/abbodi1406/vcredist/releases/download/v0.73.0/VisualCppRedist_AIO_x86_x64.exe','VisualCppRedist_AIO_x86_x64.exe')
|
||||
.\VisualCppRedist_AIO_x86_x64.exe /y
|
||||
$vcRedistUrl64 = "https://aka.ms/vs/17/release/vc_redist.x64.exe" # Or the appropriate URL for your target architecture/version
|
||||
$vcRedistPath64 = "$env:TEMP\vc_redist.x64.exe"
|
||||
$vcRedistUrl32 = "https://aka.ms/vs/17/release/vc_redist.x86.exe" # Or the appropriate URL for your target architecture/version
|
||||
$vcRedistPath32 = "$env:TEMP\vc_redist.x86.exe"
|
||||
|
||||
Write-Host "Downloading Visual C++ Redistributable from $vcRedistUrl64"
|
||||
Invoke-WebRequest -Uri $vcRedistUrl64 -OutFile $vcRedistPath64
|
||||
|
||||
Write-Host "Downloading Visual C++ Redistributable from $vcRedistUrl32"
|
||||
Invoke-WebRequest -Uri $vcRedistUrl32 -OutFile $vcRedistPath32
|
||||
|
||||
Write-Host "Installing Visual C++ Redistributable silently"
|
||||
Start-Process -FilePath $vcRedistPath64 -ArgumentList "/install /quiet /norestart" -Wait
|
||||
Start-Process -FilePath $vcRedistPath32 -ArgumentList "/install /quiet /norestart" -Wait
|
||||
Write-Host "Visual C++ Redistributable installation complete."
|
||||
- uses: ilammy/msvc-dev-cmd@v1.13.0
|
||||
with:
|
||||
arch: ${{ matrix.arch }}
|
||||
sdk: "10.0.26100.0"
|
||||
- name: Build ${{ matrix.arch }} Release
|
||||
run: |
|
||||
CMake -DCMAKE_SYSTEM_NAME=Windows -DCMAKE_SYSTEM_VERSION:STRING="10.0" -DCMAKE_SYSTEM_PROCESSOR=${{ matrix.arch }} -DESCARGOT_ARCH=${{ matrix.arch }} -Bout/ -DESCARGOT_OUTPUT=shell -DESCARGOT_LIBICU_SUPPORT=ON -DESCARGOT_WASM=ON -DESCARGOT_THREADING=ON -DESCARGOT_TCO=ON -DESCARGOT_TEST=ON -G Ninja -DCMAKE_C_COMPILER=cl -DCMAKE_CXX_COMPILER=cl -DCMAKE_BUILD_TYPE=release
|
||||
CMake -DCMAKE_SYSTEM_NAME=Windows -DCMAKE_SYSTEM_VERSION:STRING="10.0" -DCMAKE_SYSTEM_PROCESSOR=${{ matrix.arch }} -DESCARGOT_ARCH=${{ matrix.arch }} -Bout/ -DESCARGOT_OUTPUT=shell -DESCARGOT_LIBICU_SUPPORT=ON -DESCARGOT_WASM=ON -DESCARGOT_THREADING=ON -DESCARGOT_TCO=ON -DESCARGOT_TEST=ON -DESCARGOT_SHADOWREALM=ON -G Ninja -DCMAKE_C_COMPILER=cl -DCMAKE_CXX_COMPILER=cl -DCMAKE_BUILD_TYPE=release
|
||||
CMake --build out/ --config Release
|
||||
# windows internal ICU doesn't support Temporal and intl402 well
|
||||
# github action windows runner only have 2 CPUs. that's why I disable Atomics(timeout occured with some tests)
|
||||
|
|
@ -275,7 +295,7 @@ jobs:
|
|||
uwp: true
|
||||
- name: Build x64 UWP Release
|
||||
run: |
|
||||
CMake -G "Visual Studio 17 2022" -DCMAKE_SYSTEM_NAME=WindowsStore -DCMAKE_SYSTEM_VERSION:STRING="10.0" -DCMAKE_SYSTEM_PROCESSOR=x64 -Bout/win64_release_uwp/ -DESCARGOT_OUTPUT=shell -DESCARGOT_LIBICU_SUPPORT=ON -DESCARGOT_TEST=ON
|
||||
CMake -G "Visual Studio 17 2022" -DCMAKE_SYSTEM_NAME=WindowsStore -DCMAKE_SYSTEM_VERSION:STRING="10.0" -DCMAKE_SYSTEM_PROCESSOR=x64 -Bout/win64_release_uwp/ -DESCARGOT_OUTPUT=shell -DESCARGOT_LIBICU_SUPPORT=ON -DESCARGOT_TEST=ON -DESCARGOT_SHADOWREALM=ON
|
||||
CMake --build out\win64_release_uwp --config Release
|
||||
shell: cmd
|
||||
- uses: ilammy/msvc-dev-cmd@v1.13.0
|
||||
|
|
@ -284,7 +304,7 @@ jobs:
|
|||
sdk: "10.0.26100.0"
|
||||
- name: Build x86 DLL Release
|
||||
run: |
|
||||
CMake -DCMAKE_SYSTEM_NAME=Windows -DCMAKE_SYSTEM_VERSION:STRING="10.0" -DCMAKE_SYSTEM_PROCESSOR=x86 -Bout/win32_release_shared/ -DESCARGOT_OUTPUT=shared_lib -DESCARGOT_LIBICU_SUPPORT=ON -DESCARGOT_THREADING=ON -DESCARGOT_TCO=ON -DESCARGOT_TEST=ON -G Ninja -DCMAKE_C_COMPILER=cl -DCMAKE_CXX_COMPILER=cl -DCMAKE_BUILD_TYPE=release
|
||||
CMake -DCMAKE_SYSTEM_NAME=Windows -DCMAKE_SYSTEM_VERSION:STRING="10.0" -DCMAKE_SYSTEM_PROCESSOR=x86 -Bout/win32_release_shared/ -DESCARGOT_OUTPUT=shared_lib -DESCARGOT_LIBICU_SUPPORT=ON -DESCARGOT_THREADING=ON -DESCARGOT_TCO=ON -DESCARGOT_TEST=ON -DESCARGOT_SHADOWREALM=ON -G Ninja -DCMAKE_C_COMPILER=cl -DCMAKE_CXX_COMPILER=cl -DCMAKE_BUILD_TYPE=release
|
||||
CMake --build out/win32_release_shared --config Release
|
||||
shell: cmd
|
||||
- if: ${{ failure() }}
|
||||
|
|
@ -292,7 +312,7 @@ jobs:
|
|||
timeout-minutes: 15
|
||||
|
||||
build-test-on-x86-release:
|
||||
runs-on: ubuntu-22.04
|
||||
runs-on: ubuntu-24.04
|
||||
strategy:
|
||||
matrix:
|
||||
tc: ['new-es octane', 'v8 chakracore spidermonkey', 'jetstream-only-simple-parallel-1', 'jetstream-only-simple-parallel-2 jsc-stress', 'jetstream-only-simple-parallel-3 jetstream-only-cdjs']
|
||||
|
|
@ -303,34 +323,45 @@ jobs:
|
|||
- name: Install Packages
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y ninja-build gcc-multilib g++-multilib python2
|
||||
# set python2
|
||||
sudo update-alternatives --install /usr/bin/python python /usr/bin/python2 1
|
||||
- name: Install ICU
|
||||
sudo apt-get install -y ninja-build gcc-multilib g++-multilib make g++ pkg-config automake libtool git build-essential checkinstall libncurses-dev libssl-dev libsqlite3-dev tk-dev libgdbm-dev libc6-dev libbz2-dev libffi-dev
|
||||
- name: Build python2
|
||||
run: |
|
||||
wget http://mirrors.kernel.org/ubuntu/pool/main/i/icu/libicu-dev_70.1-2ubuntu1_i386.deb
|
||||
wget http://mirrors.kernel.org/ubuntu/pool/main/i/icu/libicu70_70.1-2ubuntu1_i386.deb
|
||||
dpkg -X libicu-dev_70.1-2ubuntu1_i386.deb $GITHUB_WORKSPACE/icu32
|
||||
dpkg -X libicu70_70.1-2ubuntu1_i386.deb $GITHUB_WORKSPACE/icu32
|
||||
mkdir $GITHUB_WORKSPACE/python2-build/
|
||||
cd $GITHUB_WORKSPACE/python2-build/
|
||||
wget https://www.python.org/ftp/python/2.7.18/Python-2.7.18.tgz
|
||||
tar xzf Python-2.7.18.tgz
|
||||
cd Python-2.7.18
|
||||
./configure --prefix=/usr/local/python2.7
|
||||
make -j8
|
||||
sudo make install
|
||||
sudo update-alternatives --install /usr/bin/python python /usr/local/python2.7/bin/python2.7 1
|
||||
- name: Build ICU
|
||||
run: |
|
||||
git clone --depth 1 --single-branch -b release-78.1 https://github.com/unicode-org/icu.git $GITHUB_WORKSPACE/icu-build/
|
||||
cd $GITHUB_WORKSPACE/icu-build/icu4c/source
|
||||
LDFLAGS="-m32 -Wl,-rpath=$GITHUB_WORKSPACE/icu32/lib/" CFLAGS="-m32" CXXFLAGS="-m32" ./runConfigureICU Linux/gcc --prefix="$GITHUB_WORKSPACE/icu32/"
|
||||
make -j8
|
||||
make install
|
||||
ls $GITHUB_WORKSPACE/icu32/lib/
|
||||
- name: Build x86
|
||||
env:
|
||||
BUILD_OPTIONS: -DCMAKE_SYSTEM_NAME=Linux -DCMAKE_SYSTEM_PROCESSOR=x86 -DESCARGOT_THREADING=ON -DESCARGOT_TEMPORAL=ON -DESCARGOT_TCO=ON -DESCARGOT_TLS_ACCESS_BY_ADDRESS=ON -DESCARGOT_TEST=ON -DESCARGOT_OUTPUT=shell -GNinja
|
||||
BUILD_OPTIONS: -DCMAKE_SYSTEM_NAME=Linux -DCMAKE_SYSTEM_PROCESSOR=x86 -DESCARGOT_THREADING=ON -DESCARGOT_TEMPORAL=ON -DESCARGOT_TCO=ON -DESCARGOT_TLS_ACCESS_BY_ADDRESS=ON -DESCARGOT_TEST=ON -DESCARGOT_SHADOWREALM=ON -DESCARGOT_OUTPUT=shell -GNinja
|
||||
run: |
|
||||
export CXXFLAGS="-I$GITHUB_WORKSPACE/icu32/usr/include"
|
||||
export LDFLAGS="-L$GITHUB_WORKSPACE/icu32/usr/lib/i386-linux-gnu -Wl,-rpath=$GITHUB_WORKSPACE/icu32/usr/lib/i386-linux-gnu"
|
||||
export PKG_CONFIG_PATH=$GITHUB_WORKSPACE/icu32/usr/lib/i386-linux-gnu/pkgconfig
|
||||
export CXXFLAGS="-I$GITHUB_WORKSPACE/icu32/include"
|
||||
export LDFLAGS="-L$GITHUB_WORKSPACE/icu32/lib/ -Wl,-rpath=$GITHUB_WORKSPACE/icu32/lib/"
|
||||
export PKG_CONFIG_PATH=$GITHUB_WORKSPACE/icu32/lib/pkgconfig
|
||||
cmake -DCMAKE_POLICY_VERSION_MINIMUM=3.5 -H. -Bout/release/x86 $BUILD_OPTIONS
|
||||
ninja -Cout/release/x86
|
||||
- name: Run release-x86 test
|
||||
env:
|
||||
GC_FREE_SPACE_DIVISOR: 1
|
||||
run: LD_LIBRARY_PATH=$GITHUB_WORKSPACE/icu32/usr/lib/i386-linux-gnu $RUNNER --arch=x86 --engine="$GITHUB_WORKSPACE/out/release/x86/escargot" ${{ matrix.tc }}
|
||||
run: LD_LIBRARY_PATH=$GITHUB_WORKSPACE/icu32/lib $RUNNER --arch=x86 --engine="$GITHUB_WORKSPACE/out/release/x86/escargot" ${{ matrix.tc }}
|
||||
- if: ${{ failure() }}
|
||||
uses: mxschmitt/action-tmate@v3
|
||||
timeout-minutes: 15
|
||||
|
||||
build-test-on-x64-release:
|
||||
runs-on: ubuntu-22.04
|
||||
runs-on: ubuntu-24.04
|
||||
strategy:
|
||||
matrix:
|
||||
tc: ['octane v8 web-tooling-benchmark', 'chakracore spidermonkey new-es']
|
||||
|
|
@ -346,18 +377,39 @@ jobs:
|
|||
- name: Install Packages
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y ninja-build libicu-dev python2
|
||||
sudo update-alternatives --install /usr/bin/python python /usr/bin/python2 1
|
||||
sudo apt-get install -y ninja-build g++ pkg-config automake libtool git build-essential checkinstall libncurses-dev libssl-dev libsqlite3-dev tk-dev libgdbm-dev libc6-dev libbz2-dev libffi-dev
|
||||
- name: Build ICU(64)
|
||||
run: |
|
||||
git clone --depth 1 --single-branch -b release-78.1 https://github.com/unicode-org/icu.git $GITHUB_WORKSPACE/icu64-build/
|
||||
cd $GITHUB_WORKSPACE/icu64-build/icu4c/source
|
||||
LDFLAGS="-Wl,-rpath=$GITHUB_WORKSPACE/icu64/lib/" ./runConfigureICU Linux/gcc --prefix="$GITHUB_WORKSPACE/icu64/"
|
||||
make -j8
|
||||
make install
|
||||
- name: Build python2
|
||||
run: |
|
||||
mkdir $GITHUB_WORKSPACE/python2-build/
|
||||
cd $GITHUB_WORKSPACE/python2-build/
|
||||
wget https://www.python.org/ftp/python/2.7.18/Python-2.7.18.tgz
|
||||
tar xzf Python-2.7.18.tgz
|
||||
cd Python-2.7.18
|
||||
./configure --prefix=/usr/local/python2.7
|
||||
make -j8
|
||||
sudo make install
|
||||
sudo update-alternatives --install /usr/bin/python python /usr/local/python2.7/bin/python2.7 1
|
||||
- name: Build x64
|
||||
env:
|
||||
BUILD_OPTIONS: -DESCARGOT_TEMPORAL=ON -DESCARGOT_TCO=ON -DESCARGOT_TLS_ACCESS_BY_ADDRESS=ON -DESCARGOT_TEST=ON -DESCARGOT_OUTPUT=shell -GNinja
|
||||
BUILD_OPTIONS: -DESCARGOT_TEMPORAL=ON -DESCARGOT_TCO=ON -DESCARGOT_TLS_ACCESS_BY_ADDRESS=ON -DESCARGOT_TEST=ON -DESCARGOT_SHADOWREALM=ON -DESCARGOT_OUTPUT=shell -GNinja
|
||||
run: |
|
||||
export CXXFLAGS="-I$GITHUB_WORKSPACE/icu64/include"
|
||||
export LDFLAGS="-L$GITHUB_WORKSPACE/icu64/lib -Wl,-rpath=$GITHUB_WORKSPACE/icu64/lib"
|
||||
export PKG_CONFIG_PATH=$GITHUB_WORKSPACE/icu64/lib/pkgconfig
|
||||
cmake -DCMAKE_POLICY_VERSION_MINIMUM=3.5 -H. -Bout/release/x64 $BUILD_OPTIONS ${{ matrix.build_opt }}
|
||||
ninja -Cout/release/x64
|
||||
- name: Run release-x64 test
|
||||
env:
|
||||
GC_FREE_SPACE_DIVISOR: 1
|
||||
run: |
|
||||
export LD_LIBRARY_PATH=$GITHUB_WORKSPACE/icu64/lib
|
||||
# set locale
|
||||
sudo locale-gen en_US.UTF-8
|
||||
export LANG=en_US.UTF-8
|
||||
|
|
@ -368,22 +420,33 @@ jobs:
|
|||
timeout-minutes: 15
|
||||
|
||||
build-test-on-x86-x64-debug:
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: true
|
||||
- name: Install Packages
|
||||
run: |
|
||||
# for i386 ICU
|
||||
sudo dpkg --add-architecture i386
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y ninja-build gcc-multilib g++-multilib
|
||||
sudo apt-get install -y libicu-dev:i386 # install i386 ICU
|
||||
sudo apt-get install -y ninja-build gcc-multilib g++-multilib make g++ pkg-config automake libtool git build-essential checkinstall libncurses-dev libssl-dev libsqlite3-dev tk-dev libgdbm-dev libc6-dev libbz2-dev libffi-dev
|
||||
- name: Build ICU(32)
|
||||
run: |
|
||||
git clone --depth 1 --single-branch -b release-78.1 https://github.com/unicode-org/icu.git $GITHUB_WORKSPACE/icu32-build/
|
||||
cd $GITHUB_WORKSPACE/icu32-build/icu4c/source
|
||||
LDFLAGS="-m32 -Wl,-rpath=$GITHUB_WORKSPACE/icu32/lib/" CFLAGS="-m32" CXXFLAGS="-m32" ./runConfigureICU Linux/gcc --prefix="$GITHUB_WORKSPACE/icu32/"
|
||||
make -j8
|
||||
make install
|
||||
- name: Build ICU(64)
|
||||
run: |
|
||||
git clone --depth 1 --single-branch -b release-78.1 https://github.com/unicode-org/icu.git $GITHUB_WORKSPACE/icu64-build/
|
||||
cd $GITHUB_WORKSPACE/icu64-build/icu4c/source
|
||||
LDFLAGS="-Wl,-rpath=$GITHUB_WORKSPACE/icu64/lib/" ./runConfigureICU Linux/gcc --prefix="$GITHUB_WORKSPACE/icu64/"
|
||||
make -j8
|
||||
make install
|
||||
- name: Build x86/x64
|
||||
env:
|
||||
BUILD_OPTIONS_X86: -DCMAKE_SYSTEM_NAME=Linux -DCMAKE_SYSTEM_PROCESSOR=x86 -DESCARGOT_MODE=debug -DESCARGOT_TEMPORAL=ON -DESCARGOT_TCO=ON -DESCARGOT_TLS_ACCESS_BY_ADDRESS=ON -DESCARGOT_TEST=ON -DESCARGOT_OUTPUT=shell -GNinja
|
||||
BUILD_OPTIONS_X64: -DESCARGOT_MODE=debug -DESCARGOT_TEMPORAL=ON -DESCARGOT_TCO=ON -DESCARGOT_TLS_ACCESS_BY_ADDRESS=ON -DESCARGOT_TEST=ON -DESCARGOT_OUTPUT=shell -GNinja
|
||||
BUILD_OPTIONS_X86: -DCMAKE_SYSTEM_NAME=Linux -DCMAKE_SYSTEM_PROCESSOR=x86 -DESCARGOT_MODE=debug -DESCARGOT_TEMPORAL=ON -DESCARGOT_TCO=ON -DESCARGOT_TLS_ACCESS_BY_ADDRESS=ON -DESCARGOT_TEST=ON -DESCARGOT_SHADOWREALM=ON -DESCARGOT_OUTPUT=shell -GNinja
|
||||
BUILD_OPTIONS_X64: -DESCARGOT_MODE=debug -DESCARGOT_TEMPORAL=ON -DESCARGOT_TCO=ON -DESCARGOT_TLS_ACCESS_BY_ADDRESS=ON -DESCARGOT_TEST=ON -DESCARGOT_SHADOWREALM=ON -DESCARGOT_OUTPUT=shell -GNinja
|
||||
run: |
|
||||
cmake -DCMAKE_POLICY_VERSION_MINIMUM=3.5 -H. -Bout/debug/x86 $BUILD_OPTIONS_X86
|
||||
cmake -DCMAKE_POLICY_VERSION_MINIMUM=3.5 -H. -Bout/debug/x64 $BUILD_OPTIONS_X64
|
||||
|
|
@ -391,11 +454,14 @@ jobs:
|
|||
ninja -Cout/debug/x64
|
||||
- name: Run debug-mode test
|
||||
run: |
|
||||
export LD_LIBRARY_PATH=$GITHUB_WORKSPACE/icu32/lib
|
||||
$RUNNER --arch=x86 --engine="$GITHUB_WORKSPACE/out/debug/x86/escargot" modifiedVendorTest regression-tests new-es intl sunspider-js
|
||||
export LD_LIBRARY_PATH=$GITHUB_WORKSPACE/icu64/lib
|
||||
$RUNNER --arch=x86_64 --engine="$GITHUB_WORKSPACE/out/debug/x64/escargot" modifiedVendorTest regression-tests new-es intl sunspider-js
|
||||
|
||||
build-test-on-self-hosted-linux:
|
||||
runs-on: [self-hosted, linux, x64]
|
||||
if: github.repository == 'Samsung/escargot'
|
||||
runs-on: [self-hosted, linux, x64, test]
|
||||
timeout-minutes: 60
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
|
@ -403,26 +469,36 @@ jobs:
|
|||
submodules: true
|
||||
- name: Build
|
||||
env:
|
||||
BUILD_OPTIONS: -DESCARGOT_THREADING=ON -DESCARGOT_TEMPORAL=ON -DESCARGOT_TCO=ON -DESCARGOT_TLS_ACCESS_BY_ADDRESS=ON -DESCARGOT_TEST=ON -DESCARGOT_OUTPUT=shell -GNinja
|
||||
BUILD_OPTIONS: -DESCARGOT_THREADING=ON -DESCARGOT_TEMPORAL=ON -DESCARGOT_TCO=ON -DESCARGOT_TLS_ACCESS_BY_ADDRESS=ON -DESCARGOT_TEST=ON -DESCARGOT_SHADOWREALM=ON -DESCARGOT_OUTPUT=shell -GNinja
|
||||
run: |
|
||||
LDFLAGS=" -L/usr/icu77-32/lib/ -Wl,-rpath=/usr/icu77-32/lib/" PKG_CONFIG_PATH="/usr/icu77-32/lib/pkgconfig/" cmake -DCMAKE_POLICY_VERSION_MINIMUM=3.5 -H./ -Bbuild/out_linux -DESCARGOT_ARCH=x86 -DESCARGOT_MODE=debug -DESCARGOT_TCO_DEBUG=ON $BUILD_OPTIONS
|
||||
LDFLAGS=" -L/usr/icu77-32/lib/ -Wl,-rpath=/usr/icu77-32/lib/" PKG_CONFIG_PATH="/usr/icu77-32/lib/pkgconfig/" cmake -DCMAKE_POLICY_VERSION_MINIMUM=3.5 -H./ -Bbuild/out_linux_release -DESCARGOT_ARCH=x86 $BUILD_OPTIONS
|
||||
LDFLAGS=" -L/usr/icu78-32/lib/ -Wl,-rpath=/usr/icu78-32/lib/" PKG_CONFIG_PATH="/usr/icu78-32/lib/pkgconfig/" cmake -DCMAKE_C_COMPILER=gcc -DCMAKE_CXX_COMPILER=g++ -DCMAKE_POLICY_VERSION_MINIMUM=3.5 -H./ -Bbuild/out_linux -DESCARGOT_ARCH=x86 -DESCARGOT_MODE=debug -DESCARGOT_TCO_DEBUG=ON $BUILD_OPTIONS
|
||||
LDFLAGS=" -L/usr/icu78-32/lib/ -Wl,-rpath=/usr/icu78-32/lib/" PKG_CONFIG_PATH="/usr/icu78-32/lib/pkgconfig/" cmake -DCMAKE_C_COMPILER=gcc -DCMAKE_CXX_COMPILER=g++ -DCMAKE_POLICY_VERSION_MINIMUM=3.5 -H./ -Bbuild/out_linux_release -DESCARGOT_ARCH=x86 $BUILD_OPTIONS
|
||||
LDFLAGS=" -L/usr/icu78-32/lib/ -Wl,-rpath=/usr/icu78-32/lib/" PKG_CONFIG_PATH="/usr/icu78-32/lib/pkgconfig/" cmake -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ -DCMAKE_POLICY_VERSION_MINIMUM=3.5 -H./ -Bbuild/out_linux_release_clang -DESCARGOT_ARCH=x86 $BUILD_OPTIONS
|
||||
gcc -shared -m32 -fPIC -o backtrace-hooking-32.so tools/test/test262/backtrace-hooking.c
|
||||
LDFLAGS=" -L/usr/icu77-64/lib/ -Wl,-rpath=/usr/icu77-64/lib/" PKG_CONFIG_PATH="/usr/icu77-64/lib/pkgconfig/" cmake -DCMAKE_POLICY_VERSION_MINIMUM=3.5 -H./ -Bbuild/out_linux64 -DESCARGOT_ARCH=x64 -DESCARGOT_MODE=debug -DESCARGOT_TCO_DEBUG=ON $BUILD_OPTIONS
|
||||
LDFLAGS=" -L/usr/icu77-64/lib/ -Wl,-rpath=/usr/icu77-64/lib/" PKG_CONFIG_PATH="/usr/icu77-64/lib/pkgconfig/" cmake -DCMAKE_POLICY_VERSION_MINIMUM=3.5 -H./ -Bbuild/out_linux64_release -DESCARGOT_ARCH=x64 -DESCARGOT_MODE=release $BUILD_OPTIONS
|
||||
LDFLAGS=" -L/usr/icu78-64/lib/ -Wl,-rpath=/usr/icu78-64/lib/" PKG_CONFIG_PATH="/usr/icu78-64/lib/pkgconfig/" cmake -DCMAKE_C_COMPILER=gcc -DCMAKE_CXX_COMPILER=g++ -DCMAKE_POLICY_VERSION_MINIMUM=3.5 -H./ -Bbuild/out_linux64 -DESCARGOT_ARCH=x64 -DESCARGOT_MODE=debug -DESCARGOT_TCO_DEBUG=ON $BUILD_OPTIONS
|
||||
LDFLAGS=" -L/usr/icu78-64/lib/ -Wl,-rpath=/usr/icu78-64/lib/" PKG_CONFIG_PATH="/usr/icu78-64/lib/pkgconfig/" cmake -DCMAKE_C_COMPILER=gcc -DCMAKE_CXX_COMPILER=g++ -DCMAKE_POLICY_VERSION_MINIMUM=3.5 -H./ -Bbuild/out_linux64_release -DESCARGOT_ARCH=x64 -DESCARGOT_MODE=release $BUILD_OPTIONS
|
||||
LDFLAGS=" -L/usr/icu78-64/lib/ -Wl,-rpath=/usr/icu78-64/lib/" PKG_CONFIG_PATH="/usr/icu78-64/lib/pkgconfig/" cmake -DCMAKE_C_COMPILER=clang -DCMAKE_CXX_COMPILER=clang++ -DCMAKE_POLICY_VERSION_MINIMUM=3.5 -H./ -Bbuild/out_linux64_release_clang -DESCARGOT_ARCH=x64 -DESCARGOT_MODE=release $BUILD_OPTIONS
|
||||
cmake --build build/out_linux/
|
||||
cmake --build build/out_linux64/
|
||||
cmake --build build/out_linux_release/
|
||||
cmake --build build/out_linux_release_clang/
|
||||
cmake --build build/out_linux64_release/
|
||||
cmake --build build/out_linux64_release_clang/
|
||||
- name: Test
|
||||
run: |
|
||||
LD_LIBRARY_PATH=/usr/icu77-32/lib/ GC_FREE_SPACE_DIVISOR=1 $RUNNER --arch=x86 --engine="${{ github.workspace }}/build/out_linux_release/escargot" test262
|
||||
LD_LIBRARY_PATH=/usr/icu77-32/lib/ GC_FREE_SPACE_DIVISOR=1 ESCARGOT_LD_PRELOAD=${{ github.workspace }}/backtrace-hooking-32.so $RUNNER --arch=x86 --engine="${{ github.workspace }}/build/out_linux/escargot" test262
|
||||
LD_LIBRARY_PATH=/usr/icu77-64/lib/ GC_FREE_SPACE_DIVISOR=1 $RUNNER --arch=x86_64 --engine="${{ github.workspace }}/build/out_linux64_release/escargot" test262
|
||||
LD_LIBRARY_PATH=/usr/icu77-64/lib/ python tools/kangax/run-kangax.py --engine="${{ github.workspace }}/build/out_linux64/escargot"
|
||||
LD_LIBRARY_PATH=/usr/icu78-32/lib/ GC_FREE_SPACE_DIVISOR=1 $RUNNER --arch=x86 --engine="${{ github.workspace }}/build/out_linux_release/escargot" test262
|
||||
LD_LIBRARY_PATH=/usr/icu78-32/lib/ GC_FREE_SPACE_DIVISOR=1 $RUNNER --arch=x86 --engine="${{ github.workspace }}/build/out_linux_release_clang/escargot" test262
|
||||
LD_LIBRARY_PATH=/usr/icu78-32/lib/ GC_FREE_SPACE_DIVISOR=1 ESCARGOT_LD_PRELOAD=${{ github.workspace }}/backtrace-hooking-32.so $RUNNER --arch=x86 --engine="${{ github.workspace }}/build/out_linux/escargot" test262
|
||||
LD_LIBRARY_PATH=/usr/icu78-64/lib/ GC_FREE_SPACE_DIVISOR=1 $RUNNER --arch=x86_64 --engine="${{ github.workspace }}/build/out_linux64_release/escargot" test262
|
||||
LD_LIBRARY_PATH=/usr/icu78-64/lib/ GC_FREE_SPACE_DIVISOR=1 $RUNNER --arch=x86_64 --engine="${{ github.workspace }}/build/out_linux64_release_clang/escargot" test262
|
||||
LD_LIBRARY_PATH=/usr/icu78-64/lib/ python tools/kangax/run-kangax.py --engine="${{ github.workspace }}/build/out_linux64/escargot"
|
||||
|
||||
build-test-on-self-hosted-arm-linux:
|
||||
runs-on: [self-hosted, linux, arm]
|
||||
if: github.repository == 'Samsung/escargot'
|
||||
runs-on: [self-hosted, linux, arm, test]
|
||||
strategy:
|
||||
matrix:
|
||||
compiler: [ {cxx: g++, cc: gcc}, {cxx: clang++, cc: clang} ]
|
||||
timeout-minutes: 60
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
|
@ -430,14 +506,18 @@ jobs:
|
|||
submodules: true
|
||||
- name: Build
|
||||
run: |
|
||||
cmake -DCMAKE_POLICY_VERSION_MINIMUM=3.5 -H./ -Bout -DCMAKE_C_COMPILER=gcc-11 -DCMAKE_CXX_COMPILER=g++-11 -DCMAKE_SYSTEM_NAME=Linux -DCMAKE_SYSTEM_PROCESSOR=arm32 -DESCARGOT_MODE=release -DESCARGOT_THREADING=ON -DESCARGOT_TEMPORAL=ON -DESCARGOT_TCO=ON -DESCARGOT_TLS_ACCESS_BY_ADDRESS=ON -DESCARGOT_TEST=ON -DESCARGOT_OUTPUT=shell -GNinja
|
||||
cmake -DCMAKE_POLICY_VERSION_MINIMUM=3.5 -H./ -Bout -DCMAKE_C_COMPILER=${{ matrix.compiler.cc }} -DCMAKE_CXX_COMPILER=${{ matrix.compiler.cxx }} -DCMAKE_SYSTEM_NAME=Linux -DCMAKE_SYSTEM_PROCESSOR=arm32 -DESCARGOT_MODE=release -DESCARGOT_THREADING=ON -DESCARGOT_TEMPORAL=OFF -DESCARGOT_TCO=ON -DESCARGOT_TLS_ACCESS_BY_ADDRESS=${{ matrix.compiler.tls }} -DESCARGOT_TEST=ON -DESCARGOT_SHADOWREALM=ON -DESCARGOT_OUTPUT=shell -GNinja
|
||||
cmake --build ./out/
|
||||
- name: Test
|
||||
run: |
|
||||
GC_FREE_SPACE_DIVISOR=1 $RUNNER --engine="${{ github.workspace }}/out/escargot" --test262-extra-arg="--skip intl402 --skip sm" new-es v8 spidermonkey chakracore test262
|
||||
GC_FREE_SPACE_DIVISOR=1 $RUNNER --engine="${{ github.workspace }}/out/escargot" --test262-extra-arg="--skip intl402 --skip sm --skip Temporal" new-es v8 spidermonkey chakracore test262
|
||||
|
||||
build-test-on-self-hosted-arm64-linux:
|
||||
runs-on: [self-hosted, linux, arm64]
|
||||
if: github.repository == 'Samsung/escargot'
|
||||
runs-on: [self-hosted, linux, arm64, test]
|
||||
strategy:
|
||||
matrix:
|
||||
compiler: [ {cxx: g++-11, cc: gcc-11}, {cxx: clang++, cc: clang} ]
|
||||
timeout-minutes: 60
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
|
@ -445,10 +525,11 @@ jobs:
|
|||
submodules: true
|
||||
- name: Build
|
||||
run: |
|
||||
cmake -DCMAKE_POLICY_VERSION_MINIMUM=3.5 -H./ -Bout -DESCARGOT_MODE=release -DESCARGOT_THREADING=ON -DESCARGOT_TEMPORAL=ON -DESCARGOT_TCO=ON -DESCARGOT_TEST=ON -DESCARGOT_OUTPUT=shell -GNinja
|
||||
LDFLAGS=" -L/usr/icu78-64/lib/ -Wl,-rpath=/usr/icu78-64/lib/" PKG_CONFIG_PATH="/usr/icu78-64/lib/pkgconfig/" cmake -DCMAKE_C_COMPILER=${{ matrix.compiler.cc }} -DCMAKE_CXX_COMPILER=${{ matrix.compiler.cxx }} -DCMAKE_POLICY_VERSION_MINIMUM=3.5 -H./ -Bout -DESCARGOT_MODE=release -DESCARGOT_THREADING=ON -DESCARGOT_TEMPORAL=ON -DESCARGOT_TCO=ON -DESCARGOT_TEST=ON -DESCARGOT_SHADOWREALM=ON -DESCARGOT_OUTPUT=shell -GNinja
|
||||
cmake --build ./out/
|
||||
- name: Test
|
||||
run: |
|
||||
export LD_LIBRARY_PATH=/usr/icu78-64/lib/
|
||||
GC_FREE_SPACE_DIVISOR=1 $RUNNER --engine="${{ github.workspace }}/out/escargot" --test262-extra-arg="--skip intl402 --skip sm" test262 chakracore spidermonkey v8 new-es
|
||||
|
||||
build-test-on-riscv64-release:
|
||||
|
|
@ -458,7 +539,7 @@ jobs:
|
|||
with:
|
||||
submodules: true
|
||||
- name: Build in riscv64 container
|
||||
uses: uraimo/run-on-arch-action@v2.8.1
|
||||
uses: uraimo/run-on-arch-action@v3
|
||||
with:
|
||||
arch: riscv64
|
||||
distro: ubuntu22.04
|
||||
|
|
@ -471,9 +552,9 @@ jobs:
|
|||
apt-get update
|
||||
apt-get install -y cmake build-essential ninja-build pkg-config python3 git libicu-dev
|
||||
run: |
|
||||
cmake -H. -Bout/riscv64 -DESCARGOT_TEMPORAL=ON -DESCARGOT_TCO=ON -DESCARGOT_TEST=ON -DESCARGOT_OUTPUT=shell -GNinja
|
||||
cmake -H. -Bout/riscv64 -DESCARGOT_TEMPORAL=ON -DESCARGOT_TCO=ON -DESCARGOT_TEST=ON -DESCARGOT_SHADOWREALM=ON -DESCARGOT_OUTPUT=shell -GNinja
|
||||
ninja -Cout/riscv64
|
||||
python3 ./tools/run-tests.py --engine="./out/riscv64/escargot" regression-tests new-es intl sunspider-js
|
||||
python3 ./tools/run-tests.py --engine="./out/riscv64/escargot" new-es intl sunspider-js
|
||||
|
||||
build-test-debugger:
|
||||
runs-on: ubuntu-latest
|
||||
|
|
@ -487,7 +568,7 @@ jobs:
|
|||
sudo apt-get install -y ninja-build
|
||||
- name: Build
|
||||
env:
|
||||
BUILD_OPTIONS: -DESCARGOT_MODE=debug -DESCARGOT_DEBUGGER=1 -DESCARGOT_TEMPORAL=ON -DESCARGOT_TCO=ON -DESCARGOT_TEST=ON -DESCARGOT_OUTPUT=shell -GNinja
|
||||
BUILD_OPTIONS: -DESCARGOT_MODE=debug -DESCARGOT_DEBUGGER=1 -DESCARGOT_TEMPORAL=ON -DESCARGOT_TCO=ON -DESCARGOT_TEST=ON -DESCARGOT_SHADOWREALM=ON -DESCARGOT_OUTPUT=shell -GNinja
|
||||
run: |
|
||||
cmake -DCMAKE_POLICY_VERSION_MINIMUM=3.5 -H. -Bout/debugger $BUILD_OPTIONS
|
||||
ninja -Cout/debugger
|
||||
|
|
@ -535,19 +616,19 @@ jobs:
|
|||
sudo apt-get install -y libicu-dev:i386 # install i386 ICU
|
||||
- name: Build x86
|
||||
env:
|
||||
BUILD_OPTIONS: -DCMAKE_SYSTEM_NAME=Linux -DCMAKE_SYSTEM_PROCESSOR=x86 -DESCARGOT_MODE=debug -DESCARGOT_CODE_CACHE=ON -DESCARGOT_TEMPORAL=ON -DESCARGOT_TCO=ON -DESCARGOT_TEST=ON -DESCARGOT_OUTPUT=shell -GNinja
|
||||
BUILD_OPTIONS: -DCMAKE_SYSTEM_NAME=Linux -DCMAKE_SYSTEM_PROCESSOR=x86 -DESCARGOT_MODE=debug -DESCARGOT_CODE_CACHE=ON -DESCARGOT_TEMPORAL=ON -DESCARGOT_TCO=ON -DESCARGOT_TEST=ON -DESCARGOT_SHADOWREALM=ON -DESCARGOT_OUTPUT=shell -GNinja
|
||||
run: |
|
||||
cmake -DCMAKE_POLICY_VERSION_MINIMUM=3.5 -H. -Bout/codecache/x86 $BUILD_OPTIONS
|
||||
ninja -Cout/codecache/x86
|
||||
- name: Build x64
|
||||
env:
|
||||
BUILD_OPTIONS: -DESCARGOT_MODE=debug -DESCARGOT_CODE_CACHE=ON -DESCARGOT_TEMPORAL=ON -DESCARGOT_TCO=ON -DESCARGOT_TEST=ON -DESCARGOT_OUTPUT=shell -GNinja
|
||||
BUILD_OPTIONS: -DESCARGOT_MODE=debug -DESCARGOT_CODE_CACHE=ON -DESCARGOT_TEMPORAL=ON -DESCARGOT_TCO=ON -DESCARGOT_TEST=ON -DESCARGOT_SHADOWREALM=ON -DESCARGOT_OUTPUT=shell -GNinja
|
||||
run: |
|
||||
cmake -DCMAKE_POLICY_VERSION_MINIMUM=3.5 -H. -Bout/codecache/x64 $BUILD_OPTIONS
|
||||
ninja -Cout/codecache/x64
|
||||
- name: Build x64 Release Mode
|
||||
env:
|
||||
BUILD_OPTIONS: -DESCARGOT_MODE=release -DESCARGOT_CODE_CACHE=ON -DESCARGOT_TEMPORAL=ON -DESCARGOT_TCO=ON -DESCARGOT_TEST=ON -DESCARGOT_OUTPUT=shell -GNinja
|
||||
BUILD_OPTIONS: -DESCARGOT_MODE=release -DESCARGOT_CODE_CACHE=ON -DESCARGOT_TEMPORAL=ON -DESCARGOT_TCO=ON -DESCARGOT_TEST=ON -DESCARGOT_SHADOWREALM=ON -DESCARGOT_OUTPUT=shell -GNinja
|
||||
run: |
|
||||
cmake -DCMAKE_POLICY_VERSION_MINIMUM=3.5 -H. -Bout/codecache/release/x64 $BUILD_OPTIONS
|
||||
ninja -Cout/codecache/release/x64
|
||||
|
|
@ -595,23 +676,41 @@ jobs:
|
|||
- name: Install Packages
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y ninja-build gcc-multilib g++-multilib
|
||||
sudo apt-get install -y ninja-build gcc-multilib g++-multilib make g++ pkg-config automake libtool git build-essential checkinstall libncurses-dev libssl-dev libsqlite3-dev tk-dev libgdbm-dev libc6-dev libbz2-dev libffi-dev
|
||||
- name: Build ICU(32)
|
||||
run: |
|
||||
git clone --depth 1 --single-branch -b release-78.1 https://github.com/unicode-org/icu.git $GITHUB_WORKSPACE/icu-build/
|
||||
cd $GITHUB_WORKSPACE/icu-build/icu4c/source
|
||||
LDFLAGS="-m32 -Wl,-rpath=$GITHUB_WORKSPACE/icu32/lib/" CFLAGS="-m32" CXXFLAGS="-m32" ./runConfigureICU Linux/gcc --prefix="$GITHUB_WORKSPACE/icu32/"
|
||||
make -j8
|
||||
make install
|
||||
- name: Build ICU(64)
|
||||
run: |
|
||||
git clone --depth 1 --single-branch -b release-78.1 https://github.com/unicode-org/icu.git $GITHUB_WORKSPACE/icu64-build/
|
||||
cd $GITHUB_WORKSPACE/icu64-build/icu4c/source
|
||||
LDFLAGS="-Wl,-rpath=$GITHUB_WORKSPACE/icu64/lib/" ./runConfigureICU Linux/gcc --prefix="$GITHUB_WORKSPACE/icu64/"
|
||||
make -j8
|
||||
make install
|
||||
- name: Build x86
|
||||
env:
|
||||
BUILD_OPTIONS: -DCMAKE_SYSTEM_NAME=Linux -DCMAKE_SYSTEM_PROCESSOR=x86 -DESCARGOT_MODE=debug -DESCARGOT_WASM=ON -DESCARGOT_TEMPORAL=ON -DESCARGOT_TCO=ON -DESCARGOT_TEST=ON -DESCARGOT_OUTPUT=shell -GNinja
|
||||
BUILD_OPTIONS: -DCMAKE_SYSTEM_NAME=Linux -DCMAKE_SYSTEM_PROCESSOR=x86 -DESCARGOT_MODE=debug -DESCARGOT_WASM=ON -DESCARGOT_TEMPORAL=ON -DESCARGOT_TCO=ON -DESCARGOT_TEST=ON -DESCARGOT_SHADOWREALM=ON -DESCARGOT_OUTPUT=shell -GNinja
|
||||
run: |
|
||||
cmake -DCMAKE_POLICY_VERSION_MINIMUM=3.5 -H. -Bout/wasm/x86 $BUILD_OPTIONS
|
||||
ninja -Cout/wasm/x86
|
||||
- name: Build x64
|
||||
env:
|
||||
BUILD_OPTIONS: -DESCARGOT_MODE=debug -DESCARGOT_WASM=ON -DESCARGOT_TEMPORAL=ON -DESCARGOT_TCO=ON -DESCARGOT_TEST=ON -DESCARGOT_OUTPUT=shell -GNinja
|
||||
BUILD_OPTIONS: -DESCARGOT_MODE=debug -DESCARGOT_WASM=ON -DESCARGOT_TEMPORAL=ON -DESCARGOT_TCO=ON -DESCARGOT_TEST=ON -DESCARGOT_SHADOWREALM=ON -DESCARGOT_OUTPUT=shell -GNinja
|
||||
run: |
|
||||
cmake -DCMAKE_POLICY_VERSION_MINIMUM=3.5 -H. -Bout/wasm/x64 $BUILD_OPTIONS
|
||||
ninja -Cout/wasm/x64
|
||||
- name: Run x86 test
|
||||
run: $RUNNER --arch=x86 --engine="$GITHUB_WORKSPACE/out/wasm/x86/escargot" wasm-js
|
||||
run: |
|
||||
export LD_LIBRARY_PATH=$GITHUB_WORKSPACE/icu32/lib
|
||||
$RUNNER --arch=x86 --engine="$GITHUB_WORKSPACE/out/wasm/x86/escargot" wasm-js
|
||||
- name: Run x64 test
|
||||
run: $RUNNER --arch=x86_64 --engine="$GITHUB_WORKSPACE/out/wasm/x64/escargot" wasm-js
|
||||
run: |
|
||||
export LD_LIBRARY_PATH=$GITHUB_WORKSPACE/icu64/lib
|
||||
$RUNNER --arch=x86_64 --engine="$GITHUB_WORKSPACE/out/wasm/x64/escargot" wasm-js
|
||||
- if: ${{ failure() }}
|
||||
uses: mxschmitt/action-tmate@v3
|
||||
timeout-minutes: 60
|
||||
|
|
|
|||
11
.github/workflows/gbs.conf
vendored
Normal file
11
.github/workflows/gbs.conf
vendored
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
[general]
|
||||
profile = profile.tizen
|
||||
|
||||
[profile.tizen]
|
||||
repos = repo.tizen_base_reference,repo.tizen_reference
|
||||
|
||||
[repo.tizen_reference]
|
||||
url = https://download.tizen.org/snapshots/TIZEN/Tizen/Tizen-Unified/reference/repos/standard/packages/
|
||||
|
||||
[repo.tizen_base_reference]
|
||||
url = https://download.tizen.org/snapshots/TIZEN/Tizen/Tizen-Base/reference/repos/standard/packages/
|
||||
53
.github/workflows/release.yml
vendored
53
.github/workflows/release.yml
vendored
|
|
@ -4,6 +4,7 @@ on:
|
|||
push:
|
||||
tags:
|
||||
- "v*.*.*"
|
||||
workflow_dispatch:
|
||||
|
||||
env:
|
||||
RUNNER: tools/run-tests.py
|
||||
|
|
@ -14,7 +15,7 @@ jobs:
|
|||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
matrix:
|
||||
os: [macos-13, macos-latest]
|
||||
os: [macos-15, macos-latest]
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
|
|
@ -30,7 +31,7 @@ jobs:
|
|||
# add icu path to pkg_config_path
|
||||
export PKG_CONFIG_PATH="$(brew --prefix icu4c)/lib/pkgconfig"
|
||||
echo $PKG_CONFIG_PATH
|
||||
cmake -H. -Bout/ $BUILD_OPTIONS
|
||||
cmake -DCMAKE_POLICY_VERSION_MINIMUM=3.10 -H. -Bout/ $BUILD_OPTIONS
|
||||
ninja -Cout/
|
||||
- name: Check
|
||||
run: |
|
||||
|
|
@ -71,17 +72,25 @@ jobs:
|
|||
otool -L ./deploy/escargot
|
||||
otool -L ./deploy/libicu*.dylib
|
||||
|
||||
# Ad-hoc sign
|
||||
set -e
|
||||
cd deploy
|
||||
for f in libicu*.dylib; do [ -e "$f" ] || continue; codesign -f -s - "$f"; done
|
||||
codesign --force --deep -s - ./escargot
|
||||
for f in ./escargot libicu*.dylib; do [ -e "$f" ] || continue; codesign --verify --strict --verbose=6 "$f"; done
|
||||
cd ..
|
||||
|
||||
# run test
|
||||
$RUNNER --engine="$GITHUB_WORKSPACE/deploy/escargot" new-es
|
||||
|
||||
# zip results
|
||||
if [ "${{ matrix.os }}" == "macos-13" ]; then
|
||||
if [ "${{ matrix.os }}" == "macos-15" ]; then
|
||||
zip -j escargot-mac64.zip deploy/*
|
||||
elif [ "${{ matrix.os }}" == "macos-latest" ]; then
|
||||
zip -j escargot-mac64arm.zip deploy/*
|
||||
fi
|
||||
- name: Upload mac64
|
||||
if: ${{ matrix.os == 'macos-13' }}
|
||||
if: ${{ matrix.os == 'macos-15' }}
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: build-artifact-mac64
|
||||
|
|
@ -108,8 +117,8 @@ jobs:
|
|||
sudo apt-get install -y libicu-dev:i386 # install i386 ICU
|
||||
- name: Build x86/x64
|
||||
run: |
|
||||
cmake -H. -Bout/x86 -DCMAKE_SYSTEM_NAME=Linux -DCMAKE_SYSTEM_PROCESSOR=x86 -DESCARGOT_TEMPORAL=ON $BUILD_OPTIONS
|
||||
cmake -H. -Bout/x64 -DESCARGOT_TEMPORAL=ON $BUILD_OPTIONS
|
||||
cmake -DCMAKE_POLICY_VERSION_MINIMUM=3.5 -H. -Bout/x86 -DCMAKE_SYSTEM_NAME=Linux -DCMAKE_SYSTEM_PROCESSOR=x86 -DESCARGOT_TEMPORAL=ON $BUILD_OPTIONS
|
||||
cmake -DCMAKE_POLICY_VERSION_MINIMUM=3.5 -H. -Bout/x64 -DESCARGOT_TEMPORAL=ON $BUILD_OPTIONS
|
||||
ninja -Cout/x86
|
||||
ninja -Cout/x64
|
||||
- name: Check
|
||||
|
|
@ -173,25 +182,41 @@ jobs:
|
|||
git config --global core.eol lf
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: true
|
||||
submodules: recursive
|
||||
- uses: szenius/set-timezone@v2.0
|
||||
with:
|
||||
timezoneWindows: "Pacific Standard Time"
|
||||
- uses: lukka/get-cmake@latest
|
||||
with:
|
||||
cmakeVersion: "~3.25.0"
|
||||
- uses: GuillaumeFalourd/setup-windows10-sdk-action@v2
|
||||
with:
|
||||
sdk-version: 20348
|
||||
sdk-version: 26100
|
||||
- uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: '3.11'
|
||||
- name: Install msvc redist package
|
||||
- name: Download and Install Visual C++ Redistributable
|
||||
shell: powershell
|
||||
run: |
|
||||
(new-object System.Net.WebClient).DownloadFile('https://github.com/abbodi1406/vcredist/releases/download/v0.73.0/VisualCppRedist_AIO_x86_x64.exe','VisualCppRedist_AIO_x86_x64.exe')
|
||||
.\VisualCppRedist_AIO_x86_x64.exe /y
|
||||
$vcRedistUrl64 = "https://aka.ms/vs/17/release/vc_redist.x64.exe" # Or the appropriate URL for your target architecture/version
|
||||
$vcRedistPath64 = "$env:TEMP\vc_redist.x64.exe"
|
||||
$vcRedistUrl32 = "https://aka.ms/vs/17/release/vc_redist.x86.exe" # Or the appropriate URL for your target architecture/version
|
||||
$vcRedistPath32 = "$env:TEMP\vc_redist.x86.exe"
|
||||
|
||||
Write-Host "Downloading Visual C++ Redistributable from $vcRedistUrl64"
|
||||
Invoke-WebRequest -Uri $vcRedistUrl64 -OutFile $vcRedistPath64
|
||||
|
||||
Write-Host "Downloading Visual C++ Redistributable from $vcRedistUrl32"
|
||||
Invoke-WebRequest -Uri $vcRedistUrl32 -OutFile $vcRedistPath32
|
||||
|
||||
Write-Host "Installing Visual C++ Redistributable silently"
|
||||
Start-Process -FilePath $vcRedistPath64 -ArgumentList "/install /quiet /norestart" -Wait
|
||||
Start-Process -FilePath $vcRedistPath32 -ArgumentList "/install /quiet /norestart" -Wait
|
||||
Write-Host "Visual C++ Redistributable installation complete."
|
||||
- uses: ilammy/msvc-dev-cmd@v1.13.0
|
||||
with:
|
||||
arch: ${{ matrix.arch }}
|
||||
sdk: "10.0.20348.0"
|
||||
sdk: "10.0.26100.0"
|
||||
- name: Install zip if not available
|
||||
run: |
|
||||
if (-Not (Get-Command zip -ErrorAction SilentlyContinue)) {
|
||||
|
|
@ -199,7 +224,7 @@ jobs:
|
|||
}
|
||||
- name: Build ${{ matrix.arch }}
|
||||
run: |
|
||||
CMake -DCMAKE_SYSTEM_NAME=Windows -DCMAKE_SYSTEM_VERSION:STRING="10.0" -DCMAKE_SYSTEM_PROCESSOR=${{ matrix.arch }} -DESCARGOT_ARCH=${{ matrix.arch }} -Bout/ -DESCARGOT_OUTPUT=shell -DESCARGOT_LIBICU_SUPPORT=ON -DESCARGOT_LIBICU_SUPPORT_WITH_DLOPEN=OFF -DESCARGOT_THREADING=ON -DESCARGOT_TCO=ON -DESCARGOT_TEST=ON -G Ninja -DCMAKE_C_COMPILER=cl -DCMAKE_CXX_COMPILER=cl -DCMAKE_BUILD_TYPE=release
|
||||
CMake -DCMAKE_SYSTEM_NAME=Windows -DCMAKE_SYSTEM_VERSION:STRING="10.0" -DCMAKE_SYSTEM_PROCESSOR=${{ matrix.arch }} -DESCARGOT_ARCH=${{ matrix.arch }} -Bout/ -DESCARGOT_OUTPUT=shell -DESCARGOT_LIBICU_SUPPORT=ON -DESCARGOT_WASM=ON -DESCARGOT_THREADING=ON -DESCARGOT_TCO=ON -DESCARGOT_TEST=ON -DESCARGOT_SHADOWREALM=ON -G Ninja -DCMAKE_C_COMPILER=cl -DCMAKE_CXX_COMPILER=cl -DCMAKE_BUILD_TYPE=release
|
||||
CMake --build out/ --config Release
|
||||
- name: Check
|
||||
run: |
|
||||
|
|
@ -215,7 +240,7 @@ jobs:
|
|||
|
||||
check-build-mac64:
|
||||
needs: [build-macOS]
|
||||
runs-on: macos-13
|
||||
runs-on: macos-15
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
|
|
|
|||
2
.gitignore
vendored
2
.gitignore
vendored
|
|
@ -1,5 +1,5 @@
|
|||
#error_report
|
||||
escargot
|
||||
/escargot
|
||||
escargot.x*
|
||||
.project
|
||||
.cproject
|
||||
|
|
|
|||
|
|
@ -68,7 +68,23 @@ ENDIF()
|
|||
MESSAGE(STATUS "Escargot Arch: " ${ESCARGOT_ARCH})
|
||||
MESSAGE(STATUS "Escargot Host: " ${ESCARGOT_HOST})
|
||||
MESSAGE(STATUS "Escargot Mode: " ${ESCARGOT_MODE})
|
||||
MESSAGE(STATUS "Escargot Threading: " ${ESCARGOT_THREADING})
|
||||
MESSAGE(STATUS "Escargot TLS optimize(offset): " ${ESCARGOT_TLS_ACCESS_BY_ADDRESS})
|
||||
MESSAGE(STATUS "Escargot TLS optimize(pthread_key): " ${ESCARGOT_TLS_ACCESS_BY_PTHREAD_KEY})
|
||||
MESSAGE(STATUS "Escargot Output: " ${ESCARGOT_OUTPUT})
|
||||
MESSAGE(STATUS "Escargot Output: " ${ESCARGOT_OUTPUT})
|
||||
MESSAGE(STATUS "--------------------------------------------------------------------------------")
|
||||
MESSAGE(STATUS "ESCARGOT_DEFINITIONS: " ${ESCARGOT_DEFINITIONS})
|
||||
MESSAGE(STATUS "ESCARGOT_CXXFLAGS: " ${ESCARGOT_CXXFLAGS})
|
||||
MESSAGE(STATUS "ESCARGOT_LDFLAGS: " ${ESCARGOT_LDFLAGS})
|
||||
MESSAGE(STATUS "ESCARGOT_INCDIRS: " ${ESCARGOT_INCDIRS})
|
||||
MESSAGE(STATUS "ESCARGOT_LIBRARIES: " ${ESCARGOT_LIBRARIES})
|
||||
MESSAGE(STATUS "ESCARGOT_LIBICU_SUPPORT: " ${ESCARGOT_LIBICU_SUPPORT})
|
||||
MESSAGE(STATUS "ESCARGOT_LIBICU_SUPPORT_WITH_DLOPEN: " ${ESCARGOT_LIBICU_SUPPORT_WITH_DLOPEN})
|
||||
MESSAGE(STATUS "ESCARGOT_SMALL_CONFIG: " ${ESCARGOT_SMALL_CONFIG})
|
||||
MESSAGE(STATUS "ESCARGOT_CODE_CACHE: " ${ESCARGOT_CODE_CACHE})
|
||||
MESSAGE(STATUS "ESCARGOT_WASM: " ${ESCARGOT_WASM})
|
||||
MESSAGE(STATUS "ESCARGOT_THREADING: " ${ESCARGOT_THREADING})
|
||||
MESSAGE(STATUS "ESCARGOT_TLS_ACCESS_BY_ADDRESS: " ${ESCARGOT_TLS_ACCESS_BY_ADDRESS})
|
||||
MESSAGE(STATUS "ESCARGOT_TLS_ACCESS_BY_PTHREAD_KEY: " ${ESCARGOT_TLS_ACCESS_BY_PTHREAD_KEY})
|
||||
MESSAGE(STATUS "ESCARGOT_EXPORT_ALL: " ${ESCARGOT_EXPORT_ALL})
|
||||
MESSAGE(STATUS "ESCARGOT_TCO: " ${ESCARGOT_TCO})
|
||||
MESSAGE(STATUS "ESCARGOT_TEMPORAL: " ${ESCARGOT_TEMPORAL})
|
||||
MESSAGE(STATUS "ESCARGOT_SHADOWREALM: " ${ESCARGOT_SHADOWREALM})
|
||||
MESSAGE(STATUS "ESCARGOT_TEST: " ${ESCARGOT_TEST})
|
||||
|
|
|
|||
|
|
@ -1,30 +1,33 @@
|
|||
Copyright (c) 1988, 1989 Hans-J. Boehm, Alan J. Demers
|
||||
MIT-style License
|
||||
|
||||
Copyright (c) 1988-1989 Hans-J. Boehm, Alan J. Demers
|
||||
Copyright (c) 1991-1996 by Xerox Corporation. All rights reserved.
|
||||
Copyright (c) 1996-1999 by Silicon Graphics. All rights reserved.
|
||||
Copyright (c) 1999-2004 Hewlett-Packard Development Company, L.P.
|
||||
|
||||
The file linux_threads.c is also
|
||||
Copyright (c) 1998 by Fergus Henderson. All rights reserved.
|
||||
Copyright (c) 1999-2001 by Red Hat, Inc. All rights reserved.
|
||||
Copyright (c) 1999-2011 Hewlett-Packard Development Company, L.P.
|
||||
Copyright (c) 2004-2005 Andrei Polushin
|
||||
Copyright (c) 2007 Free Software Foundation, Inc.
|
||||
Copyright (c) 2008-2022 Ivan Maidanski
|
||||
Copyright (c) 2011 Ludovic Courtes
|
||||
Copyright (c) 2018 Petter A. Urkedal
|
||||
|
||||
The files Makefile.am, and configure.in are
|
||||
Copyright (c) 2001 by Red Hat Inc. All rights reserved.
|
||||
|
||||
Several files supporting GNU-style builds are copyrighted by the Free
|
||||
Software Foundation, and carry a different license from that given
|
||||
below.
|
||||
|
||||
THIS MATERIAL IS PROVIDED AS IS, WITH ABSOLUTELY NO WARRANTY EXPRESSED
|
||||
OR IMPLIED. ANY USE IS AT YOUR OWN RISK.
|
||||
|
||||
Permission is hereby granted to use or copy this program
|
||||
for any purpose, provided the above notices are retained on all copies.
|
||||
for any purpose, provided the above notices are retained on all copies.
|
||||
Permission to modify the code and to distribute modified code is granted,
|
||||
provided the above notices are retained, and a notice that the code was
|
||||
modified is included with the above copyright notice.
|
||||
|
||||
A few of the files needed to use the GNU-style build procedure come with
|
||||
slightly different licenses, though they are all similar in spirit. A few
|
||||
are GPL'ed, but with an exception that should cover all uses in the
|
||||
collector. (If you are concerned about such things, I recommend you look
|
||||
at the notice in config.guess or ltmain.sh.)
|
||||
|
||||
Several files (gc/gc_allocator.h, extra/msvc_dbg.c) come with slightly
|
||||
different licenses, though they are all similar in spirit (the exact
|
||||
licensing terms are given at the beginning of the corresponding source file).
|
||||
|
||||
A few of the files needed to use the GNU-style build procedure come with
|
||||
a modified GPL license that appears not to significantly restrict use of
|
||||
the collector, though use of those files for a purpose other than building
|
||||
the collector may require the resulting code to be covered by the GPL.
|
||||
|
|
|
|||
28
README.md
28
README.md
|
|
@ -41,7 +41,7 @@ Escargot is an open-source project that allows developers to contribute to its d
|
|||
|
||||
### Build Options
|
||||
|
||||
The following build options are supported when generating ninja rules using cmake.
|
||||
The following build options are supported when generating build rules using cmake.
|
||||
|
||||
| **Option** | **Description** | **Flag** | **Value** | **Default** |
|
||||
|-|-|-|-|-|
|
||||
|
|
@ -50,14 +50,17 @@ The following build options are supported when generating ninja rules using cmak
|
|||
| **MODE** | Choose release/debug mode | -DESCARGOT_MODE | release/debug | release |
|
||||
| **OUTPUT** | Choose build output type | -DESCARGOT_OUTPUT | shared_lib/static_lib/shell/cctest | shell |
|
||||
| **LIBICU** | Include libicu library | -DESCARGOT_LIBICU_SUPPORT | ON/OFF | ON |
|
||||
| **THREADING** | Enable threading features (e.g. Atomics, SharedArrayBuffer) | -DESCARGOT_THREADING | ON/OFF | ON |
|
||||
| **WASM** | Enable WebAssembly support | -DESCARGOT_WASM | ON/OFF | OFF |
|
||||
| **CODE_CACHE** | Enable code cache | -DESCARGOT_CODE_CACHE | ON/OFF | OFF |
|
||||
| **TCO** | Enable tail call optimization | -DESCARGOT_TCO | ON/OFF | OFF |
|
||||
| **THREADING** | Enable threading features (e.g. Atomics, SharedArrayBuffer) | -DESCARGOT_THREADING | ON/OFF | ON |
|
||||
| **TLS_ADDRESS_OFFSET** | Enable thread local storge access optimization(offset) | -DESCARGOT_TLS_ACCESS_BY_ADDRESS | ON/OFF | OFF |
|
||||
| **TLS_PTHREAD_KEY** | Enable thread local storge access optimization(pthread_key) | -DESCARGOT_TLS_ACCESS_BY_PTHREAD_KEY | ON/OFF | OFF |
|
||||
| **TEMPORAL** | Enable Temporal support | -ESCARGOT_TEMPORAL | ON/OFF | OFF |
|
||||
| **SHADOWREALM** | Enable ShadowRealm support | -ESCARGOT_SHADOWREALM | ON/OFF | OFF |
|
||||
| **SMALL_CONFIG** | Enable aggressive memory optimizations for tiny devices | -DESCARGOT_SMALL_CONFIG | ON/OFF | OFF |
|
||||
| **TEST** | Enable additional features used only for testing | -DESCARGOT_TEST | ON/OFF | OFF |
|
||||
| **DEBUGGER** | Enable Debug server | -DESCARGOT_DEBUGGER | ON/OFF | OFF |
|
||||
|
||||
### Linux
|
||||
|
||||
|
|
@ -133,6 +136,27 @@ cd out
|
|||
msbuild ESCARGOT.sln /property:Configuration=Release /p:platform=[ Win32 | x64 ]
|
||||
```
|
||||
|
||||
## Debugger
|
||||
|
||||
Make sure Escargot is built with the `-DESCARGOT_DEBUGGER=1` flag (off by default) enabled;
|
||||
then start Escargot with the `--start-debug-server` option.
|
||||
|
||||
### Connect using a debugger client
|
||||
|
||||
- Escargot python debugger
|
||||
- run `./tools/debugger/debugger.py`; It will automatically connect to a debug server on the default port `6501`
|
||||
- run `./tools/debugger/debugger.py --help` for a list of options
|
||||
- [Visual Studio Code extension](https://github.com/Samsung/escargot-vscode-extension/?tab=readme-ov-file#how-to-use)
|
||||
- Chrome Devtools `⚠️ Early in development ⚠️`
|
||||
- Initial setup:
|
||||
- Navigate to [chrome://inspect](chrome://inspect)
|
||||
- Make sure *Discover network targets* is enabled; click configure
|
||||
- Add `localhost:6501` as a target; click Done
|
||||
- Usage:
|
||||
- The started debug server will be listed in the *Remote Target* list (If it is not, the page may need to be reloaded using the browser reload button)
|
||||
- Click `inspect`
|
||||
- A new window with the Chrome Devtools debugger UI will open
|
||||
|
||||
## Testing ✅
|
||||
|
||||
Escargot supports various benchmark sets, which can be run using the [tools/run-tests.py](https://github.com/Samsung/escargot/blob/master/tools/run-tests.py) script.
|
||||
|
|
|
|||
|
|
@ -50,9 +50,7 @@ if (ENABLE_SHELL)
|
|||
ADD_LINK_OPTIONS(-static-libstdc++)
|
||||
ADD_EXECUTABLE(escargot-shell ${CMAKE_CURRENT_SOURCE_DIR}/../../../../../../src/shell/Shell.cpp)
|
||||
TARGET_INCLUDE_DIRECTORIES(escargot-shell PRIVATE ADD_SUBDIRECTORY
|
||||
(${CMAKE_CURRENT_SOURCE_DIR}/../../../../../../third_party/GCutil/)
|
||||
(${CMAKE_CURRENT_SOURCE_DIR}/../../../../../../third_party/GCutil/bdwgc/include)
|
||||
(${CMAKE_CURRENT_SOURCE_DIR}/../../../../../../third_party/GCutil/bdwgc/include/gc)
|
||||
(${CMAKE_CURRENT_SOURCE_DIR}/../../../../../../third_party/GCutil/include)
|
||||
(${CMAKE_CURRENT_SOURCE_DIR}/../../../../../../src))
|
||||
TARGET_LINK_LIBRARIES (escargot-shell PRIVATE escargot ${LOG_LIBRARY})
|
||||
|
||||
|
|
|
|||
|
|
@ -165,6 +165,13 @@ ENDIF()
|
|||
|
||||
IF (ESCARGOT_TEMPORAL)
|
||||
SET (ESCARGOT_DEFINITIONS ${ESCARGOT_DEFINITIONS} -DENABLE_TEMPORAL)
|
||||
IF (NOT ESCARGOT_LIBICU_SUPPORT)
|
||||
MESSAGE (FATAL_ERROR "Temporal feature needs ICU")
|
||||
ENDIF()
|
||||
ENDIF()
|
||||
|
||||
IF (ESCARGOT_SHADOWREALM)
|
||||
SET (ESCARGOT_DEFINITIONS ${ESCARGOT_DEFINITIONS} -DENABLE_SHADOWREALM)
|
||||
ENDIF()
|
||||
|
||||
IF (ESCARGOT_TLS_ACCESS_BY_ADDRESS)
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ CMAKE_MINIMUM_REQUIRED (VERSION 2.8.12 FATAL_ERROR)
|
|||
SET (ESCARGOT_INCDIRS
|
||||
${ESCARGOT_INCDIRS}
|
||||
${ESCARGOT_ROOT}/src/
|
||||
${ESCARGOT_THIRD_PARTY_ROOT}
|
||||
${ESCARGOT_THIRD_PARTY_ROOT}/checked_arithmetic/
|
||||
${ESCARGOT_THIRD_PARTY_ROOT}/double_conversion/
|
||||
${ESCARGOT_THIRD_PARTY_ROOT}/lz4/
|
||||
|
|
@ -165,12 +166,12 @@ SET (ESCARGOT_LIBRARIES ${ESCARGOT_LIBRARIES} libbf)
|
|||
#######################################################
|
||||
# RUNTIME ICU BINDER
|
||||
#######################################################
|
||||
IF (ESCARGOT_LIBICU_SUPPORT_WITH_DLOPEN)
|
||||
SET (RIB_CFLAGS ${ESCARGOT_THIRDPARTY_CFLAGS})
|
||||
SET (RIB_MODE ${ESCARGOT_MODE})
|
||||
SET (RIB_CFLAGS_FROM_EXTERNAL ${ESCARGOT_CFLAGS_FROM_EXTERNAL})
|
||||
SET (RIB_CFLAGS ${ESCARGOT_THIRDPARTY_CFLAGS})
|
||||
SET (RIB_MODE ${ESCARGOT_MODE})
|
||||
SET (RIB_CFLAGS_FROM_EXTERNAL ${ESCARGOT_CFLAGS_FROM_EXTERNAL})
|
||||
ADD_SUBDIRECTORY (third_party/runtime_icu_binder)
|
||||
|
||||
ADD_SUBDIRECTORY (third_party/runtime_icu_binder)
|
||||
IF (ESCARGOT_LIBICU_SUPPORT_WITH_DLOPEN)
|
||||
SET (ESCARGOT_LIBRARIES ${ESCARGOT_LIBRARIES} runtime-icu-binder-static)
|
||||
ENDIF()
|
||||
|
||||
|
|
@ -199,6 +200,120 @@ IF (ESCARGOT_WASM)
|
|||
SET (ESCARGOT_LIBRARIES ${ESCARGOT_LIBRARIES} walrus)
|
||||
ENDIF()
|
||||
|
||||
MAKE_DIRECTORY(${CMAKE_BINARY_DIR}/escargot_generated/tmp)
|
||||
|
||||
# Generate UnicodeIdentifierTables.cpp
|
||||
MAKE_DIRECTORY(${CMAKE_BINARY_DIR}/escargot_generated/parser)
|
||||
EXECUTE_PROCESS(
|
||||
COMMAND python3 ${PROJECT_SOURCE_DIR}/tools/code_generators/gen_unicode.py --derived_core_properties ${PROJECT_SOURCE_DIR}/tools/unicode_data/DerivedCoreProperties.txt --dst ${CMAKE_BINARY_DIR}/escargot_generated/tmp/UnicodeIdentifierTables.cpp
|
||||
RESULT_VARIABLE GENERATE_RESULT
|
||||
OUTPUT_VARIABLE GENERATE_OUTPUT
|
||||
ERROR_VARIABLE GENERATE_ERROR
|
||||
)
|
||||
|
||||
IF (NOT GENERATE_RESULT EQUAL 0)
|
||||
MESSAGE(STATUS "Output:\n${GENERATE_OUTPUT}")
|
||||
MESSAGE(FATAL_ERROR "${GENERATE_ERROR}")
|
||||
ENDIF()
|
||||
|
||||
EXECUTE_PROCESS (COMMAND ${CMAKE_COMMAND} -E compare_files ${CMAKE_BINARY_DIR}/escargot_generated/tmp/UnicodeIdentifierTables.cpp ${CMAKE_BINARY_DIR}/escargot_generated/parser/UnicodeIdentifierTables.cpp
|
||||
RESULT_VARIABLE COMPARE_RESULT
|
||||
OUTPUT_VARIABLE COMPARE_OUTPUT
|
||||
ERROR_VARIABLE COMPARE_ERROR
|
||||
)
|
||||
|
||||
IF (NOT ${COMPARE_RESULT} EQUAL 0)
|
||||
FILE (COPY ${CMAKE_BINARY_DIR}/escargot_generated/tmp/UnicodeIdentifierTables.cpp DESTINATION ${CMAKE_BINARY_DIR}/escargot_generated/parser/)
|
||||
ENDIF()
|
||||
|
||||
SET (ESCARGOT_SRC_LIST ${ESCARGOT_SRC_LIST} ${CMAKE_BINARY_DIR}/escargot_generated/parser/UnicodeIdentifierTables.cpp)
|
||||
|
||||
# Generate YarrCanonicalizeUnicode.cpp
|
||||
MAKE_DIRECTORY(${CMAKE_BINARY_DIR}/escargot_generated/yarr)
|
||||
EXECUTE_PROCESS(
|
||||
COMMAND python3 ${PROJECT_SOURCE_DIR}/tools/code_generators/generateYarrCanonicalizeUnicode.py ${PROJECT_SOURCE_DIR}/tools/unicode_data/CaseFolding.txt ${CMAKE_BINARY_DIR}/escargot_generated/tmp/YarrCanonicalizeUnicode.cpp
|
||||
RESULT_VARIABLE GENERATE_RESULT
|
||||
OUTPUT_VARIABLE GENERATE_OUTPUT
|
||||
ERROR_VARIABLE GENERATE_ERROR
|
||||
)
|
||||
|
||||
IF (NOT GENERATE_RESULT EQUAL 0)
|
||||
MESSAGE(STATUS "Output:\n${GENERATE_OUTPUT}")
|
||||
MESSAGE(FATAL_ERROR "${GENERATE_ERROR}")
|
||||
ENDIF()
|
||||
|
||||
FILE(READ ${CMAKE_BINARY_DIR}/escargot_generated/tmp/YarrCanonicalizeUnicode.cpp UNICODE_FILE_CONTENTS)
|
||||
STRING(REPLACE "config.h" "WTFBridge.h" UNICODE_FILE_CONTENTS "${UNICODE_FILE_CONTENTS}")
|
||||
STRING(REPLACE "constexpr const" "const" UNICODE_FILE_CONTENTS "${UNICODE_FILE_CONTENTS}")
|
||||
STRING(REPLACE "constexpr size_t UNICODE" "const size_t UNICODE" UNICODE_FILE_CONTENTS "${UNICODE_FILE_CONTENTS}")
|
||||
STRING(REPLACE "constexpr CanonicalizationRange unicodeRangeInfo" "const CanonicalizationRange unicodeRangeInfo" UNICODE_FILE_CONTENTS "${UNICODE_FILE_CONTENTS}")
|
||||
FILE(WRITE ${CMAKE_BINARY_DIR}/escargot_generated/tmp/YarrCanonicalizeUnicode.cpp "${UNICODE_FILE_CONTENTS}")
|
||||
|
||||
EXECUTE_PROCESS (COMMAND ${CMAKE_COMMAND} -E compare_files ${CMAKE_BINARY_DIR}/escargot_generated/tmp/YarrCanonicalizeUnicode.cpp ${CMAKE_BINARY_DIR}/escargot_generated/yarr/YarrCanonicalizeUnicode.cpp
|
||||
RESULT_VARIABLE COMPARE_RESULT
|
||||
OUTPUT_VARIABLE COMPARE_OUTPUT
|
||||
ERROR_VARIABLE COMPARE_ERROR
|
||||
)
|
||||
|
||||
IF (NOT ${COMPARE_RESULT} EQUAL 0)
|
||||
FILE (COPY ${CMAKE_BINARY_DIR}/escargot_generated/tmp/YarrCanonicalizeUnicode.cpp DESTINATION ${CMAKE_BINARY_DIR}/escargot_generated/yarr/)
|
||||
ENDIF()
|
||||
|
||||
SET(ESCARGOT_SRC_LIST ${ESCARGOT_SRC_LIST} ${CMAKE_BINARY_DIR}/escargot_generated/yarr/YarrCanonicalizeUnicode.cpp)
|
||||
|
||||
# yarr/UnicodePatternTables.h
|
||||
EXECUTE_PROCESS(
|
||||
COMMAND python3 ${PROJECT_SOURCE_DIR}/tools/code_generators/generateYarrUnicodePropertyTables.py ${PROJECT_SOURCE_DIR}/tools/unicode_data ${CMAKE_BINARY_DIR}/escargot_generated/tmp/UnicodePatternTables.h
|
||||
RESULT_VARIABLE GENERATE_RESULT
|
||||
OUTPUT_VARIABLE GENERATE_OUTPUT
|
||||
ERROR_VARIABLE GENERATE_ERROR
|
||||
)
|
||||
|
||||
IF (NOT GENERATE_RESULT EQUAL 0)
|
||||
MESSAGE(STATUS "Output:\n${GENERATE_OUTPUT}")
|
||||
MESSAGE(FATAL_ERROR "${GENERATE_ERROR}")
|
||||
ENDIF()
|
||||
|
||||
EXECUTE_PROCESS (COMMAND ${CMAKE_COMMAND} -E compare_files ${CMAKE_BINARY_DIR}/escargot_generated/tmp/UnicodePatternTables.h ${CMAKE_BINARY_DIR}/escargot_generated/yarr/UnicodePatternTables.h
|
||||
RESULT_VARIABLE COMPARE_RESULT
|
||||
OUTPUT_VARIABLE COMPARE_OUTPUT
|
||||
ERROR_VARIABLE COMPARE_ERROR
|
||||
)
|
||||
|
||||
IF (NOT ${COMPARE_RESULT} EQUAL 0)
|
||||
FILE (COPY ${CMAKE_BINARY_DIR}/escargot_generated/tmp/UnicodePatternTables.h DESTINATION ${CMAKE_BINARY_DIR}/escargot_generated/yarr/)
|
||||
ENDIF()
|
||||
|
||||
SET (ESCARGOT_INCDIRS
|
||||
${ESCARGOT_INCDIRS}
|
||||
${CMAKE_BINARY_DIR}/escargot_generated/yarr/
|
||||
)
|
||||
|
||||
# YarrCanonicalizeUCS2.cpp
|
||||
EXECUTE_PROCESS(
|
||||
COMMAND python3 ${PROJECT_SOURCE_DIR}/tools/code_generators/generateYarrCanonicalizeUCS2.py ${PROJECT_SOURCE_DIR}/tools/unicode_data/UnicodeData.txt ${CMAKE_BINARY_DIR}/escargot_generated/tmp/YarrCanonicalizeUCS2.cpp
|
||||
RESULT_VARIABLE GENERATE_RESULT
|
||||
OUTPUT_VARIABLE GENERATE_OUTPUT
|
||||
ERROR_VARIABLE GENERATE_ERROR
|
||||
)
|
||||
|
||||
IF (NOT GENERATE_RESULT EQUAL 0)
|
||||
MESSAGE(STATUS "Output:\n${GENERATE_OUTPUT}")
|
||||
MESSAGE(FATAL_ERROR "${GENERATE_ERROR}")
|
||||
ENDIF()
|
||||
|
||||
EXECUTE_PROCESS (COMMAND ${CMAKE_COMMAND} -E compare_files ${CMAKE_BINARY_DIR}/escargot_generated/tmp/YarrCanonicalizeUCS2.cpp ${CMAKE_BINARY_DIR}/escargot_generated/yarr/YarrCanonicalizeUCS2.cpp
|
||||
RESULT_VARIABLE COMPARE_RESULT
|
||||
OUTPUT_VARIABLE COMPARE_OUTPUT
|
||||
ERROR_VARIABLE COMPARE_ERROR
|
||||
)
|
||||
|
||||
IF (NOT ${COMPARE_RESULT} EQUAL 0)
|
||||
FILE (COPY ${CMAKE_BINARY_DIR}/escargot_generated/tmp/YarrCanonicalizeUCS2.cpp DESTINATION ${CMAKE_BINARY_DIR}/escargot_generated/yarr/)
|
||||
ENDIF()
|
||||
|
||||
SET(ESCARGOT_SRC_LIST ${ESCARGOT_SRC_LIST} ${CMAKE_BINARY_DIR}/escargot_generated/yarr/YarrCanonicalizeUCS2.cpp)
|
||||
|
||||
# BUILD
|
||||
IF (${ESCARGOT_OUTPUT} STREQUAL "shell")
|
||||
ADD_EXECUTABLE (${ESCARGOT_TARGET} ${ESCARGOT_SRC_LIST})
|
||||
|
|
|
|||
|
|
@ -9,6 +9,9 @@ SET (ESCARGOT_THIRDPARTY_CFLAGS)
|
|||
SET (ESCARGOT_BUILD_32BIT OFF)
|
||||
SET (ESCARGOT_BUILD_64BIT OFF)
|
||||
SET (ESCARGOT_BUILD_64BIT_LARGE OFF)
|
||||
IF (ESCARGOT_ASAN)
|
||||
SET (ESCARGOT_BUILD_64BIT_LARGE ON)
|
||||
ENDIF()
|
||||
|
||||
# clang-cl defines ${CMAKE_CXX_COMPILER_ID} "Clang" and ${CMAKE_CXX_COMPILER_FRONTEND_VARIANT} "MSVC"
|
||||
SET (COMPILER_CLANG_CL OFF)
|
||||
|
|
@ -22,6 +25,7 @@ ENDIF()
|
|||
|
||||
# Default options per compiler
|
||||
IF (${CMAKE_CXX_COMPILER_ID} STREQUAL "MSVC" OR ${COMPILER_CLANG_CL})
|
||||
SET (ESCARGOT_COMPILER_ID "MSVC")
|
||||
SET (ESCARGOT_CXXFLAGS /std:c++17 /fp:strict /Zc:__cplusplus /EHs /source-charset:utf-8 /MP /D_CRT_SECURE_NO_WARNINGS /DGC_NOT_DLL /D_SILENCE_CXX17_ITERATOR_BASE_CLASS_DEPRECATION_WARNING /wd4244 /wd4267 /wd4805 /wd4018 /wd4172 /wd4146)
|
||||
SET (ESCARGOT_CXXFLAGS_RELEASE /O2 /Oy-)
|
||||
SET (ESCARGOT_THIRDPARTY_CFLAGS /D_CRT_SECURE_NO_WARNINGS /DGC_NOT_DLL /Oy- /wd4146 /EHs)
|
||||
|
|
@ -32,6 +36,7 @@ IF (${CMAKE_CXX_COMPILER_ID} STREQUAL "MSVC" OR ${COMPILER_CLANG_CL})
|
|||
SET (ESCARGOT_CXXFLAGS_RELEASE ${ESCARGOT_CXXFLAGS_RELEASE} /Os)
|
||||
ENDIF()
|
||||
ELSEIF (${CMAKE_CXX_COMPILER_ID} STREQUAL "GNU")
|
||||
SET (ESCARGOT_COMPILER_ID "GCC")
|
||||
SET (ESCARGOT_CXXFLAGS
|
||||
${ESCARGOT_CXXFLAGS}
|
||||
-std=c++11 -g3
|
||||
|
|
@ -46,6 +51,11 @@ ELSEIF (${CMAKE_CXX_COMPILER_ID} STREQUAL "GNU")
|
|||
-Wno-unused-but-set-variable -Wno-unused-but-set-parameter
|
||||
-Wno-deprecated-declarations -Wno-unused-function
|
||||
)
|
||||
|
||||
IF (CMAKE_CXX_COMPILER_VERSION VERSION_GREATER_EQUAL 16)
|
||||
SET (ESCARGOT_CXXFLAGS ${ESCARGOT_CXXFLAGS} -Wno-maybe-uninitialized)
|
||||
ENDIF()
|
||||
|
||||
IF (CMAKE_CXX_COMPILER_VERSION VERSION_GREATER 9)
|
||||
SET (ESCARGOT_CXXFLAGS ${ESCARGOT_CXXFLAGS} -Wno-attributes -Wno-class-memaccess -Wno-deprecated-copy -Wno-cast-function-type -Wno-stringop-truncation -Wno-pessimizing-move -Wno-mismatched-new-delete -Wno-overloaded-virtual -Wno-dangling-pointer)
|
||||
endif()
|
||||
|
|
@ -59,7 +69,8 @@ ELSEIF (${CMAKE_CXX_COMPILER_ID} STREQUAL "GNU")
|
|||
ENDIF()
|
||||
ENDIF()
|
||||
SET (ESCARGOT_THIRDPARTY_CFLAGS -w -g3 -fdata-sections -ffunction-sections -fno-omit-frame-pointer -fvisibility=hidden)
|
||||
ELSEIF (${CMAKE_CXX_COMPILER_ID} STREQUAL "Clang")
|
||||
ELSEIF (${CMAKE_CXX_COMPILER_ID} MATCHES "Clang") #include Clang and AppleClang both
|
||||
SET (ESCARGOT_COMPILER_ID "CLANG")
|
||||
SET (ESCARGOT_CXXFLAGS
|
||||
${ESCARGOT_CXXFLAGS}
|
||||
-std=c++11 -g3
|
||||
|
|
@ -72,7 +83,8 @@ ELSEIF (${CMAKE_CXX_COMPILER_ID} STREQUAL "Clang")
|
|||
-Wno-type-limits -Wno-unused-result -Wno-unused-variable -Wno-invalid-offsetof -Wno-unused-function
|
||||
-Wno-deprecated-declarations -Wno-parentheses-equality -Wno-dynamic-class-memaccess -Wno-deprecated-register
|
||||
-Wno-expansion-to-defined -Wno-return-type -Wno-overloaded-virtual -Wno-unused-private-field -Wno-deprecated-copy -Wno-atomic-alignment
|
||||
-Wno-ambiguous-reversed-operator -Wno-deprecated-enum-enum-conversion -Wno-deprecated-enum-float-conversion -Wno-braced-scalar-init -Wno-unused-parameter
|
||||
-Wno-ambiguous-reversed-operator -Wno-deprecated-enum-enum-conversion -Wno-deprecated-enum-float-conversion -Wno-braced-scalar-init -Wno-unused-parameter -Wno-deprecated-literal-operator -Wno-cast-function-type-mismatch
|
||||
-Wno-unknown-warning-option
|
||||
)
|
||||
IF (CMAKE_CXX_COMPILER_VERSION VERSION_GREATER 10)
|
||||
# this feature supported after clang version 11
|
||||
|
|
|
|||
|
|
@ -123,6 +123,7 @@ Requires(postun): /sbin/ldconfig
|
|||
# build requirements
|
||||
BuildRequires: cmake
|
||||
BuildRequires: ninja
|
||||
BuildRequires: python3
|
||||
BuildRequires: pkgconfig(dlog)
|
||||
BuildRequires: pkgconfig(bundle)
|
||||
|
||||
|
|
@ -280,7 +281,7 @@ cmake --build build/out_tizen_%{rpm}
|
|||
CXXFLAGS+=' -DESCARGOT_ENABLE_TEST '
|
||||
%endif
|
||||
|
||||
g++ src/shell/Shell.cpp -std=c++11 -Lbuild/out_tizen_%{rpm} -Isrc/ -Ithird_party/GCutil -Ithird_party/GCutil/bdwgc/include/gc -o build/out_tizen_%{rpm}/escargot -O2 -DNDEBUG -Wl,-rpath=\$ORIGIN -Wl,-rpath=%{_libdir}/escargot ${CXXFLAGS} -lescargot -lpthread
|
||||
g++ src/shell/Shell.cpp -std=c++11 -Lbuild/out_tizen_%{rpm} -Isrc/ -Ithird_party/GCutil/include -o build/out_tizen_%{rpm}/escargot -O2 -DNDEBUG -Wl,-rpath=\$ORIGIN -Wl,-rpath=%{_libdir}/escargot ${CXXFLAGS} -lescargot -lpthread
|
||||
g++ tools/test/test-data-runner/test-data-runner.cpp -o build/out_tizen_%{rpm}/test-data-runner -std=c++11 ${CXXFLAGS} -lpthread
|
||||
%endif
|
||||
|
||||
|
|
|
|||
|
|
@ -264,6 +264,16 @@ if (f.type == Type::B) { puts("failed in msvc."); }
|
|||
#define HAVE_BUILTIN_ATOMIC_FUNCTIONS
|
||||
#endif
|
||||
|
||||
#if defined(COMPILER_CLANG)
|
||||
#if __has_feature(address_sanitizer)
|
||||
#define ASAN_ENABLED
|
||||
#endif
|
||||
#elif defined(COMPILER_GCC)
|
||||
#if defined(__SANITIZE_ADDRESS__)
|
||||
#define ASAN_ENABLED
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#if defined(COMPILER_MSVC)
|
||||
#include <stddef.h>
|
||||
#endif
|
||||
|
|
@ -293,6 +303,7 @@ extern "C" {
|
|||
#if defined(OS_WINDOWS)
|
||||
#include <icu.h>
|
||||
#else
|
||||
#include <unicode/utypes.h>
|
||||
#include <unicode/locid.h>
|
||||
#include <unicode/uchar.h>
|
||||
#include <unicode/ustring.h>
|
||||
|
|
@ -328,6 +339,11 @@ UBool vzone_getTZURL(VZone* zone, UChar*& url, int32_t& urlLength);
|
|||
void vzone_getOffset3(VZone* zone, UDate date, UBool local, int32_t& rawOffset,
|
||||
int32_t& dstOffset, UErrorCode& ec);
|
||||
int32_t vzone_getRawOffset(VZone* zone);
|
||||
|
||||
// uresimp.h
|
||||
U_CAPI UResourceBundle* U_EXPORT2
|
||||
ures_findResource(const char* pathToResource,
|
||||
UResourceBundle* fillIn, UErrorCode* status);
|
||||
}
|
||||
#endif // !defined(OS_WINDOWS_UWP)
|
||||
|
||||
|
|
@ -619,6 +635,7 @@ using HashMap = tsl::robin_map<Key, T, Hash, KeyEqual, Allocator, StoreHash, Gro
|
|||
#include "heap/Heap.h"
|
||||
#include "util/Util.h"
|
||||
#include "util/Optional.h"
|
||||
#include "util/Variant.h"
|
||||
#include "runtime/ThreadLocal.h"
|
||||
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -43,6 +43,7 @@
|
|||
#include "runtime/BooleanObject.h"
|
||||
#include "runtime/RegExpObject.h"
|
||||
#include "runtime/ModuleNamespaceObject.h"
|
||||
#include "runtime/Job.h"
|
||||
#include "runtime/JobQueue.h"
|
||||
#include "runtime/PromiseObject.h"
|
||||
#include "runtime/ProxyObject.h"
|
||||
|
|
@ -269,15 +270,15 @@ void Globals::initialize(PlatformRef* platform)
|
|||
|
||||
void Globals::finalize()
|
||||
{
|
||||
RELEASE_ASSERT(!!g_globalsInited);
|
||||
g_globalsInited = false;
|
||||
// finalize global value or context including thread-local variables
|
||||
// this function should be invoked once at the end of the program
|
||||
RELEASE_ASSERT(!!g_globalsInited);
|
||||
ThreadLocal::finalize();
|
||||
|
||||
// Global::finalize should be called at the end of program
|
||||
// because it holds Platform which could be used in other Object's finalizer
|
||||
Global::finalize();
|
||||
g_globalsInited = false;
|
||||
}
|
||||
|
||||
bool Globals::isInitialized()
|
||||
|
|
@ -296,11 +297,12 @@ void Globals::initializeThread()
|
|||
|
||||
void Globals::finalizeThread()
|
||||
{
|
||||
RELEASE_ASSERT(!!g_globalsInited);
|
||||
g_globalsInited = false;
|
||||
Global::finalizeGC();
|
||||
// finalize thread-local variables
|
||||
// this function should be invoked once at the end of sub-thread
|
||||
RELEASE_ASSERT(!!g_globalsInited);
|
||||
ThreadLocal::finalize();
|
||||
g_globalsInited = false;
|
||||
}
|
||||
|
||||
bool Globals::supportsThreading()
|
||||
|
|
@ -1263,7 +1265,13 @@ StringRef* Evaluator::EvaluatorResult::resultOrErrorToString(ContextRef* ctx) co
|
|||
if (isSuccessful()) {
|
||||
return result->toStringWithoutException(ctx);
|
||||
} else {
|
||||
return ((ValueRef*)error.value())->toStringWithoutException(ctx);
|
||||
// Check if error value is valid before dereferencing
|
||||
// In some edge cases (e.g., nested eval throw with finally allocation),
|
||||
// the error value might be invalid or null
|
||||
if (error.hasValue()) {
|
||||
return ((ValueRef*)error.value())->toStringWithoutException(ctx);
|
||||
}
|
||||
return StringRef::emptyString();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1484,6 +1492,25 @@ Evaluator::EvaluatorResult VMInstanceRef::executePendingJob()
|
|||
return toEvaluatorResultRef(result);
|
||||
}
|
||||
|
||||
void VMInstanceRef::enqueueEvaluateJob(ContextRef* relalatedContext, EvaluateJobCallback callback, void* data)
|
||||
{
|
||||
struct Holder : public gc {
|
||||
EvaluateJobCallback callback;
|
||||
void* data;
|
||||
Holder(EvaluateJobCallback callback, void* data)
|
||||
: callback(callback)
|
||||
, data(data)
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
auto job = new EvaluateJob(toImpl(relalatedContext), [](ExecutionState& state, void* data) -> Value {
|
||||
Holder* holder = reinterpret_cast<Holder*>(data);
|
||||
return toImpl(holder->callback(toRef(&state), holder->data)); }, new Holder(callback, data));
|
||||
|
||||
toImpl(this)->enqueueJob(job);
|
||||
}
|
||||
|
||||
bool VMInstanceRef::hasPendingJobFromAnotherThread()
|
||||
{
|
||||
return toImpl(this)->hasPendingJobFromAnotherThread();
|
||||
|
|
@ -1963,8 +1990,9 @@ bool DebuggerOperationsRef::updateBreakpoint(WeakCodeRef* weakCodeRef, uint32_t
|
|||
|
||||
class DebuggerC : public Debugger {
|
||||
public:
|
||||
virtual void init(const char* options, Context* context) override {}
|
||||
virtual void parseCompleted(String* source, String* srcName, size_t originLineOffset, String* error = nullptr) override;
|
||||
virtual void stopAtBreakpoint(ByteCodeBlock* byteCodeBlock, uint32_t offset, ExecutionState* state) override;
|
||||
virtual bool stopAtBreakpoint(ByteCodeBlock* byteCodeBlock, uint32_t offset, ExecutionState* state) override;
|
||||
virtual void byteCodeReleaseNotification(ByteCodeBlock* byteCodeBlock) override;
|
||||
virtual void exceptionCaught(String* message, SavedStackTraceDataVector& exceptionTrace) override;
|
||||
virtual void consoleOut(String* output) override;
|
||||
|
|
@ -2035,7 +2063,7 @@ static LexicalEnvironment* getFunctionLexEnv(ExecutionState* state)
|
|||
return nullptr;
|
||||
}
|
||||
|
||||
void DebuggerC::stopAtBreakpoint(ByteCodeBlock* byteCodeBlock, uint32_t offset, ExecutionState* state)
|
||||
bool DebuggerC::stopAtBreakpoint(ByteCodeBlock* byteCodeBlock, uint32_t offset, ExecutionState* state)
|
||||
{
|
||||
DebuggerOperationsRef::BreakpointOperations operations(reinterpret_cast<DebuggerOperationsRef::WeakCodeRef*>(byteCodeBlock), toRef(state), offset);
|
||||
|
||||
|
|
@ -2070,6 +2098,7 @@ void DebuggerC::stopAtBreakpoint(ByteCodeBlock* byteCodeBlock, uint32_t offset,
|
|||
break;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void DebuggerC::byteCodeReleaseNotification(ByteCodeBlock* byteCodeBlock)
|
||||
|
|
@ -3364,7 +3393,7 @@ void ContextRef::throwException(ValueRef* exceptionValue)
|
|||
toImpl(this)->throwException(s, toImpl(exceptionValue));
|
||||
}
|
||||
|
||||
bool ContextRef::initDebugger(DebuggerOperationsRef::DebuggerClient* debuggerClient)
|
||||
bool ContextRef::initDebuggerClient(DebuggerOperationsRef::DebuggerClient* debuggerClient)
|
||||
{
|
||||
#ifdef ESCARGOT_DEBUGGER
|
||||
Context* context = toImpl(this);
|
||||
|
|
@ -3393,10 +3422,10 @@ bool ContextRef::disableDebugger()
|
|||
#endif
|
||||
}
|
||||
|
||||
bool ContextRef::initDebuggerRemote(const char* options)
|
||||
bool ContextRef::initDebugger(const char* options)
|
||||
{
|
||||
#ifdef ESCARGOT_DEBUGGER
|
||||
return toImpl(this)->initDebuggerRemote(options);
|
||||
return toImpl(this)->initDebugger(options);
|
||||
#else /* !ESCARGOT_DEBUGGER */
|
||||
return false;
|
||||
#endif /* ESCARGOT_DEBUGGER */
|
||||
|
|
@ -3420,6 +3449,24 @@ bool ContextRef::isWaitBeforeExit()
|
|||
#endif /* ESCARGOT_DEBUGGER */
|
||||
}
|
||||
|
||||
bool ContextRef::isDebuggerRestartTrue()
|
||||
{
|
||||
#ifdef ESCARGOT_DEBUGGER
|
||||
return isDebuggerRunning() && toImpl(this)->debugger()->getRestart();
|
||||
#else /* !ESCARGOT_DEBUGGER */
|
||||
return false;
|
||||
#endif /* ESCARGOT_DEBUGGER */
|
||||
}
|
||||
|
||||
void ContextRef::setDebuggerRestart()
|
||||
{
|
||||
#ifdef ESCARGOT_DEBUGGER
|
||||
if (isDebuggerRunning()) {
|
||||
toImpl(this)->debugger()->setRestart(false);
|
||||
}
|
||||
#endif /* ESCARGOT_DEBUGGER */
|
||||
}
|
||||
|
||||
void ContextRef::printDebugger(StringRef* output)
|
||||
{
|
||||
#ifdef ESCARGOT_DEBUGGER
|
||||
|
|
@ -5106,6 +5153,10 @@ ValueRef* SerializerRef::deserializeFrom(ContextRef* context, std::istringstream
|
|||
{
|
||||
std::unique_ptr<SerializedValue> value = Serializer::deserializeFrom(input);
|
||||
|
||||
if (!value) {
|
||||
return ValueRef::createUndefined();
|
||||
}
|
||||
|
||||
SandBox sb(toImpl(context));
|
||||
auto result = sb.run([](ExecutionState& state, void* data) -> Value {
|
||||
std::unique_ptr<SerializedValue>* value = (std::unique_ptr<SerializedValue>*)data;
|
||||
|
|
|
|||
|
|
@ -767,6 +767,8 @@ public:
|
|||
|
||||
bool hasPendingJob();
|
||||
Evaluator::EvaluatorResult executePendingJob();
|
||||
typedef ValueRef* (*EvaluateJobCallback)(ExecutionStateRef* state, void* data);
|
||||
void enqueueEvaluateJob(ContextRef* relatedContext, EvaluateJobCallback callback, void* data);
|
||||
|
||||
bool hasPendingJobFromAnotherThread();
|
||||
bool waitEventFromAnotherThread(unsigned timeoutInMillisecond = 0); // zero means infinity
|
||||
|
|
@ -948,16 +950,18 @@ public:
|
|||
bool canThrowException();
|
||||
void throwException(ValueRef* exceptionValue);
|
||||
|
||||
bool initDebugger(DebuggerOperationsRef::DebuggerClient* debuggerClient);
|
||||
bool initDebuggerClient(DebuggerOperationsRef::DebuggerClient* debuggerClient);
|
||||
bool disableDebugger();
|
||||
// available options(separator is ';')
|
||||
// "--port=6501", default for TCP debugger
|
||||
bool initDebuggerRemote(const char* options);
|
||||
bool initDebugger(const char* options);
|
||||
bool isDebuggerRunning();
|
||||
bool isWaitBeforeExit();
|
||||
bool isDebuggerRestartTrue();
|
||||
void printDebugger(StringRef* output);
|
||||
void pumpDebuggerEvents();
|
||||
void setAsAlwaysStopState();
|
||||
void setDebuggerRestart();
|
||||
StringRef* getClientSource(StringRef** sourceName);
|
||||
|
||||
typedef OptionalRef<ValueRef> (*VirtualIdentifierCallback)(ExecutionStateRef* state, ValueRef* name);
|
||||
|
|
|
|||
|
|
@ -23,9 +23,12 @@
|
|||
#include "runtime/VMInstance.h"
|
||||
#include "runtime/ArrayObject.h"
|
||||
#include "runtime/IteratorObject.h"
|
||||
#include "runtime/AsyncFromSyncIteratorObject.h"
|
||||
#include "runtime/ToStringRecursionPreventer.h"
|
||||
#include "runtime/ErrorObject.h"
|
||||
#include "runtime/NativeFunctionObject.h"
|
||||
#include "runtime/ExtendedNativeFunctionObject.h"
|
||||
#include "runtime/PromiseObject.h"
|
||||
#include "interpreter/ByteCodeInterpreter.h"
|
||||
|
||||
namespace Escargot {
|
||||
|
|
@ -327,6 +330,403 @@ static Value builtinArrayFrom(ExecutionState& state, Value thisValue, size_t arg
|
|||
return A;
|
||||
}
|
||||
|
||||
struct ArrayFromAsyncRecord : public PointerValue {
|
||||
bool m_mapping;
|
||||
int m_awaitResumeStage;
|
||||
EncodedValue m_mapper;
|
||||
EncodedValue m_thisArg;
|
||||
EncodedValue m_a;
|
||||
int64_t m_k;
|
||||
IteratorRecord* m_iteratorRecord;
|
||||
EncodedValue m_asyncCloseReturnValue;
|
||||
EncodedValue m_asyncCloseInnerResult;
|
||||
EncodedValue m_throwCompletion;
|
||||
EncodedValue m_awaitResult;
|
||||
EncodedValue m_nextResult;
|
||||
EncodedValue m_nextValue;
|
||||
EncodedValue m_mappedValue;
|
||||
|
||||
PromiseReaction::Capability m_promiseCapability;
|
||||
|
||||
ArrayFromAsyncRecord(bool mapping, EncodedValue mapper, EncodedValue thisArg, EncodedValue a, IteratorRecord* record, PromiseReaction::Capability promiseCapability)
|
||||
: m_mapping(mapping)
|
||||
, m_awaitResumeStage(0)
|
||||
, m_mapper(mapper)
|
||||
, m_thisArg(thisArg)
|
||||
, m_a(a)
|
||||
, m_k(0)
|
||||
, m_iteratorRecord(record)
|
||||
, m_promiseCapability(promiseCapability)
|
||||
{
|
||||
}
|
||||
|
||||
virtual bool isArrayFromAsyncRecord() const override
|
||||
{
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
static PromiseObject* arrayFromAsyncAsyncAwaitOperation(ExecutionState& state, const Value& awaitValue, ArrayFromAsyncRecord* data, size_t stage);
|
||||
|
||||
#define ASYNCITERATOR_CLOSE(iteratorRecord, stage, completion, completionHasThrownRecord) \
|
||||
try { \
|
||||
data->m_asyncCloseReturnValue = Object::getMethod(state, iteratorRecord->m_iterator, state.context()->staticStrings().stringReturn); \
|
||||
} catch (const Value& error) { \
|
||||
if (completionHasThrownRecord) { \
|
||||
data->m_promiseCapability.m_promise->asPromiseObject()->reject(state, completion); \
|
||||
} \
|
||||
data->m_promiseCapability.m_promise->asPromiseObject()->reject(state, error); \
|
||||
return; \
|
||||
} \
|
||||
if (!data->m_asyncCloseReturnValue.isUndefined()) { \
|
||||
try { \
|
||||
data->m_asyncCloseInnerResult = Object::call(state, data->m_asyncCloseReturnValue, iteratorRecord->m_iterator, 0, nullptr); \
|
||||
} catch (const Value& error) { \
|
||||
if (completionHasThrownRecord) { \
|
||||
data->m_promiseCapability.m_promise->asPromiseObject()->reject(state, completion); \
|
||||
} \
|
||||
data->m_promiseCapability.m_promise->asPromiseObject()->reject(state, error); \
|
||||
return; \
|
||||
} \
|
||||
arrayFromAsyncAsyncAwaitOperation(state, data->m_asyncCloseInnerResult, data, stage); \
|
||||
return; \
|
||||
} \
|
||||
ArrayFromAsyncAsyncWorkerStage##stage : if (completionHasThrownRecord) { data->m_promiseCapability.m_promise->asPromiseObject()->reject(state, completion); } \
|
||||
return;
|
||||
|
||||
static void builtinArrayFromAsyncAsyncWorker(ExecutionState& state, ArrayFromAsyncRecord* data)
|
||||
{
|
||||
if (data->m_awaitResumeStage == 1) {
|
||||
data->m_awaitResumeStage = 0;
|
||||
goto ArrayFromAsyncAsyncWorkerStage1;
|
||||
} else if (data->m_awaitResumeStage == 2) {
|
||||
data->m_awaitResumeStage = 0;
|
||||
goto ArrayFromAsyncAsyncWorkerStage2;
|
||||
} else if (data->m_awaitResumeStage == 3) {
|
||||
data->m_awaitResumeStage = 0;
|
||||
goto ArrayFromAsyncAsyncWorkerStage3;
|
||||
} else if (data->m_awaitResumeStage == 4) {
|
||||
data->m_awaitResumeStage = 0;
|
||||
goto ArrayFromAsyncAsyncWorkerStage4;
|
||||
} else if (data->m_awaitResumeStage == 5) {
|
||||
data->m_awaitResumeStage = 0;
|
||||
goto ArrayFromAsyncAsyncWorkerStage5;
|
||||
}
|
||||
// Repeat,
|
||||
while (true) {
|
||||
// If k ≥ 2**53 - 1, then
|
||||
if (data->m_k >= ((1LL << 53) - 1)) {
|
||||
// Let error be ThrowCompletion(a newly created TypeError object).
|
||||
data->m_throwCompletion = ErrorObject::createError(state, ErrorCode::TypeError, new ASCIIStringFromExternalMemory("Got invalid index"));
|
||||
// Return ? AsyncIteratorClose(iteratorRecord, error).
|
||||
ASYNCITERATOR_CLOSE(data->m_iteratorRecord, 1, data->m_throwCompletion, true);
|
||||
}
|
||||
// Let Pk be ! ToString(𝔽(k)).
|
||||
// Let nextResult be ? Call(iteratorRecord.[[NextMethod]], iteratorRecord.[[Iterator]]).
|
||||
data->m_nextResult = Object::call(state, data->m_iteratorRecord->m_nextMethod, data->m_iteratorRecord->m_iterator, 0, nullptr);
|
||||
// Set nextResult to ? Await(nextResult).
|
||||
arrayFromAsyncAsyncAwaitOperation(state, data->m_nextResult, data, 2);
|
||||
return;
|
||||
ArrayFromAsyncAsyncWorkerStage2:
|
||||
data->m_nextResult = data->m_awaitResult;
|
||||
// If nextResult is not an Object, throw a TypeError excetion.
|
||||
if (!data->m_nextResult.toValue().isObject()) {
|
||||
ErrorObject::throwBuiltinError(state, ErrorCode::TypeError, "iterator result is not Object");
|
||||
}
|
||||
// Let done be ? IteratorComplete(nextResult).
|
||||
// If done is true, then
|
||||
if (IteratorObject::iteratorComplete(state, data->m_nextResult.toValue().asObject())) {
|
||||
// Perform ? Set(A, "length", 𝔽(k), true).
|
||||
try {
|
||||
data->m_a.toValue().toObject(state)->setThrowsException(state, state.context()->staticStrings().length, Value(data->m_k), data->m_a.toValue());
|
||||
} catch (const Value& error) {
|
||||
data->m_promiseCapability.m_promise->asPromiseObject()->reject(state, error);
|
||||
return;
|
||||
}
|
||||
// Return A.
|
||||
data->m_promiseCapability.m_promise->asPromiseObject()->fulfill(state, data->m_a);
|
||||
return;
|
||||
}
|
||||
// Let nextValue be ? IteratorValue(nextResult)
|
||||
data->m_nextValue = IteratorObject::iteratorValue(state, data->m_nextResult.toValue().asObject());
|
||||
|
||||
// If mapping is true, then
|
||||
if (data->m_mapping) {
|
||||
// Let mappedValue be Completion(Call(mapper, thisArg, « nextValue, 𝔽(k) »)).
|
||||
// IfAbruptCloseAsyncIterator(mappedValue, iteratorRecord).
|
||||
try {
|
||||
data->m_throwCompletion = Value(Value::EmptyValue);
|
||||
Value arg[2] = { data->m_nextValue, Value(data->m_k) };
|
||||
data->m_mappedValue = Object::call(state, data->m_mapper, data->m_thisArg, 2, arg);
|
||||
} catch (const Value& error) {
|
||||
data->m_throwCompletion = error;
|
||||
}
|
||||
if (!data->m_throwCompletion.isEmpty()) {
|
||||
ASYNCITERATOR_CLOSE(data->m_iteratorRecord, 3, data->m_throwCompletion, true);
|
||||
}
|
||||
// Set mappedValue to Completion(Await(mappedValue)).
|
||||
// IfAbruptCloseAsyncIterator(mappedValue, iteratorRecord).
|
||||
arrayFromAsyncAsyncAwaitOperation(state, data->m_mappedValue, data, 4);
|
||||
return;
|
||||
ArrayFromAsyncAsyncWorkerStage4:
|
||||
data->m_mappedValue = data->m_awaitResult;
|
||||
} else {
|
||||
data->m_mappedValue = data->m_nextValue;
|
||||
}
|
||||
|
||||
// Let defineStatus be Completion(CreateDataPropertyOrThrow(A, Pk, mappedValue)).
|
||||
// IfAbruptCloseAsyncIterator(defineStatus, iteratorRecord).
|
||||
try {
|
||||
data->m_throwCompletion = Value(Value::EmptyValue);
|
||||
data->m_a.toValue().toObject(state)->defineOwnPropertyThrowsException(state, ObjectPropertyName(state, data->m_k),
|
||||
ObjectPropertyDescriptor(data->m_mappedValue, ObjectPropertyDescriptor::AllPresent));
|
||||
} catch (const Value& error) {
|
||||
data->m_throwCompletion = error;
|
||||
}
|
||||
if (!data->m_throwCompletion.isEmpty()) {
|
||||
ASYNCITERATOR_CLOSE(data->m_iteratorRecord, 5, data->m_throwCompletion, true);
|
||||
}
|
||||
// Set k to k + 1.
|
||||
data->m_k++;
|
||||
}
|
||||
}
|
||||
|
||||
static Value arrayFromAsyncAsyncAwaitFulfilledFunction(ExecutionState& state, Value thisValue, size_t argc, Value* argv, Optional<Object*> newTarget)
|
||||
{
|
||||
auto s = state.resolveCallee()->asExtendedNativeFunctionObject();
|
||||
auto data = s->internalSlot(0).asPointerValue()->asArrayFromAsyncRecord();
|
||||
data->m_awaitResult = argv[0];
|
||||
builtinArrayFromAsyncAsyncWorker(state, data);
|
||||
return Value();
|
||||
}
|
||||
|
||||
static Value arrayFromAsyncAsyncAwaitRejectedFunction(ExecutionState& state, Value thisValue, size_t argc, Value* argv, Optional<Object*> newTarget)
|
||||
{
|
||||
auto s = state.resolveCallee()->asExtendedNativeFunctionObject();
|
||||
auto data = s->internalSlot(0).asPointerValue()->asArrayFromAsyncRecord();
|
||||
data->m_promiseCapability.m_promise->asPromiseObject()->reject(state, *argv);
|
||||
return Value();
|
||||
}
|
||||
|
||||
static PromiseObject* arrayFromAsyncAsyncAwaitOperation(ExecutionState& state, const Value& awaitValue, ArrayFromAsyncRecord* source, size_t stage)
|
||||
{
|
||||
source->m_awaitResumeStage = stage;
|
||||
PromiseObject* promise = PromiseObject::promiseResolve(state, state.context()->globalObject()->promise(), awaitValue)->asPromiseObject();
|
||||
ExtendedNativeFunctionObject* onFulfilled = new ExtendedNativeFunctionObjectImpl<1>(state, NativeFunctionInfo(AtomicString(), arrayFromAsyncAsyncAwaitFulfilledFunction, 1));
|
||||
onFulfilled->setInternalSlot(0, source);
|
||||
ExtendedNativeFunctionObject* onRejected = new ExtendedNativeFunctionObjectImpl<1>(state, NativeFunctionInfo(AtomicString(), arrayFromAsyncAsyncAwaitRejectedFunction, 1));
|
||||
onRejected->setInternalSlot(0, source);
|
||||
promise->then(state, onFulfilled, onRejected, Optional<PromiseReaction::Capability>());
|
||||
return promise;
|
||||
}
|
||||
|
||||
struct ArrayFromAsyncSyncRecord : public PointerValue {
|
||||
bool m_mapping;
|
||||
int m_awaitResumeStage;
|
||||
EncodedValue m_mapper;
|
||||
EncodedValue m_thisArg;
|
||||
EncodedValue m_a;
|
||||
int64_t m_len;
|
||||
Object* m_arrayLike;
|
||||
int64_t m_k;
|
||||
EncodedValue m_awaitResult;
|
||||
EncodedValue m_kValue;
|
||||
EncodedValue m_mappedValue;
|
||||
|
||||
PromiseReaction::Capability m_promiseCapability;
|
||||
|
||||
ArrayFromAsyncSyncRecord(bool mapping, EncodedValue mapper, EncodedValue thisArg, EncodedValue a, int64_t len, Object* arrayLike, PromiseReaction::Capability promiseCapability)
|
||||
: m_mapping(mapping)
|
||||
, m_awaitResumeStage(0)
|
||||
, m_mapper(mapper)
|
||||
, m_thisArg(thisArg)
|
||||
, m_a(a)
|
||||
, m_len(len)
|
||||
, m_arrayLike(arrayLike)
|
||||
, m_k(0)
|
||||
, m_promiseCapability(promiseCapability)
|
||||
{
|
||||
}
|
||||
|
||||
virtual bool isArrayFromAsyncSyncRecord() const override
|
||||
{
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
static PromiseObject* arrayFromAsyncSyncAwaitOperation(ExecutionState& state, const Value& awaitValue, ArrayFromAsyncSyncRecord* data, size_t stage);
|
||||
|
||||
static void builtinArrayFromSyncAsyncWorker(ExecutionState& state, ArrayFromAsyncSyncRecord* data)
|
||||
{
|
||||
if (data->m_awaitResumeStage == 1) {
|
||||
data->m_awaitResumeStage = 0;
|
||||
goto ArrayFromAsyncSyncWorkerStage1;
|
||||
} else if (data->m_awaitResumeStage == 2) {
|
||||
data->m_awaitResumeStage = 0;
|
||||
goto ArrayFromAsyncSyncWorkerStage2;
|
||||
}
|
||||
// Let k be 0.
|
||||
// Repeat, while k < len,
|
||||
while (data->m_k < data->m_len) {
|
||||
// Let Pk be ! ToString(𝔽(k)).
|
||||
// Let kValue be ? Get(arrayLike, Pk).
|
||||
try {
|
||||
data->m_kValue = data->m_arrayLike->get(state, ObjectPropertyName(state, data->m_k)).value(state, data->m_arrayLike);
|
||||
} catch (const Value& error) {
|
||||
data->m_promiseCapability.m_promise->asPromiseObject()->reject(state, error);
|
||||
return;
|
||||
}
|
||||
// Set kValue to ? Await(kValue).
|
||||
arrayFromAsyncSyncAwaitOperation(state, data->m_kValue, data, 1);
|
||||
return;
|
||||
ArrayFromAsyncSyncWorkerStage1:
|
||||
data->m_kValue = data->m_awaitResult;
|
||||
|
||||
// If mapping is true, then
|
||||
if (data->m_mapping) {
|
||||
// Let mappedValue be ? Call(mapper, thisArg, « kValue, 𝔽(k) »).
|
||||
try {
|
||||
Value arg[2] = { data->m_kValue, Value(data->m_k) };
|
||||
data->m_mappedValue = Object::call(state, data->m_mapper, data->m_thisArg, 2, arg);
|
||||
} catch (const Value& error) {
|
||||
data->m_promiseCapability.m_promise->asPromiseObject()->reject(state, error);
|
||||
return;
|
||||
}
|
||||
// Set mappedValue to ? Await(mappedValue).
|
||||
arrayFromAsyncSyncAwaitOperation(state, data->m_mappedValue, data, 2);
|
||||
return;
|
||||
ArrayFromAsyncSyncWorkerStage2:
|
||||
data->m_mappedValue = data->m_awaitResult;
|
||||
} else {
|
||||
// Else,
|
||||
// Let mappedValue be kValue.
|
||||
data->m_mappedValue = data->m_kValue;
|
||||
}
|
||||
// Perform ? CreateDataPropertyOrThrow(A, Pk, mappedValue).
|
||||
data->m_a.toValue().toObject(state)->defineOwnPropertyThrowsException(
|
||||
state, ObjectPropertyName(state, data->m_k), ObjectPropertyDescriptor(data->m_mappedValue, ObjectPropertyDescriptor::AllPresent));
|
||||
// Set k to k + 1.
|
||||
data->m_k++;
|
||||
}
|
||||
// Perform ? Set(A, "length", 𝔽(len), true).
|
||||
// Return A.
|
||||
try {
|
||||
data->m_a.toValue().toObject(state)->setThrowsException(state, state.context()->staticStrings().length, Value(data->m_k), data->m_a.toValue());
|
||||
} catch (const Value& error) {
|
||||
data->m_promiseCapability.m_promise->asPromiseObject()->reject(state, error);
|
||||
return;
|
||||
}
|
||||
data->m_promiseCapability.m_promise->asPromiseObject()->fulfill(state, data->m_a);
|
||||
}
|
||||
|
||||
static Value arrayFromAsyncSyncAwaitFulfilledFunction(ExecutionState& state, Value thisValue, size_t argc, Value* argv, Optional<Object*> newTarget)
|
||||
{
|
||||
auto s = state.resolveCallee()->asExtendedNativeFunctionObject();
|
||||
auto data = s->internalSlot(0).asPointerValue()->asArrayFromAsyncSyncRecord();
|
||||
data->m_awaitResult = argv[0];
|
||||
builtinArrayFromSyncAsyncWorker(state, data);
|
||||
return Value();
|
||||
}
|
||||
|
||||
static Value arrayFromAsyncSyncAwaitRejectedFunction(ExecutionState& state, Value thisValue, size_t argc, Value* argv, Optional<Object*> newTarget)
|
||||
{
|
||||
auto s = state.resolveCallee()->asExtendedNativeFunctionObject();
|
||||
auto data = s->internalSlot(0).asPointerValue()->asArrayFromAsyncSyncRecord();
|
||||
data->m_promiseCapability.m_promise->asPromiseObject()->reject(state, *argv);
|
||||
return Value();
|
||||
}
|
||||
|
||||
static PromiseObject* arrayFromAsyncSyncAwaitOperation(ExecutionState& state, const Value& awaitValue, ArrayFromAsyncSyncRecord* source, size_t stage)
|
||||
{
|
||||
source->m_awaitResumeStage = stage;
|
||||
PromiseObject* promise = PromiseObject::promiseResolve(state, state.context()->globalObject()->promise(), awaitValue)->asPromiseObject();
|
||||
ExtendedNativeFunctionObject* onFulfilled = new ExtendedNativeFunctionObjectImpl<1>(state, NativeFunctionInfo(AtomicString(), arrayFromAsyncSyncAwaitFulfilledFunction, 1));
|
||||
onFulfilled->setInternalSlot(0, source);
|
||||
ExtendedNativeFunctionObject* onRejected = new ExtendedNativeFunctionObjectImpl<1>(state, NativeFunctionInfo(AtomicString(), arrayFromAsyncSyncAwaitRejectedFunction, 1));
|
||||
onRejected->setInternalSlot(0, source);
|
||||
promise->then(state, onFulfilled, onRejected, Optional<PromiseReaction::Capability>());
|
||||
return promise;
|
||||
}
|
||||
|
||||
|
||||
// Array.fromAsync ( asyncItems [ , mapper [ , thisArg ] ] )
|
||||
static Value builtinArrayFromAsync(ExecutionState& state, Value thisValue, size_t argc, Value* argv, Optional<Object*> newTarget)
|
||||
{
|
||||
// Let C be the this value.
|
||||
const Value& C = thisValue;
|
||||
Value mapper = argc < 2 ? Value() : argv[1];
|
||||
Value thisArg = argc < 3 ? Value() : argv[2];
|
||||
// If mapper is undefined, then
|
||||
bool mapping = false;
|
||||
if (mapper.isUndefined()) {
|
||||
// Let mapping be false.
|
||||
} else {
|
||||
// Else,
|
||||
// If IsCallable(mapper) is false, throw a TypeError exception.
|
||||
if (!mapper.isCallable()) {
|
||||
ErrorObject::throwBuiltinError(state, ErrorCode::TypeError, "argument mapper is not callable nor undefined");
|
||||
}
|
||||
// Let mapping be true.
|
||||
mapping = true;
|
||||
}
|
||||
// Let usingAsyncIterator be ? GetMethod(asyncItems, %Symbol.asyncIterator%).
|
||||
auto usingAsyncIterator = Object::getMethod(state, argv[0], state.context()->vmInstance()->globalSymbols().asyncIterator);
|
||||
// If usingAsyncIterator is undefined, then
|
||||
Value usingSyncIterator;
|
||||
if (usingAsyncIterator.isUndefined()) {
|
||||
// Let usingSyncIterator be ? GetMethod(asyncItems, %Symbol.iterator%).
|
||||
usingSyncIterator = Object::getMethod(state, argv[0], state.context()->vmInstance()->globalSymbols().iterator);
|
||||
}
|
||||
// Let iteratorRecord be undefined.
|
||||
Optional<IteratorRecord*> iteratorRecord;
|
||||
// If usingAsyncIterator is not undefined, then
|
||||
if (!usingAsyncIterator.isUndefined()) {
|
||||
// Set iteratorRecord to ? GetIteratorFromMethod(asyncItems, usingAsyncIterator).
|
||||
iteratorRecord = IteratorObject::getIteratorFromMethod(state, argv[0], usingAsyncIterator);
|
||||
} else if (!usingSyncIterator.isUndefined()) {
|
||||
// Else if usingSyncIterator is not undefined, then
|
||||
// Set iteratorRecord to CreateAsyncFromSyncIterator(? GetIteratorFromMethod(asyncItems, usingSyncIterator)).
|
||||
iteratorRecord = AsyncFromSyncIteratorObject::createAsyncFromSyncIterator(state, IteratorObject::getIteratorFromMethod(state, argv[0], usingSyncIterator));
|
||||
}
|
||||
// If iteratorRecord is not undefined, then
|
||||
if (iteratorRecord) {
|
||||
// If IsConstructor(C) is true, then
|
||||
Value A;
|
||||
if (C.isConstructor()) {
|
||||
// Let A be ? Construct(C).
|
||||
A = Object::construct(state, C, 0, nullptr);
|
||||
} else {
|
||||
// Else,
|
||||
// Let A be ! ArrayCreate(0).
|
||||
A = new ArrayObject(state);
|
||||
}
|
||||
// Let k be 0.
|
||||
auto record = new ArrayFromAsyncRecord(mapping, mapper, thisArg, A, iteratorRecord.value(), PromiseObject::newPromiseCapability(state, state.context()->globalObject()->promise()));
|
||||
arrayFromAsyncAsyncAwaitOperation(state, Value(), record, 0);
|
||||
return record->m_promiseCapability.m_promise;
|
||||
} else {
|
||||
// Let arrayLike be ! ToObject(asyncItems).
|
||||
auto arrayLike = argv[0].toObject(state);
|
||||
// Let len be ? LengthOfArrayLike(arrayLike).
|
||||
int64_t len = static_cast<int64_t>(arrayLike->length(state));
|
||||
Value A;
|
||||
// If IsConstructor(C) is true, then
|
||||
if (C.isConstructor()) {
|
||||
// Let A be ? Construct(C, « 𝔽(len) »).
|
||||
Value v(len);
|
||||
A = Object::construct(state, C, 1, &v);
|
||||
} else {
|
||||
// Else,
|
||||
// Let A be ? ArrayCreate(len).
|
||||
A = new ArrayObject(state, len);
|
||||
}
|
||||
|
||||
auto record = new ArrayFromAsyncSyncRecord(mapping, mapper, thisArg, A, len, arrayLike, PromiseObject::newPromiseCapability(state, state.context()->globalObject()->promise()));
|
||||
arrayFromAsyncSyncAwaitOperation(state, Value(), record, 0);
|
||||
return record->m_promiseCapability.m_promise;
|
||||
}
|
||||
}
|
||||
|
||||
// Array.of ( ...items )
|
||||
static Value builtinArrayOf(ExecutionState& state, Value thisValue, size_t argc, Value* argv, Optional<Object*> newTarget)
|
||||
{
|
||||
|
|
@ -391,8 +791,8 @@ static Value builtinArrayJoin(ExecutionState& state, Value thisValue, size_t arg
|
|||
if (elem.isUndefined()) {
|
||||
struct Data {
|
||||
bool exists;
|
||||
int64_t cur;
|
||||
int64_t ret;
|
||||
Value::ValueIndex cur;
|
||||
Value::ValueIndex ret;
|
||||
} data;
|
||||
data.exists = false;
|
||||
data.cur = curIndex;
|
||||
|
|
@ -405,12 +805,11 @@ static Value builtinArrayJoin(ExecutionState& state, Value thisValue, size_t arg
|
|||
break;
|
||||
}
|
||||
ptr.asObject()->enumeration(state, [](ExecutionState& state, Object* self, const ObjectPropertyName& name, const ObjectStructurePropertyDescriptor& desc, void* data) {
|
||||
int64_t index;
|
||||
Data* e = (Data*)data;
|
||||
int64_t* ret = &e->ret;
|
||||
Value::ValueIndex* ret = &e->ret;
|
||||
Value key = name.toPlainValue();
|
||||
index = key.toNumber(state);
|
||||
if ((uint64_t)index != Value::InvalidIndexValue) {
|
||||
Value::ValueIndex index = key.tryToUseAsIndex(state);
|
||||
if (index != Value::InvalidIndexValue) {
|
||||
if (self->get(state, name).value(state, self).isUndefined()) {
|
||||
return true;
|
||||
}
|
||||
|
|
@ -2100,6 +2499,9 @@ void GlobalObject::installArray(ExecutionState& state)
|
|||
m_array->directDefineOwnProperty(state, ObjectPropertyName(state.context()->staticStrings().from),
|
||||
ObjectPropertyDescriptor(new NativeFunctionObject(state, NativeFunctionInfo(state.context()->staticStrings().from, builtinArrayFrom, 1, NativeFunctionInfo::Strict)), (ObjectPropertyDescriptor::PresentAttribute)(ObjectPropertyDescriptor::WritablePresent | ObjectPropertyDescriptor::ConfigurablePresent)));
|
||||
|
||||
m_array->directDefineOwnProperty(state, ObjectPropertyName(state.context()->staticStrings().fromAsync),
|
||||
ObjectPropertyDescriptor(new NativeFunctionObject(state, NativeFunctionInfo(state.context()->staticStrings().fromAsync, builtinArrayFromAsync, 1, NativeFunctionInfo::Strict)), (ObjectPropertyDescriptor::PresentAttribute)(ObjectPropertyDescriptor::WritablePresent | ObjectPropertyDescriptor::ConfigurablePresent)));
|
||||
|
||||
m_array->directDefineOwnProperty(state, ObjectPropertyName(state.context()->staticStrings().of),
|
||||
ObjectPropertyDescriptor(new NativeFunctionObject(state, NativeFunctionInfo(state.context()->staticStrings().of, builtinArrayOf, 0, NativeFunctionInfo::Strict)), (ObjectPropertyDescriptor::PresentAttribute)(ObjectPropertyDescriptor::WritablePresent | ObjectPropertyDescriptor::ConfigurablePresent)));
|
||||
|
||||
|
|
@ -2176,6 +2578,8 @@ void GlobalObject::installArray(ExecutionState& state)
|
|||
ObjectPropertyDescriptor(new NativeFunctionObject(state, NativeFunctionInfo(state.context()->staticStrings().findLast, builtinArrayFindLast, 1, NativeFunctionInfo::Strict)), (ObjectPropertyDescriptor::PresentAttribute)(ObjectPropertyDescriptor::WritablePresent | ObjectPropertyDescriptor::ConfigurablePresent)));
|
||||
m_arrayPrototype->directDefineOwnProperty(state, ObjectPropertyName(state.context()->staticStrings().findLastIndex),
|
||||
ObjectPropertyDescriptor(new NativeFunctionObject(state, NativeFunctionInfo(state.context()->staticStrings().findLastIndex, builtinArrayFindLastIndex, 1, NativeFunctionInfo::Strict)), (ObjectPropertyDescriptor::PresentAttribute)(ObjectPropertyDescriptor::WritablePresent | ObjectPropertyDescriptor::ConfigurablePresent)));
|
||||
m_arrayPrototype->directDefineOwnProperty(state, ObjectPropertyName(state.context()->staticStrings().fromAsync),
|
||||
ObjectPropertyDescriptor(new NativeFunctionObject(state, NativeFunctionInfo(state.context()->staticStrings().fromAsync, builtinArrayFromAsync, 1, NativeFunctionInfo::Strict)), (ObjectPropertyDescriptor::PresentAttribute)(ObjectPropertyDescriptor::WritablePresent | ObjectPropertyDescriptor::ConfigurablePresent)));
|
||||
|
||||
Object* blackList = new Object(state, Object::PrototypeIsNull);
|
||||
blackList->markThisObjectDontNeedStructureTransitionTable();
|
||||
|
|
|
|||
|
|
@ -123,13 +123,12 @@ static Value builtinArrayBufferDetachedGetter(ExecutionState& state, Value thisV
|
|||
static Value builtinArrayBufferResize(ExecutionState& state, Value thisValue, size_t argc, Value* argv, Optional<Object*> newTarget)
|
||||
{
|
||||
RESOLVE_THIS_BINDING_TO_ARRAYBUFFER(obj, ArrayBuffer, resize);
|
||||
obj->throwTypeErrorIfDetached(state);
|
||||
|
||||
if (!obj->isResizableArrayBuffer()) {
|
||||
ErrorObject::throwBuiltinError(state, ErrorCode::TypeError, state.context()->staticStrings().ArrayBuffer.string(), true, state.context()->staticStrings().resize.string(), ErrorObject::Messages::GlobalObject_CalledOnIncompatibleReceiver);
|
||||
}
|
||||
|
||||
double newByteLength = argv[0].toInteger(state);
|
||||
obj->throwTypeErrorIfDetached(state);
|
||||
if (newByteLength < 0 || newByteLength > obj->maxByteLength()) {
|
||||
ErrorObject::throwBuiltinError(state, ErrorCode::RangeError, state.context()->staticStrings().ArrayBuffer.string(), true, state.context()->staticStrings().resize.string(), ErrorObject::Messages::GlobalObject_FirstArgumentInvalidLength);
|
||||
}
|
||||
|
|
@ -147,13 +146,23 @@ static Value builtinArrayBufferTransfer(ExecutionState& state, Value thisValue,
|
|||
uint64_t newByteLength = obj->byteLength();
|
||||
if (argc > 0 && !argv[0].isUndefined()) {
|
||||
newByteLength = argv[0].toIndex(state);
|
||||
if (UNLIKELY(newByteLength == Value::InvalidIndexValue)) {
|
||||
ErrorObject::throwBuiltinError(state, ErrorCode::RangeError, state.context()->staticStrings().ArrayBuffer.string(), true, state.context()->staticStrings().transfer.string(), ErrorObject::Messages::GlobalObject_FirstArgumentInvalidLength);
|
||||
}
|
||||
}
|
||||
|
||||
obj->throwTypeErrorIfDetached(state);
|
||||
Optional<uint64_t> maxLength;
|
||||
if (obj->isResizableArrayBuffer()) {
|
||||
maxLength = obj->maxByteLength();
|
||||
if (newByteLength > maxLength.value()) {
|
||||
ErrorObject::throwBuiltinError(state, ErrorCode::RangeError, state.context()->staticStrings().ArrayBuffer.string(), true, state.context()->staticStrings().transfer.string(), ErrorObject::Messages::GlobalObject_FirstArgumentInvalidLength);
|
||||
}
|
||||
} else {
|
||||
// For non-resizable ArrayBuffer, the new buffer should also be non-resizable
|
||||
maxLength = newByteLength;
|
||||
}
|
||||
ArrayBuffer* newValue = ArrayBufferObject::allocateArrayBuffer(state, state.context()->globalObject()->arrayBuffer(), newByteLength, maxLength);
|
||||
ArrayBuffer* newValue = ArrayBufferObject::allocateArrayBuffer(state, state.context()->globalObject()->arrayBuffer(), newByteLength, maxLength, obj->isResizableArrayBuffer());
|
||||
|
||||
// Let copyLength be min(newByteLength, O.[[ArrayBufferByteLength]]).
|
||||
// Perform CopyDataBlockBytes(toBlock, 0, fromBlock, 0, copyLength).
|
||||
|
|
@ -172,6 +181,9 @@ static Value builtinArrayBufferTransferToFixedLength(ExecutionState& state, Valu
|
|||
uint64_t newByteLength = obj->byteLength();
|
||||
if (argc > 0 && !argv[0].isUndefined()) {
|
||||
newByteLength = argv[0].toIndex(state);
|
||||
if (UNLIKELY(newByteLength == Value::InvalidIndexValue)) {
|
||||
ErrorObject::throwBuiltinError(state, ErrorCode::RangeError, state.context()->staticStrings().ArrayBuffer.string(), true, state.context()->staticStrings().transferToFixedLength.string(), ErrorObject::Messages::GlobalObject_FirstArgumentInvalidLength);
|
||||
}
|
||||
}
|
||||
|
||||
ArrayBuffer* newValue = ArrayBufferObject::allocateArrayBuffer(state, state.context()->globalObject()->arrayBuffer(), newByteLength, newByteLength, false);
|
||||
|
|
|
|||
|
|
@ -579,11 +579,6 @@ static Value builtinAtomicsIsLockFree(ExecutionState& state, Value thisValue, si
|
|||
#endif
|
||||
}
|
||||
|
||||
inline bool isIntegralNumber(double value)
|
||||
{
|
||||
return std::isfinite(value) && std::trunc(value) == value;
|
||||
}
|
||||
|
||||
// https://tc39.es/proposal-atomics-microwait/#Atomics.pause
|
||||
static Value builtinAtomicsPause(ExecutionState& state, Value thisValue, size_t argc, Value* argv, Optional<Object*> newTarget)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -41,54 +41,90 @@ namespace Escargot {
|
|||
|
||||
static Value builtinDataViewConstructor(ExecutionState& state, Value thisValue, size_t argc, Value* argv, Optional<Object*> newTarget)
|
||||
{
|
||||
// If NewTarget is undefined, throw a TypeError exception.
|
||||
if (!newTarget.hasValue()) {
|
||||
ErrorObject::throwBuiltinError(state, ErrorCode::TypeError, ErrorObject::Messages::GlobalObject_ConstructorRequiresNew);
|
||||
return Value();
|
||||
}
|
||||
// Perform ? RequireInternalSlot(buffer, [[ArrayBufferData]]).
|
||||
if (!(argv[0].isObject() && argv[0].asPointerValue()->isArrayBuffer())) {
|
||||
ErrorObject::throwBuiltinError(state, ErrorCode::TypeError, state.context()->staticStrings().DataView.string(), false, String::emptyString(), ErrorObject::Messages::GlobalObject_ThisNotArrayBufferObject);
|
||||
}
|
||||
|
||||
ArrayBuffer* buffer = argv[0].asObject()->asArrayBuffer();
|
||||
double byteOffset = 0;
|
||||
// Let offset be ? ToIndex(byteOffset).
|
||||
double offset = 0;
|
||||
if (argc >= 2) {
|
||||
Value& val = argv[1];
|
||||
byteOffset = val.toIndex(state);
|
||||
if (byteOffset == Value::InvalidIndexValue) {
|
||||
offset = argv[1].toIndex(state);
|
||||
if (offset == Value::InvalidIndexValue) {
|
||||
ErrorObject::throwBuiltinError(state, ErrorCode::RangeError, state.context()->staticStrings().DataView.string(), false, String::emptyString(), ErrorObject::Messages::GlobalObject_InvalidArrayBufferOffset);
|
||||
}
|
||||
}
|
||||
// If IsDetachedBuffer(buffer) is true, throw a TypeError exception.
|
||||
if (buffer->isDetachedBuffer()) {
|
||||
ErrorObject::throwBuiltinError(state, ErrorCode::TypeError, state.context()->staticStrings().DataView.string(), false, String::emptyString(), "%s: ArrayBuffer is detached buffer");
|
||||
}
|
||||
double bufferByteLength = buffer->byteLength();
|
||||
|
||||
if (byteOffset > bufferByteLength) {
|
||||
// Let bufferByteLength be ArrayBufferByteLength(buffer, seq-cst).
|
||||
auto bufferByteLegnth = buffer->byteLength();
|
||||
// If offset > bufferByteLength, throw a RangeError exception.
|
||||
if (offset > bufferByteLegnth) {
|
||||
ErrorObject::throwBuiltinError(state, ErrorCode::RangeError, state.context()->staticStrings().DataView.string(), false, String::emptyString(), ErrorObject::Messages::GlobalObject_InvalidArrayBufferOffset);
|
||||
}
|
||||
double byteLength = bufferByteLength - byteOffset;
|
||||
|
||||
if (argc >= 3) {
|
||||
Value& val = argv[2];
|
||||
if (!val.isUndefined()) {
|
||||
byteLength = val.toIndex(state);
|
||||
if (byteOffset + byteLength > bufferByteLength || byteLength == Value::InvalidIndexValue) {
|
||||
ErrorObject::throwBuiltinError(state, ErrorCode::RangeError, state.context()->staticStrings().DataView.string(), false, String::emptyString(), ErrorObject::Messages::GlobalObject_InvalidArrayBufferOffset);
|
||||
}
|
||||
// Let bufferIsFixedLength be IsFixedLengthArrayBuffer(buffer).
|
||||
bool bufferIsFixedLength = buffer->isFixedLengthArrayBuffer();
|
||||
// If byteLength is undefined, then
|
||||
Optional<Value::ValueIndex> viewByteLength;
|
||||
if (argc < 3 || argv[2].isUndefined()) {
|
||||
// If bufferIsFixedLength is true, then
|
||||
if (bufferIsFixedLength) {
|
||||
// Let viewByteLength be bufferByteLength - offset.
|
||||
viewByteLength = bufferByteLegnth - offset;
|
||||
} else {
|
||||
// Else
|
||||
// Let viewByteLength be auto.
|
||||
}
|
||||
} else {
|
||||
// Else,
|
||||
// Let viewByteLength be ? ToIndex(byteLength).
|
||||
viewByteLength = argv[2].toIndex(state);
|
||||
if (viewByteLength.value() == Value::InvalidIndexValue) {
|
||||
ErrorObject::throwBuiltinError(state, ErrorCode::RangeError, state.context()->staticStrings().DataView.string(), false, String::emptyString(), ErrorObject::Messages::GlobalObject_InvalidArrayBufferSize);
|
||||
}
|
||||
// If offset + viewByteLength > bufferByteLength, throw a RangeError exception.
|
||||
if (offset + viewByteLength.value() > bufferByteLegnth) {
|
||||
ErrorObject::throwBuiltinError(state, ErrorCode::RangeError, state.context()->staticStrings().DataView.string(), false, String::emptyString(), ErrorObject::Messages::GlobalObject_InvalidArrayBufferSize);
|
||||
}
|
||||
}
|
||||
|
||||
// Let O be ? OrdinaryCreateFromConstructor(NewTarget, "%DataView.prototype%", « [[DataView]], [[ViewedArrayBuffer]], [[ByteLength]], [[ByteOffset]] »).
|
||||
Object* proto = Object::getPrototypeFromConstructor(state, newTarget.value(), [](ExecutionState& state, Context* constructorRealm) -> Object* {
|
||||
return constructorRealm->globalObject()->dataViewPrototype();
|
||||
});
|
||||
ArrayBufferView* obj = new DataViewObject(state, proto);
|
||||
obj->setBuffer(buffer, byteOffset, byteLength, 0, argc < 3);
|
||||
ArrayBufferView* O = new DataViewObject(state, proto);
|
||||
|
||||
if (obj->buffer()->isDetachedBuffer()) {
|
||||
ErrorObject::throwBuiltinError(state, ErrorCode::TypeError, state.context()->staticStrings().DataView.string(), false, String::emptyString(), ErrorObject::Messages::GlobalObject_DetachedBuffer);
|
||||
// If IsDetachedBuffer(buffer) is true, throw a TypeError exception.
|
||||
if (buffer->isDetachedBuffer()) {
|
||||
ErrorObject::throwBuiltinError(state, ErrorCode::TypeError, state.context()->staticStrings().DataView.string(), false, String::emptyString(), "%s: ArrayBuffer is detached buffer");
|
||||
}
|
||||
|
||||
return obj;
|
||||
// Set bufferByteLength to ArrayBufferByteLength(buffer, seq-cst).
|
||||
bufferByteLegnth = buffer->byteLength();
|
||||
// If offset > bufferByteLength, throw a RangeError exception.
|
||||
if (offset > bufferByteLegnth) {
|
||||
ErrorObject::throwBuiltinError(state, ErrorCode::RangeError, state.context()->staticStrings().DataView.string(), false, String::emptyString(), ErrorObject::Messages::GlobalObject_InvalidArrayBufferSize);
|
||||
}
|
||||
// If byteLength is not undefined, then
|
||||
if (argc >= 3 && !argv[2].isUndefined()) {
|
||||
// If offset + viewByteLength > bufferByteLength, throw a RangeError exception.
|
||||
if (offset + viewByteLength.value() > bufferByteLegnth) {
|
||||
ErrorObject::throwBuiltinError(state, ErrorCode::RangeError, state.context()->staticStrings().DataView.string(), false, String::emptyString(), ErrorObject::Messages::GlobalObject_InvalidArrayBufferSize);
|
||||
}
|
||||
}
|
||||
// Set O.[[ViewedArrayBuffer]] to buffer.
|
||||
// Set O.[[ByteLength]] to viewByteLength.
|
||||
// Set O.[[ByteOffset]] to offset.
|
||||
// Return O.
|
||||
O->setBuffer(buffer, offset, viewByteLength ? viewByteLength.value() : bufferByteLegnth - offset, 0, argc < 3);
|
||||
return O;
|
||||
}
|
||||
|
||||
#define DECLARE_DATAVIEW_GETTER(Name) \
|
||||
|
|
|
|||
|
|
@ -29,6 +29,10 @@
|
|||
#include "intl/IntlDateTimeFormat.h"
|
||||
#endif
|
||||
|
||||
#if defined(ENABLE_TEMPORAL)
|
||||
#include "runtime/TemporalInstantObject.h"
|
||||
#endif
|
||||
|
||||
namespace Escargot {
|
||||
|
||||
#define FOR_EACH_DATE_VALUES(F) \
|
||||
|
|
@ -216,21 +220,23 @@ static Value builtinDateToTimeString(ExecutionState& state, Value thisValue, siz
|
|||
}
|
||||
|
||||
#if defined(ENABLE_ICU) && defined(ENABLE_INTL)
|
||||
#define INTL_DATE_TIME_FORMAT_FORMAT(REQUIRED, DEFUALT) \
|
||||
double x = thisObject->primitiveValue(); \
|
||||
if (std::isnan(x)) { \
|
||||
return new ASCIIStringFromExternalMemory("Invalid Date"); \
|
||||
} \
|
||||
Value locales, options; \
|
||||
if (argc >= 1) { \
|
||||
locales = argv[0]; \
|
||||
} \
|
||||
if (argc >= 2) { \
|
||||
options = argv[1]; \
|
||||
} \
|
||||
auto dateTimeOption = IntlDateTimeFormatObject::toDateTimeOptions(state, options, String::fromASCII(REQUIRED), String::fromASCII(DEFUALT)); \
|
||||
IntlDateTimeFormatObject* dateFormat = new IntlDateTimeFormatObject(state, locales, dateTimeOption); \
|
||||
auto result = dateFormat->format(state, x); \
|
||||
#define INTL_DATE_TIME_FORMAT_FORMAT(REQUIRED, DEFUALT) \
|
||||
double x = thisObject->primitiveValue(); \
|
||||
if (std::isnan(x)) { \
|
||||
return new ASCIIStringFromExternalMemory("Invalid Date"); \
|
||||
} \
|
||||
Value locales, options; \
|
||||
if (argc >= 1) { \
|
||||
locales = argv[0]; \
|
||||
} \
|
||||
if (argc >= 2) { \
|
||||
options = argv[1]; \
|
||||
} \
|
||||
auto dateTimeOption = IntlDateTimeFormatObject:: \
|
||||
toDateTimeOptions(state, options, String::fromASCII(REQUIRED), String::fromASCII(DEFUALT)) \
|
||||
.first; \
|
||||
IntlDateTimeFormatObject* dateFormat = new IntlDateTimeFormatObject(state, locales, dateTimeOption); \
|
||||
auto result = dateFormat->format(state, x); \
|
||||
return new UTF16String(result.data(), result.length());
|
||||
#endif
|
||||
|
||||
|
|
@ -445,6 +451,12 @@ static Value builtinDateSetYear(ExecutionState& state, Value thisValue, size_t a
|
|||
|
||||
double y;
|
||||
int month, date, hour, minute, second, millisecond;
|
||||
month = d->getMonth(state);
|
||||
date = d->getDate(state);
|
||||
hour = d->getHours(state);
|
||||
minute = d->getMinutes(state);
|
||||
second = d->getSeconds(state);
|
||||
millisecond = d->getMilliseconds(state);
|
||||
|
||||
// Let y be ToNumber(year).
|
||||
y = argv[0].toNumber(state);
|
||||
|
|
@ -454,13 +466,6 @@ static Value builtinDateSetYear(ExecutionState& state, Value thisValue, size_t a
|
|||
return Value(Value::NanInit);
|
||||
}
|
||||
|
||||
month = d->getMonth(state);
|
||||
date = d->getDate(state);
|
||||
hour = d->getHours(state);
|
||||
minute = d->getMinutes(state);
|
||||
second = d->getSeconds(state);
|
||||
millisecond = d->getMilliseconds(state);
|
||||
|
||||
double yyyy;
|
||||
double yAsInteger = Value(Value::DoubleToIntConvertibleTestNeeds, y).toInteger(state);
|
||||
// If y is not NaN and 0 ≤ ToInteger(y) ≤ 99, let yyyy be ToInteger(y) + 1900.
|
||||
|
|
@ -471,9 +476,7 @@ static Value builtinDateSetYear(ExecutionState& state, Value thisValue, size_t a
|
|||
yyyy = y;
|
||||
}
|
||||
|
||||
if (d->isValid()) {
|
||||
d->setTimeValue(state, yyyy, month, date, hour, minute, second, millisecond);
|
||||
}
|
||||
d->setTimeValue(state, yyyy, month, date, hour, minute, second, millisecond);
|
||||
|
||||
return Value(Value::DoubleToIntConvertibleTestNeeds, d->primitiveValue());
|
||||
}
|
||||
|
|
@ -517,6 +520,14 @@ static Value builtinDateToPrimitive(ExecutionState& state, Value thisValue, size
|
|||
}
|
||||
}
|
||||
|
||||
#if defined(ENABLE_TEMPORAL)
|
||||
static Value builtinDateToTemporalInstant(ExecutionState& state, Value thisValue, size_t argc, Value* argv, Optional<Object*> newTarget)
|
||||
{
|
||||
RESOLVE_THIS_BINDING_TO_DATE(thisObject, Date, lazyToTemporalInstant());
|
||||
return thisObject->toTemporalInstant(state);
|
||||
}
|
||||
#endif
|
||||
|
||||
void GlobalObject::initializeDate(ExecutionState& state)
|
||||
{
|
||||
ObjectPropertyNativeGetterSetterData* nativeData = new ObjectPropertyNativeGetterSetterData(true, false, true, [](ExecutionState& state, Object* self, const Value& receiver, const EncodedValue& privateDataFromObjectPrivateArea) -> Value {
|
||||
|
|
@ -612,6 +623,12 @@ void GlobalObject::installDate(ExecutionState& state)
|
|||
|
||||
FOR_EACH_DATE_VALUES(DATE_DEFINE_SETTER);
|
||||
|
||||
#if defined(ENABLE_TEMPORAL)
|
||||
m_datePrototype->directDefineOwnProperty(state, ObjectPropertyName(state.context()->staticStrings().lazyToTemporalInstant()),
|
||||
ObjectPropertyDescriptor(new NativeFunctionObject(state, NativeFunctionInfo(state.context()->staticStrings().lazyToTemporalInstant(), builtinDateToTemporalInstant, 0, NativeFunctionInfo::Strict)),
|
||||
(ObjectPropertyDescriptor::PresentAttribute)(ObjectPropertyDescriptor::WritablePresent | ObjectPropertyDescriptor::ConfigurablePresent)));
|
||||
#endif
|
||||
|
||||
m_date->setFunctionPrototype(state, m_datePrototype);
|
||||
|
||||
redefineOwnProperty(state, ObjectPropertyName(state.context()->staticStrings().Date),
|
||||
|
|
|
|||
|
|
@ -198,13 +198,8 @@ static Value builtinIntlDateTimeFormatFormat(ExecutionState& state, Value thisVa
|
|||
}
|
||||
|
||||
IntlDateTimeFormatObject* dateTimeFormat = callee->internalSlot()->asIntlDateTimeFormatObject();
|
||||
double value;
|
||||
if (argc == 0 || argv[0].isUndefined()) {
|
||||
value = DateObject::currentTime();
|
||||
} else {
|
||||
value = argv[0].toNumber(state);
|
||||
}
|
||||
auto result = dateTimeFormat->format(state, value);
|
||||
Value value = argc ? argv[0] : Value();
|
||||
UTF16StringDataNonGCStd result = dateTimeFormat->format(state, value);
|
||||
|
||||
return Value(new UTF16String(result.data(), result.length()));
|
||||
}
|
||||
|
|
@ -235,19 +230,7 @@ static Value builtinIntlDateTimeFormatFormatToParts(ExecutionState& state, Value
|
|||
ErrorObject::throwBuiltinError(state, ErrorCode::TypeError, "Method called on incompatible receiver");
|
||||
}
|
||||
IntlDateTimeFormatObject* dtf = thisValue.asObject()->asIntlDateTimeFormatObject();
|
||||
Value date = argv[0];
|
||||
double x;
|
||||
// If date is undefined, then
|
||||
if (date.isUndefined()) {
|
||||
// Let x be Call(%Date_now%, undefined).
|
||||
x = DateObject::currentTime();
|
||||
} else {
|
||||
// Else,
|
||||
// Let x be ? ToNumber(date).
|
||||
x = date.toNumber(state);
|
||||
}
|
||||
// Return ? FormatDateTimeToParts(dtf, x).
|
||||
return dtf->formatToParts(state, x);
|
||||
return dtf->formatToParts(state, argv[0]);
|
||||
}
|
||||
|
||||
static Value builtinIntlDateTimeFormatFormatRange(ExecutionState& state, Value thisValue, size_t argc, Value* argv, Optional<Object*> newTarget)
|
||||
|
|
@ -261,9 +244,7 @@ static Value builtinIntlDateTimeFormatFormatRange(ExecutionState& state, Value t
|
|||
}
|
||||
|
||||
IntlDateTimeFormatObject* dtf = thisValue.asObject()->asIntlDateTimeFormatObject();
|
||||
double x = argv[0].toNumber(state);
|
||||
double y = argv[1].toNumber(state);
|
||||
auto result = dtf->formatRange(state, x, y);
|
||||
auto result = dtf->formatRange(state, argv[0], argv[1]);
|
||||
return Value(new UTF16String(result.data(), result.length()));
|
||||
}
|
||||
|
||||
|
|
@ -278,9 +259,7 @@ static Value builtinIntlDateTimeFormatFormatRangeToParts(ExecutionState& state,
|
|||
}
|
||||
|
||||
IntlDateTimeFormatObject* dtf = thisValue.asObject()->asIntlDateTimeFormatObject();
|
||||
double x = argv[0].toNumber(state);
|
||||
double y = argv[1].toNumber(state);
|
||||
return dtf->formatRangeToParts(state, x, y);
|
||||
return dtf->formatRangeToParts(state, argv[0], argv[1]);
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -1459,7 +1438,7 @@ static ValueVector availableCalendars()
|
|||
{
|
||||
ValueVector resultVector;
|
||||
|
||||
auto ns = Intl::calendarsForLocale(String::emptyString());
|
||||
auto ns = Intl::calendarsForLocale(String::emptyString(), false);
|
||||
|
||||
for (const auto& s : ns) {
|
||||
resultVector.pushBack(String::fromUTF8(s.data(), s.length()));
|
||||
|
|
@ -1546,42 +1525,9 @@ static ValueVector availableTimeZone()
|
|||
// i. Append timeZoneIdentifierRecord.[[Identifier]] to result.
|
||||
// 4. Return result.
|
||||
ValueVector resultVector;
|
||||
UErrorCode status = U_ZERO_ERROR;
|
||||
|
||||
LocalResourcePointer<UEnumeration> tzs(ucal_openTimeZoneIDEnumeration(UCAL_ZONE_TYPE_CANONICAL_LOCATION, nullptr, nullptr, &status),
|
||||
[](UEnumeration* fmt) { uenum_close(fmt); });
|
||||
|
||||
if (!U_SUCCESS(status)) {
|
||||
return {};
|
||||
}
|
||||
|
||||
const char* buffer;
|
||||
int32_t bufferLength = 0;
|
||||
while ((buffer = uenum_next(tzs.get(), &bufferLength, &status)) && U_SUCCESS(status)) {
|
||||
std::string id(buffer, bufferLength);
|
||||
if (id == "UTC" || (id.find("Etc/GMT") != std::string::npos)) {
|
||||
continue;
|
||||
}
|
||||
resultVector.pushBack(String::fromUTF8(buffer, bufferLength));
|
||||
}
|
||||
|
||||
resultVector.pushBack(String::fromASCII("UTC"));
|
||||
|
||||
for (int i = 1; i <= 12; i++) {
|
||||
std::string s;
|
||||
s += "Etc/GMT+" + std::to_string(i);
|
||||
resultVector.pushBack(String::fromASCII(s.data(), s.length()));
|
||||
s = "";
|
||||
s += "Etc/GMT-" + std::to_string(i);
|
||||
resultVector.pushBack(String::fromASCII(s.data(), s.length()));
|
||||
}
|
||||
|
||||
resultVector.pushBack(String::fromASCII("Etc/GMT-13"));
|
||||
resultVector.pushBack(String::fromASCII("Etc/GMT-14"));
|
||||
|
||||
if (!U_SUCCESS(status)) {
|
||||
return {};
|
||||
}
|
||||
Intl::availableTimeZones([&](const char* data, size_t len) {
|
||||
resultVector.pushBack(String::fromUTF8(data, len));
|
||||
});
|
||||
return resultVector;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -46,6 +46,105 @@ static Value builtinIteratorFrom(ExecutionState& state, Value thisValue, size_t
|
|||
return wrapper;
|
||||
}
|
||||
|
||||
struct IteratorConcatIterables : public gc {
|
||||
EncodedValue openMethod;
|
||||
EncodedValue iterable;
|
||||
};
|
||||
|
||||
struct IteratorConcatData : public gc {
|
||||
IteratorConcatData(size_t size, IteratorConcatIterables* iterables)
|
||||
: counter(0)
|
||||
, iterablesSize(size)
|
||||
, iterables(iterables)
|
||||
, innerAlive(false)
|
||||
{
|
||||
}
|
||||
|
||||
StorePositiveNumberAsOddNumber counter;
|
||||
StorePositiveNumberAsOddNumber iterablesSize;
|
||||
IteratorConcatIterables* iterables;
|
||||
bool innerAlive;
|
||||
Optional<IteratorRecord*> iteratorRecord;
|
||||
};
|
||||
|
||||
static std::pair<Value, bool> iteratorConcatClosure(ExecutionState& state, IteratorHelperObject* obj, void* data)
|
||||
{
|
||||
auto iterData = reinterpret_cast<IteratorConcatData*>(data);
|
||||
// For each Record iterable of iterables, do
|
||||
while (iterData->counter < iterData->iterablesSize && !obj->isDone()) {
|
||||
// check re-enter case with iteratorRecord
|
||||
if (!iterData->iteratorRecord) {
|
||||
auto iterable = iterData->iterables[iterData->counter];
|
||||
// Let iter be ? Call(iterable.[[OpenMethod]], iterable.[[Iterable]]).
|
||||
auto iter = Object::call(state, iterable.openMethod, iterable.iterable, 0, nullptr);
|
||||
// If iter is not an Object, throw a TypeError exception.
|
||||
if (!iter.isObject()) {
|
||||
ErrorObject::throwBuiltinError(state, ErrorCode::TypeError, "Invalid iterable value");
|
||||
}
|
||||
// Let iteratorRecord be ? GetIteratorDirect(iter).
|
||||
iterData->iteratorRecord = IteratorObject::getIteratorDirect(state, iter.asObject());
|
||||
obj->underlyingIterators().pushBack(iterData->iteratorRecord.value());
|
||||
// Let innerAlive be true.
|
||||
iterData->innerAlive = true;
|
||||
}
|
||||
// Repeat, while innerAlive is true,
|
||||
while (iterData->innerAlive) {
|
||||
// Let innerValue be ? IteratorStepValue(iteratorRecord).
|
||||
try {
|
||||
auto innerValue = IteratorObject::iteratorStepValue(state, iterData->iteratorRecord.value());
|
||||
// If innerValue is done, then
|
||||
if (!innerValue) {
|
||||
// Set innerAlive to false.
|
||||
iterData->innerAlive = false;
|
||||
iterData->iteratorRecord = nullptr;
|
||||
iterData->counter = StorePositiveNumberAsOddNumber(iterData->counter + 1);
|
||||
} else {
|
||||
// Else,
|
||||
// Let completion be Completion(Yield(innerValue)).
|
||||
return std::make_pair(innerValue.value(), false);
|
||||
}
|
||||
} catch (const Value& error) {
|
||||
// If completion is an abrupt completion, then
|
||||
// Return ? IteratorClose(iteratorRecord, completion).
|
||||
Value value = IteratorObject::iteratorClose(state, iterData->iteratorRecord.value(), error, true);
|
||||
iterData->iteratorRecord = nullptr;
|
||||
return std::make_pair(value, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
obj->markIteratorIsDone();
|
||||
return std::make_pair(Value(), true);
|
||||
}
|
||||
|
||||
// https://tc39.es/ecma262/#sec-iterator.concat
|
||||
static Value builtinIteratorConcat(ExecutionState& state, Value thisValue, size_t argc, Value* argv, Optional<Object*> newTarget)
|
||||
{
|
||||
// Let iterables be a new empty List.
|
||||
IteratorConcatIterables* iterables = reinterpret_cast<IteratorConcatIterables*>(GC_MALLOC(sizeof(IteratorConcatIterables) * argc));
|
||||
// For each element item of items, do
|
||||
for (size_t i = 0; i < argc; i++) {
|
||||
// If item is not an Object, throw a TypeError exception.
|
||||
Value item = argv[i];
|
||||
if (!item.isObject()) {
|
||||
ErrorObject::throwBuiltinError(state, ErrorCode::TypeError, "item is not an Object");
|
||||
}
|
||||
// Let method be ? GetMethod(item, %Symbol.iterator%).
|
||||
auto method = Object::getMethod(state, item, state.context()->vmInstance()->globalSymbols().iterator);
|
||||
// If method is undefined, throw a TypeError exception.
|
||||
if (method.isUndefined()) {
|
||||
ErrorObject::throwBuiltinError(state, ErrorCode::TypeError, "Invalid iterator method");
|
||||
}
|
||||
// Append the Record { [[OpenMethod]]: method, [[Iterable]]: item } to iterables.
|
||||
iterables[i].iterable = item;
|
||||
iterables[i].openMethod = method;
|
||||
}
|
||||
// Let gen be CreateIteratorFromClosure(closure, "Iterator Helper", %IteratorHelperPrototype%, « [[UnderlyingIterators]] »).
|
||||
// Set gen.[[UnderlyingIterators]] to a new empty List.
|
||||
IteratorHelperObject* gen = new IteratorHelperObject(state, iteratorConcatClosure, NullOption, new IteratorConcatData(argc, iterables));
|
||||
// Return gen.
|
||||
return gen;
|
||||
}
|
||||
|
||||
// https://tc39.es/proposal-iterator-helpers/#sec-iterator-constructor
|
||||
static Value builtinIteratorConstructor(ExecutionState& state, Value thisValue, size_t argc, Value* argv, Optional<Object*> newTarget)
|
||||
{
|
||||
|
|
@ -127,13 +226,24 @@ static Value builtinIteratorHelperPrototypeReturn(ExecutionState& state, Value t
|
|||
// Perform ? RequireInternalSlot(O, [[UnderlyingIterator]]).
|
||||
RESOLVE_THIS_BINDING_TO_ITERATOR_HELPER(O, Iterator, stringReturn);
|
||||
// Assert: O has a [[GeneratorState]] slot.
|
||||
if (O->isRunning()) {
|
||||
ErrorObject::throwBuiltinError(state, ErrorCode::TypeError, "You cannot call Iterator helper function recursively");
|
||||
}
|
||||
|
||||
IteratorHelperObject::IteratorHelperObjectRunningStateChanger changeRunningState(*O);
|
||||
|
||||
// If O.[[GeneratorState]] is suspended-start, then
|
||||
if (!O->underlyingIterator()->m_done) {
|
||||
if (!O->isDone()) {
|
||||
// Set O.[[GeneratorState]] to completed.
|
||||
O->underlyingIterator()->m_done = true;
|
||||
O->markIteratorIsDone();
|
||||
// NOTE: Once a generator enters the completed state it never leaves it and its associated execution context is never resumed. Any execution state associated with O can be discarded at this point.
|
||||
// Perform ? IteratorClose(O.[[UnderlyingIterator]], NormalCompletion(unused)).
|
||||
IteratorObject::iteratorClose(state, O->underlyingIterator(), Value(), false);
|
||||
for (auto underlyingIterator : O->underlyingIterators()) {
|
||||
if (!underlyingIterator->m_done) {
|
||||
underlyingIterator->m_done = true;
|
||||
IteratorObject::iteratorClose(state, underlyingIterator, Value(), false);
|
||||
}
|
||||
}
|
||||
// Return CreateIterResultObject(undefined, true).
|
||||
return IteratorObject::createIterResultObject(state, Value(), true);
|
||||
}
|
||||
|
|
@ -177,12 +287,13 @@ static std::pair<Value, bool> iteratorMapClosure(ExecutionState& state, Iterator
|
|||
// IfAbruptCloseIterator(completion, iterated).
|
||||
// Set counter to counter + 1.
|
||||
IteratorData* closureData = reinterpret_cast<IteratorData*>(data);
|
||||
IteratorRecord* iterated = obj->underlyingIterator();
|
||||
IteratorRecord* iterated = obj->underlyingIterators()[0];
|
||||
Value mapper = closureData->callback;
|
||||
|
||||
auto value = IteratorObject::iteratorStepValue(state, iterated);
|
||||
if (!value) {
|
||||
iterated->m_done = true;
|
||||
obj->markIteratorIsDone();
|
||||
return std::make_pair(Value(), true);
|
||||
}
|
||||
|
||||
|
|
@ -304,13 +415,14 @@ static std::pair<Value, bool> iteratorFilterClosure(ExecutionState& state, Itera
|
|||
// IfAbruptCloseIterator(completion, iterated).
|
||||
// Set counter to counter + 1.
|
||||
IteratorData* closureData = reinterpret_cast<IteratorData*>(data);
|
||||
IteratorRecord* iterated = obj->underlyingIterator();
|
||||
IteratorRecord* iterated = obj->underlyingIterators()[0];
|
||||
Value predicate = closureData->callback;
|
||||
|
||||
while (true) {
|
||||
auto value = IteratorObject::iteratorStepValue(state, iterated);
|
||||
if (!value) {
|
||||
iterated->m_done = true;
|
||||
obj->markIteratorIsDone();
|
||||
return std::make_pair(Value(), true);
|
||||
}
|
||||
|
||||
|
|
@ -599,10 +711,11 @@ static std::pair<Value, bool> iteratorTakeClosure(ExecutionState& state, Iterato
|
|||
// Let completion be Completion(Yield(value)).
|
||||
// IfAbruptCloseIterator(completion, iterated).
|
||||
IteratorData* closureData = reinterpret_cast<IteratorData*>(data);
|
||||
IteratorRecord* iterated = obj->underlyingIterator();
|
||||
IteratorRecord* iterated = obj->underlyingIterators()[0];
|
||||
double remaining = closureData->callback.toNumber(state);
|
||||
|
||||
if (remaining == 0) {
|
||||
obj->markIteratorIsDone();
|
||||
if (!iterated->m_done) {
|
||||
iterated->m_done = true;
|
||||
IteratorObject::iteratorClose(state, iterated, Value(), false);
|
||||
|
|
@ -616,6 +729,7 @@ static std::pair<Value, bool> iteratorTakeClosure(ExecutionState& state, Iterato
|
|||
auto value = IteratorObject::iteratorStepValue(state, iterated);
|
||||
if (!value) {
|
||||
iterated->m_done = true;
|
||||
obj->markIteratorIsDone();
|
||||
return std::make_pair(Value(), true);
|
||||
}
|
||||
|
||||
|
|
@ -727,7 +841,7 @@ static std::pair<Value, bool> iteratorDropClosure(ExecutionState& state, Iterato
|
|||
// If value is done, return ReturnCompletion(undefined)
|
||||
// Let completion be Completion(Yield(value)).
|
||||
// IfAbruptCloseIterator(completion, iterated).
|
||||
IteratorRecord* iterated = obj->underlyingIterator();
|
||||
IteratorRecord* iterated = obj->underlyingIterators()[0];
|
||||
IteratorData* closureData = reinterpret_cast<IteratorData*>(data);
|
||||
double remaining = closureData->callback.asNumber();
|
||||
|
||||
|
|
@ -739,6 +853,7 @@ static std::pair<Value, bool> iteratorDropClosure(ExecutionState& state, Iterato
|
|||
auto next = IteratorObject::iteratorStep(state, iterated);
|
||||
if (!next) {
|
||||
iterated->m_done = true;
|
||||
obj->markIteratorIsDone();
|
||||
return std::make_pair(Value(), true);
|
||||
}
|
||||
}
|
||||
|
|
@ -753,6 +868,7 @@ static std::pair<Value, bool> iteratorDropClosure(ExecutionState& state, Iterato
|
|||
auto value = IteratorObject::iteratorStepValue(state, iterated);
|
||||
if (!value) {
|
||||
iterated->m_done = true;
|
||||
obj->markIteratorIsDone();
|
||||
return std::make_pair(Value(), true);
|
||||
}
|
||||
|
||||
|
|
@ -863,7 +979,7 @@ static std::pair<Value, bool> iteratorFlatMapClosure(ExecutionState& state, Iter
|
|||
// ii. IfAbruptCloseIterator(backupCompletion, iterated).
|
||||
// iii. Return ? IteratorClose(iterated, completion).
|
||||
// ix. Set counter to counter + 1.
|
||||
IteratorRecord* iterated = obj->underlyingIterator();
|
||||
IteratorRecord* iterated = obj->underlyingIterators()[0];
|
||||
FlatMapIteratorData* closureData = reinterpret_cast<FlatMapIteratorData*>(data);
|
||||
Value mapper = closureData->callback;
|
||||
|
||||
|
|
@ -875,7 +991,7 @@ static std::pair<Value, bool> iteratorFlatMapClosure(ExecutionState& state, Iter
|
|||
innerValue = IteratorObject::iteratorStepValue(state, closureData->innerIterator);
|
||||
} catch (const Value& e) {
|
||||
// IfAbruptCloseIterator(innerValue, iterated).
|
||||
IteratorObject::iteratorClose(state, obj->underlyingIterator(), e, true);
|
||||
IteratorObject::iteratorClose(state, iterated, e, true);
|
||||
}
|
||||
if (!innerValue) {
|
||||
// If innerValue is done, then Set innerAlive to false.
|
||||
|
|
@ -892,6 +1008,7 @@ static std::pair<Value, bool> iteratorFlatMapClosure(ExecutionState& state, Iter
|
|||
// If value is done, return ReturnCompletion(undefined).
|
||||
if (!value) {
|
||||
iterated->m_done = true;
|
||||
obj->markIteratorIsDone();
|
||||
return std::make_pair(Value(), true);
|
||||
}
|
||||
|
||||
|
|
@ -949,7 +1066,6 @@ static Value builtinIteratorFlatMap(ExecutionState& state, Value thisValue, size
|
|||
return result;
|
||||
}
|
||||
|
||||
|
||||
static Value builtinGenericIteratorNext(ExecutionState& state, Value thisValue, size_t argc, Value* argv, Optional<Object*> newTarget)
|
||||
{
|
||||
if (!thisValue.isObject() || !thisValue.asObject()->isGenericIteratorObject()) {
|
||||
|
|
@ -1005,6 +1121,10 @@ void GlobalObject::installIterator(ExecutionState& state)
|
|||
m_iterator->directDefineOwnProperty(state, ObjectPropertyName(strings->from),
|
||||
ObjectPropertyDescriptor(new NativeFunctionObject(state, NativeFunctionInfo(strings->from, builtinIteratorFrom, 1, NativeFunctionInfo::Strict)), (ObjectPropertyDescriptor::PresentAttribute)(ObjectPropertyDescriptor::WritablePresent | ObjectPropertyDescriptor::ConfigurablePresent)));
|
||||
|
||||
m_iterator->directDefineOwnProperty(state, ObjectPropertyName(strings->concat),
|
||||
ObjectPropertyDescriptor(new NativeFunctionObject(state, NativeFunctionInfo(strings->concat, builtinIteratorConcat, 0, NativeFunctionInfo::Strict)), (ObjectPropertyDescriptor::PresentAttribute)(ObjectPropertyDescriptor::WritablePresent | ObjectPropertyDescriptor::ConfigurablePresent)));
|
||||
|
||||
|
||||
// https://tc39.es/proposal-iterator-helpers/#sec-iterator.prototype
|
||||
m_iterator->setFunctionPrototype(state, m_iteratorPrototype);
|
||||
|
||||
|
|
|
|||
|
|
@ -216,7 +216,7 @@ static Value builtinPromiseAll(ExecutionState& state, Value thisValue, size_t ar
|
|||
result = IteratorObject::iteratorClose(state, iteratorRecord, exceptionValue, true);
|
||||
}
|
||||
} catch (const Value& v) {
|
||||
exceptionValue = v;
|
||||
// ignore error value
|
||||
}
|
||||
|
||||
// If value is an abrupt completion,
|
||||
|
|
@ -323,7 +323,7 @@ static Value builtinPromiseRace(ExecutionState& state, Value thisValue, size_t a
|
|||
result = IteratorObject::iteratorClose(state, record, exceptionValue, true);
|
||||
}
|
||||
} catch (const Value& v) {
|
||||
exceptionValue = v;
|
||||
// ignore error here
|
||||
}
|
||||
// If value is an abrupt completion,
|
||||
// Perform ? Call(capability.[[Reject]], undefined, « value.[[Value]] »).
|
||||
|
|
@ -565,7 +565,7 @@ static Value builtinPromiseAllSettled(ExecutionState& state, Value thisValue, si
|
|||
try {
|
||||
result = IteratorObject::iteratorClose(state, iteratorRecord, exceptionValue, true);
|
||||
} catch (const Value& v) {
|
||||
exceptionValue = v;
|
||||
// ignore error value
|
||||
// IfAbruptRejectPromise(result, promiseCapability).
|
||||
// If value is an abrupt completion,
|
||||
// Perform ? Call(capability.[[Reject]], undefined, « value.[[Value]] »).
|
||||
|
|
@ -731,7 +731,7 @@ static Value builtinPromiseAny(ExecutionState& state, Value thisValue, size_t ar
|
|||
IteratorObject::iteratorClose(state, iteratorRecord, thrownValue, true);
|
||||
}
|
||||
} catch (const Value& v) {
|
||||
thrownValue = v;
|
||||
// ignore error here
|
||||
}
|
||||
// IfAbruptRejectPromise(result, promiseCapability).
|
||||
Object::call(state, promiseCapability.m_rejectFunction, Value(), 1, &thrownValue);
|
||||
|
|
|
|||
|
|
@ -593,7 +593,7 @@ static Value builtinRegExpMatchAll(ExecutionState& state, Value thisValue, size_
|
|||
if (flags->find("g") != SIZE_MAX) {
|
||||
global = true;
|
||||
}
|
||||
if (flags->find("u") != SIZE_MAX) {
|
||||
if (flags->find("u") != SIZE_MAX || flags->find("v") != SIZE_MAX) {
|
||||
unicode = true;
|
||||
}
|
||||
return new RegExpStringIteratorObject(state, global, unicode, matcher->asRegExpObject(), s);
|
||||
|
|
|
|||
173
src/builtins/BuiltinShadowRealm.cpp
Normal file
173
src/builtins/BuiltinShadowRealm.cpp
Normal file
|
|
@ -0,0 +1,173 @@
|
|||
/*
|
||||
* Copyright (c) 2025-present Samsung Electronics Co., Ltd
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
|
||||
* USA
|
||||
*/
|
||||
|
||||
#include "Escargot.h"
|
||||
#include "interpreter/ByteCode.h"
|
||||
#include "interpreter/ByteCodeGenerator.h"
|
||||
#include "interpreter/ByteCodeInterpreter.h"
|
||||
#include "runtime/Global.h"
|
||||
#include "runtime/GlobalObject.h"
|
||||
#include "runtime/Context.h"
|
||||
#include "runtime/Environment.h"
|
||||
#include "runtime/EnvironmentRecord.h"
|
||||
#include "runtime/ExtendedNativeFunctionObject.h"
|
||||
#include "runtime/VMInstance.h"
|
||||
#include "runtime/ShadowRealmObject.h"
|
||||
#include "runtime/NativeFunctionObject.h"
|
||||
#include "runtime/ScriptFunctionObject.h"
|
||||
#include "parser/Script.h"
|
||||
#include "parser/ScriptParser.h"
|
||||
|
||||
namespace Escargot {
|
||||
|
||||
#if defined(ENABLE_SHADOWREALM)
|
||||
|
||||
// https://tc39.es/proposal-shadowrealm/#sec-shadowrealm
|
||||
static Value builtinShadowRealmConstructor(ExecutionState& state, Value thisValue, size_t argc, Value* argv, Optional<Object*> newTarget)
|
||||
{
|
||||
// If NewTarget is undefined, throw a TypeError exception.
|
||||
if (!newTarget) {
|
||||
ErrorObject::throwBuiltinError(state, ErrorCode::TypeError, ErrorObject::Messages::GlobalObject_ConstructorRequiresNew);
|
||||
}
|
||||
|
||||
// Let O be ? OrdinaryCreateFromConstructor(NewTarget, "%ShadowRealm.prototype%", « [[ShadowRealm]] »).
|
||||
Object* proto = Object::getPrototypeFromConstructor(state, newTarget.value(), [](ExecutionState& state, Context* constructorRealm) -> Object* {
|
||||
return constructorRealm->globalObject()->shadowRealmPrototype();
|
||||
});
|
||||
// Let callerContext be the running execution context.
|
||||
// Perform ? InitializeHostDefinedRealm().
|
||||
// Let innerContext be the running execution context.
|
||||
Context* innerContext = new Context(state.context()->vmInstance());
|
||||
// Remove innerContext from the execution context stack and restore callerContext as the running execution context.
|
||||
// Let realmRec be the Realm of innerContext.
|
||||
// Set O.[[ShadowRealm]] to realmRec.
|
||||
auto targetState = &state;
|
||||
Optional<Script*> referrer;
|
||||
while (targetState) {
|
||||
auto callee = targetState->resolveCallee();
|
||||
if (callee && !callee->isNativeFunctionObject()) {
|
||||
auto fn = targetState->mostNearestFunctionLexicalEnvironment()->record()->asDeclarativeEnvironmentRecord()->asFunctionEnvironmentRecord()->functionObject();
|
||||
referrer = fn->codeBlock()->asInterpretedCodeBlock()->script();
|
||||
break;
|
||||
}
|
||||
auto outerScript = targetState->resolveOuterScript();
|
||||
if (outerScript && outerScript->topCodeBlock() && !outerScript->topCodeBlock()->isEvalCode()) {
|
||||
referrer = outerScript;
|
||||
break;
|
||||
}
|
||||
targetState = targetState->parent();
|
||||
}
|
||||
if (!referrer) {
|
||||
ErrorObject::throwBuiltinError(state, ErrorCode::TypeError, "Shadow Realm needs Javascript code as importValue referrer");
|
||||
}
|
||||
ShadowRealmObject* O = new ShadowRealmObject(state, proto, innerContext, referrer.value());
|
||||
// Perform ? HostInitializeShadowRealm(realmRec, innerContext, O).
|
||||
// Assert: realmRec.[[GlobalObject]] is an ordinary object.
|
||||
ASSERT(innerContext->globalObject()->isOrdinary());
|
||||
// Return O.
|
||||
return O;
|
||||
}
|
||||
|
||||
static Value builtinShadowRealmEvaluate(ExecutionState& state, Value thisValue, size_t argc, Value* argv, Optional<Object*> newTarget)
|
||||
{
|
||||
// Let O be the this value.
|
||||
const Value& O = thisValue;
|
||||
// Perform ? ValidateShadowRealmObject(O).
|
||||
if (!O.isObject() || !O.asObject()->isShadowRealmObject()) {
|
||||
ErrorObject::throwBuiltinError(state, ErrorCode::TypeError, "this value must be a ShadowRealm object");
|
||||
}
|
||||
// If sourceText is not a String, throw a TypeError exception.
|
||||
if (!argv[0].isString()) {
|
||||
ErrorObject::throwBuiltinError(state, ErrorCode::TypeError, "sourceText must be a String");
|
||||
}
|
||||
// Let callerRealm be the current Realm Record.
|
||||
Context* callerRealm = state.context();
|
||||
// Let evalRealm be O.[[ShadowRealm]].
|
||||
Context* evalRealm = O.asObject()->asShadowRealmObject()->realmContext();
|
||||
// Return ? PerformShadowRealmEval(sourceText, callerRealm, evalRealm).
|
||||
return O.asObject()->asShadowRealmObject()->eval(state, argv[0].asString(), callerRealm);
|
||||
}
|
||||
|
||||
static Value builtinShadowRealmImportValue(ExecutionState& state, Value thisValue, size_t argc, Value* argv, Optional<Object*> newTarget)
|
||||
{
|
||||
// Let O be the this value.
|
||||
const Value& O = thisValue;
|
||||
// Perform ? ValidateShadowRealmObject(O).
|
||||
if (!O.isObject() || !O.asObject()->isShadowRealmObject()) {
|
||||
ErrorObject::throwBuiltinError(state, ErrorCode::TypeError, "this value must be a ShadowRealm object");
|
||||
}
|
||||
|
||||
// Let specifierString be ? ToString(specifier).
|
||||
auto specifierString = argv[0].toString(state);
|
||||
// If exportName is not a String, throw a TypeError exception.
|
||||
if (!argv[1].isString()) {
|
||||
ErrorObject::throwBuiltinError(state, ErrorCode::TypeError, "exportName must be a String");
|
||||
}
|
||||
// Let callerRealm be the current Realm Record.
|
||||
Context* callerRealm = state.context();
|
||||
// Let evalRealm be O.[[ShadowRealm]].
|
||||
Context* evalRealm = O.asObject()->asShadowRealmObject()->realmContext();
|
||||
// Return ShadowRealmImportValue(specifierString, exportName, callerRealm, evalRealm).
|
||||
return O.asObject()->asShadowRealmObject()->importValue(state, specifierString, argv[1].asString(), callerRealm);
|
||||
}
|
||||
|
||||
void GlobalObject::initializeShadowRealm(ExecutionState& state)
|
||||
{
|
||||
ObjectPropertyNativeGetterSetterData* nativeData = new ObjectPropertyNativeGetterSetterData(true, false, true, [](ExecutionState& state, Object* self, const Value& receiver, const EncodedValue& privateDataFromObjectPrivateArea) -> Value {
|
||||
ASSERT(self->isGlobalObject());
|
||||
return self->asGlobalObject()->shadowRealm(); }, nullptr);
|
||||
|
||||
defineNativeDataAccessorProperty(state, ObjectPropertyName(state.context()->staticStrings().ShadowRealm), nativeData, Value(Value::EmptyValue));
|
||||
}
|
||||
|
||||
void GlobalObject::installShadowRealm(ExecutionState& state)
|
||||
{
|
||||
const StaticStrings* strings = &state.context()->staticStrings();
|
||||
|
||||
m_shadowRealm = new NativeFunctionObject(state, NativeFunctionInfo(strings->ShadowRealm, builtinShadowRealmConstructor, 0), NativeFunctionObject::__ForBuiltinConstructor__);
|
||||
m_shadowRealm->setGlobalIntrinsicObject(state);
|
||||
|
||||
m_shadowRealmPrototype = new PrototypeObject(state);
|
||||
m_shadowRealmPrototype->setGlobalIntrinsicObject(state, true);
|
||||
|
||||
m_shadowRealm->setFunctionPrototype(state, m_shadowRealmPrototype);
|
||||
|
||||
m_shadowRealmPrototype->directDefineOwnProperty(state, ObjectPropertyName(state.context()->vmInstance()->globalSymbols().toStringTag),
|
||||
ObjectPropertyDescriptor(state.context()->staticStrings().ShadowRealm.string(), ObjectPropertyDescriptor::ConfigurablePresent));
|
||||
|
||||
m_shadowRealmPrototype->directDefineOwnProperty(state, ObjectPropertyName(strings->evaluate),
|
||||
ObjectPropertyDescriptor(new NativeFunctionObject(state, NativeFunctionInfo(strings->evaluate, builtinShadowRealmEvaluate, 1, NativeFunctionInfo::Strict)), (ObjectPropertyDescriptor::PresentAttribute)(ObjectPropertyDescriptor::WritablePresent | ObjectPropertyDescriptor::ConfigurablePresent)));
|
||||
|
||||
m_shadowRealmPrototype->directDefineOwnProperty(state, ObjectPropertyName(strings->importValue),
|
||||
ObjectPropertyDescriptor(new NativeFunctionObject(state, NativeFunctionInfo(strings->importValue, builtinShadowRealmImportValue, 2, NativeFunctionInfo::Strict)), (ObjectPropertyDescriptor::PresentAttribute)(ObjectPropertyDescriptor::WritablePresent | ObjectPropertyDescriptor::ConfigurablePresent)));
|
||||
|
||||
redefineOwnProperty(state, ObjectPropertyName(state.context()->staticStrings().ShadowRealm),
|
||||
ObjectPropertyDescriptor(m_shadowRealm, (ObjectPropertyDescriptor::PresentAttribute)(ObjectPropertyDescriptor::WritablePresent | ObjectPropertyDescriptor::ConfigurablePresent)));
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
void GlobalObject::initializeShadowRealm(ExecutionState& state)
|
||||
{
|
||||
// dummy initialize function
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
} // namespace Escargot
|
||||
|
|
@ -194,7 +194,7 @@ static Value builtinStringMatch(ExecutionState& state, Value thisValue, size_t a
|
|||
}
|
||||
|
||||
Value regexp = argv[0];
|
||||
if (!regexp.isUndefinedOrNull()) {
|
||||
if (regexp.isObject()) {
|
||||
Value matcher = Object::getMethod(state, regexp, ObjectPropertyName(state.context()->vmInstance()->globalSymbols().match));
|
||||
if (!matcher.isUndefined()) {
|
||||
Value args[1] = { thisValue };
|
||||
|
|
@ -216,8 +216,8 @@ static Value builtinStringMatchAll(ExecutionState& state, Value thisValue, size_
|
|||
}
|
||||
|
||||
Value regexp = argv[0];
|
||||
if (!regexp.isUndefinedOrNull()) {
|
||||
if (regexp.isObject() && regexp.asObject()->isRegExpObject()) {
|
||||
if (regexp.isObject()) {
|
||||
if (regexp.asObject()->isRegExpObject()) {
|
||||
String* flags = regexp.asObject()->get(state, ObjectPropertyName(state, state.context()->staticStrings().flags)).value(state, regexp).toString(state);
|
||||
if (flags->find("g") == SIZE_MAX) {
|
||||
ErrorObject::throwBuiltinError(state, ErrorCode::TypeError, state.context()->staticStrings().String.string(), true, state.context()->staticStrings().match.string(), ErrorObject::Messages::GlobalObject_ThisUndefinedOrNull);
|
||||
|
|
@ -438,7 +438,7 @@ static Value builtinStringReplace(ExecutionState& state, Value thisValue, size_t
|
|||
bool isSearchValueRegExp = searchValue.isPointerValue() && searchValue.asPointerValue()->isRegExpObject();
|
||||
// we should keep fast-path while performace issue is unresolved
|
||||
bool canUseFastPath = searchValue.isString() || (isSearchValueRegExp && searchValue.asPointerValue()->asRegExpObject()->yarrPatern()->m_captureGroupNames.size() == 0);
|
||||
if (!searchValue.isUndefinedOrNull()) {
|
||||
if (searchValue.isObject()) {
|
||||
Value replacer = Object::getMethod(state, searchValue, ObjectPropertyName(state.context()->vmInstance()->globalSymbols().replace));
|
||||
if (canUseFastPath && isSearchValueRegExp && replacer.isPointerValue() && replacer.asPointerValue() == state.context()->globalObject()->regexpReplaceMethod()) {
|
||||
auto exec = searchValue.asObject()->get(state, ObjectPropertyName(state.context()->staticStrings().exec));
|
||||
|
|
@ -571,8 +571,7 @@ static Value builtinStringReplaceAll(ExecutionState& state, Value thisValue, siz
|
|||
}
|
||||
Value searchValue = argv[0];
|
||||
Value replaceValue = argv[1];
|
||||
// If searchValue is neither undefined nor null, then
|
||||
if (!searchValue.isUndefinedOrNull()) {
|
||||
if (searchValue.isObject()) {
|
||||
// If isRegExp is true, then
|
||||
if (searchValue.isObject() && searchValue.asObject()->isRegExp(state)) {
|
||||
Value flags = searchValue.asObject()->get(state, ObjectPropertyName(state, state.context()->staticStrings().flags)).value(state, searchValue);
|
||||
|
|
@ -642,7 +641,7 @@ static Value builtinStringSearch(ExecutionState& state, Value thisValue, size_t
|
|||
}
|
||||
|
||||
Value regexp = argv[0];
|
||||
if (!regexp.isUndefinedOrNull()) {
|
||||
if (regexp.isObject()) {
|
||||
Value searcher = Object::getMethod(state, regexp, ObjectPropertyName(state.context()->vmInstance()->globalSymbols().search));
|
||||
if (!searcher.isUndefined()) {
|
||||
Value args[1] = { thisValue };
|
||||
|
|
@ -667,8 +666,7 @@ static Value builtinStringSplit(ExecutionState& state, Value thisValue, size_t a
|
|||
Value limit = argv[1];
|
||||
bool isSeparatorRegExp = separator.isPointerValue() && separator.asPointerValue()->isRegExpObject();
|
||||
|
||||
// If separator is neither undefined nor null, then
|
||||
if (!separator.isUndefinedOrNull()) {
|
||||
if (separator.isObject()) {
|
||||
// Let splitter be GetMethod(separator, @@split).
|
||||
Value splitter = Object::getMethod(state, separator, ObjectPropertyName(state.context()->vmInstance()->globalSymbols().split));
|
||||
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -418,7 +418,7 @@ static Value builtinTypedArrayConstructor(ExecutionState& state, Value thisValue
|
|||
return obj;
|
||||
}
|
||||
|
||||
// https://www.ecma-international.org/ecma-262/10.0/#sec-%typedarray%.prototype.copywithin
|
||||
// https://tc39.es/ecma262/#sec-%typedarray%.prototype.copywithin
|
||||
static Value builtinTypedArrayCopyWithin(ExecutionState& state, Value thisValue, size_t argc, Value* argv, Optional<Object*> newTarget)
|
||||
{
|
||||
// validateTypedArray is applied to the this value prior to evaluating the algorithm.
|
||||
|
|
@ -428,23 +428,53 @@ static Value builtinTypedArrayCopyWithin(ExecutionState& state, Value thisValue,
|
|||
// Let len be O.[[ArrayLength]].
|
||||
double len = O->arrayLength();
|
||||
|
||||
// Let relativeTarget be ToInteger(target).
|
||||
double relativeTarget = argv[0].toInteger(state);
|
||||
// If relativeTarget < 0, let to be max((len + relativeTarget),0); else let to be min(relativeTarget, len).
|
||||
double to = (relativeTarget < 0.0) ? std::max((len + relativeTarget), 0.0) : std::min(relativeTarget, len);
|
||||
// Let relativeTarget be ? ToIntegerOrInfinity(target).
|
||||
auto relativeTarget = argv[0].toInteger(state);
|
||||
// If relativeTarget = -∞, let targetIndex be 0.
|
||||
double targetIndex;
|
||||
if (std::isinf(relativeTarget) && std::signbit(relativeTarget)) {
|
||||
targetIndex = 0;
|
||||
} else if (relativeTarget < 0) {
|
||||
// Else if relativeTarget < 0, let targetIndex be max(len + relativeTarget, 0).
|
||||
targetIndex = std::max(len + relativeTarget, 0.0);
|
||||
} else {
|
||||
// Else, let targetIndex be min(relativeTarget, len).
|
||||
targetIndex = std::min(relativeTarget, len);
|
||||
}
|
||||
// Let relativeStart be ? ToIntegerOrInfinity(start).
|
||||
auto relativeStart = argv[1].toInteger(state);
|
||||
// If relativeStart = -∞, let startIndex be 0.
|
||||
double startIndex;
|
||||
if (std::isinf(relativeStart) && std::signbit(relativeStart)) {
|
||||
startIndex = 0;
|
||||
} else if (relativeStart < 0) {
|
||||
// Else if relativeStart < 0, let startIndex be max(len + relativeStart, 0).
|
||||
startIndex = std::max(len + relativeStart, 0.0);
|
||||
} else {
|
||||
// Else, let startIndex be min(relativeStart, len).
|
||||
startIndex = std::min(relativeStart, len);
|
||||
}
|
||||
// If end is undefined, let relativeEnd be len; else let relativeEnd be ? ToIntegerOrInfinity(end).
|
||||
double relativeEnd;
|
||||
if (argc < 3 || argv[2].isUndefined()) {
|
||||
relativeEnd = len;
|
||||
} else {
|
||||
relativeEnd = argv[2].toInteger(state);
|
||||
}
|
||||
|
||||
// Let relativeStart be ToInteger(start).
|
||||
double relativeStart = argv[1].toInteger(state);
|
||||
// If relativeStart < 0, let from be max((len + relativeStart),0); else let from be min(relativeStart, len).
|
||||
double from = (relativeStart < 0.0) ? std::max((len + relativeStart), 0.0) : std::min(relativeStart, len);
|
||||
|
||||
// If end is undefined, let relativeEnd be len; else let relativeEnd be ToInteger(end).
|
||||
double relativeEnd = (argc <= 2 || argv[2].isUndefined()) ? len : argv[2].toInteger(state);
|
||||
// If relativeEnd < 0, let final be max((len + relativeEnd),0); else let final be min(relativeEnd, len).
|
||||
double finalEnd = (relativeEnd < 0.0) ? std::max((len + relativeEnd), 0.0) : std::min(relativeEnd, len);
|
||||
|
||||
// Let count be min(final-from, len-to).
|
||||
double count = std::min(finalEnd - from, len - to);
|
||||
double endIndex;
|
||||
// If relativeEnd = -∞, let endIndex be 0.
|
||||
if (std::isinf(relativeEnd) && std::signbit(relativeEnd)) {
|
||||
endIndex = 0;
|
||||
} else if (relativeEnd < 0) {
|
||||
// Else if relativeEnd < 0, let endIndex be max(len + relativeEnd, 0).
|
||||
endIndex = std::max(len + relativeEnd, 0.0);
|
||||
} else {
|
||||
// Else, let endIndex be min(relativeEnd, len).
|
||||
endIndex = std::min(relativeEnd, len);
|
||||
}
|
||||
// Let count be min(endIndex - startIndex, len - targetIndex).
|
||||
auto count = std::min(endIndex - startIndex, len - targetIndex);
|
||||
// If count > 0, then
|
||||
if (count > 0) {
|
||||
// Let buffer be O.[[ViewedArrayBuffer]].
|
||||
|
|
@ -454,17 +484,19 @@ static Value builtinTypedArrayCopyWithin(ExecutionState& state, Value thisValue,
|
|||
TypedArrayObject::validateTypedArray(state, thisValue);
|
||||
// Set len to TypedArrayLength(taRecord).
|
||||
len = O->arrayLength();
|
||||
// Set count to min(count, len - startIndex, len - targetIndex).
|
||||
// NOTE: After buffer resize during argument coercion, len - startIndex or len - targetIndex can be negative.
|
||||
// We must clamp count to non-negative to prevent integer underflow when casting to size_t.
|
||||
count = std::max(0.0, std::min(std::min(count, len - startIndex), len - targetIndex));
|
||||
// Let typedArrayName be the String value of O.[[TypedArrayName]].
|
||||
// Let elementSize be the Number value of the Element Size value specified in Table 59 for typedArrayName.
|
||||
size_t elementSize = O->elementSize();
|
||||
// Let byteOffset be O.[[ByteOffset]].
|
||||
size_t byteOffset = O->byteOffset();
|
||||
// Let bufferByteLimit be (len × elementSize) + byteOffset.
|
||||
size_t bufferByteLimit = (len * elementSize) + byteOffset;
|
||||
// Let toByteIndex be to × elementSize + byteOffset.
|
||||
size_t toByteIndex = to * elementSize + byteOffset;
|
||||
// Let fromByteIndex be from × elementSize + byteOffset.
|
||||
size_t fromByteIndex = from * elementSize + byteOffset;
|
||||
// Let toByteIndex be (targetIndex × elementSize) + byteOffset.
|
||||
size_t toByteIndex = targetIndex * elementSize + byteOffset;
|
||||
// Let fromByteIndex be (startIndex × elementSize) + byteOffset.
|
||||
size_t fromByteIndex = startIndex * elementSize + byteOffset;
|
||||
// Let countBytes be count × elementSize.
|
||||
size_t countBytes = count * elementSize;
|
||||
|
||||
|
|
@ -482,23 +514,18 @@ static Value builtinTypedArrayCopyWithin(ExecutionState& state, Value thisValue,
|
|||
direction = 1;
|
||||
}
|
||||
|
||||
// Repeat, while countBytes > 0
|
||||
// Repeat, while countBytes > 0,
|
||||
while (countBytes > 0) {
|
||||
// If fromByteIndex < bufferByteLimit and toByteIndex < bufferByteLimit, then
|
||||
if (fromByteIndex < bufferByteLimit && toByteIndex < bufferByteLimit) {
|
||||
// Let value be GetValueFromBuffer(buffer, fromByteIndex, "Uint8", true, "Unordered").
|
||||
Value value = buffer->getValueFromBuffer(state, fromByteIndex, TypedArrayType::Uint8);
|
||||
// Perform SetValueInBuffer(buffer, toByteIndex, "Uint8", value, true, "Unordered").
|
||||
buffer->setValueInBuffer(state, toByteIndex, TypedArrayType::Uint8, value);
|
||||
// Set fromByteIndex to fromByteIndex + direction.
|
||||
fromByteIndex += direction;
|
||||
// Set toByteIndex to toByteIndex + direction.
|
||||
toByteIndex += direction;
|
||||
// Decrease countBytes by 1.
|
||||
countBytes--;
|
||||
} else {
|
||||
countBytes = 0;
|
||||
}
|
||||
// Let value be GetValueFromBuffer(buffer, fromByteIndex, uint8, true, unordered).
|
||||
Value value = buffer->getValueFromBuffer(state, fromByteIndex, TypedArrayType::Uint8);
|
||||
// Perform SetValueInBuffer(buffer, toByteIndex, uint8, value, true, unordered).
|
||||
buffer->setValueInBuffer(state, toByteIndex, TypedArrayType::Uint8, value);
|
||||
// Set fromByteIndex to fromByteIndex + direction.
|
||||
fromByteIndex += direction;
|
||||
// Set toByteIndex to toByteIndex + direction.
|
||||
toByteIndex += direction;
|
||||
// Set countBytes to countBytes - 1.
|
||||
countBytes--;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1887,6 +1914,7 @@ static Value builtinTypedArrayToLocaleString(ExecutionState& state, Value thisVa
|
|||
// Let k be 0.
|
||||
size_t k = 0;
|
||||
|
||||
Value* toLocaleStringArgv = ALLOCA(sizeof(Value) * argc, Value);
|
||||
// Repeat, while k < len
|
||||
while (k < len) {
|
||||
// If k > 0, then
|
||||
|
|
@ -1903,7 +1931,8 @@ static Value builtinTypedArrayToLocaleString(ExecutionState& state, Value thisVa
|
|||
if (!nextElement.isUndefinedOrNull()) {
|
||||
// Let S be ? ToString(? Invoke(nextElement, "toLocaleString")).
|
||||
Value func = nextElement.toObject(state)->get(state, state.context()->staticStrings().toLocaleString).value(state, nextElement);
|
||||
String* S = Object::call(state, func, nextElement, 0, nullptr).toString(state);
|
||||
memcpy(toLocaleStringArgv, argv, sizeof(Value) * argc);
|
||||
String* S = Object::call(state, func, nextElement, argc, toLocaleStringArgv).toString(state);
|
||||
// Set R to the string-concatenation of R and S.
|
||||
StringBuilder builder2;
|
||||
builder2.appendString(R, &state);
|
||||
|
|
|
|||
|
|
@ -424,46 +424,66 @@ bool CodeCache::loadGlobalCache(Context* context, const CodeCacheIndex& cacheInd
|
|||
{
|
||||
ASSERT(m_enabled && cacheIndex.isValid());
|
||||
|
||||
// load global CodeBlock and its related information
|
||||
prepareCacheLoading(context, cacheIndex, entry);
|
||||
|
||||
InterpretedCodeBlock* topCodeBlock = loadCodeBlockTree(context, script);
|
||||
ByteCodeBlock* topByteCodeBlock = loadByteCodeBlock(context, topCodeBlock);
|
||||
|
||||
if (!postCacheLoading()) {
|
||||
if (m_status != Status::READY) {
|
||||
return false;
|
||||
}
|
||||
|
||||
ASSERT(!!topCodeBlock && !!topByteCodeBlock);
|
||||
script->m_topCodeBlock = topCodeBlock;
|
||||
topCodeBlock->m_byteCodeBlock = topByteCodeBlock;
|
||||
try {
|
||||
// load global CodeBlock and its related information
|
||||
prepareCacheLoading(context, cacheIndex, entry);
|
||||
|
||||
ESCARGOT_LOG_INFO("[CodeCache] Load CodeCache Done (%s)\n", script->srcName()->toUTF8StringData().data());
|
||||
InterpretedCodeBlock* topCodeBlock = loadCodeBlockTree(context, script);
|
||||
ByteCodeBlock* topByteCodeBlock = loadByteCodeBlock(context, topCodeBlock);
|
||||
|
||||
return true;
|
||||
if (!postCacheLoading()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
ASSERT(!!topCodeBlock && !!topByteCodeBlock);
|
||||
script->m_topCodeBlock = topCodeBlock;
|
||||
topCodeBlock->m_byteCodeBlock = topByteCodeBlock;
|
||||
|
||||
ESCARGOT_LOG_INFO("[CodeCache] Load CodeCache Done (%s)\n", script->srcName()->toUTF8StringData().data());
|
||||
|
||||
return true;
|
||||
} catch (CodeCacheReader::Error& error) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool CodeCache::loadFunctionCache(Context* context, const CodeCacheIndex& cacheIndex, const CodeCacheEntry& entry, InterpretedCodeBlock* codeBlock)
|
||||
{
|
||||
ASSERT(m_enabled && cacheIndex.isValid());
|
||||
|
||||
// load function CodeBlock and its related information
|
||||
prepareCacheLoading(context, cacheIndex, entry);
|
||||
|
||||
codeBlock->m_byteCodeBlock = loadByteCodeBlock(context, codeBlock);
|
||||
|
||||
bool result = postCacheLoading();
|
||||
if (result) {
|
||||
ESCARGOT_LOG_INFO("[CodeCache] Load CodeCache Done (%s: index %zu size %zu)\n", codeBlock->script()->srcName()->toNonGCUTF8StringData().data(),
|
||||
codeBlock->functionStart().index, codeBlock->src().length());
|
||||
if (m_status != Status::READY) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
// load function CodeBlock and its related information
|
||||
prepareCacheLoading(context, cacheIndex, entry);
|
||||
|
||||
codeBlock->m_byteCodeBlock = loadByteCodeBlock(context, codeBlock);
|
||||
|
||||
bool result = postCacheLoading();
|
||||
if (result) {
|
||||
ESCARGOT_LOG_INFO("[CodeCache] Load CodeCache Done (%s: index %zu size %zu)\n", codeBlock->script()->srcName()->toNonGCUTF8StringData().data(),
|
||||
codeBlock->functionStart().index, codeBlock->src().length());
|
||||
}
|
||||
return result;
|
||||
} catch (CodeCacheReader::Error& error) {
|
||||
return false;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
bool CodeCache::storeGlobalCache(Context* context, const CodeCacheIndex& cacheIndex, InterpretedCodeBlock* topCodeBlock, CodeBlockCacheInfo* codeBlockCacheInfo, Node* programNode, bool inWith)
|
||||
{
|
||||
ASSERT(m_enabled && cacheIndex.isValid());
|
||||
|
||||
if (m_status != Status::READY) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// store global CodeBlock and its related information
|
||||
prepareCacheWriting(cacheIndex);
|
||||
|
||||
|
|
@ -494,6 +514,10 @@ bool CodeCache::storeFunctionCache(Context* context, const CodeCacheIndex& cache
|
|||
{
|
||||
ASSERT(m_enabled && cacheIndex.isValid());
|
||||
|
||||
if (m_status != Status::READY) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// store function ByteCodeBlock and its related information
|
||||
prepareCacheWriting(cacheIndex);
|
||||
|
||||
|
|
|
|||
|
|
@ -69,7 +69,9 @@ void CacheStringTable::initAdd(const AtomicString& string)
|
|||
|
||||
AtomicString& CacheStringTable::get(size_t index)
|
||||
{
|
||||
ASSERT(index < m_table.size());
|
||||
if (index >= m_table.size()) {
|
||||
throw CodeCacheReader::Error("out of range");
|
||||
}
|
||||
return m_table[index];
|
||||
}
|
||||
|
||||
|
|
@ -210,6 +212,12 @@ void CodeCacheWriter::storeInterpretedCodeBlock(InterpretedCodeBlock* codeBlock)
|
|||
m_buffer.put(codeBlock->m_bodyEndLOC);
|
||||
#endif
|
||||
|
||||
#ifndef ESCARGOT_DEBUGGER
|
||||
// InterpretedCodeBlock::m_parameterUsed
|
||||
m_buffer.ensureSize(sizeof(uint16_t));
|
||||
m_buffer.put(codeBlock->m_parameterUsed);
|
||||
#endif
|
||||
|
||||
m_buffer.ensureSize(5 * sizeof(uint16_t));
|
||||
m_buffer.put(codeBlock->m_functionLength);
|
||||
m_buffer.put(codeBlock->m_parameterCount);
|
||||
|
|
@ -785,6 +793,11 @@ InterpretedCodeBlock* CodeCacheReader::loadInterpretedCodeBlock(Context* context
|
|||
codeBlock->m_bodyEndLOC = m_buffer.get<ExtendedNodeLOC>();
|
||||
#endif
|
||||
|
||||
#ifndef ESCARGOT_DEBUGGER
|
||||
// InterpretedCodeBlock::m_parameterUsed
|
||||
codeBlock->m_parameterUsed = m_buffer.get<uint16_t>();
|
||||
#endif
|
||||
|
||||
codeBlock->m_functionLength = m_buffer.get<uint16_t>();
|
||||
codeBlock->m_parameterCount = m_buffer.get<uint16_t>();
|
||||
codeBlock->m_identifierOnStackCount = m_buffer.get<uint16_t>();
|
||||
|
|
@ -921,6 +934,9 @@ CacheStringTable* CodeCacheReader::loadStringTable(Context* context)
|
|||
LChar* buffer = new LChar[maxLength + 1];
|
||||
for (size_t i = 0; i < tableSize; i++) {
|
||||
size_t length = m_buffer.get<size_t>();
|
||||
if (maxLength < length) {
|
||||
throw CodeCacheReader::Error("invalid maxLength");
|
||||
}
|
||||
m_buffer.getData(buffer, length);
|
||||
buffer[length] = '\0';
|
||||
|
||||
|
|
@ -939,6 +955,9 @@ CacheStringTable* CodeCacheReader::loadStringTable(Context* context)
|
|||
for (size_t i = 0; i < tableSize; i++) {
|
||||
bool is8Bit = m_buffer.get<bool>();
|
||||
size_t length = m_buffer.get<size_t>();
|
||||
if (maxLength < length) {
|
||||
throw CodeCacheReader::Error("invalid maxLength");
|
||||
}
|
||||
|
||||
if (is8Bit) {
|
||||
m_buffer.getData(lBuffer, length);
|
||||
|
|
@ -1007,6 +1026,10 @@ void CodeCacheReader::loadByteCodeStream(Context* context, ByteCodeBlock* block)
|
|||
ByteCodeRelocInfo& info = relocInfoVector[i];
|
||||
ByteCode* currentCode = reinterpret_cast<ByteCode*>(code + info.codeOffset);
|
||||
|
||||
if (info.codeOffset >= byteCodeStream.size()) {
|
||||
throw CodeCacheReader::Error("out of range");
|
||||
}
|
||||
|
||||
#if defined(ESCARGOT_COMPUTED_GOTO_INTERPRETER)
|
||||
Opcode opcode = (Opcode)(size_t)currentCode->m_opcodeInAddress;
|
||||
#else
|
||||
|
|
|
|||
|
|
@ -227,6 +227,14 @@ private:
|
|||
|
||||
class CodeCacheReader {
|
||||
public:
|
||||
class Error {
|
||||
public:
|
||||
std::string message;
|
||||
Error(const std::string& message)
|
||||
: message(message)
|
||||
{
|
||||
}
|
||||
};
|
||||
class CacheBuffer {
|
||||
public:
|
||||
CacheBuffer()
|
||||
|
|
@ -250,7 +258,9 @@ public:
|
|||
template <typename IntegralType>
|
||||
IntegralType get()
|
||||
{
|
||||
ASSERT(m_index < m_capacity);
|
||||
if (m_index + sizeof(IntegralType) > m_capacity) {
|
||||
throw Error("out of range");
|
||||
}
|
||||
IntegralType value;
|
||||
memcpy(&value, m_buffer + m_index, sizeof(IntegralType));
|
||||
m_index += sizeof(IntegralType);
|
||||
|
|
@ -260,10 +270,13 @@ public:
|
|||
template <typename IntegralType>
|
||||
void getData(IntegralType* data, size_t size)
|
||||
{
|
||||
size_t dataSize = size * sizeof(IntegralType);
|
||||
if (m_index + dataSize > m_capacity) {
|
||||
throw Error("out of range");
|
||||
}
|
||||
if (UNLIKELY(!size)) {
|
||||
return;
|
||||
}
|
||||
size_t dataSize = size * sizeof(IntegralType);
|
||||
memcpy(data, m_buffer + m_index, dataSize);
|
||||
m_index += dataSize;
|
||||
}
|
||||
|
|
@ -274,16 +287,19 @@ public:
|
|||
String* str = nullptr;
|
||||
bool is8Bit = get<bool>();
|
||||
size_t length = get<size_t>();
|
||||
if (length > STRING_MAXIMUM_LENGTH) {
|
||||
throw Error("out of range");
|
||||
}
|
||||
ASSERT(length);
|
||||
if (LIKELY(is8Bit)) {
|
||||
LChar* buffer = ALLOCA(sizeof(LChar) * (length + 1), LChar);
|
||||
buffer[length] = '\0';
|
||||
getData(buffer, length);
|
||||
buffer[length] = '\0';
|
||||
str = new Latin1String(buffer, length);
|
||||
} else {
|
||||
UChar* buffer = ALLOCA(sizeof(UChar) * (length + 1), UChar);
|
||||
buffer[length] = '\0';
|
||||
getData(buffer, length);
|
||||
buffer[length] = '\0';
|
||||
str = new UTF16String(buffer, length);
|
||||
}
|
||||
return str;
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -20,22 +20,22 @@
|
|||
#ifndef __Debugger__
|
||||
#define __Debugger__
|
||||
|
||||
#include "runtime/Environment.h"
|
||||
#include "util/Vector.h"
|
||||
|
||||
#ifdef ESCARGOT_DEBUGGER
|
||||
namespace Escargot {
|
||||
|
||||
class ByteCode;
|
||||
|
||||
#define ESCARGOT_DEBUGGER_MAX_STACK_TRACE_LENGTH 8
|
||||
|
||||
/* WebSocket max length encoded in one byte. */
|
||||
#define ESCARGOT_DEBUGGER_MAX_MESSAGE_LENGTH 125
|
||||
#define ESCARGOT_DEBUGGER_VERSION 1
|
||||
#define ESCARGOT_DEBUGGER_MESSAGE_PROCESS_DELAY 10
|
||||
#define ESCARGOT_DEBUGGER_IN_WAIT_MODE (nullptr)
|
||||
#define ESCARGOT_DEBUGGER_IN_EVAL_MODE (reinterpret_cast<ExecutionState*>(0x1))
|
||||
#define ESCARGOT_DEBUGGER_ALWAYS_STOP (reinterpret_cast<ExecutionState*>(0x2))
|
||||
#define ESCARGOT_DEBUGGER_NO_STACK_TRACE_RESTORE (reinterpret_cast<ExecutionState*>(0x1))
|
||||
#define ESCARGOT_DEBUGGER_MAX_VARIABLE_LENGTH 128
|
||||
|
||||
class Context;
|
||||
class Object;
|
||||
|
|
@ -60,6 +60,17 @@ public:
|
|||
uint32_t offset; // bytecode offset
|
||||
};
|
||||
|
||||
struct BreakpointByteCodeLocation {
|
||||
BreakpointByteCodeLocation(const uint32_t line, ByteCode* breakpointByteCode)
|
||||
: line(line)
|
||||
, byteCode(breakpointByteCode)
|
||||
{
|
||||
}
|
||||
|
||||
uint32_t line; // source code line
|
||||
ByteCode* byteCode; // bytecode pointer
|
||||
};
|
||||
|
||||
typedef std::vector<BreakpointLocation> BreakpointLocationVector;
|
||||
|
||||
struct BreakpointLocationsInfo {
|
||||
|
|
@ -131,7 +142,7 @@ public:
|
|||
return m_activeSavedStackTrace;
|
||||
}
|
||||
|
||||
inline void processDisabledBreakpoint(ByteCodeBlock* byteCodeBlock, uint32_t offset, ExecutionState* state)
|
||||
inline bool processDisabledBreakpoint(ByteCodeBlock* byteCodeBlock, uint32_t offset, ExecutionState* state)
|
||||
{
|
||||
if (m_stopState != ESCARGOT_DEBUGGER_ALWAYS_STOP && m_stopState != state) {
|
||||
m_delay--;
|
||||
|
|
@ -143,6 +154,8 @@ public:
|
|||
if (m_stopState == ESCARGOT_DEBUGGER_ALWAYS_STOP || m_stopState == state) {
|
||||
stopAtBreakpoint(byteCodeBlock, offset, state);
|
||||
}
|
||||
|
||||
return m_restartDebugging;
|
||||
}
|
||||
|
||||
static inline void updateStopState(Debugger* debugger, ExecutionState* state, ExecutionState* newState)
|
||||
|
|
@ -153,15 +166,32 @@ public:
|
|||
}
|
||||
}
|
||||
|
||||
static LexicalEnvironment* getFunctionLexEnv(ExecutionState* state)
|
||||
{
|
||||
LexicalEnvironment* lexEnv = state->lexicalEnvironment();
|
||||
|
||||
while (lexEnv) {
|
||||
EnvironmentRecord* record = lexEnv->record();
|
||||
|
||||
if (record->isDeclarativeEnvironmentRecord()
|
||||
&& record->asDeclarativeEnvironmentRecord()->isFunctionEnvironmentRecord()) {
|
||||
return lexEnv;
|
||||
}
|
||||
|
||||
lexEnv = lexEnv->outerEnvironment();
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
|
||||
void setStopState(ExecutionState* stopState)
|
||||
{
|
||||
m_stopState = stopState;
|
||||
}
|
||||
|
||||
static void createDebuggerRemote(const char* options, Context* context);
|
||||
|
||||
virtual void init(const char* options, Context* context) = 0;
|
||||
virtual void parseCompleted(String* source, String* srcName, size_t originLineOffset, String* error = nullptr) = 0;
|
||||
virtual void stopAtBreakpoint(ByteCodeBlock* byteCodeBlock, uint32_t offset, ExecutionState* state) = 0;
|
||||
virtual bool stopAtBreakpoint(ByteCodeBlock* byteCodeBlock, uint32_t offset, ExecutionState* state) = 0;
|
||||
virtual void byteCodeReleaseNotification(ByteCodeBlock* byteCodeBlock) = 0;
|
||||
virtual void exceptionCaught(String* message, SavedStackTraceDataVector& exceptionTrace) = 0;
|
||||
virtual void consoleOut(String* output) = 0;
|
||||
|
|
@ -176,6 +206,21 @@ public:
|
|||
static SavedStackTraceDataVector* saveStackTrace(ExecutionState& state);
|
||||
|
||||
void pumpDebuggerEvents(ExecutionState* state);
|
||||
static void createDebugger(const char* options, Context* context);
|
||||
|
||||
void enable(Context* context);
|
||||
|
||||
Vector<Object*, GCUtil::gc_malloc_allocator<Object*>> m_activeObjects;
|
||||
|
||||
bool getRestart()
|
||||
{
|
||||
return m_restartDebugging;
|
||||
}
|
||||
|
||||
void setRestart(bool b)
|
||||
{
|
||||
m_restartDebugging = b;
|
||||
}
|
||||
|
||||
protected:
|
||||
Debugger()
|
||||
|
|
@ -193,7 +238,6 @@ protected:
|
|||
return m_context != nullptr;
|
||||
}
|
||||
|
||||
void enable(Context* context);
|
||||
void disable();
|
||||
|
||||
virtual bool processEvents(ExecutionState* state, Optional<ByteCodeBlock*> byteCodeBlock, bool isBlockingRequest = true) = 0;
|
||||
|
|
@ -201,6 +245,8 @@ protected:
|
|||
uint32_t m_delay;
|
||||
ExecutionState* m_stopState;
|
||||
std::vector<BreakpointLocationsInfo*> m_breakpointLocationsVector;
|
||||
Vector<uintptr_t, GCUtil::gc_malloc_atomic_allocator<uintptr_t>> m_releasedFunctions;
|
||||
bool m_restartDebugging;
|
||||
|
||||
private:
|
||||
Context* m_context;
|
||||
|
|
@ -212,232 +258,6 @@ private:
|
|||
bool m_inDebuggingCodeMode;
|
||||
};
|
||||
|
||||
class DebuggerRemote : public Debugger {
|
||||
public:
|
||||
// Messages sent by Escargot to the debugger client
|
||||
enum {
|
||||
ESCARGOT_MESSAGE_VERSION = 0,
|
||||
ESCARGOT_MESSAGE_CONFIGURATION = 1,
|
||||
ESCARGOT_MESSAGE_CLOSE_CONNECTION = 2,
|
||||
ESCARGOT_MESSAGE_RELEASE_FUNCTION = 3,
|
||||
ESCARGOT_MESSAGE_PARSE_DONE = 4,
|
||||
ESCARGOT_MESSAGE_PARSE_ERROR = 5,
|
||||
// These four must be in the same order.
|
||||
ESCARGOT_MESSAGE_SOURCE_8BIT = 6,
|
||||
ESCARGOT_MESSAGE_SOURCE_8BIT_END = 7,
|
||||
ESCARGOT_MESSAGE_SOURCE_16BIT = 8,
|
||||
ESCARGOT_MESSAGE_SOURCE_16BIT_END = 9,
|
||||
// These four must be in the same order.
|
||||
ESCARGOT_MESSAGE_FILE_NAME_8BIT = 10,
|
||||
ESCARGOT_MESSAGE_FILE_NAME_8BIT_END = 11,
|
||||
ESCARGOT_MESSAGE_FILE_NAME_16BIT = 12,
|
||||
ESCARGOT_MESSAGE_FILE_NAME_16BIT_END = 13,
|
||||
// These four must be in the same order.
|
||||
ESCARGOT_MESSAGE_FUNCTION_NAME_8BIT = 14,
|
||||
ESCARGOT_MESSAGE_FUNCTION_NAME_8BIT_END = 15,
|
||||
ESCARGOT_MESSAGE_FUNCTION_NAME_16BIT = 16,
|
||||
ESCARGOT_MESSAGE_FUNCTION_NAME_16BIT_END = 17,
|
||||
ESCARGOT_MESSAGE_BREAKPOINT_LOCATION = 18,
|
||||
ESCARGOT_MESSAGE_FUNCTION_PTR = 19,
|
||||
ESCARGOT_MESSAGE_BREAKPOINT_HIT = 20,
|
||||
ESCARGOT_MESSAGE_EXCEPTION_HIT = 21,
|
||||
// These four must be in the same order.
|
||||
ESCARGOT_MESSAGE_EVAL_RESULT_8BIT = 22,
|
||||
ESCARGOT_MESSAGE_EVAL_RESULT_8BIT_END = 23,
|
||||
ESCARGOT_MESSAGE_EVAL_RESULT_16BIT = 24,
|
||||
ESCARGOT_MESSAGE_EVAL_RESULT_16BIT_END = 25,
|
||||
// These four must be in the same order.
|
||||
ESCARGOT_MESSAGE_EVAL_FAILED_8BIT = 26,
|
||||
ESCARGOT_MESSAGE_EVAL_FAILED_8BIT_END = 27,
|
||||
ESCARGOT_MESSAGE_EVAL_FAILED_16BIT = 28,
|
||||
ESCARGOT_MESSAGE_EVAL_FAILED_16BIT_END = 29,
|
||||
ESCARGOT_MESSAGE_BACKTRACE_TOTAL = 30,
|
||||
ESCARGOT_MESSAGE_BACKTRACE = 31,
|
||||
ESCARGOT_MESSAGE_BACKTRACE_END = 32,
|
||||
ESCARGOT_MESSAGE_SCOPE_CHAIN = 33,
|
||||
ESCARGOT_MESSAGE_SCOPE_CHAIN_END = 34,
|
||||
// These four must be in the same order.
|
||||
ESCARGOT_MESSAGE_STRING_8BIT = 35,
|
||||
ESCARGOT_MESSAGE_STRING_8BIT_END = 36,
|
||||
ESCARGOT_MESSAGE_STRING_16BIT = 37,
|
||||
ESCARGOT_MESSAGE_STRING_16BIT_END = 38,
|
||||
ESCARGOT_MESSAGE_VARIABLE = 39,
|
||||
ESCARGOT_MESSAGE_PRINT = 40,
|
||||
ESCARGOT_MESSAGE_EXCEPTION = 41,
|
||||
ESCARGOT_MESSAGE_EXCEPTION_BACKTRACE = 42,
|
||||
ESCARGOT_DEBUGGER_WAIT_FOR_SOURCE = 43,
|
||||
ESCARGOT_DEBUGGER_WAITING_AFTER_PENDING = 44,
|
||||
ESCARGOT_DEBUGGER_WAIT_FOR_WAIT_EXIT = 45,
|
||||
};
|
||||
|
||||
// Messages sent by the debugger client to Escargot
|
||||
enum {
|
||||
ESCARGOT_MESSAGE_FUNCTION_RELEASED = 0,
|
||||
ESCARGOT_MESSAGE_UPDATE_BREAKPOINT = 1,
|
||||
ESCARGOT_MESSAGE_CONTINUE = 2,
|
||||
ESCARGOT_MESSAGE_STEP = 3,
|
||||
ESCARGOT_MESSAGE_NEXT = 4,
|
||||
ESCARGOT_MESSAGE_FINISH = 5,
|
||||
// These four must be in the same order.
|
||||
ESCARGOT_MESSAGE_EVAL_8BIT_START = 6,
|
||||
ESCARGOT_MESSAGE_EVAL_8BIT = 7,
|
||||
ESCARGOT_MESSAGE_EVAL_16BIT_START = 8,
|
||||
ESCARGOT_MESSAGE_EVAL_16BIT = 9,
|
||||
// These four must be in the same order.
|
||||
ESCARGOT_MESSAGE_EVAL_WITHOUT_STOP_8BIT_START = 10,
|
||||
ESCARGOT_MESSAGE_EVAL_WITHOUT_STOP_8BIT = 11,
|
||||
ESCARGOT_MESSAGE_EVAL_WITHOUT_STOP_16BIT_START = 12,
|
||||
ESCARGOT_MESSAGE_EVAL_WITHOUT_STOP_16BIT = 13,
|
||||
ESCARGOT_MESSAGE_GET_BACKTRACE = 14,
|
||||
ESCARGOT_MESSAGE_GET_SCOPE_CHAIN = 15,
|
||||
ESCARGOT_MESSAGE_GET_SCOPE_VARIABLES = 16,
|
||||
ESCARGOT_MESSAGE_GET_OBJECT = 17,
|
||||
// These four must be in the same order.
|
||||
ESCARGOT_DEBUGGER_CLIENT_SOURCE_8BIT_START = 18,
|
||||
ESCARGOT_DEBUGGER_CLIENT_SOURCE_8BIT = 19,
|
||||
ESCARGOT_DEBUGGER_CLIENT_SOURCE_16BIT_START = 20,
|
||||
ESCARGOT_DEBUGGER_CLIENT_SOURCE_16BIT = 21,
|
||||
ESCARGOT_DEBUGGER_THERE_WAS_NO_SOURCE = 22,
|
||||
ESCARGOT_DEBUGGER_PENDING_CONFIG = 23,
|
||||
ESCARGOT_DEBUGGER_PENDING_RESUME = 24,
|
||||
ESCARGOT_DEBUGGER_WAIT_BEFORE_EXIT = 25,
|
||||
};
|
||||
|
||||
// Environment record types
|
||||
enum {
|
||||
ESCARGOT_RECORD_GLOBAL_ENVIRONMENT = 0,
|
||||
ESCARGOT_RECORD_FUNCTION_ENVIRONMENT = 1,
|
||||
ESCARGOT_RECORD_DECLARATIVE_ENVIRONMENT = 2,
|
||||
ESCARGOT_RECORD_OBJECT_ENVIRONMENT = 3,
|
||||
ESCARGOT_RECORD_MODULE_ENVIRONMENT = 4,
|
||||
ESCARGOT_RECORD_UNKNOWN_ENVIRONMENT = 5,
|
||||
};
|
||||
|
||||
// Variable types
|
||||
enum {
|
||||
ESCARGOT_VARIABLE_END = 0,
|
||||
ESCARGOT_VARIABLE_UNACCESSIBLE = 1,
|
||||
ESCARGOT_VARIABLE_UNDEFINED = 2,
|
||||
ESCARGOT_VARIABLE_NULL = 3,
|
||||
ESCARGOT_VARIABLE_TRUE = 4,
|
||||
ESCARGOT_VARIABLE_FALSE = 5,
|
||||
ESCARGOT_VARIABLE_NUMBER = 6,
|
||||
ESCARGOT_VARIABLE_STRING = 7,
|
||||
ESCARGOT_VARIABLE_SYMBOL = 8,
|
||||
ESCARGOT_VARIABLE_BIGINT = 9,
|
||||
// Only object types should be defined after this point.
|
||||
ESCARGOT_VARIABLE_OBJECT = 10,
|
||||
ESCARGOT_VARIABLE_ARRAY = 11,
|
||||
ESCARGOT_VARIABLE_FUNCTION = 12,
|
||||
ESCARGOT_VARIABLE_TYPE_MASK = 0x3f,
|
||||
ESCARGOT_VARIABLE_LONG_NAME = 0x40,
|
||||
ESCARGOT_VARIABLE_LONG_VALUE = 0x80,
|
||||
};
|
||||
|
||||
inline bool pendingWait(void)
|
||||
{
|
||||
return m_pendingWait;
|
||||
}
|
||||
|
||||
inline bool connected(void)
|
||||
{
|
||||
return enabled();
|
||||
}
|
||||
|
||||
void sendType(uint8_t type);
|
||||
void sendSubtype(uint8_t type, uint8_t subType);
|
||||
void sendString(uint8_t type, const String* string);
|
||||
void sendPointer(uint8_t type, const void* ptr);
|
||||
|
||||
virtual void init(const char* options, Context* context) = 0;
|
||||
virtual void parseCompleted(String* source, String* srcName, size_t originLineOffset, String* error = nullptr) override;
|
||||
virtual void stopAtBreakpoint(ByteCodeBlock* byteCodeBlock, uint32_t offset, ExecutionState* state) override;
|
||||
virtual void byteCodeReleaseNotification(ByteCodeBlock* byteCodeBlock) override;
|
||||
virtual void exceptionCaught(String* message, SavedStackTraceDataVector& exceptionTrace) override;
|
||||
virtual void consoleOut(String* output) override;
|
||||
virtual String* getClientSource(String** sourceName) override;
|
||||
virtual bool getWaitBeforeExitClient() override;
|
||||
|
||||
void sendBacktraceInfo(uint8_t type, ByteCodeBlock* byteCodeBlock, uint32_t line, uint32_t column, uint32_t executionStateDepth);
|
||||
void sendVariableObjectInfo(uint8_t subType, Object* object);
|
||||
void waitForResolvingPendingBreakpoints();
|
||||
|
||||
protected:
|
||||
enum CloseReason {
|
||||
CloseEndConnection,
|
||||
CloseAbortConnection,
|
||||
CloseProtocolUnsupported,
|
||||
CloseProtocolError,
|
||||
};
|
||||
|
||||
DebuggerRemote()
|
||||
: m_exitClient(false)
|
||||
, m_pendingWait(false)
|
||||
, m_waitForResume(false)
|
||||
, m_clientSourceData(nullptr)
|
||||
, m_clientSourceName(nullptr)
|
||||
{
|
||||
}
|
||||
|
||||
virtual bool processEvents(ExecutionState* state, Optional<ByteCodeBlock*> byteCodeBlock, bool isBlockingRequest = true) override;
|
||||
|
||||
virtual bool send(uint8_t type, const void* buffer, size_t length) = 0;
|
||||
virtual bool receive(uint8_t* buffer, size_t& length) = 0;
|
||||
virtual bool isThereAnyEvent() = 0;
|
||||
virtual void close(CloseReason reason) = 0;
|
||||
|
||||
private:
|
||||
// Packed structure definitions to reduce network traffic
|
||||
|
||||
struct MessageVersion {
|
||||
uint8_t littleEndian;
|
||||
uint8_t version[sizeof(uint32_t)];
|
||||
};
|
||||
|
||||
struct MessageConfiguration {
|
||||
uint8_t maxMessageSize;
|
||||
uint8_t pointerSize;
|
||||
};
|
||||
|
||||
struct FunctionInfo {
|
||||
uint8_t byteCodeStart[sizeof(void*)];
|
||||
uint8_t startLine[sizeof(uint32_t)];
|
||||
uint8_t startColumn[sizeof(uint32_t)];
|
||||
};
|
||||
|
||||
struct BreakpointOffset {
|
||||
uint8_t byteCodeStart[sizeof(void*)];
|
||||
uint8_t offset[sizeof(uint32_t)];
|
||||
};
|
||||
|
||||
struct BacktraceInfo {
|
||||
uint8_t byteCode[sizeof(void*)];
|
||||
uint8_t line[sizeof(uint32_t)];
|
||||
uint8_t column[sizeof(uint32_t)];
|
||||
uint8_t executionStateDepth[sizeof(uint32_t)];
|
||||
};
|
||||
|
||||
struct VariableObjectInfo {
|
||||
uint8_t subType;
|
||||
uint8_t index[sizeof(uint32_t)];
|
||||
};
|
||||
|
||||
uint32_t appendToActiveObjects(Object* object);
|
||||
bool doEval(ExecutionState* state, Optional<ByteCodeBlock*> byteCodeBlock, uint8_t* buffer, size_t length);
|
||||
void getBacktrace(ExecutionState* state, uint32_t minDepth, uint32_t maxDepth, bool getTotal);
|
||||
void getScopeChain(ExecutionState* state, uint32_t stateIndex);
|
||||
void getScopeVariables(ExecutionState* state, uint32_t stateIndex, uint32_t index);
|
||||
|
||||
bool m_exitClient : 1;
|
||||
bool m_pendingWait : 1;
|
||||
bool m_waitForResume : 1;
|
||||
String* m_clientSourceData;
|
||||
String* m_clientSourceName;
|
||||
|
||||
Vector<uintptr_t, GCUtil::gc_malloc_atomic_allocator<uintptr_t>> m_releasedFunctions;
|
||||
Vector<Object*, GCUtil::gc_malloc_allocator<Object*>> m_activeObjects;
|
||||
};
|
||||
|
||||
} // namespace Escargot
|
||||
#endif /* ESCARGOT_DEBUGGER */
|
||||
|
||||
|
|
|
|||
1188
src/debugger/DebuggerDevtools.cpp
Normal file
1188
src/debugger/DebuggerDevtools.cpp
Normal file
File diff suppressed because it is too large
Load diff
137
src/debugger/DebuggerDevtools.h
Normal file
137
src/debugger/DebuggerDevtools.h
Normal file
|
|
@ -0,0 +1,137 @@
|
|||
/*
|
||||
* Copyright (c) 2026-present Samsung Electronics Co., Ltd
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
|
||||
* USA
|
||||
*/
|
||||
|
||||
#ifndef __DebuggerDevtools__
|
||||
#define __DebuggerDevtools__
|
||||
|
||||
#include "Debugger.h"
|
||||
#include "DebuggerTcp.h"
|
||||
#include "interpreter/ByteCode.h"
|
||||
#include "rapidjson/document.h"
|
||||
#include "rapidjson/rapidjson.h"
|
||||
|
||||
#ifdef ESCARGOT_DEBUGGER
|
||||
namespace Escargot {
|
||||
|
||||
#ifdef WIN32
|
||||
#include <winsock2.h>
|
||||
typedef SOCKET EscargotSocket;
|
||||
#else /* !WIN32 */
|
||||
typedef int EscargotSocket;
|
||||
#endif /* WIN32 */
|
||||
|
||||
struct ScriptInfo {
|
||||
uint8_t scriptId;
|
||||
String* url;
|
||||
String* source;
|
||||
};
|
||||
|
||||
typedef HashMap<AtomicString, Value, std::hash<AtomicString>, std::equal_to<AtomicString>, GCUtil::gc_malloc_allocator<std::pair<AtomicString const, Value>>> PropertyNameValueMap;
|
||||
typedef Vector<PropertyNameValueMap*, GCUtil::gc_malloc_allocator<PropertyNameValueMap*>> PropertyNameValueMapVector;
|
||||
|
||||
class DebuggerDevtools : public DebuggerTcp {
|
||||
public:
|
||||
DebuggerDevtools(EscargotSocket socket, String* skipSource)
|
||||
: DebuggerTcp(socket, skipSource, ESCARGOT_WS_BUFFER_SIZE, ESCARGOT_DEBUGGER_WEBSOCKET_TEXT_FRAME)
|
||||
{
|
||||
}
|
||||
|
||||
bool sendMessage(const std::string& msg, size_t length = -1);
|
||||
bool sendJSONDocument(const rapidjson::Document& document);
|
||||
void init(const char* options, Context* context) override;
|
||||
bool skipSourceCode(String* srcName) const override;
|
||||
|
||||
void parseCompleted(String* source, String* srcName, size_t originLineOffset, String* error = nullptr) override;
|
||||
bool stopAtBreakpoint(ByteCodeBlock* byteCodeBlock, uint32_t offset, ExecutionState* state) override;
|
||||
void byteCodeReleaseNotification(ByteCodeBlock* byteCodeBlock) override;
|
||||
void exceptionCaught(String* message, SavedStackTraceDataVector& exceptionTrace) override;
|
||||
void consoleOut(String* output) override;
|
||||
String* getClientSource(String** sourceName) override;
|
||||
bool getWaitBeforeExitClient() override;
|
||||
|
||||
|
||||
void sendPausedEvent(ByteCodeBlock* byteCodeBlock, uint32_t offset, ExecutionState* state, bool breakpoint = false);
|
||||
|
||||
protected:
|
||||
bool processEvents(ExecutionState* state, Optional<ByteCodeBlock*> byteCodeBlock, bool isBlockingRequest = true) override;
|
||||
|
||||
private:
|
||||
bool evaluate(rapidjson::Document& jsonMessage, ExecutionState* state = nullptr);
|
||||
bool sendProperties(rapidjson::Document& jsonMessage, ExecutionState* state = nullptr);
|
||||
bool resume(rapidjson::Document& jsonMessage, ExecutionState* state = nullptr);
|
||||
bool stepOver(rapidjson::Document& jsonMessage, ExecutionState* state = nullptr);
|
||||
bool stepOut(rapidjson::Document& jsonMessage, ExecutionState* state);
|
||||
bool stepInto(rapidjson::Document& jsonMessage, ExecutionState* state = nullptr);
|
||||
bool sendSourceCode(rapidjson::Document& jsonMessage, ExecutionState* state = nullptr);
|
||||
bool replyOK(rapidjson::Document& jsonMessage, ExecutionState* state = nullptr);
|
||||
bool enableNetwork(rapidjson::Document& jsonMessage, ExecutionState* state = nullptr);
|
||||
bool enableDebugger(rapidjson::Document& jsonMessage, ExecutionState* state = nullptr);
|
||||
bool enableRuntime(rapidjson::Document& jsonMessage, ExecutionState* state = nullptr);
|
||||
bool enableProfiler(rapidjson::Document& jsonMessage, ExecutionState* state = nullptr);
|
||||
bool setPauseOnExceptions(rapidjson::Document& jsonMessage, ExecutionState* state = nullptr);
|
||||
bool setBreakpointsActive(rapidjson::Document& jsonMessage, ExecutionState* state = nullptr);
|
||||
bool setBreakpointByUrl(rapidjson::Document& jsonMessage, ExecutionState* state = nullptr);
|
||||
bool removeBreakpoint(rapidjson::Document& jsonMessage, ExecutionState* state = nullptr);
|
||||
bool sendPossibleBreakpoints(rapidjson::Document& jsonMessage, ExecutionState* state = nullptr);
|
||||
bool takeHeapSnapshot(rapidjson::Document& jsonMessage, ExecutionState* state = nullptr);
|
||||
bool replyMethodNotFound(rapidjson::Document& jsonMessage, ExecutionState* state = nullptr);
|
||||
|
||||
uint32_t registerValuesMap(PropertyNameValueMap* newPropertyMap);
|
||||
|
||||
static bool compareBreakpointLocations(const BreakpointByteCodeLocation& a, const BreakpointByteCodeLocation& b)
|
||||
{
|
||||
if (LIKELY(a.line != b.line)) {
|
||||
return a.line < b.line;
|
||||
}
|
||||
return a.byteCode->m_loc.column <= b.byteCode->m_loc.column;
|
||||
}
|
||||
|
||||
uint8_t registerScript(String* url, String* source);
|
||||
|
||||
bool m_networkEnabled = false;
|
||||
bool m_debuggerEnabled = false;
|
||||
bool m_runtimeEnabled = false;
|
||||
bool m_profilerEnabled = false;
|
||||
bool m_pauseOnExceptions = false;
|
||||
bool m_breakpointsActive = false;
|
||||
bool m_startBreakpoint = true;
|
||||
|
||||
std::unordered_map<uint8_t, ScriptInfo> m_scriptsById;
|
||||
std::unordered_map<std::string, uint8_t> m_scriptIdByUrl;
|
||||
uint8_t m_nextScriptId = 1;
|
||||
std::unordered_map<uint8_t, std::set<BreakpointByteCodeLocation, decltype(compareBreakpointLocations)*>> m_breakpointInfo;
|
||||
PropertyNameValueMapVector m_propertyMapsById;
|
||||
uint32_t m_objectIdVectorIndexOffset = 0;
|
||||
uint32_t m_nextObjectId = 0;
|
||||
uint32_t m_nextCallFrameId = 1;
|
||||
|
||||
std::vector<std::string> m_pendingMessages;
|
||||
std::set<ByteCode*> m_setBreakPoints; // stores set breakpoints for enabling/disabling in bulk with the `Deactivate Breakpoints` button
|
||||
};
|
||||
|
||||
using MessageHandler = bool (DebuggerDevtools::*)(rapidjson::Document&, ExecutionState* state);
|
||||
|
||||
struct MessageType {
|
||||
const char* methodName;
|
||||
MessageHandler handler;
|
||||
};
|
||||
} // namespace Escargot
|
||||
#endif /* ESCARGOT_DEBUGGER */
|
||||
|
||||
#endif
|
||||
81
src/debugger/DebuggerDevtoolsMessageBuilder.cpp
Normal file
81
src/debugger/DebuggerDevtoolsMessageBuilder.cpp
Normal file
|
|
@ -0,0 +1,81 @@
|
|||
/*
|
||||
* Copyright (c) 2026-present Samsung Electronics Co., Ltd
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
|
||||
* USA
|
||||
*/
|
||||
|
||||
#include "DebuggerDevtoolsMessageBuilder.h"
|
||||
|
||||
#ifdef ESCARGOT_DEBUGGER
|
||||
|
||||
#include "Escargot.h"
|
||||
|
||||
#include "runtime/String.h" // for split function
|
||||
#include "rapidjson/document.h"
|
||||
#include "rapidjson/prettywriter.h"
|
||||
#include "rapidjson/stringbuffer.h"
|
||||
|
||||
namespace Escargot {
|
||||
|
||||
std::string DebuggerDevtoolsMessageBuilder::buildEmptyMessage(const uint32_t id)
|
||||
{
|
||||
char buffer[64];
|
||||
const int written = snprintf(buffer, sizeof(buffer),
|
||||
R"({"id":%u,"result":{}})",
|
||||
id);
|
||||
|
||||
if (written < 0) {
|
||||
return {};
|
||||
}
|
||||
|
||||
return { buffer, static_cast<size_t>(written) };
|
||||
}
|
||||
|
||||
std::string DebuggerDevtoolsMessageBuilder::buildSourceCodeMessage(const uint8_t requestId, const String* source)
|
||||
{
|
||||
if (!source || !source->is8Bit()) {
|
||||
return {};
|
||||
}
|
||||
|
||||
const LChar* sourceCode = source->characters8();
|
||||
const size_t sourceCodeLength = source->length();
|
||||
|
||||
rapidjson::StringBuffer sb;
|
||||
rapidjson::Writer<rapidjson::StringBuffer> writer(sb);
|
||||
writer.String(reinterpret_cast<const rapidjson::Writer<rapidjson::GenericStringBuffer<rapidjson::UTF8<>>>::Ch*>(sourceCode), sourceCodeLength);
|
||||
const std::string escaped = sb.GetString();
|
||||
|
||||
// FIXME: buffer size depends on length of source.
|
||||
char buffer[4096];
|
||||
const int written = snprintf(buffer, sizeof(buffer),
|
||||
"{\"id\":%u,"
|
||||
"\"result\":{"
|
||||
"\"scriptSource\":%s"
|
||||
"}"
|
||||
"}",
|
||||
requestId,
|
||||
escaped.c_str());
|
||||
|
||||
if (written < 0 || static_cast<size_t>(written) >= sizeof(buffer)) {
|
||||
return {};
|
||||
}
|
||||
|
||||
return { buffer, static_cast<size_t>(written) };
|
||||
}
|
||||
|
||||
} // namespace Escargot
|
||||
|
||||
#endif /* ESCARGOT_DEBUGGER */
|
||||
41
src/debugger/DebuggerDevtoolsMessageBuilder.h
Normal file
41
src/debugger/DebuggerDevtoolsMessageBuilder.h
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
/*
|
||||
* Copyright (c) 2026-present Samsung Electronics Co., Ltd
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
|
||||
* USA
|
||||
*/
|
||||
|
||||
#ifndef __DebuggerDevtoolsMessageBuilder__
|
||||
#define __DebuggerDevtoolsMessageBuilder__
|
||||
|
||||
#include "rapidjson/document.h"
|
||||
#include "rapidjson/rapidjson.h"
|
||||
|
||||
#ifdef ESCARGOT_DEBUGGER
|
||||
namespace Escargot {
|
||||
|
||||
class String;
|
||||
|
||||
class DebuggerDevtoolsMessageBuilder {
|
||||
public:
|
||||
static std::string buildEmptyMessage(uint32_t id);
|
||||
static std::string buildScriptParsedMessage(uint8_t scriptId, const String* source, const String* srcName);
|
||||
static std::string buildSourceCodeMessage(uint8_t requestId, const String* source);
|
||||
};
|
||||
|
||||
} // namespace Escargot
|
||||
#endif /* ESCARGOT_DEBUGGER */
|
||||
|
||||
#endif
|
||||
1123
src/debugger/DebuggerEscargot.cpp
Normal file
1123
src/debugger/DebuggerEscargot.cpp
Normal file
File diff suppressed because it is too large
Load diff
270
src/debugger/DebuggerEscargot.h
Normal file
270
src/debugger/DebuggerEscargot.h
Normal file
|
|
@ -0,0 +1,270 @@
|
|||
/*
|
||||
* Copyright (c) 2016-present Samsung Electronics Co., Ltd
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
|
||||
* USA
|
||||
*/
|
||||
|
||||
#ifndef __DebuggerEscargot__
|
||||
#define __DebuggerEscargot__
|
||||
|
||||
#include "Debugger.h"
|
||||
#include "DebuggerTcp.h"
|
||||
|
||||
#ifdef ESCARGOT_DEBUGGER
|
||||
|
||||
#define ESCARGOT_DEBUGGER_VERSION 1
|
||||
#define ESCARGOT_DEBUGGER_MAX_VARIABLE_LENGTH 128
|
||||
|
||||
namespace Escargot {
|
||||
|
||||
class Context;
|
||||
class Object;
|
||||
class String;
|
||||
class ExecutionState;
|
||||
class ByteCodeBlock;
|
||||
class InterpretedCodeBlock;
|
||||
|
||||
class DebuggerEscargot : public DebuggerTcp {
|
||||
public:
|
||||
// Messages sent by Escargot to the debugger client
|
||||
enum {
|
||||
ESCARGOT_MESSAGE_VERSION = 0,
|
||||
ESCARGOT_MESSAGE_CONFIGURATION = 1,
|
||||
ESCARGOT_MESSAGE_CLOSE_CONNECTION = 2,
|
||||
ESCARGOT_MESSAGE_RELEASE_FUNCTION = 3,
|
||||
ESCARGOT_MESSAGE_PARSE_DONE = 4,
|
||||
ESCARGOT_MESSAGE_PARSE_ERROR = 5,
|
||||
// These four must be in the same order.
|
||||
ESCARGOT_MESSAGE_SOURCE_8BIT = 6,
|
||||
ESCARGOT_MESSAGE_SOURCE_8BIT_END = 7,
|
||||
ESCARGOT_MESSAGE_SOURCE_16BIT = 8,
|
||||
ESCARGOT_MESSAGE_SOURCE_16BIT_END = 9,
|
||||
// These four must be in the same order.
|
||||
ESCARGOT_MESSAGE_FILE_NAME_8BIT = 10,
|
||||
ESCARGOT_MESSAGE_FILE_NAME_8BIT_END = 11,
|
||||
ESCARGOT_MESSAGE_FILE_NAME_16BIT = 12,
|
||||
ESCARGOT_MESSAGE_FILE_NAME_16BIT_END = 13,
|
||||
// These four must be in the same order.
|
||||
ESCARGOT_MESSAGE_FUNCTION_NAME_8BIT = 14,
|
||||
ESCARGOT_MESSAGE_FUNCTION_NAME_8BIT_END = 15,
|
||||
ESCARGOT_MESSAGE_FUNCTION_NAME_16BIT = 16,
|
||||
ESCARGOT_MESSAGE_FUNCTION_NAME_16BIT_END = 17,
|
||||
ESCARGOT_MESSAGE_BREAKPOINT_LOCATION = 18,
|
||||
ESCARGOT_MESSAGE_FUNCTION_PTR = 19,
|
||||
ESCARGOT_MESSAGE_BREAKPOINT_HIT = 20,
|
||||
ESCARGOT_MESSAGE_EXCEPTION_HIT = 21,
|
||||
// These four must be in the same order.
|
||||
ESCARGOT_MESSAGE_EVAL_RESULT_8BIT = 22,
|
||||
ESCARGOT_MESSAGE_EVAL_RESULT_8BIT_END = 23,
|
||||
ESCARGOT_MESSAGE_EVAL_RESULT_16BIT = 24,
|
||||
ESCARGOT_MESSAGE_EVAL_RESULT_16BIT_END = 25,
|
||||
// These four must be in the same order.
|
||||
ESCARGOT_MESSAGE_EVAL_FAILED_8BIT = 26,
|
||||
ESCARGOT_MESSAGE_EVAL_FAILED_8BIT_END = 27,
|
||||
ESCARGOT_MESSAGE_EVAL_FAILED_16BIT = 28,
|
||||
ESCARGOT_MESSAGE_EVAL_FAILED_16BIT_END = 29,
|
||||
// These four must be in the same order.
|
||||
ESCARGOT_MESSAGE_WATCH_RESULT_8BIT = 30,
|
||||
ESCARGOT_MESSAGE_WATCH_RESULT_8BIT_END = 31,
|
||||
ESCARGOT_MESSAGE_WATCH_RESULT_16BIT = 32,
|
||||
ESCARGOT_MESSAGE_WATCH_RESULT_16BIT_END = 33,
|
||||
ESCARGOT_MESSAGE_BACKTRACE_TOTAL = 34,
|
||||
ESCARGOT_MESSAGE_BACKTRACE = 35,
|
||||
ESCARGOT_MESSAGE_BACKTRACE_END = 36,
|
||||
ESCARGOT_MESSAGE_SCOPE_CHAIN = 37,
|
||||
ESCARGOT_MESSAGE_SCOPE_CHAIN_END = 38,
|
||||
// These four must be in the same order.
|
||||
ESCARGOT_MESSAGE_STRING_8BIT = 39,
|
||||
ESCARGOT_MESSAGE_STRING_8BIT_END = 40,
|
||||
ESCARGOT_MESSAGE_STRING_16BIT = 41,
|
||||
ESCARGOT_MESSAGE_STRING_16BIT_END = 42,
|
||||
ESCARGOT_MESSAGE_VARIABLE = 43,
|
||||
ESCARGOT_MESSAGE_PRINT = 44,
|
||||
ESCARGOT_MESSAGE_EXCEPTION = 45,
|
||||
ESCARGOT_MESSAGE_EXCEPTION_BACKTRACE = 46,
|
||||
ESCARGOT_DEBUGGER_WAIT_FOR_SOURCE = 47,
|
||||
ESCARGOT_DEBUGGER_WAITING_AFTER_PENDING = 48,
|
||||
ESCARGOT_DEBUGGER_WAIT_FOR_WAIT_EXIT = 49,
|
||||
ESCARGOT_DEBUGGER_SNAPSHOT_FINISHED = 50
|
||||
};
|
||||
|
||||
// Messages sent by the debugger client to Escargot
|
||||
enum {
|
||||
ESCARGOT_MESSAGE_FUNCTION_RELEASED = 0,
|
||||
ESCARGOT_MESSAGE_UPDATE_BREAKPOINT = 1,
|
||||
ESCARGOT_MESSAGE_CONTINUE = 2,
|
||||
ESCARGOT_MESSAGE_STEP = 3,
|
||||
ESCARGOT_MESSAGE_NEXT = 4,
|
||||
ESCARGOT_MESSAGE_FINISH = 5,
|
||||
ESCARGOT_MESSAGE_RESTART = 6,
|
||||
// These four must be in the same order.
|
||||
ESCARGOT_MESSAGE_EVAL_8BIT_START = 7,
|
||||
ESCARGOT_MESSAGE_EVAL_8BIT = 8,
|
||||
ESCARGOT_MESSAGE_EVAL_16BIT_START = 9,
|
||||
ESCARGOT_MESSAGE_EVAL_16BIT = 10,
|
||||
// These four must be in the same order.
|
||||
ESCARGOT_MESSAGE_EVAL_WITHOUT_STOP_8BIT_START = 11,
|
||||
ESCARGOT_MESSAGE_EVAL_WITHOUT_STOP_8BIT = 12,
|
||||
ESCARGOT_MESSAGE_EVAL_WITHOUT_STOP_16BIT_START = 13,
|
||||
ESCARGOT_MESSAGE_EVAL_WITHOUT_STOP_16BIT = 14,
|
||||
// These four must be in the same order.
|
||||
ESCARGOT_MESSAGE_WATCH_8BIT_START = 15,
|
||||
ESCARGOT_MESSAGE_WATCH_8BIT = 16,
|
||||
ESCARGOT_MESSAGE_WATCH_16BIT_START = 17,
|
||||
ESCARGOT_MESSAGE_WATCH_16BIT = 18,
|
||||
ESCARGOT_MESSAGE_GET_BACKTRACE = 19,
|
||||
ESCARGOT_MESSAGE_GET_SCOPE_CHAIN = 20,
|
||||
ESCARGOT_MESSAGE_GET_SCOPE_VARIABLES = 21,
|
||||
ESCARGOT_MESSAGE_GET_OBJECT = 22,
|
||||
// These four must be in the same order.
|
||||
ESCARGOT_DEBUGGER_CLIENT_SOURCE_8BIT_START = 23,
|
||||
ESCARGOT_DEBUGGER_CLIENT_SOURCE_8BIT = 24,
|
||||
ESCARGOT_DEBUGGER_CLIENT_SOURCE_16BIT_START = 25,
|
||||
ESCARGOT_DEBUGGER_CLIENT_SOURCE_16BIT = 26,
|
||||
ESCARGOT_DEBUGGER_THERE_WAS_NO_SOURCE = 27,
|
||||
ESCARGOT_DEBUGGER_PENDING_CONFIG = 28,
|
||||
ESCARGOT_DEBUGGER_PENDING_RESUME = 29,
|
||||
ESCARGOT_DEBUGGER_WAIT_BEFORE_EXIT = 30,
|
||||
ESCARGOT_DEBUGGER_TAKE_HEAP_SNAPSHOT = 31,
|
||||
ESCARGOT_DEBUGGER_STOP = 32
|
||||
};
|
||||
|
||||
|
||||
// Environment record types
|
||||
enum {
|
||||
ESCARGOT_RECORD_GLOBAL_ENVIRONMENT = 0,
|
||||
ESCARGOT_RECORD_FUNCTION_ENVIRONMENT = 1,
|
||||
ESCARGOT_RECORD_DECLARATIVE_ENVIRONMENT = 2,
|
||||
ESCARGOT_RECORD_OBJECT_ENVIRONMENT = 3,
|
||||
ESCARGOT_RECORD_MODULE_ENVIRONMENT = 4,
|
||||
ESCARGOT_RECORD_UNKNOWN_ENVIRONMENT = 5,
|
||||
};
|
||||
|
||||
// Variable types
|
||||
enum {
|
||||
ESCARGOT_VARIABLE_END = 0,
|
||||
ESCARGOT_VARIABLE_UNACCESSIBLE = 1,
|
||||
ESCARGOT_VARIABLE_UNDEFINED = 2,
|
||||
ESCARGOT_VARIABLE_NULL = 3,
|
||||
ESCARGOT_VARIABLE_TRUE = 4,
|
||||
ESCARGOT_VARIABLE_FALSE = 5,
|
||||
ESCARGOT_VARIABLE_NUMBER = 6,
|
||||
ESCARGOT_VARIABLE_STRING = 7,
|
||||
ESCARGOT_VARIABLE_SYMBOL = 8,
|
||||
ESCARGOT_VARIABLE_BIGINT = 9,
|
||||
// Only object types should be defined after this point.
|
||||
ESCARGOT_VARIABLE_OBJECT = 10,
|
||||
ESCARGOT_VARIABLE_ARRAY = 11,
|
||||
ESCARGOT_VARIABLE_FUNCTION = 12,
|
||||
ESCARGOT_VARIABLE_TYPE_MASK = 0x3f,
|
||||
ESCARGOT_VARIABLE_LONG_NAME = 0x40,
|
||||
ESCARGOT_VARIABLE_LONG_VALUE = 0x80,
|
||||
};
|
||||
|
||||
inline bool pendingWait(void)
|
||||
{
|
||||
return m_pendingWait;
|
||||
}
|
||||
|
||||
inline bool connected(void)
|
||||
{
|
||||
return enabled();
|
||||
}
|
||||
|
||||
void sendType(uint8_t type);
|
||||
void sendSubtype(uint8_t type, uint8_t subType);
|
||||
void sendString(uint8_t type, const String* string);
|
||||
void sendPointer(uint8_t type, const void* ptr);
|
||||
|
||||
void init(const char* options, Context* context) override;
|
||||
void parseCompleted(String* source, String* srcName, size_t originLineOffset, String* error = nullptr) override;
|
||||
bool stopAtBreakpoint(ByteCodeBlock* byteCodeBlock, uint32_t offset, ExecutionState* state) override;
|
||||
void byteCodeReleaseNotification(ByteCodeBlock* byteCodeBlock) override;
|
||||
void exceptionCaught(String* message, SavedStackTraceDataVector& exceptionTrace) override;
|
||||
void consoleOut(String* output) override;
|
||||
String* getClientSource(String** sourceName) override;
|
||||
bool getWaitBeforeExitClient() override;
|
||||
|
||||
void sendBacktraceInfo(uint8_t type, ByteCodeBlock* byteCodeBlock, uint32_t line, uint32_t column, uint32_t executionStateDepth);
|
||||
void sendVariableObjectInfo(uint8_t subType, Object* object);
|
||||
void waitForResolvingPendingBreakpoints();
|
||||
|
||||
DebuggerEscargot(EscargotSocket socket, String* skipSource)
|
||||
: DebuggerTcp(socket, skipSource, ESCARGOT_WS_HEADER_BASE_SIZE + ESCARGOT_WS_MASK_SIZE + ESCARGOT_DEBUGGER_MAX_MESSAGE_LENGTH, ESCARGOT_DEBUGGER_WEBSOCKET_BINARY_FRAME)
|
||||
, m_exitClient(false)
|
||||
, m_pendingWait(false)
|
||||
, m_waitForResume(false)
|
||||
, m_watchEval(false)
|
||||
, m_clientSourceData(nullptr)
|
||||
, m_clientSourceName(nullptr)
|
||||
{
|
||||
}
|
||||
|
||||
bool processEvents(ExecutionState* state, Optional<ByteCodeBlock*> byteCodeBlock, bool isBlockingRequest = true) override;
|
||||
|
||||
private:
|
||||
// Packed structure definitions to reduce network traffic
|
||||
|
||||
struct MessageVersion {
|
||||
uint8_t littleEndian;
|
||||
uint8_t version[sizeof(uint32_t)];
|
||||
};
|
||||
|
||||
struct MessageConfiguration {
|
||||
uint8_t maxMessageSize;
|
||||
uint8_t pointerSize;
|
||||
};
|
||||
|
||||
struct FunctionInfo {
|
||||
uint8_t byteCodeStart[sizeof(void*)];
|
||||
uint8_t startLine[sizeof(uint32_t)];
|
||||
uint8_t startColumn[sizeof(uint32_t)];
|
||||
};
|
||||
|
||||
struct BreakpointOffset {
|
||||
uint8_t byteCodeStart[sizeof(void*)];
|
||||
uint8_t offset[sizeof(uint32_t)];
|
||||
};
|
||||
|
||||
struct BacktraceInfo {
|
||||
uint8_t byteCode[sizeof(void*)];
|
||||
uint8_t line[sizeof(uint32_t)];
|
||||
uint8_t column[sizeof(uint32_t)];
|
||||
uint8_t executionStateDepth[sizeof(uint32_t)];
|
||||
};
|
||||
|
||||
struct VariableObjectInfo {
|
||||
uint8_t subType;
|
||||
uint8_t index[sizeof(uint32_t)];
|
||||
};
|
||||
|
||||
bool doEval(ExecutionState* state, Optional<ByteCodeBlock*> byteCodeBlock, uint8_t* buffer, size_t length);
|
||||
void getBacktrace(ExecutionState* state, uint32_t minDepth, uint32_t maxDepth, bool getTotal);
|
||||
void getScopeChain(ExecutionState* state, uint32_t stateIndex);
|
||||
void getScopeVariables(ExecutionState* state, uint32_t stateIndex, uint32_t index);
|
||||
|
||||
bool m_exitClient : 1;
|
||||
bool m_pendingWait : 1;
|
||||
bool m_waitForResume : 1;
|
||||
bool m_watchEval : 1;
|
||||
String* m_clientSourceData;
|
||||
String* m_clientSourceName;
|
||||
};
|
||||
|
||||
} // namespace Escargot
|
||||
#endif /* ESCARGOT_DEBUGGER */
|
||||
|
||||
#endif
|
||||
321
src/debugger/DebuggerHttpRouter.cpp
Normal file
321
src/debugger/DebuggerHttpRouter.cpp
Normal file
|
|
@ -0,0 +1,321 @@
|
|||
/*
|
||||
* Copyright (c) 2026-present Samsung Electronics Co., Ltd
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
|
||||
* USA
|
||||
*/
|
||||
|
||||
#include "Escargot.h"
|
||||
#include "DebuggerHttpRouter.h"
|
||||
#include "DebuggerTcp.h"
|
||||
|
||||
#ifdef ESCARGOT_DEBUGGER
|
||||
namespace Escargot {
|
||||
|
||||
#ifdef WIN32
|
||||
#include <winsock2.h>
|
||||
#include <ws2tcpip.h>
|
||||
#else
|
||||
#include <arpa/inet.h>
|
||||
#endif
|
||||
|
||||
static uint8_t toBase64Character(uint8_t value)
|
||||
{
|
||||
if (value < 26) {
|
||||
return static_cast<uint8_t>(value + 'A');
|
||||
}
|
||||
|
||||
if (value < 52) {
|
||||
return static_cast<uint8_t>(value - 26 + 'a');
|
||||
}
|
||||
|
||||
if (value < 62) {
|
||||
return static_cast<uint8_t>(value - 52 + '0');
|
||||
}
|
||||
|
||||
if (value == 62) {
|
||||
return '+';
|
||||
}
|
||||
|
||||
return '/';
|
||||
}
|
||||
|
||||
/**
|
||||
* Encode a byte sequence into Base64 string.
|
||||
*/
|
||||
static void toBase64(const uint8_t* source, uint8_t* destination, size_t length)
|
||||
{
|
||||
while (length >= 3) {
|
||||
uint8_t value = (source[0] >> 2);
|
||||
destination[0] = toBase64Character(value);
|
||||
|
||||
value = static_cast<uint8_t>(((source[0] << 4) | (source[1] >> 4)) & 0x3f);
|
||||
destination[1] = toBase64Character(value);
|
||||
|
||||
value = static_cast<uint8_t>(((source[1] << 2) | (source[2] >> 6)) & 0x3f);
|
||||
destination[2] = toBase64Character(value);
|
||||
|
||||
value = static_cast<uint8_t>(source[2] & 0x3f);
|
||||
destination[3] = toBase64Character(value);
|
||||
|
||||
source += 3;
|
||||
destination += 4;
|
||||
length -= 3;
|
||||
}
|
||||
}
|
||||
|
||||
bool DebuggerHttpRouter::requestStartsWith(const uint8_t* buffer, const size_t length, const char* prefix, const size_t prefixLength)
|
||||
{
|
||||
if (length < prefixLength)
|
||||
return false;
|
||||
|
||||
return memcmp(buffer, prefix, prefixLength) == 0;
|
||||
}
|
||||
|
||||
static bool buildHttpResponse(const char* body, uint8_t* buffer, size_t bufferSize, size_t& outLen)
|
||||
{
|
||||
size_t bodyLen = strlen(body);
|
||||
|
||||
int len = snprintf(reinterpret_cast<char*>(buffer), bufferSize,
|
||||
"HTTP/1.1 200 OK\r\n"
|
||||
"Content-Type: application/json; charset=UTF-8\r\n"
|
||||
"Cache-Control: no-cache\r\n"
|
||||
"Content-Length: %zu\r\n"
|
||||
"Connection: close\r\n"
|
||||
"\r\n"
|
||||
"%s",
|
||||
bodyLen,
|
||||
body);
|
||||
|
||||
if (len < 0 || static_cast<size_t>(len) >= bufferSize)
|
||||
return false;
|
||||
|
||||
outLen = static_cast<size_t>(len);
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool buildVersionResponse(uint8_t* buffer, size_t bufferSize, size_t& outLen)
|
||||
{
|
||||
static constexpr const char body[] = "{"
|
||||
"\"Browser\":\"Escargot/1.0\","
|
||||
"\"Protocol-Version\":\"1.3\""
|
||||
"}";
|
||||
|
||||
return buildHttpResponse(body, buffer, bufferSize, outLen);
|
||||
}
|
||||
|
||||
static bool buildListResponse(uint8_t* buffer, size_t bufferSize, const char* ip, uint16_t port, size_t& outLen)
|
||||
{
|
||||
char body[512];
|
||||
|
||||
int bodyLen = snprintf(body, sizeof(body),
|
||||
"[{"
|
||||
"\"description\":\"Escargot CDP target\","
|
||||
"\"devtoolsFrontendUrl\":\"/devtools/inspector.html?ws=%s:%u/devtools/page/1\","
|
||||
"\"id\":\"1\","
|
||||
"\"title\":\"Escargot\","
|
||||
"\"type\":\"node\","
|
||||
"\"url\":\"file:///\","
|
||||
"\"webSocketDebuggerUrl\":\"ws://%s:%u/devtools/page/1\""
|
||||
"}]",
|
||||
ip, static_cast<unsigned>(port),
|
||||
ip, static_cast<unsigned>(port));
|
||||
|
||||
if (bodyLen < 0 || static_cast<size_t>(bodyLen) >= sizeof(body)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return buildHttpResponse(body, buffer, bufferSize, outLen);
|
||||
}
|
||||
|
||||
static bool webSocketHandshake(EscargotSocket socket, uint8_t* buffer, size_t length)
|
||||
{
|
||||
uint8_t* websocketKey = buffer;
|
||||
|
||||
constexpr char expectedWebsocketKey[] = "Sec-WebSocket-Key:";
|
||||
constexpr size_t expectedWebsocketKeyLength = sizeof(expectedWebsocketKey) - 1;
|
||||
|
||||
while (true) {
|
||||
if (length < expectedWebsocketKeyLength) {
|
||||
ESCARGOT_LOG_ERROR("Sec-WebSocket-Key not found.\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (websocketKey[0] == 'S'
|
||||
&& websocketKey[-1] == '\n'
|
||||
&& websocketKey[-2] == '\r'
|
||||
&& memcmp(websocketKey, expectedWebsocketKey, expectedWebsocketKeyLength) == 0) {
|
||||
websocketKey += expectedWebsocketKeyLength;
|
||||
break;
|
||||
}
|
||||
|
||||
websocketKey++;
|
||||
}
|
||||
|
||||
/* String terminated by double newlines. */
|
||||
while (*websocketKey == ' ') {
|
||||
websocketKey++;
|
||||
}
|
||||
|
||||
uint8_t* websocketKeyEnd = websocketKey;
|
||||
|
||||
while (*websocketKeyEnd > ' ') {
|
||||
websocketKeyEnd++;
|
||||
}
|
||||
|
||||
/* Since the buffer is not needed anymore it can
|
||||
* be reused for storing the SHA-1 key and Base64 string. */
|
||||
constexpr size_t sha1Length = 20;
|
||||
|
||||
DebuggerTcp::computeSha1(websocketKey,
|
||||
static_cast<size_t>(websocketKeyEnd - websocketKey),
|
||||
reinterpret_cast<const uint8_t*>("258EAFA5-E914-47DA-95CA-C5AB0DC85B11"),
|
||||
36,
|
||||
buffer);
|
||||
|
||||
/* The SHA-1 key is 20 bytes long but toBase64 expects a length
|
||||
* divisible by 3 so an extra 0 is appended at the end. */
|
||||
buffer[sha1Length] = 0;
|
||||
|
||||
toBase64(buffer, buffer + sha1Length + 1, sha1Length + 1);
|
||||
|
||||
/* Last value must be replaced by equal sign. */
|
||||
constexpr uint8_t responsePrefix[] = "HTTP/1.1 101 Switching Protocols\r\nUpgrade: websocket\r\nConnection: Upgrade\r\nSec-WebSocket-Accept: ";
|
||||
|
||||
if (!DebuggerTcp::tcpSend(socket, responsePrefix, sizeof(responsePrefix) - 1)
|
||||
|| !DebuggerTcp::tcpSend(socket, buffer + sha1Length + 1, 27)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
constexpr uint8_t responseSuffix[] = "=\r\n\r\n";
|
||||
return DebuggerTcp::tcpSend(socket, responseSuffix, sizeof(responseSuffix) - 1);
|
||||
}
|
||||
|
||||
bool DebuggerHttpRouter::webSocketEstablished() const
|
||||
{
|
||||
return m_client != DebuggerClient::None;
|
||||
}
|
||||
|
||||
DebuggerClient DebuggerHttpRouter::client() const
|
||||
{
|
||||
return m_client;
|
||||
}
|
||||
|
||||
bool handleWebSocketRequest(const RequestContext& ctx)
|
||||
{
|
||||
return webSocketHandshake(ctx.socket, ctx.request, ctx.requestLength);
|
||||
}
|
||||
|
||||
bool handleJsonVersion(const RequestContext& ctx)
|
||||
{
|
||||
uint8_t buffer[1024];
|
||||
size_t responseLength = 0;
|
||||
|
||||
if (!buildVersionResponse(buffer, sizeof(buffer), responseLength)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return DebuggerTcp::tcpSend(ctx.socket, buffer, responseLength);
|
||||
}
|
||||
|
||||
bool handleJsonList(const RequestContext& ctx)
|
||||
{
|
||||
struct sockaddr_in addr{};
|
||||
socklen_t len = sizeof(addr);
|
||||
|
||||
getsockname(ctx.socket, reinterpret_cast<struct sockaddr*>(&addr), &len);
|
||||
|
||||
char ip[INET_ADDRSTRLEN];
|
||||
inet_ntop(AF_INET, &addr.sin_addr, ip, sizeof(ip));
|
||||
const uint16_t port = ntohs(addr.sin_port);
|
||||
|
||||
uint8_t buffer[1024];
|
||||
size_t responseLength = 0;
|
||||
|
||||
if (!buildListResponse(buffer, sizeof(buffer), ip, port, responseLength)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return DebuggerTcp::tcpSend(ctx.socket, buffer, responseLength);
|
||||
}
|
||||
|
||||
static bool readHttpMessage(EscargotSocket socket, uint8_t* buffer, size_t capacity, size_t& messageLength)
|
||||
{
|
||||
size_t remainingLength = capacity;
|
||||
uint8_t* message = buffer;
|
||||
|
||||
while (true) {
|
||||
size_t receivedLength = 0;
|
||||
if (!DebuggerTcp::tcpReceive(socket, message, remainingLength, &receivedLength)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
message += receivedLength;
|
||||
remainingLength -= receivedLength;
|
||||
|
||||
if (message > (buffer + 4) && memcmp(message - 4, "\r\n\r\n", 4) == 0) {
|
||||
messageLength = static_cast<size_t>(message - buffer);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (remainingLength == 0) {
|
||||
ESCARGOT_LOG_ERROR("WebSocket Error: Request too long\n");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <size_t N>
|
||||
constexpr Route route(const char (&prefix)[N], DebuggerClient client, RouteHandler handler)
|
||||
{
|
||||
return Route{ prefix, N - 1, client, handler };
|
||||
}
|
||||
|
||||
bool DebuggerHttpRouter::handleHttpRequest(EscargotSocket socket)
|
||||
{
|
||||
uint8_t buffer[1024];
|
||||
size_t messageLength = 0;
|
||||
|
||||
if (!readHttpMessage(socket, buffer, sizeof(buffer), messageLength)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
static constexpr Route routes[] = {
|
||||
route("GET /escargot-debugger", DebuggerClient::Escargot, handleWebSocketRequest),
|
||||
route("GET /devtools/page/1", DebuggerClient::DevTools, handleWebSocketRequest),
|
||||
route("GET /json/version", DebuggerClient::None, handleJsonVersion),
|
||||
route("GET /json/list", DebuggerClient::None, handleJsonList),
|
||||
route("GET /json", DebuggerClient::None, handleJsonList)
|
||||
};
|
||||
|
||||
for (const auto& route : routes) {
|
||||
if (!requestStartsWith(buffer, messageLength, route.prefix, route.prefixLength)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
m_client = route.client;
|
||||
// Skip the matched route prefix
|
||||
uint8_t* remainder = buffer + route.prefixLength;
|
||||
size_t remainderLength = messageLength - route.prefixLength;
|
||||
|
||||
return route.handler(RequestContext{ socket, remainder, remainderLength });
|
||||
}
|
||||
|
||||
ESCARGOT_LOG_ERROR("Unsupported http request\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace Escargot
|
||||
#endif /* ESCARGOT_DEBUGGER */
|
||||
70
src/debugger/DebuggerHttpRouter.h
Normal file
70
src/debugger/DebuggerHttpRouter.h
Normal file
|
|
@ -0,0 +1,70 @@
|
|||
/*
|
||||
* Copyright (c) 2026-present Samsung Electronics Co., Ltd
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
|
||||
* USA
|
||||
*/
|
||||
|
||||
#ifndef __DebuggerHttpRouter__
|
||||
#define __DebuggerHttpRouter__
|
||||
|
||||
#ifdef ESCARGOT_DEBUGGER
|
||||
namespace Escargot {
|
||||
|
||||
#ifdef WIN32
|
||||
#include <winsock2.h>
|
||||
typedef SOCKET EscargotSocket;
|
||||
#else /* !WIN32 */
|
||||
typedef int EscargotSocket;
|
||||
#endif /* WIN32 */
|
||||
|
||||
enum class DebuggerClient : uint8_t {
|
||||
None,
|
||||
Escargot,
|
||||
DevTools
|
||||
};
|
||||
|
||||
struct RequestContext {
|
||||
EscargotSocket socket;
|
||||
uint8_t* request;
|
||||
size_t requestLength;
|
||||
};
|
||||
|
||||
using RouteHandler = bool (*)(const RequestContext&);
|
||||
|
||||
struct Route {
|
||||
const char* prefix;
|
||||
size_t prefixLength;
|
||||
DebuggerClient client;
|
||||
RouteHandler handler;
|
||||
};
|
||||
|
||||
class DebuggerHttpRouter {
|
||||
public:
|
||||
DebuggerHttpRouter() = default;
|
||||
|
||||
static bool requestStartsWith(const uint8_t* buffer, size_t length, const char* prefix, size_t prefixLength);
|
||||
bool handleHttpRequest(EscargotSocket socket);
|
||||
bool webSocketEstablished() const;
|
||||
DebuggerClient client() const;
|
||||
|
||||
private:
|
||||
DebuggerClient m_client{ DebuggerClient::None };
|
||||
};
|
||||
|
||||
} // namespace Escargot
|
||||
#endif /* ESCARGOT_DEBUGGER */
|
||||
|
||||
#endif
|
||||
|
|
@ -19,6 +19,9 @@
|
|||
|
||||
#include "Escargot.h"
|
||||
#include "DebuggerTcp.h"
|
||||
#include "DebuggerDevtools.h"
|
||||
#include "DebuggerEscargot.h"
|
||||
#include "DebuggerHttpRouter.h"
|
||||
#include "runtime/String.h" // for split function
|
||||
|
||||
#ifdef ESCARGOT_DEBUGGER
|
||||
|
|
@ -110,7 +113,7 @@ static inline void tcpCloseSocket(EscargotSocket socket)
|
|||
#endif /* WIN32 */
|
||||
}
|
||||
|
||||
static bool tcpSend(EscargotSocket socket, const uint8_t* message, size_t messageLength)
|
||||
bool DebuggerTcp::tcpSend(EscargotSocket socket, const uint8_t* message, size_t messageLength)
|
||||
{
|
||||
do {
|
||||
#ifdef OS_POSIX
|
||||
|
|
@ -122,7 +125,7 @@ static bool tcpSend(EscargotSocket socket, const uint8_t* message, size_t messag
|
|||
}
|
||||
#endif /* OS_POSIX */
|
||||
|
||||
ssize_t sentBytes = send(socket, message, messageLength, 0);
|
||||
ssize_t sentBytes = Escargot::send(socket, message, messageLength, 0);
|
||||
|
||||
if (sentBytes < 0) {
|
||||
int errorNumber = tcpGetErrno();
|
||||
|
|
@ -142,7 +145,7 @@ static bool tcpSend(EscargotSocket socket, const uint8_t* message, size_t messag
|
|||
return true;
|
||||
}
|
||||
|
||||
static bool tcpReceive(EscargotSocket socket, uint8_t* message, size_t maxLength, size_t* receivedLength)
|
||||
bool DebuggerTcp::tcpReceive(EscargotSocket socket, uint8_t* message, size_t maxLength, size_t* receivedLength)
|
||||
{
|
||||
*receivedLength = 0;
|
||||
|
||||
|
|
@ -162,152 +165,168 @@ static bool tcpReceive(EscargotSocket socket, uint8_t* message, size_t maxLength
|
|||
return true;
|
||||
}
|
||||
|
||||
static uint8_t toBase64Character(uint8_t value)
|
||||
bool DebuggerTcp::send(const uint8_t type, const void* buffer, const size_t length)
|
||||
{
|
||||
if (value < 26) {
|
||||
return (uint8_t)(value + 'A');
|
||||
}
|
||||
ASSERT(enabled());
|
||||
ASSERT(m_websocketMessageType == ESCARGOT_DEBUGGER_WEBSOCKET_TEXT_FRAME
|
||||
|| (m_websocketMessageType == ESCARGOT_DEBUGGER_WEBSOCKET_BINARY_FRAME && length <= ESCARGOT_DEBUGGER_MAX_MESSAGE_LENGTH));
|
||||
|
||||
if (value < 52) {
|
||||
return (uint8_t)(value - 26 + 'a');
|
||||
}
|
||||
|
||||
if (value < 62) {
|
||||
return (uint8_t)(value - 52 + '0');
|
||||
}
|
||||
|
||||
if (value == 62) {
|
||||
return (uint8_t)'+';
|
||||
}
|
||||
|
||||
return (uint8_t)'/';
|
||||
}
|
||||
|
||||
/**
|
||||
* Encode a byte sequence into Base64 string.
|
||||
*/
|
||||
static void toBase64(const uint8_t* source, uint8_t* destination, size_t length)
|
||||
{
|
||||
while (length >= 3) {
|
||||
uint8_t value = (source[0] >> 2);
|
||||
destination[0] = toBase64Character(value);
|
||||
|
||||
value = (uint8_t)(((source[0] << 4) | (source[1] >> 4)) & 0x3f);
|
||||
destination[1] = toBase64Character(value);
|
||||
|
||||
value = (uint8_t)(((source[1] << 2) | (source[2] >> 6)) & 0x3f);
|
||||
destination[2] = toBase64Character(value);
|
||||
|
||||
value = (uint8_t)(source[2] & 0x3f);
|
||||
destination[3] = toBase64Character(value);
|
||||
|
||||
source += 3;
|
||||
destination += 4;
|
||||
length -= 3;
|
||||
}
|
||||
}
|
||||
|
||||
static bool webSocketHandshake(EscargotSocket socket)
|
||||
{
|
||||
uint8_t buffer[1024];
|
||||
size_t remainingLength = sizeof(buffer);
|
||||
uint8_t* message = buffer;
|
||||
|
||||
while (true) {
|
||||
size_t receivedLength;
|
||||
if (!tcpReceive(socket, message, remainingLength, &receivedLength)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
message += receivedLength;
|
||||
remainingLength -= receivedLength;
|
||||
|
||||
if (message > (buffer + 4) && memcmp(message - 4, "\r\n\r\n", 4) == 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (remainingLength == 0) {
|
||||
ESCARGOT_LOG_ERROR("WebSocket Error: Request too long\n");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/* Check protocol. */
|
||||
const char expectedProtocol[] = "GET /escargot-debugger";
|
||||
const size_t expectedProtocolLength = sizeof(expectedProtocol) - 1;
|
||||
|
||||
if ((size_t)(message - buffer) < expectedProtocolLength
|
||||
|| memcmp(buffer, expectedProtocol, expectedProtocolLength) != 0) {
|
||||
ESCARGOT_LOG_ERROR("WebSocket Error: Invalid handshake format.\n");
|
||||
if (length > ESCARGOT_WS_MAX_MESSAGE_LENGTH) {
|
||||
ESCARGOT_LOG_ERROR("Cannot send WebSocket payload: 64-bit payload length is not supported.\n");
|
||||
close(CloseAbortConnection);
|
||||
return false;
|
||||
}
|
||||
|
||||
uint8_t* websocketKey = buffer + expectedProtocolLength;
|
||||
size_t headerLength = 0;
|
||||
uint8_t message[ESCARGOT_WS_HEADER_BASE_SIZE + ESCARGOT_WS_EXT_LEN16_SIZE + length];
|
||||
message[0] = ESCARGOT_DEBUGGER_WEBSOCKET_FIN_BIT | m_websocketMessageType;
|
||||
|
||||
const char expectedWebsocketKey[] = "Sec-WebSocket-Key:";
|
||||
const size_t expectedWebsocketKeyLength = sizeof(expectedWebsocketKey) - 1;
|
||||
// Server-to-client WebSocket frames are not masked,
|
||||
// therefore the masking key is not included in the header.
|
||||
if (length <= ESCARGOT_DEBUGGER_MAX_MESSAGE_LENGTH) {
|
||||
const uint8_t type_byte = (m_websocketMessageType == ESCARGOT_DEBUGGER_WEBSOCKET_TEXT_FRAME ? 0 : 1);
|
||||
headerLength = ESCARGOT_WS_HEADER_SIZE - ESCARGOT_WS_MASK_SIZE + type_byte;
|
||||
|
||||
while (true) {
|
||||
if ((size_t)(message - websocketKey) < expectedWebsocketKeyLength) {
|
||||
ESCARGOT_LOG_ERROR("Sec-WebSocket-Key not found.\n");
|
||||
return false;
|
||||
message[1] = static_cast<uint8_t>(length + type_byte);
|
||||
if (type_byte) {
|
||||
message[2] = type;
|
||||
}
|
||||
} else {
|
||||
headerLength = ESCARGOT_WS_HEADER_LEN16_SIZE - ESCARGOT_WS_MASK_SIZE;
|
||||
|
||||
if (websocketKey[0] == 'S'
|
||||
&& websocketKey[-1] == '\n'
|
||||
&& websocketKey[-2] == '\r'
|
||||
&& memcmp(websocketKey, expectedWebsocketKey, expectedWebsocketKeyLength) == 0) {
|
||||
websocketKey += expectedWebsocketKeyLength;
|
||||
break;
|
||||
}
|
||||
|
||||
websocketKey++;
|
||||
message[1] = ESCARGOT_WS_MESSAGE_16BIT_LENGTH_MARKER;
|
||||
message[2] = static_cast<uint8_t>((length >> 8) & 0xFF);
|
||||
message[3] = static_cast<uint8_t>(length & 0xFF);
|
||||
}
|
||||
|
||||
/* String terminated by double newlines. */
|
||||
while (*websocketKey == ' ') {
|
||||
websocketKey++;
|
||||
}
|
||||
memcpy(message + headerLength, buffer, length);
|
||||
|
||||
uint8_t* websocketKeyEnd = websocketKey;
|
||||
|
||||
while (*websocketKeyEnd > ' ') {
|
||||
websocketKeyEnd++;
|
||||
}
|
||||
|
||||
/* Since the buffer is not needed anymore it can
|
||||
* be reused for storing the SHA-1 key and Base64 string. */
|
||||
const size_t sha1Length = 20;
|
||||
|
||||
DebuggerTcp::computeSha1(websocketKey,
|
||||
(size_t)(websocketKeyEnd - websocketKey),
|
||||
(const uint8_t*)"258EAFA5-E914-47DA-95CA-C5AB0DC85B11",
|
||||
36,
|
||||
buffer);
|
||||
|
||||
/* The SHA-1 key is 20 bytes long but toBase64 expects a length
|
||||
* divisible by 3 so an extra 0 is appended at the end. */
|
||||
buffer[sha1Length] = 0;
|
||||
|
||||
toBase64(buffer, buffer + sha1Length + 1, sha1Length + 1);
|
||||
|
||||
/* Last value must be replaced by equal sign. */
|
||||
const uint8_t responsePrefix[] = "HTTP/1.1 101 Switching Protocols\r\nUpgrade: websocket\r\nConnection: Upgrade\r\nSec-WebSocket-Accept: ";
|
||||
|
||||
if (!tcpSend(socket, responsePrefix, sizeof(responsePrefix) - 1)
|
||||
|| !tcpSend(socket, buffer + sha1Length + 1, 27)) {
|
||||
if (!tcpSend(m_socket, message, headerLength + length)) {
|
||||
ESCARGOT_LOG_ERROR("Failed to send data via WebSocket connection.\n");
|
||||
close(CloseAbortConnection);
|
||||
return false;
|
||||
}
|
||||
|
||||
const uint8_t responseSuffix[] = "=\r\n\r\n";
|
||||
return tcpSend(socket, responseSuffix, sizeof(responseSuffix) - 1);
|
||||
return true;
|
||||
}
|
||||
|
||||
void DebuggerTcp::init(const char* options, Context* context)
|
||||
bool DebuggerTcp::receive(uint8_t* buffer, size_t& length)
|
||||
{
|
||||
size_t receivedLength = 0;
|
||||
|
||||
if (m_payloadLength == 0 || m_receiveBufferFill < m_headerLength + m_payloadLength) {
|
||||
/* Cannot extract a whole message from the buffer. */
|
||||
if (!tcpReceive(m_socket,
|
||||
m_receiveBuffer + m_receiveBufferFill,
|
||||
m_bufferSize - m_receiveBufferFill,
|
||||
&receivedLength)) {
|
||||
ESCARGOT_LOG_ERROR("Failed to receive data from WebSocket connection.\n");
|
||||
close(CloseAbortConnection);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (receivedLength == 0 && m_receiveBufferFill < m_headerLength) {
|
||||
// ESCARGOT_LOG_INFO("Incomplete WebSocket frame header, waiting for more data.\n");
|
||||
return false;
|
||||
}
|
||||
|
||||
m_receiveBufferFill = static_cast<uint32_t>(m_receiveBufferFill + receivedLength);
|
||||
}
|
||||
|
||||
if (m_payloadLength == 0) {
|
||||
if (m_receiveBufferFill < ESCARGOT_WS_HEADER_BASE_SIZE) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ((m_receiveBuffer[0] & ESCARGOT_DEBUGGER_WEBSOCKET_OPCODE_MASK) != m_websocketMessageType) {
|
||||
if ((m_receiveBuffer[0] & ESCARGOT_DEBUGGER_WEBSOCKET_OPCODE_MASK) == ESCARGOT_DEBUGGER_WEBSOCKET_CLOSE_FRAME) {
|
||||
close(CloseEndConnection);
|
||||
return false;
|
||||
}
|
||||
|
||||
ESCARGOT_LOG_ERROR("Unsupported Websocket opcode.\n");
|
||||
close(CloseProtocolUnsupported);
|
||||
return false;
|
||||
}
|
||||
|
||||
if ((m_receiveBuffer[0] & ~ESCARGOT_DEBUGGER_WEBSOCKET_OPCODE_MASK) != ESCARGOT_DEBUGGER_WEBSOCKET_FIN_BIT
|
||||
|| !(m_receiveBuffer[1] & ESCARGOT_DEBUGGER_WEBSOCKET_MASK_BIT)) {
|
||||
ESCARGOT_LOG_ERROR("Unsupported Websocket message.\n");
|
||||
close(CloseProtocolUnsupported);
|
||||
return false;
|
||||
}
|
||||
|
||||
uint8_t payloadLengthField = m_receiveBuffer[1] & ESCARGOT_DEBUGGER_WEBSOCKET_LENGTH_MASK;
|
||||
|
||||
if (payloadLengthField > ESCARGOT_WS_MESSAGE_16BIT_LENGTH_MARKER) {
|
||||
ESCARGOT_LOG_ERROR("64-bit WebSocket payload length is not supported.\n");
|
||||
close(CloseProtocolUnsupported);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (payloadLengthField <= ESCARGOT_DEBUGGER_MAX_MESSAGE_LENGTH) {
|
||||
m_payloadLength = payloadLengthField;
|
||||
m_headerLength = ESCARGOT_WS_HEADER_SIZE;
|
||||
} else {
|
||||
ASSERT(payloadLengthField == ESCARGOT_WS_MESSAGE_16BIT_LENGTH_MARKER);
|
||||
// Ensure that the extended payload length field is available
|
||||
if (m_receiveBufferFill < ESCARGOT_WS_HEADER_LEN16_SIZE) {
|
||||
return false;
|
||||
}
|
||||
|
||||
m_headerLength = ESCARGOT_WS_HEADER_LEN16_SIZE;
|
||||
m_payloadLength = (static_cast<uint16_t>(m_receiveBuffer[2]) << 8) | static_cast<uint16_t>(m_receiveBuffer[3]);
|
||||
}
|
||||
|
||||
if (m_payloadLength == 0) {
|
||||
ESCARGOT_LOG_ERROR("Invalid WebSocket payload length: zero-length messages are not supported.\n");
|
||||
close(CloseProtocolUnsupported);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
const size_t totalSize = m_headerLength + m_payloadLength;
|
||||
|
||||
if (m_receiveBufferFill < totalSize) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const uint8_t* mask = m_receiveBuffer + m_headerLength - ESCARGOT_WS_MASK_SIZE;
|
||||
const uint8_t* mask_end = mask + ESCARGOT_WS_MASK_SIZE;
|
||||
const uint8_t* source = mask_end;
|
||||
uint8_t* buffer_end = buffer + m_payloadLength;
|
||||
|
||||
while (buffer < buffer_end) {
|
||||
*buffer++ = *source++ ^ *mask++;
|
||||
|
||||
if (mask >= mask_end) {
|
||||
mask -= 4;
|
||||
}
|
||||
}
|
||||
*buffer_end = 0;
|
||||
|
||||
length = m_payloadLength;
|
||||
m_headerLength = ESCARGOT_WS_HEADER_SIZE;
|
||||
m_payloadLength = 0;
|
||||
|
||||
if (m_receiveBufferFill == totalSize) {
|
||||
m_receiveBufferFill = 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
m_receiveBufferFill = static_cast<uint32_t>(m_receiveBufferFill - totalSize);
|
||||
memmove(m_receiveBuffer, m_receiveBuffer + totalSize, m_receiveBufferFill);
|
||||
return true;
|
||||
}
|
||||
|
||||
Debugger* DebuggerTcp::createDebugger(const char* options, Context* context)
|
||||
{
|
||||
uint16_t port = 6501;
|
||||
int timeout = -1;
|
||||
|
||||
String* skipSourceName = nullptr;
|
||||
EscargotSocket clientSocket = 0;
|
||||
|
||||
if (options) {
|
||||
auto v = split(options, ';');
|
||||
const char portOption[] = "--port=";
|
||||
|
|
@ -325,31 +344,29 @@ void DebuggerTcp::init(const char* options, Context* context)
|
|||
} else if (s.find(skipOption) == 0) {
|
||||
const char* skipStr = const_cast<const char*>(s.data() + sizeof(skipOption) - 1);
|
||||
size_t skipLen = strlen(skipStr);
|
||||
m_skipSourceName = String::fromASCII(skipStr, skipLen);
|
||||
skipSourceName = String::fromASCII(skipStr, skipLen);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ASSERT(enabled() == false);
|
||||
|
||||
#ifdef WIN32
|
||||
WSADATA wsaData;
|
||||
int wsa_init_status = WSAStartup(MAKEWORD(2, 2), &wsaData);
|
||||
if (wsa_init_status != NO_ERROR) {
|
||||
return;
|
||||
return nullptr;
|
||||
}
|
||||
#endif /* WIN32*/
|
||||
|
||||
EscargotSocket serverSocket = socket(AF_INET, SOCK_STREAM, 0);
|
||||
if (m_socket == ESCARGOT_INVALID_SOCKET) {
|
||||
return;
|
||||
if (serverSocket == ESCARGOT_INVALID_SOCKET) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (!tcpConfigureSocket(serverSocket, port)) {
|
||||
int error = tcpGetErrno();
|
||||
tcpCloseSocket(serverSocket);
|
||||
tcpLogError(error);
|
||||
return;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
ESCARGOT_LOG_INFO("Waiting for client connection 0.0.0.0:%hd\n", port);
|
||||
|
|
@ -372,58 +389,72 @@ void DebuggerTcp::init(const char* options, Context* context)
|
|||
if (timeout < 0) {
|
||||
ESCARGOT_LOG_ERROR("Waiting for client connection error: timeout reached\n");
|
||||
tcpCloseSocket(serverSocket);
|
||||
return;
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
sockaddr_in addr;
|
||||
socklen_t sinSize = sizeof(sockaddr_in);
|
||||
DebuggerHttpRouter httpRouter;
|
||||
|
||||
m_socket = accept(serverSocket, (sockaddr*)&addr, &sinSize);
|
||||
while (true) {
|
||||
clientSocket = accept(serverSocket, (sockaddr*)&addr, &sinSize);
|
||||
|
||||
if (clientSocket == ESCARGOT_INVALID_SOCKET) {
|
||||
tcpLogError(tcpGetErrno());
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
#ifdef WIN32
|
||||
u_long nonblockingEnabled = 1;
|
||||
|
||||
/* Set non-blocking mode. */
|
||||
if (ioctlsocket(clientSocket, FIONBIO, &nonblockingEnabled) != NO_ERROR) {
|
||||
tcpCloseSocket(clientSocket);
|
||||
return nullptr;
|
||||
}
|
||||
#else /* !WIN32 */
|
||||
int socketFlags = fcntl(clientSocket, F_GETFL, 0);
|
||||
|
||||
if (socketFlags < 0) {
|
||||
tcpCloseSocket(clientSocket);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
/* Set non-blocking mode. */
|
||||
if (fcntl(clientSocket, F_SETFL, socketFlags | O_NONBLOCK) == -1) {
|
||||
tcpCloseSocket(clientSocket);
|
||||
return nullptr;
|
||||
}
|
||||
#endif /* WIN32 */
|
||||
|
||||
ESCARGOT_LOG_INFO("Connected from: %s\n", inet_ntoa(addr.sin_addr));
|
||||
|
||||
if (!httpRouter.handleHttpRequest(clientSocket)) {
|
||||
tcpCloseSocket(clientSocket);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (httpRouter.webSocketEstablished()) {
|
||||
break;
|
||||
}
|
||||
|
||||
// Close connection, waiting for the next request
|
||||
tcpCloseSocket(clientSocket);
|
||||
}
|
||||
|
||||
tcpCloseSocket(serverSocket);
|
||||
|
||||
if (m_socket == ESCARGOT_INVALID_SOCKET) {
|
||||
tcpLogError(tcpGetErrno());
|
||||
return;
|
||||
Debugger* client;
|
||||
|
||||
if (httpRouter.client() == DebuggerClient::Escargot) {
|
||||
client = new DebuggerEscargot(clientSocket, skipSourceName);
|
||||
} else {
|
||||
client = new DebuggerDevtools(clientSocket, skipSourceName);
|
||||
}
|
||||
|
||||
#ifdef WIN32
|
||||
u_long nonblockingEnabled = 1;
|
||||
|
||||
/* Set non-blocking mode. */
|
||||
if (ioctlsocket(m_socket, FIONBIO, &nonblockingEnabled) != NO_ERROR) {
|
||||
tcpCloseSocket(m_socket);
|
||||
return;
|
||||
}
|
||||
#else /* !WIN32 */
|
||||
int socketFlags = fcntl(m_socket, F_GETFL, 0);
|
||||
|
||||
if (socketFlags < 0) {
|
||||
tcpCloseSocket(m_socket);
|
||||
return;
|
||||
}
|
||||
|
||||
/* Set non-blocking mode. */
|
||||
if (fcntl(m_socket, F_SETFL, socketFlags | O_NONBLOCK) == -1) {
|
||||
tcpCloseSocket(m_socket);
|
||||
return;
|
||||
}
|
||||
#endif /* WIN32 */
|
||||
|
||||
ESCARGOT_LOG_INFO("Connected from: %s\n", inet_ntoa(addr.sin_addr));
|
||||
|
||||
if (!webSocketHandshake(m_socket)) {
|
||||
tcpCloseSocket(m_socket);
|
||||
return;
|
||||
}
|
||||
|
||||
m_receiveBufferFill = 0;
|
||||
m_messageLength = 0;
|
||||
|
||||
enable(context);
|
||||
|
||||
return DebuggerRemote::init(nullptr, context);
|
||||
client->enable(context);
|
||||
return client;
|
||||
}
|
||||
|
||||
bool DebuggerTcp::skipSourceCode(String* srcName) const
|
||||
|
|
@ -436,34 +467,6 @@ bool DebuggerTcp::skipSourceCode(String* srcName) const
|
|||
return srcName->contains(m_skipSourceName);
|
||||
}
|
||||
|
||||
#define ESCARGOT_DEBUGGER_WEBSOCKET_FIN_BIT 0x80
|
||||
#define ESCARGOT_DEBUGGER_WEBSOCKET_BINARY_FRAME 2
|
||||
#define ESCARGOT_DEBUGGER_WEBSOCKET_CLOSE_FRAME 8
|
||||
#define ESCARGOT_DEBUGGER_WEBSOCKET_OPCODE_MASK 0x0f
|
||||
#define ESCARGOT_DEBUGGER_WEBSOCKET_LENGTH_MASK 0x7f
|
||||
#define ESCARGOT_DEBUGGER_WEBSOCKET_ONE_BYTE_LEN_MAX 125
|
||||
#define ESCARGOT_DEBUGGER_WEBSOCKET_MASK_BIT 0x80
|
||||
|
||||
bool DebuggerTcp::send(uint8_t type, const void* buffer, size_t length)
|
||||
{
|
||||
ASSERT(enabled());
|
||||
ASSERT(length < ESCARGOT_DEBUGGER_MAX_MESSAGE_LENGTH);
|
||||
|
||||
uint8_t message[ESCARGOT_DEBUGGER_MAX_MESSAGE_LENGTH + 2];
|
||||
|
||||
message[0] = ESCARGOT_DEBUGGER_WEBSOCKET_FIN_BIT | ESCARGOT_DEBUGGER_WEBSOCKET_BINARY_FRAME;
|
||||
message[1] = (uint8_t)(length + 1);
|
||||
message[2] = type;
|
||||
memcpy(message + 3, buffer, length);
|
||||
|
||||
if (tcpSend(m_socket, message, length + 3)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
close(CloseAbortConnection);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool DebuggerTcp::isThereAnyEvent()
|
||||
{
|
||||
// if there is remained receive buffer data,
|
||||
|
|
@ -484,91 +487,6 @@ bool DebuggerTcp::isThereAnyEvent()
|
|||
return true;
|
||||
}
|
||||
|
||||
bool DebuggerTcp::receive(uint8_t* buffer, size_t& length)
|
||||
{
|
||||
size_t receivedLength;
|
||||
|
||||
if (m_messageLength == 0 || m_receiveBufferFill < 2 + sizeof(uint32_t) + m_messageLength) {
|
||||
/* Cannot extract a whole message from the buffer. */
|
||||
if (!tcpReceive(m_socket,
|
||||
m_receiveBuffer + m_receiveBufferFill,
|
||||
ESCARGOT_DEBUGGER_MAX_MESSAGE_LENGTH + 2 + sizeof(uint32_t) - m_receiveBufferFill,
|
||||
&receivedLength)) {
|
||||
close(CloseAbortConnection);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (receivedLength == 0 && m_receiveBufferFill < (2 + sizeof(uint32_t))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
m_receiveBufferFill = (uint8_t)(m_receiveBufferFill + receivedLength);
|
||||
}
|
||||
|
||||
if (m_messageLength == 0) {
|
||||
if (m_receiveBufferFill < 3) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ((m_receiveBuffer[0] & ESCARGOT_DEBUGGER_WEBSOCKET_OPCODE_MASK) != ESCARGOT_DEBUGGER_WEBSOCKET_BINARY_FRAME) {
|
||||
if ((m_receiveBuffer[0] & ESCARGOT_DEBUGGER_WEBSOCKET_OPCODE_MASK) == ESCARGOT_DEBUGGER_WEBSOCKET_CLOSE_FRAME) {
|
||||
close(CloseEndConnection);
|
||||
return false;
|
||||
}
|
||||
|
||||
ESCARGOT_LOG_ERROR("Unsupported Websocket opcode.\n");
|
||||
close(CloseProtocolUnsupported);
|
||||
return false;
|
||||
}
|
||||
|
||||
if ((m_receiveBuffer[0] & ~ESCARGOT_DEBUGGER_WEBSOCKET_OPCODE_MASK) != ESCARGOT_DEBUGGER_WEBSOCKET_FIN_BIT
|
||||
|| !(m_receiveBuffer[1] & ESCARGOT_DEBUGGER_WEBSOCKET_MASK_BIT)) {
|
||||
ESCARGOT_LOG_ERROR("Unsupported Websocket message.\n");
|
||||
close(CloseProtocolUnsupported);
|
||||
return false;
|
||||
}
|
||||
|
||||
m_messageLength = (uint8_t)(m_receiveBuffer[1] & ESCARGOT_DEBUGGER_WEBSOCKET_LENGTH_MASK);
|
||||
|
||||
if (m_messageLength == 0 || m_messageLength > ESCARGOT_DEBUGGER_WEBSOCKET_ONE_BYTE_LEN_MAX) {
|
||||
ESCARGOT_LOG_ERROR("Unsupported Websocket message size.\n");
|
||||
close(CloseProtocolUnsupported);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
size_t totalSize = 2 + sizeof(uint32_t) + m_messageLength;
|
||||
|
||||
if (m_receiveBufferFill < totalSize) {
|
||||
return false;
|
||||
}
|
||||
|
||||
uint8_t* mask = m_receiveBuffer + 2;
|
||||
uint8_t* mask_end = mask + sizeof(uint32_t);
|
||||
uint8_t* source = mask_end;
|
||||
uint8_t* buffer_end = buffer + m_messageLength;
|
||||
|
||||
while (buffer < buffer_end) {
|
||||
*buffer++ = *source++ ^ *mask++;
|
||||
|
||||
if (mask >= mask_end) {
|
||||
mask -= 4;
|
||||
}
|
||||
}
|
||||
|
||||
length = m_messageLength;
|
||||
m_messageLength = 0;
|
||||
|
||||
if (m_receiveBufferFill == totalSize) {
|
||||
m_receiveBufferFill = 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
m_receiveBufferFill = (uint8_t)(m_receiveBufferFill - totalSize);
|
||||
memmove(m_receiveBuffer, m_receiveBuffer + totalSize, m_receiveBufferFill);
|
||||
return true;
|
||||
}
|
||||
|
||||
void DebuggerTcp::close(CloseReason reason)
|
||||
{
|
||||
if (!enabled()) {
|
||||
|
|
|
|||
|
|
@ -31,39 +31,75 @@ typedef SOCKET EscargotSocket;
|
|||
#else /* !WIN32 */
|
||||
typedef int EscargotSocket;
|
||||
#endif /* WIN32 */
|
||||
#define ESCARGOT_DEBUGGER_MAX_MESSAGE_LENGTH 125
|
||||
|
||||
class DebuggerTcp : public DebuggerRemote {
|
||||
#define ESCARGOT_DEBUGGER_WEBSOCKET_FIN_BIT 0x80
|
||||
#define ESCARGOT_DEBUGGER_WEBSOCKET_TEXT_FRAME 1
|
||||
#define ESCARGOT_DEBUGGER_WEBSOCKET_BINARY_FRAME 2
|
||||
#define ESCARGOT_DEBUGGER_WEBSOCKET_CLOSE_FRAME 8
|
||||
#define ESCARGOT_DEBUGGER_WEBSOCKET_OPCODE_MASK 0x0f
|
||||
#define ESCARGOT_DEBUGGER_WEBSOCKET_LENGTH_MASK 0x7f
|
||||
#define ESCARGOT_DEBUGGER_WEBSOCKET_ONE_BYTE_LEN_MAX 125
|
||||
#define ESCARGOT_DEBUGGER_WEBSOCKET_MASK_BIT 0x80
|
||||
|
||||
#define ESCARGOT_WS_HEADER_BASE_SIZE 2
|
||||
#define ESCARGOT_WS_EXT_LEN16_SIZE 2
|
||||
#define ESCARGOT_WS_MASK_SIZE 4
|
||||
|
||||
#define ESCARGOT_WS_MESSAGE_16BIT_LENGTH_MARKER 126
|
||||
#define ESCARGOT_WS_MAX_MESSAGE_LENGTH 65535
|
||||
|
||||
#define ESCARGOT_WS_HEADER_SIZE (ESCARGOT_WS_HEADER_BASE_SIZE + ESCARGOT_WS_MASK_SIZE)
|
||||
#define ESCARGOT_WS_HEADER_LEN16_SIZE (ESCARGOT_WS_HEADER_SIZE + ESCARGOT_WS_EXT_LEN16_SIZE)
|
||||
#define ESCARGOT_WS_BUFFER_SIZE (ESCARGOT_WS_HEADER_LEN16_SIZE + ESCARGOT_WS_MAX_MESSAGE_LENGTH)
|
||||
|
||||
class DebuggerTcp : public Debugger {
|
||||
public:
|
||||
DebuggerTcp()
|
||||
: m_socket(0)
|
||||
, m_receiveBuffer{}
|
||||
DebuggerTcp(const EscargotSocket socket, String* skipSource, const uint32_t bufferSize, const uint8_t type)
|
||||
: m_socket(socket)
|
||||
, m_receiveBuffer(new uint8_t[bufferSize])
|
||||
, m_bufferSize(bufferSize)
|
||||
, m_receiveBufferFill(0)
|
||||
, m_messageLength(0)
|
||||
, m_skipSourceName(nullptr)
|
||||
, m_payloadLength(0)
|
||||
, m_headerLength(0)
|
||||
, m_websocketMessageType(type)
|
||||
, m_skipSourceName(skipSource)
|
||||
{
|
||||
}
|
||||
|
||||
virtual void init(const char* options, Context* context) override;
|
||||
static Debugger* createDebugger(const char* options, Context* context);
|
||||
void init(const char* options, Context* context) override {}
|
||||
|
||||
virtual bool skipSourceCode(String* srcName) const override;
|
||||
bool skipSourceCode(String* srcName) const override;
|
||||
|
||||
static void computeSha1(const uint8_t* source1, size_t source1Length,
|
||||
const uint8_t* source2, size_t source2Length,
|
||||
uint8_t destination[20]);
|
||||
|
||||
protected:
|
||||
virtual bool send(uint8_t type, const void* buffer, size_t length) override;
|
||||
virtual bool receive(uint8_t* buffer, size_t& length) override;
|
||||
virtual bool isThereAnyEvent() override;
|
||||
virtual void close(CloseReason reason) override;
|
||||
static bool tcpReceive(EscargotSocket socket, uint8_t* message, size_t maxLength, size_t* receivedLength);
|
||||
static bool tcpSend(EscargotSocket socket, const uint8_t* message, size_t messageLength);
|
||||
|
||||
private:
|
||||
void receiveData();
|
||||
protected:
|
||||
enum CloseReason {
|
||||
CloseEndConnection,
|
||||
CloseAbortConnection,
|
||||
CloseProtocolUnsupported,
|
||||
CloseProtocolError,
|
||||
};
|
||||
|
||||
bool send(uint8_t type, const void* buffer, size_t length);
|
||||
bool receive(uint8_t* buffer, size_t& length);
|
||||
bool isThereAnyEvent();
|
||||
void close(CloseReason reason);
|
||||
|
||||
EscargotSocket m_socket;
|
||||
uint8_t m_receiveBuffer[2 + sizeof(uint32_t) + ESCARGOT_DEBUGGER_MAX_MESSAGE_LENGTH];
|
||||
uint8_t m_receiveBufferFill;
|
||||
uint8_t m_messageLength;
|
||||
uint8_t* m_receiveBuffer;
|
||||
uint32_t m_bufferSize;
|
||||
uint32_t m_receiveBufferFill;
|
||||
uint16_t m_payloadLength;
|
||||
uint8_t m_headerLength;
|
||||
uint8_t m_websocketMessageType;
|
||||
|
||||
|
||||
// skip generating debugging bytecode for source code whose name contains m_skipSourceName
|
||||
String* m_skipSourceName;
|
||||
|
|
|
|||
529
src/debugger/HeapSnapshot.cpp
Normal file
529
src/debugger/HeapSnapshot.cpp
Normal file
|
|
@ -0,0 +1,529 @@
|
|||
/*
|
||||
* Copyright (c) 2026-present Samsung Electronics Co., Ltd
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
|
||||
* USA
|
||||
*/
|
||||
#include <cstdio>
|
||||
|
||||
#include "Escargot.h"
|
||||
#include "HeapSnapshot.h"
|
||||
|
||||
#include "Debugger.h"
|
||||
#include "runtime/Context.h"
|
||||
#include "runtime/Environment.h"
|
||||
#include "runtime/EnvironmentRecord.h"
|
||||
#include "runtime/GlobalObject.h"
|
||||
#include "parser/Script.h"
|
||||
#include "util/Util.h"
|
||||
|
||||
#include "rapidjson/document.h"
|
||||
#include "rapidjson/filewritestream.h"
|
||||
#include "rapidjson/writer.h"
|
||||
#include "rapidjson/prettywriter.h"
|
||||
|
||||
#ifdef ESCARGOT_DEBUGGER
|
||||
namespace Escargot {
|
||||
|
||||
uint64_t HeapSnapshot::addNode(Node::nodeType type, const std::string& name, uint64_t& id, uint64_t size, bool detachedness)
|
||||
{
|
||||
auto iter = m_strings.find(name);
|
||||
if (iter == m_strings.end()) {
|
||||
auto result = m_strings.insert(name);
|
||||
iter = result.first;
|
||||
}
|
||||
|
||||
m_nodes.push_back(Node(type, &(*iter), id, size, detachedness));
|
||||
id += 1;
|
||||
m_nodeCount++;
|
||||
|
||||
return id - 1;
|
||||
}
|
||||
|
||||
void HeapSnapshot::addEdge(Node::Edge::edgeType type, uint64_t from, uint64_t to, const std::string& name)
|
||||
{
|
||||
Node& node = m_nodes[from];
|
||||
node.m_edges.push_back(Node::Edge(type, &name, to * heapSnapshotNodeFieldCount));
|
||||
|
||||
m_edgeCount++;
|
||||
}
|
||||
|
||||
uint64_t HeapSnapshot::addNodeAndEdge(Node::nodeType nodeType, Node::Edge::edgeType edgeType, const std::string& name, uint64_t& id, uint64_t ownerId, uint64_t size, bool detachedness)
|
||||
{
|
||||
uint64_t node = addNode(nodeType, name, id, size, detachedness);
|
||||
addEdge(edgeType, ownerId, node, name);
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
void HeapSnapshot::addObjectProperties(ExecutionState* state, uint64_t& id)
|
||||
{
|
||||
Object* current = nullptr;
|
||||
uint64_t owner = 0;
|
||||
while (!m_objects.empty()) {
|
||||
current = m_objects.front().first;
|
||||
owner = m_objects.front().second;
|
||||
m_objects.pop();
|
||||
|
||||
Object::OwnPropertyKeyVector objectKeys = current->ownPropertyKeys(*state);
|
||||
|
||||
for (size_t i = 0; i < objectKeys.size(); i++) {
|
||||
ObjectPropertyName currentName(*state, objectKeys[i]);
|
||||
ObjectGetResult ownProperty = current->getOwnProperty(*state, currentName);
|
||||
|
||||
if (ownProperty.isDataAccessorProperty() && !ownProperty.isDataProperty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Value val = ownProperty.value(*state, val);
|
||||
|
||||
Value currPropertyName = currentName.toPlainValue();
|
||||
std::string name = "";
|
||||
if (currPropertyName.isString()) {
|
||||
name = currPropertyName.asString()->toNonGCUTF8StringData();
|
||||
} else if (currPropertyName.isSymbol()) {
|
||||
name = currPropertyName.asSymbol()->descriptionString()->toNonGCUTF8StringData();
|
||||
}
|
||||
|
||||
Node::Edge::edgeType edgeType = Node::Edge::property;
|
||||
if (m_nodes.at(owner).m_type == Node::array) {
|
||||
edgeType = Node::Edge::element;
|
||||
}
|
||||
|
||||
uint64_t propertyId = 0;
|
||||
if (val.isString()) {
|
||||
propertyId = addNode(Node::string, name, id, sizeof(String));
|
||||
addEdge(Node::Edge::property, owner, propertyId, val.toString(*state)->toNonGCUTF8StringData());
|
||||
} else if (val.isNumber() || val.isBoolean()) {
|
||||
propertyId = addNode(Node::number, name, id, sizeof(val.asNumber()));
|
||||
addEdge(Node::Edge::property, owner, propertyId, val.toString(*state)->toNonGCUTF8StringData());
|
||||
} else if (val.isSymbol()) {
|
||||
propertyId = addNode(Node::symbol, name, id, sizeof(*val.asSymbol()));
|
||||
std::string str = val.asSymbol()->symbolDescriptiveString()->toNonGCUTF8StringData();
|
||||
addEdge(Node::Edge::property, owner, propertyId, str);
|
||||
} else if (val.isUndefined()) {
|
||||
propertyId = addNode(Node::hidden, name, id, sizeof(Value));
|
||||
addEdge(Node::Edge::property, owner, propertyId, val.toString(*state)->toNonGCUTF8StringData());
|
||||
} else if (val.isObject()) {
|
||||
if (m_seenObjects.find(val.asObject()) != m_seenObjects.end()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (val.isFunction()) {
|
||||
propertyId = addNode(Node::closure, name, id, sizeof(*val.asFunction()));
|
||||
addEdge(Node::Edge::property, owner, propertyId, "Function");
|
||||
} else if (val.isBigInt()) {
|
||||
propertyId = addNode(Node::bigint, name, id, sizeof(*val.asBigInt()));
|
||||
addEdge(Node::Edge::property, owner, propertyId, "BigInt");
|
||||
} else if (val.asObject()->isArray(*state)) {
|
||||
propertyId = addNode(Node::array, name, id, sizeof(*val.asObject()));
|
||||
addEdge(Node::Edge::property, owner, propertyId, "Array");
|
||||
} else {
|
||||
propertyId = addNode(Node::object, name, id, sizeof(*val.asObject()));
|
||||
addEdge(Node::Edge::property, owner, propertyId, "Object");
|
||||
}
|
||||
|
||||
m_seenObjects.insert(val.asObject());
|
||||
m_objects.push(std::pair<Object*, uint64_t>(val.asObject(), propertyId));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void HeapSnapshot::addGlobalEnvrionmentRecord(ExecutionState* state, GlobalEnvironmentRecord* env, uint64_t& id, uint64_t LexicalEnvIdx)
|
||||
{
|
||||
GlobalObject* globalObj = env->globalObject();
|
||||
uint64_t gObj = addNodeAndEdge(Node::hidden, Node::Edge::property, "Global Object", id, LexicalEnvIdx, sizeof(GlobalObject));
|
||||
uint64_t varDeclarations = addNodeAndEdge(Node::synthetic, Node::Edge::property, "Var Declarations", id, gObj, 0);
|
||||
|
||||
InterpretedCodeBlock* gCodeBlock = env->globalCodeBlock();
|
||||
for (size_t i = 0; i < gCodeBlock->identifierInfos().size(); i++) {
|
||||
if (!gCodeBlock->identifierInfos()[i].m_isVarDeclaration) {
|
||||
continue;
|
||||
}
|
||||
|
||||
uint64_t propertyId = 0;
|
||||
std::string name = gCodeBlock->identifierInfos()[i].m_name.string()->toNonGCUTF8StringData();
|
||||
auto result = env->getBindingValue(*state, gCodeBlock->identifierInfos()[i].m_name);
|
||||
if (result.m_hasBindingValue) {
|
||||
Value val = result.m_value;
|
||||
|
||||
if (val.isString()) {
|
||||
propertyId = addNode(Node::string, name, id, sizeof(String));
|
||||
addEdge(Node::Edge::property, varDeclarations, propertyId, val.toString(*state)->toNonGCUTF8StringData());
|
||||
} else if (val.isNumber() || val.isBoolean()) {
|
||||
propertyId = addNode(Node::number, name, id, sizeof(val.asNumber()));
|
||||
addEdge(Node::Edge::property, varDeclarations, propertyId, val.toString(*state)->toNonGCUTF8StringData());
|
||||
} else if (val.isSymbol()) {
|
||||
propertyId = addNode(Node::symbol, name, id, sizeof(*val.asSymbol()));
|
||||
std::string str = val.asSymbol()->symbolDescriptiveString()->toNonGCUTF8StringData();
|
||||
addEdge(Node::Edge::property, varDeclarations, propertyId, str);
|
||||
} else if (val.isUndefined()) {
|
||||
propertyId = addNode(Node::hidden, name, id, sizeof(Value));
|
||||
addEdge(Node::Edge::property, varDeclarations, propertyId, val.toString(*state)->toNonGCUTF8StringData());
|
||||
} else if (val.isObject()) {
|
||||
propertyId = addNode(Node::object, name, id, sizeof(Value));
|
||||
|
||||
m_seenObjects.insert(val.asObject());
|
||||
m_objects.push(std::pair<Object*, uint64_t>(val.asObject(), propertyId));
|
||||
addObjectProperties(state, id);
|
||||
}
|
||||
} else {
|
||||
propertyId = addNode(Node::hidden, name, id, sizeof(0));
|
||||
}
|
||||
|
||||
addEdge(Node::Edge::property, varDeclarations, propertyId, "Var Declaration");
|
||||
}
|
||||
|
||||
m_objects.push(std::pair<Object*, uint64_t>(env->globalObject(), gObj));
|
||||
addObjectProperties(state, id);
|
||||
}
|
||||
|
||||
void HeapSnapshot::addObjectEnvironmentRecord(ExecutionState* state, ObjectEnvironmentRecord* env, uint64_t& id, uint64_t& LexicalEnvIdx)
|
||||
{
|
||||
Object* bindingObj = env->bindingObject();
|
||||
uint64_t objId = addNodeAndEdge(Node::object, Node::Edge::element, "Object", id, LexicalEnvIdx, sizeof(Object));
|
||||
m_objects.push(std::pair<Object*, uint64_t>(bindingObj, objId));
|
||||
addObjectProperties(state, id);
|
||||
}
|
||||
|
||||
void HeapSnapshot::addFunctionEnvironmentRecord(ExecutionState* state, FunctionEnvironmentRecord* env, uint64_t& id, uint64_t& LexicalEnvIdx)
|
||||
{
|
||||
ScriptFunctionObject* obj = env->functionObject();
|
||||
uint64_t funcObj = addNodeAndEdge(Node::object, Node::Edge::element, "Function Object", id, LexicalEnvIdx, sizeof(ScriptFunctionObject));
|
||||
m_objects.push(std::pair<Object*, uint64_t>(obj, funcObj));
|
||||
addObjectProperties(state, id);
|
||||
}
|
||||
|
||||
void HeapSnapshot::addModuleEnvironmentRecord(ExecutionState* state, ModuleEnvironmentRecord* env, uint64_t& id, uint64_t& owner)
|
||||
{
|
||||
std::string scriptName = env->script()->srcName()->toNonGCUTF8StringData();
|
||||
uint64_t module = addNodeAndEdge(Node::code, Node::Edge::property, scriptName, id, owner, sizeof(ScriptFunctionObject));
|
||||
|
||||
for (uint64_t i = 0; i < env->moduleBindings().size(); i++) {
|
||||
std::string name = env->moduleBindings()[i].m_localName.string()->toNonGCUTF8StringData();
|
||||
Value val = env->moduleBindings()[i].m_value.toValue();
|
||||
if (val.isEmpty()) {
|
||||
uint64_t node = addNode(Node::object, name, id, sizeof(String));
|
||||
addEdge(Node::Edge::property, module, node, "Empty");
|
||||
} else if (val.isString()) {
|
||||
uint64_t node = addNode(Node::string, name, id, sizeof(String));
|
||||
addEdge(Node::Edge::property, module, node, val.toString(*state)->toNonGCUTF8StringData());
|
||||
} else if (val.isNumber() || val.isBoolean()) {
|
||||
uint64_t node = addNode(Node::number, name, id, sizeof(val.asNumber()));
|
||||
addEdge(Node::Edge::property, module, node, val.toString(*state)->toNonGCUTF8StringData());
|
||||
} else if (val.isSymbol()) {
|
||||
uint64_t node = addNode(Node::symbol, name, id, sizeof(val.asSymbol()));
|
||||
addEdge(Node::Edge::property, module, node, val.toString(*state)->toNonGCUTF8StringData());
|
||||
} else if (val.isUndefined()) {
|
||||
uint64_t node = addNode(Node::hidden, name, id, sizeof(Value));
|
||||
addEdge(Node::Edge::property, module, node, val.toString(*state)->toNonGCUTF8StringData());
|
||||
} else if (val.isObject()) {
|
||||
uint64_t node = addNode(Node::object, name, id, sizeof(Object));
|
||||
addEdge(Node::Edge::property, module, node, "Object");
|
||||
|
||||
if (m_seenObjects.find(val.asObject()) != m_seenObjects.end()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
m_seenObjects.insert(val.asObject());
|
||||
m_objects.push(std::pair<Object*, uint64_t>(val.asObject(), node));
|
||||
|
||||
addObjectProperties(state, id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void HeapSnapshot::addIndexedDeclarativeEnvironmentRecord(ExecutionState* state, DeclarativeEnvironmentRecordIndexed* env, uint64_t& id, uint64_t& LexicalEnvIdx)
|
||||
{
|
||||
uint64_t declEnv = addNode(Node::object, "declarative environment record", id, sizeof(*env), false);
|
||||
addEdge(Node::Edge::element, declEnv, LexicalEnvIdx, "");
|
||||
}
|
||||
|
||||
void HeapSnapshot::addNotIndexedDeclarativeEnvironmentRecord(ExecutionState* state, DeclarativeEnvironmentRecordNotIndexed* env, uint64_t& id, uint64_t& LexicalEnvIdx)
|
||||
{
|
||||
uint64_t declEnv = addNode(Node::object, "not indexed declarative environment record", id, sizeof(*env), false);
|
||||
addEdge(Node::Edge::element, LexicalEnvIdx, declEnv, "");
|
||||
|
||||
IdentifierRecordVector recordVector = env->getIdentifierRecordVector();
|
||||
for (uint64_t i = 0; i < recordVector.size(); i++) {
|
||||
uint64_t identifier = addNode(Node::object, "identifier record vector element", id, sizeof(recordVector[i]), false);
|
||||
addEdge(Node::Edge::element, declEnv, identifier, "");
|
||||
|
||||
uint64_t name = addNode(Node::string, recordVector[i].m_name.string()->toUTF8StringData().data(), id, sizeof(recordVector[i].m_name), false);
|
||||
addEdge(Node::Edge::element, identifier, name, "");
|
||||
}
|
||||
}
|
||||
|
||||
std::string HeapSnapshot::prepareHeapSnapshotFile()
|
||||
{
|
||||
auto addFields = [](rapidjson::Value& array, Vector<std::string, GCUtil::gc_malloc_allocator<std::string>>& strings, rapidjson::Document::AllocatorType& allocator) {
|
||||
for (const std::string& elem : strings) {
|
||||
rapidjson::Value s;
|
||||
s.SetString(elem.c_str(), elem.length(), allocator);
|
||||
array.PushBack(s, allocator);
|
||||
}
|
||||
};
|
||||
|
||||
rapidjson::Document jsonDoc;
|
||||
rapidjson::Document::AllocatorType& allocator = jsonDoc.GetAllocator();
|
||||
jsonDoc.SetObject();
|
||||
|
||||
rapidjson::Value meta(rapidjson::kObjectType);
|
||||
|
||||
rapidjson::Value node_fields(rapidjson::kArrayType);
|
||||
{
|
||||
Vector<std::string, GCUtil::gc_malloc_allocator<std::string>> strings{
|
||||
"type",
|
||||
"name",
|
||||
"id",
|
||||
"self_size",
|
||||
"edge_count",
|
||||
"trace_node_id",
|
||||
"detachedness",
|
||||
"script_id",
|
||||
"line",
|
||||
"column"
|
||||
};
|
||||
addFields(node_fields, strings, allocator);
|
||||
meta.AddMember("node_fields", node_fields, allocator);
|
||||
}
|
||||
|
||||
rapidjson::Value types(rapidjson::kArrayType);
|
||||
{
|
||||
Vector<std::string, GCUtil::gc_malloc_allocator<std::string>> strings{
|
||||
"hidden", "array", "string", "object", "code", "closure", "regexp",
|
||||
"number", "native", "synthetic", "concatenated string",
|
||||
"sliced string", "symbol", "bigint", "object shape"
|
||||
};
|
||||
addFields(types, strings, allocator);
|
||||
}
|
||||
|
||||
rapidjson::Value node_types(rapidjson::kArrayType);
|
||||
{
|
||||
node_types.PushBack(types, allocator);
|
||||
Vector<std::string, GCUtil::gc_malloc_allocator<std::string>> strings{
|
||||
"string", "number", "number", "number", "number"
|
||||
};
|
||||
addFields(node_types, strings, allocator);
|
||||
meta.AddMember("node_types", node_types, allocator);
|
||||
}
|
||||
|
||||
rapidjson::Value edge_fields(rapidjson::kArrayType);
|
||||
{
|
||||
Vector<std::string, GCUtil::gc_malloc_allocator<std::string>> strings{ "type", "name_or_index", "to_node" };
|
||||
addFields(edge_fields, strings, allocator);
|
||||
meta.AddMember("edge_fields", edge_fields, allocator);
|
||||
}
|
||||
|
||||
rapidjson::Value edge_types_inner(rapidjson::kArrayType);
|
||||
{
|
||||
Vector<std::string, GCUtil::gc_malloc_allocator<std::string>> strings{ "type", "name_or_index", "to_node" };
|
||||
addFields(edge_types_inner, strings, allocator);
|
||||
}
|
||||
|
||||
rapidjson::Value edge_types(rapidjson::kArrayType);
|
||||
{
|
||||
edge_types.PushBack(edge_types_inner, allocator);
|
||||
Vector<std::string, GCUtil::gc_malloc_allocator<std::string>> strings{ "string_or_number", "node" };
|
||||
addFields(edge_types, strings, allocator);
|
||||
meta.AddMember("edge_types", edge_types, allocator);
|
||||
}
|
||||
|
||||
rapidjson::Value trace_function_info_fields(rapidjson::kArrayType);
|
||||
{
|
||||
Vector<std::string, GCUtil::gc_malloc_allocator<std::string>> strings{ "function_id", "name", "script_name", "script_id", "line", "column" };
|
||||
addFields(trace_function_info_fields, strings, allocator);
|
||||
meta.AddMember("trace_function_info_fields", trace_function_info_fields, allocator);
|
||||
}
|
||||
|
||||
rapidjson::Value trace_node_fields(rapidjson::kArrayType);
|
||||
{
|
||||
Vector<std::string, GCUtil::gc_malloc_allocator<std::string>> strings{ "id", "function_info_index", "count", "size", "children" };
|
||||
addFields(trace_node_fields, strings, allocator);
|
||||
meta.AddMember("trace_node_fields", trace_node_fields, allocator);
|
||||
}
|
||||
|
||||
rapidjson::Value sample_fields(rapidjson::kArrayType);
|
||||
{
|
||||
Vector<std::string, GCUtil::gc_malloc_allocator<std::string>> strings{ "timestamp_us", "last_assigned_id" };
|
||||
addFields(sample_fields, strings, allocator);
|
||||
meta.AddMember("sample_fields", sample_fields, allocator);
|
||||
}
|
||||
|
||||
rapidjson::Value location_fields(rapidjson::kArrayType);
|
||||
{
|
||||
Vector<std::string, GCUtil::gc_malloc_allocator<std::string>> strings{ "object_index", "script_id", "line", "column" };
|
||||
addFields(location_fields, strings, allocator);
|
||||
meta.AddMember("location_fields", location_fields, allocator);
|
||||
}
|
||||
|
||||
rapidjson::Value snapshot(rapidjson::kObjectType);
|
||||
{
|
||||
snapshot.AddMember("meta", meta, allocator);
|
||||
snapshot.AddMember("node_count", rapidjson::Value(m_nodeCount * heapSnapshotNodeFieldCount), allocator);
|
||||
snapshot.AddMember("edge_count", rapidjson::Value(m_edgeCount * heapSnapshotEdgeFieldCount), allocator);
|
||||
snapshot.AddMember("extra_native_bytes", 0, allocator);
|
||||
snapshot.AddMember("trace_function_count", 0, allocator);
|
||||
}
|
||||
|
||||
rapidjson::Value nodes(rapidjson::kArrayType);
|
||||
rapidjson::Value edges(rapidjson::kArrayType);
|
||||
for (Node& node : m_nodes) {
|
||||
nodes.PushBack(node.m_type, allocator);
|
||||
auto iter = m_strings.find(*node.m_name);
|
||||
auto index = std::distance(m_strings.begin(), iter);
|
||||
nodes.PushBack(index, allocator);
|
||||
nodes.PushBack(node.m_id, allocator);
|
||||
nodes.PushBack(node.m_self_size, allocator);
|
||||
nodes.PushBack(node.m_edges.size(), allocator);
|
||||
nodes.PushBack(0, allocator);
|
||||
nodes.PushBack((uint32_t)node.m_detachedness, allocator);
|
||||
nodes.PushBack(0, allocator);
|
||||
nodes.PushBack(0, allocator);
|
||||
nodes.PushBack(0, allocator);
|
||||
|
||||
for (Node::Edge& edge : node.m_edges) {
|
||||
edges.PushBack(edge.m_type, allocator);
|
||||
edges.PushBack(index, allocator);
|
||||
edges.PushBack(edge.m_to_node_idx, allocator);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
rapidjson::Value trace_function_infos(rapidjson::kArrayType);
|
||||
rapidjson::Value trace_tree(rapidjson::kArrayType);
|
||||
rapidjson::Value samples(rapidjson::kArrayType);
|
||||
rapidjson::Value locations(rapidjson::kArrayType);
|
||||
|
||||
rapidjson::Value strings(rapidjson::kArrayType);
|
||||
for (const std::string& str : m_strings) {
|
||||
rapidjson::Value s;
|
||||
s = rapidjson::StringRef(str.c_str());
|
||||
strings.PushBack(s, allocator);
|
||||
}
|
||||
|
||||
|
||||
jsonDoc.AddMember("snapshot", snapshot, allocator);
|
||||
jsonDoc.AddMember("nodes", nodes, allocator);
|
||||
jsonDoc.AddMember("edges", edges, allocator);
|
||||
jsonDoc.AddMember("trace_function_infos", trace_function_infos, allocator);
|
||||
jsonDoc.AddMember("trace_tree", trace_tree, allocator);
|
||||
jsonDoc.AddMember("samples", samples, allocator);
|
||||
jsonDoc.AddMember("locations", locations, allocator);
|
||||
jsonDoc.AddMember("strings", strings, allocator);
|
||||
|
||||
std::string outputName = "output-" + std::to_string(timestamp()) + ".heapsnapshot";
|
||||
auto deleter = [&](std::FILE* fp) { fclose(fp); };
|
||||
std::unique_ptr<std::FILE, decltype(deleter)> output(fopen(outputName.c_str(), "w"), deleter);
|
||||
|
||||
if (output.get() == nullptr) {
|
||||
ESCARGOT_LOG_ERROR("Could not create heap snapshot file.\n");
|
||||
return "";
|
||||
}
|
||||
|
||||
char buffer[65536];
|
||||
rapidjson::FileWriteStream os(output.get(), buffer, sizeof(buffer));
|
||||
rapidjson::Writer<rapidjson::FileWriteStream> writer(os);
|
||||
jsonDoc.Accept(writer);
|
||||
|
||||
return outputName;
|
||||
}
|
||||
|
||||
std::string HeapSnapshot::takeHeapSnapshot(ExecutionState* state)
|
||||
{
|
||||
uint64_t id = 0;
|
||||
uint64_t stateIdx = 0;
|
||||
|
||||
uint64_t root = addNode(HeapSnapshot::Node::hidden, "Root", id, sizeof(ExecutionState));
|
||||
uint64_t context = addNodeAndEdge(Node::hidden, Node::Edge::property, "Context", id, root, sizeof(Context));
|
||||
uint64_t staticStrings = addNodeAndEdge(Node::hidden, Node::Edge::property, "Static Strings", id, context, sizeof(StaticStrings));
|
||||
|
||||
for (auto& str : *state->context()->atomicStringMap()) {
|
||||
addNodeAndEdge(Node::string, Node::Edge::property, str->toNonGCUTF8StringData(), id, staticStrings, sizeof(String));
|
||||
}
|
||||
|
||||
uint64_t userDefined = addNodeAndEdge(Node::synthetic, Node::Edge::property, "User Defined variables", id, root, 0);
|
||||
for (size_t i = 0; i < state->context()->globalDeclarativeRecord()->size(); i++) {
|
||||
std::string name = state->context()->globalDeclarativeRecord()->at(i).m_name.string()->toNonGCUTF8StringData();
|
||||
|
||||
if (state->context()->globalDeclarativeStorage()->at(i).isEmpty()) {
|
||||
addNodeAndEdge(Node::hidden, Node::Edge::property, name, id, userDefined, 0);
|
||||
continue;
|
||||
}
|
||||
|
||||
Value val = state->context()->globalDeclarativeStorage()->at(i).toValue();
|
||||
if (val.isString()) {
|
||||
uint64_t node = addNode(Node::string, name, id, sizeof(String));
|
||||
addEdge(Node::Edge::property, userDefined, node, val.toString(*state)->toNonGCUTF8StringData());
|
||||
} else if (val.isNumber() || val.isBoolean()) {
|
||||
uint64_t node = addNode(Node::number, name, id, sizeof(val.asNumber()));
|
||||
addEdge(Node::Edge::property, userDefined, node, val.toString(*state)->toNonGCUTF8StringData());
|
||||
} else if (val.isSymbol()) {
|
||||
uint64_t node = addNode(Node::symbol, name, id, sizeof(val.asSymbol()));
|
||||
addEdge(Node::Edge::property, userDefined, node, val.toString(*state)->toNonGCUTF8StringData());
|
||||
} else if (val.isUndefined()) {
|
||||
uint64_t node = addNode(Node::hidden, name, id, sizeof(Value));
|
||||
addEdge(Node::Edge::property, userDefined, node, val.toString(*state)->toNonGCUTF8StringData());
|
||||
} else if (val.isObject()) {
|
||||
uint64_t node = addNode(Node::object, name, id, sizeof(Object));
|
||||
addEdge(Node::Edge::property, userDefined, node, "Object");
|
||||
|
||||
if (m_seenObjects.find(val.asObject()) != m_seenObjects.end()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
m_seenObjects.insert(val.asObject());
|
||||
m_objects.push(std::pair<Object*, uint64_t>(val.asObject(), node));
|
||||
|
||||
addObjectProperties(state, id);
|
||||
}
|
||||
}
|
||||
|
||||
LexicalEnvironment* lexEnv = state->lexicalEnvironment();
|
||||
while (lexEnv) {
|
||||
uint64_t lexEnvId = addNodeAndEdge(Node::hidden, Node::Edge::property, "LexicalEnvironment", id, root, sizeof(LexicalEnvironment));
|
||||
EnvironmentRecord* envRecord = lexEnv->record();
|
||||
|
||||
if (envRecord->isGlobalEnvironmentRecord()) {
|
||||
addGlobalEnvrionmentRecord(state, envRecord->asGlobalEnvironmentRecord(), id, lexEnvId);
|
||||
} else if (envRecord->isObjectEnvironmentRecord()) {
|
||||
addObjectEnvironmentRecord(state, envRecord->asObjectEnvironmentRecord(), id, lexEnvId);
|
||||
} else if (envRecord->isDeclarativeEnvironmentRecord()) {
|
||||
DeclarativeEnvironmentRecord* declarativeRecord = envRecord->asDeclarativeEnvironmentRecord();
|
||||
if (declarativeRecord->isFunctionEnvironmentRecord()) {
|
||||
FunctionEnvironmentRecord* funcEnv = envRecord->asDeclarativeEnvironmentRecord()->asFunctionEnvironmentRecord();
|
||||
addFunctionEnvironmentRecord(state, funcEnv, id, lexEnvId);
|
||||
} else if (envRecord->isModuleEnvironmentRecord()) {
|
||||
ModuleEnvironmentRecord* moduleEnv = envRecord->asDeclarativeEnvironmentRecord()->asModuleEnvironmentRecord();
|
||||
uint64_t modules = addNodeAndEdge(Node::synthetic, Node::Edge::property, "Modules", id, root, 0);
|
||||
addModuleEnvironmentRecord(state, moduleEnv, id, modules);
|
||||
} else if (envRecord->asDeclarativeEnvironmentRecord()->isDeclarativeEnvironmentRecordIndexed()) {
|
||||
DeclarativeEnvironmentRecordIndexed* declEnv = envRecord->asDeclarativeEnvironmentRecord()->asDeclarativeEnvironmentRecordIndexed();
|
||||
addIndexedDeclarativeEnvironmentRecord(state, declEnv, id, lexEnvId);
|
||||
} else {
|
||||
DeclarativeEnvironmentRecordNotIndexed* declEnv = envRecord->asDeclarativeEnvironmentRecord()->asDeclarativeEnvironmentRecordNotIndexed();
|
||||
addNotIndexedDeclarativeEnvironmentRecord(state, declEnv, id, lexEnvId);
|
||||
}
|
||||
}
|
||||
|
||||
lexEnv = lexEnv->outerEnvironment();
|
||||
}
|
||||
|
||||
ESCARGOT_LOG_INFO("nodes: %ld | edges: %ld | strings: %zu\n", m_nodeCount, m_edgeCount, m_strings.size());
|
||||
return prepareHeapSnapshotFile();
|
||||
}
|
||||
} // namespace Escargot
|
||||
#endif
|
||||
148
src/debugger/HeapSnapshot.h
Normal file
148
src/debugger/HeapSnapshot.h
Normal file
|
|
@ -0,0 +1,148 @@
|
|||
/*
|
||||
* Copyright (c) 2026-present Samsung Electronics Co., Ltd
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
|
||||
* USA
|
||||
*/
|
||||
|
||||
#ifndef __EscargotHeapSnapshot__
|
||||
#define __EscargotHeapSnapshot__
|
||||
|
||||
#include "runtime/Object.h"
|
||||
#include "util/Vector.h"
|
||||
|
||||
namespace Escargot {
|
||||
|
||||
class Object;
|
||||
class EnvironmentRecord;
|
||||
class ObjectEnvironmentRecord;
|
||||
class GlobalEnvironmentRecord;
|
||||
class ModuleEnvironmentRecord;
|
||||
class FunctionEnvironmentRecord;
|
||||
class DeclarativeEnvironmentRecord;
|
||||
class DeclarativeEnvironmentRecordIndexed;
|
||||
class DeclarativeEnvironmentRecordNotIndexed;
|
||||
|
||||
class HeapSnapshot : public gc {
|
||||
public:
|
||||
const uint32_t heapSnapshotNodeFieldCount = 10;
|
||||
const uint32_t heapSnapshotEdgeFieldCount = 3;
|
||||
|
||||
struct Node {
|
||||
// /"edge_types":[["context","element","property","internal","hidden","shortcut","weak"]
|
||||
struct Edge {
|
||||
enum edgeType {
|
||||
context,
|
||||
element,
|
||||
property,
|
||||
internal,
|
||||
hidden,
|
||||
shortcut,
|
||||
weak,
|
||||
};
|
||||
|
||||
Edge(edgeType type, const std::string* name, uint64_t to_node_idx)
|
||||
: m_type(type)
|
||||
, m_name(const_cast<std::string*>(name))
|
||||
, m_to_node_idx(to_node_idx)
|
||||
{
|
||||
}
|
||||
|
||||
edgeType m_type;
|
||||
std::string* m_name;
|
||||
uint64_t m_to_node_idx;
|
||||
};
|
||||
|
||||
enum nodeType {
|
||||
hidden,
|
||||
array,
|
||||
string,
|
||||
object,
|
||||
code,
|
||||
closure,
|
||||
regexp,
|
||||
number,
|
||||
native,
|
||||
synthetic,
|
||||
concatenated_string,
|
||||
sliced_string,
|
||||
symbol,
|
||||
bigint,
|
||||
object_shape
|
||||
};
|
||||
|
||||
Node(nodeType type, const std::string* name, uint64_t id, uint64_t self_size, bool detachedness)
|
||||
: m_type(type)
|
||||
, m_name(const_cast<std::string*>(name))
|
||||
, m_id(id)
|
||||
, m_self_size(self_size)
|
||||
, m_detachedness(detachedness)
|
||||
{
|
||||
}
|
||||
|
||||
~Node()
|
||||
{
|
||||
m_edges.clear();
|
||||
}
|
||||
|
||||
nodeType m_type;
|
||||
const std::string* m_name;
|
||||
uint64_t m_id;
|
||||
uint64_t m_self_size;
|
||||
bool m_detachedness;
|
||||
Vector<Edge, GCUtil::gc_malloc_allocator<Edge>> m_edges;
|
||||
};
|
||||
|
||||
|
||||
HeapSnapshot()
|
||||
{
|
||||
m_nodeCount = 0;
|
||||
m_edgeCount = 0;
|
||||
}
|
||||
|
||||
~HeapSnapshot()
|
||||
{
|
||||
m_nodes.clear();
|
||||
m_strings.clear();
|
||||
}
|
||||
|
||||
uint64_t addNode(Node::nodeType type, const std::string& name, uint64_t& id, uint64_t size, bool detachedness = false);
|
||||
void addEdge(Node::Edge::edgeType type, uint64_t from, uint64_t to, const std::string& name);
|
||||
uint64_t addNodeAndEdge(Node::nodeType nodeType, Node::Edge::edgeType edgeType, const std::string& name, uint64_t& id, uint64_t ownerId, uint64_t size, bool detachedness = false);
|
||||
void addObjectProperties(ExecutionState* state, uint64_t& id);
|
||||
|
||||
void addGlobalEnvrionmentRecord(ExecutionState* state, GlobalEnvironmentRecord* env, uint64_t& id, uint64_t LexicalEnvIdx);
|
||||
void addObjectEnvironmentRecord(ExecutionState* state, ObjectEnvironmentRecord* env, uint64_t& id, uint64_t& LexicalEnvIdx);
|
||||
void addFunctionEnvironmentRecord(ExecutionState* state, FunctionEnvironmentRecord* env, uint64_t& id, uint64_t& LexicalEnvIdx);
|
||||
void addModuleEnvironmentRecord(ExecutionState* state, ModuleEnvironmentRecord* env, uint64_t& id, uint64_t& LexicalEnvIdx);
|
||||
void addIndexedDeclarativeEnvironmentRecord(ExecutionState* state, DeclarativeEnvironmentRecordIndexed* env, uint64_t& id, uint64_t& LexicalEnvIdx);
|
||||
void addNotIndexedDeclarativeEnvironmentRecord(ExecutionState* state, DeclarativeEnvironmentRecordNotIndexed* env, uint64_t& id, uint64_t& LexicalEnvIdx);
|
||||
|
||||
std::string prepareHeapSnapshotFile();
|
||||
std::string takeHeapSnapshot(ExecutionState* state);
|
||||
|
||||
private:
|
||||
uint64_t m_nodeCount;
|
||||
uint64_t m_edgeCount;
|
||||
|
||||
Vector<Node, GCUtil::gc_malloc_allocator<Node>> m_nodes;
|
||||
std::unordered_set<std::string> m_strings;
|
||||
std::unordered_set<Object*> m_seenObjects;
|
||||
std::queue<std::pair<Object*, uint64_t>> m_objects;
|
||||
};
|
||||
|
||||
} // namespace Escargot
|
||||
|
||||
#endif
|
||||
|
|
@ -41,6 +41,12 @@ void Heap::initialize()
|
|||
GC_set_warn_proc(GC_ignore_warn_proc);
|
||||
#endif
|
||||
|
||||
GC_set_oom_fn([](size_t sz) -> void* {
|
||||
ESCARGOT_LOG_ERROR("Out of memory!");
|
||||
abort();
|
||||
return nullptr;
|
||||
});
|
||||
|
||||
GC_set_force_unmap_on_gcollect(1);
|
||||
initializeCustomAllocators();
|
||||
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@
|
|||
#include "interpreter/ByteCodeBlockData.h"
|
||||
#include "interpreter/ByteCodeGenerator.h"
|
||||
#include "runtime/ExecutionPauser.h"
|
||||
#include "util/BloomFilter.h"
|
||||
|
||||
#ifndef NDEBUG
|
||||
#include "parser/CodeBlock.h"
|
||||
|
|
@ -93,6 +94,7 @@ struct GlobalVariableAccessCacheItem;
|
|||
F(ToNumericIncrement) \
|
||||
F(ToNumericDecrement) \
|
||||
F(ToNumber) \
|
||||
F(ToPropertyKey) \
|
||||
F(UnaryMinus) \
|
||||
F(UnaryNot) \
|
||||
F(UnaryBitwiseNot) \
|
||||
|
|
@ -137,6 +139,7 @@ struct GlobalVariableAccessCacheItem;
|
|||
F(StoreByNameWithAddress) \
|
||||
F(InitializeDisposable) \
|
||||
F(FinalizeDisposable) \
|
||||
F(SetExecutionStateInStrictMode) \
|
||||
F(FillOpcodeTable) \
|
||||
F(End)
|
||||
|
||||
|
|
@ -963,19 +966,31 @@ public:
|
|||
DefineGetterSetter,
|
||||
};
|
||||
|
||||
typedef BloomFilter<1024> CreateObjectPropertyFilter;
|
||||
typedef BloomFilter<1024 * 64> CreateObjectBigPropertyFilter;
|
||||
|
||||
struct CreateObjectData : public gc {
|
||||
bool m_allPrecomputed;
|
||||
bool m_wasStructureComputed;
|
||||
bool m_canStoreStructureOnCode;
|
||||
bool m_allPrecomputed : 1;
|
||||
bool m_wasStructureComputed : 1;
|
||||
bool m_canStoreStructureOnCode : 1;
|
||||
bool m_needsToUsePropertyFilterOnInterpreter : 1;
|
||||
bool m_needsToUseBigPropertyFilterOnInterpreter : 1;
|
||||
VectorWithInlineStorage<6, ObjectStructureItem, GCUtil::gc_malloc_allocator<ObjectStructureItem>> m_properties;
|
||||
EncodedValueVector m_values;
|
||||
Object* m_target;
|
||||
CreateObjectPrepare* m_initCode;
|
||||
union {
|
||||
Optional<CreateObjectPropertyFilter*> m_filter;
|
||||
Optional<CreateObjectBigPropertyFilter*> m_bigFilter;
|
||||
};
|
||||
CreateObjectData(bool allPrecomputed, bool wasStructureComputed, bool canStoreStructureOnCode,
|
||||
bool needsToUsePropertyFilterOnInterpreter, bool needsToUseBigPropertyFilterOnInterpreter,
|
||||
size_t reserveSize, Object* target, CreateObjectPrepare* initCode)
|
||||
: m_allPrecomputed(allPrecomputed)
|
||||
, m_wasStructureComputed(wasStructureComputed)
|
||||
, m_canStoreStructureOnCode(canStoreStructureOnCode)
|
||||
, m_needsToUsePropertyFilterOnInterpreter(needsToUsePropertyFilterOnInterpreter)
|
||||
, m_needsToUseBigPropertyFilterOnInterpreter(needsToUseBigPropertyFilterOnInterpreter)
|
||||
, m_target(target)
|
||||
, m_initCode(initCode)
|
||||
{
|
||||
|
|
@ -986,10 +1001,13 @@ public:
|
|||
}
|
||||
};
|
||||
|
||||
CreateObjectPrepare(const ByteCodeLOC& loc, const size_t dataRegisterIndex, const size_t objectIndex)
|
||||
CreateObjectPrepare(const ByteCodeLOC& loc, const size_t dataRegisterIndex, const size_t objectIndex, bool needsToUseNameFilterOnIntepreter,
|
||||
bool needsToUseBigPropertyFilterOnInterpreter)
|
||||
: ByteCode(Opcode::CreateObjectPrepareOpcode, loc)
|
||||
, m_stage(Stage::Init)
|
||||
, m_allPrecomputed(false)
|
||||
, m_needsToUsePropertyFilterOnInterpreter(needsToUseNameFilterOnIntepreter)
|
||||
, m_needsToUseBigPropertyFilterOnInterpreter(needsToUseBigPropertyFilterOnInterpreter)
|
||||
, m_hasPrecomputedKey(false)
|
||||
, m_needsToUpdateFunctionName(false)
|
||||
, m_isGetter(false)
|
||||
|
|
@ -1003,6 +1021,8 @@ public:
|
|||
: ByteCode(Opcode::CreateObjectPrepareOpcode, loc)
|
||||
, m_stage(Stage::FillKeyValue)
|
||||
, m_allPrecomputed(false)
|
||||
, m_needsToUsePropertyFilterOnInterpreter(false)
|
||||
, m_needsToUseBigPropertyFilterOnInterpreter(false)
|
||||
, m_hasPrecomputedKey(hasPreComputedKey)
|
||||
, m_needsToUpdateFunctionName(needsToUpdateFunctionName)
|
||||
, m_isGetter(false)
|
||||
|
|
@ -1016,6 +1036,8 @@ public:
|
|||
: ByteCode(Opcode::CreateObjectPrepareOpcode, loc)
|
||||
, m_stage(Stage::DefineGetterSetter)
|
||||
, m_allPrecomputed(false)
|
||||
, m_needsToUsePropertyFilterOnInterpreter(false)
|
||||
, m_needsToUseBigPropertyFilterOnInterpreter(false)
|
||||
, m_hasPrecomputedKey(hasPreComputedKey)
|
||||
, m_needsToUpdateFunctionName(false)
|
||||
, m_isGetter(isGetter)
|
||||
|
|
@ -1027,6 +1049,8 @@ public:
|
|||
|
||||
Stage m_stage : 2;
|
||||
bool m_allPrecomputed : 1;
|
||||
bool m_needsToUsePropertyFilterOnInterpreter : 1;
|
||||
bool m_needsToUseBigPropertyFilterOnInterpreter : 1;
|
||||
bool m_hasPrecomputedKey : 1;
|
||||
bool m_needsToUpdateFunctionName : 1;
|
||||
bool m_isGetter : 1; // other case, this is setter
|
||||
|
|
@ -1548,6 +1572,26 @@ public:
|
|||
#endif
|
||||
};
|
||||
|
||||
class ToPropertyKey : public ByteCode {
|
||||
public:
|
||||
ToPropertyKey(const ByteCodeLOC& loc, const size_t srcIndex, const size_t dstIndex)
|
||||
: ByteCode(Opcode::ToPropertyKeyOpcode, loc)
|
||||
, m_srcIndex(srcIndex)
|
||||
, m_dstIndex(dstIndex)
|
||||
{
|
||||
}
|
||||
|
||||
ByteCodeRegisterIndex m_srcIndex;
|
||||
ByteCodeRegisterIndex m_dstIndex;
|
||||
|
||||
#ifndef NDEBUG
|
||||
void dump()
|
||||
{
|
||||
printf("to property key r%u <- r%u", m_dstIndex, m_srcIndex);
|
||||
}
|
||||
#endif
|
||||
};
|
||||
|
||||
class Increment : public ByteCode {
|
||||
public:
|
||||
Increment(const ByteCodeLOC& loc, const size_t srcIndex, const size_t dstIndex)
|
||||
|
|
@ -3096,6 +3140,32 @@ public:
|
|||
#endif
|
||||
};
|
||||
|
||||
// A class definition is always strict mode code, including its heritage
|
||||
// expression and computed property names, even when it appears inside
|
||||
// non-strict code. This opcode temporarily forces the running execution state
|
||||
// into strict mode while those parts are evaluated.
|
||||
// When m_enter is true, the current strict flag is saved into m_savedStrictRegisterIndex
|
||||
// and strict mode is turned on. When m_enter is false, the strict flag is restored
|
||||
// from m_savedStrictRegisterIndex (so that nested classes restore correctly).
|
||||
class SetExecutionStateInStrictMode : public ByteCode {
|
||||
public:
|
||||
SetExecutionStateInStrictMode(const ByteCodeLOC& loc, bool enter, const size_t savedStrictRegisterIndex)
|
||||
: ByteCode(Opcode::SetExecutionStateInStrictModeOpcode, loc)
|
||||
, m_enter(enter)
|
||||
, m_savedStrictRegisterIndex(savedStrictRegisterIndex)
|
||||
{
|
||||
}
|
||||
|
||||
bool m_enter;
|
||||
ByteCodeRegisterIndex m_savedStrictRegisterIndex;
|
||||
#ifndef NDEBUG
|
||||
void dump()
|
||||
{
|
||||
printf("set execution state in strict mode (%s) <-> r%u", m_enter ? "enter" : "exit", m_savedStrictRegisterIndex);
|
||||
}
|
||||
#endif
|
||||
};
|
||||
|
||||
class BindingCalleeIntoRegister : public ByteCode {
|
||||
public:
|
||||
BindingCalleeIntoRegister(const ByteCodeLOC& loc)
|
||||
|
|
@ -3265,12 +3335,19 @@ public:
|
|||
context->m_locData->push_back(std::make_pair(start, idx));
|
||||
}
|
||||
|
||||
#if !defined(NDEBUG) && defined(ESCARGOT_DEBUGGER)
|
||||
const auto loc = computeNodeLOC(m_codeBlock->src(), m_codeBlock->functionStart(), idx);
|
||||
ByteCodeLOC* bytecodeLoc = &reinterpret_cast<ByteCode*>(first)->m_loc;
|
||||
bytecodeLoc->index = loc.index;
|
||||
bytecodeLoc->line = loc.line;
|
||||
bytecodeLoc->column = loc.column;
|
||||
#endif
|
||||
|
||||
m_code.resizeWithUninitializedValues(m_code.size() + sizeof(CodeType));
|
||||
for (size_t i = 0; i < sizeof(CodeType); i++) {
|
||||
m_code[start++] = *first;
|
||||
first++;
|
||||
}
|
||||
|
||||
m_requiredOperandRegisterNumber = std::max(m_requiredOperandRegisterNumber, (ByteCodeRegisterIndex)context->m_baseRegisterCount);
|
||||
|
||||
// TODO throw exception
|
||||
|
|
|
|||
|
|
@ -573,6 +573,7 @@ void ByteCodeGenerator::relocateByteCode(ByteCodeBlock* block)
|
|||
break;
|
||||
}
|
||||
case ToNumberOpcode:
|
||||
case ToPropertyKeyOpcode:
|
||||
case IncrementOpcode:
|
||||
case DecrementOpcode:
|
||||
case UnaryMinusOpcode:
|
||||
|
|
|
|||
|
|
@ -105,6 +105,7 @@ struct ByteCodeGenerateContext {
|
|||
, m_lexicalBlockIndex(contextBefore.m_lexicalBlockIndex)
|
||||
, m_classInfo(contextBefore.m_classInfo)
|
||||
, m_numeralLiteralData(contextBefore.m_numeralLiteralData) // should be NumeralLiteralVector
|
||||
, m_currentLoopLabels(contextBefore.m_currentLoopLabels)
|
||||
#if defined(ENABLE_TCO)
|
||||
, m_returnRegister(contextBefore.m_returnRegister)
|
||||
#endif
|
||||
|
|
@ -182,26 +183,33 @@ struct ByteCodeGenerateContext {
|
|||
void registerJumpPositionsToComplexCase(size_t frontlimit)
|
||||
{
|
||||
ASSERT(tryCatchWithBlockStatementCount());
|
||||
// `frontlimit` is the byte position of the first instruction of the block body
|
||||
// (i.e. lexicalBlockStartPosition, set right after the BlockOperation opcode).
|
||||
// A break/continue jump located at exactly that position is the very first
|
||||
// statement of the block body and therefore IS inside the block, so it must be
|
||||
// morphed into a JumpComplexCase to unwind the block's lexical environment.
|
||||
// Using strictly greater-than (`>`) here would skip such a jump and leave the
|
||||
// block environment un-popped (Issue #1571), so the comparison must be `>=`.
|
||||
for (unsigned i = 0; i < m_breakStatementPositions.size(); i++) {
|
||||
if (m_breakStatementPositions[i] > (unsigned long)frontlimit && m_complexCaseStatementPositions.find(m_breakStatementPositions[i]) == m_complexCaseStatementPositions.end()) {
|
||||
if (m_breakStatementPositions[i] >= (unsigned long)frontlimit && m_complexCaseStatementPositions.find(m_breakStatementPositions[i]) == m_complexCaseStatementPositions.end()) {
|
||||
m_complexCaseStatementPositions.insert(std::make_pair(m_breakStatementPositions[i], tryCatchWithBlockStatementCount()));
|
||||
}
|
||||
}
|
||||
|
||||
for (unsigned i = 0; i < m_continueStatementPositions.size(); i++) {
|
||||
if (m_continueStatementPositions[i] > (unsigned long)frontlimit && m_complexCaseStatementPositions.find(m_continueStatementPositions[i]) == m_complexCaseStatementPositions.end()) {
|
||||
if (m_continueStatementPositions[i] >= (unsigned long)frontlimit && m_complexCaseStatementPositions.find(m_continueStatementPositions[i]) == m_complexCaseStatementPositions.end()) {
|
||||
m_complexCaseStatementPositions.insert(std::make_pair(m_continueStatementPositions[i], tryCatchWithBlockStatementCount()));
|
||||
}
|
||||
}
|
||||
|
||||
for (unsigned i = 0; i < m_labelledBreakStatmentPositions.size(); i++) {
|
||||
if (m_labelledBreakStatmentPositions[i].second > (unsigned long)frontlimit && m_complexCaseStatementPositions.find(m_labelledBreakStatmentPositions[i].second) == m_complexCaseStatementPositions.end()) {
|
||||
if (m_labelledBreakStatmentPositions[i].second >= (unsigned long)frontlimit && m_complexCaseStatementPositions.find(m_labelledBreakStatmentPositions[i].second) == m_complexCaseStatementPositions.end()) {
|
||||
m_complexCaseStatementPositions.insert(std::make_pair(m_labelledBreakStatmentPositions[i].second, tryCatchWithBlockStatementCount()));
|
||||
}
|
||||
}
|
||||
|
||||
for (unsigned i = 0; i < m_labelledContinueStatmentPositions.size(); i++) {
|
||||
if (m_labelledContinueStatmentPositions[i].second > (unsigned long)frontlimit && m_complexCaseStatementPositions.find(m_labelledContinueStatmentPositions[i].second) == m_complexCaseStatementPositions.end()) {
|
||||
if (m_labelledContinueStatmentPositions[i].second >= (unsigned long)frontlimit && m_complexCaseStatementPositions.find(m_labelledContinueStatmentPositions[i].second) == m_complexCaseStatementPositions.end()) {
|
||||
m_complexCaseStatementPositions.insert(std::make_pair(m_labelledContinueStatmentPositions[i].second, tryCatchWithBlockStatementCount()));
|
||||
}
|
||||
}
|
||||
|
|
@ -390,6 +398,10 @@ struct ByteCodeGenerateContext {
|
|||
ClassContextInformation m_classInfo;
|
||||
std::map<size_t, size_t> m_complexCaseStatementPositions;
|
||||
void* m_numeralLiteralData; // should be NumeralLiteralVector
|
||||
// Labels directly targeting the loop that is about to be generated (a loop may
|
||||
// carry more than one label, e.g. `L1: L2: for (...)`). A loop clears this list
|
||||
// before generating its body so that nested loops do not inherit it. - Issue #1571/#1577
|
||||
std::vector<String*> m_currentLoopLabels;
|
||||
|
||||
#if defined(ENABLE_TCO)
|
||||
size_t m_returnRegister; // for tail call optimizaiton (TCO)
|
||||
|
|
|
|||
|
|
@ -198,6 +198,8 @@ public:
|
|||
static void initializeDisposable(ExecutionState& state, Value* registerFile, size_t& programCounter);
|
||||
static bool finalizeDisposable(ExecutionState& state, Value* registerFile, size_t& programCounter, ByteCodeBlock* byteCodeBlock);
|
||||
|
||||
static void setExecutionStateInStrictModeOperation(ExecutionState& state, Value* registerFile, size_t& programCounter);
|
||||
|
||||
#if defined(ENABLE_TCO)
|
||||
static Value tailRecursionSlowCase(ExecutionState& state, TailRecursion* code, ByteCodeBlock* byteCodeBlock, const Value& callee, Value* registerFile);
|
||||
static Value prepareTailCallOptimization(ExecutionState*& state, TailCall* code, ScriptFunctionObject* callee, ByteCodeBlock*& callerBlock, size_t& programCounter, const Value* registerFile);
|
||||
|
|
@ -763,6 +765,14 @@ Value Interpreter::interpret(ExecutionState* state, ByteCodeBlock* byteCodeBlock
|
|||
// Return F.[[Call]](V, argumentsList).
|
||||
registerFile[code->m_resultIndex] = callee.asPointerValue()->call(*state, Value(), code->m_argumentCount, ®isterFile[code->m_argumentsStartIndex]);
|
||||
|
||||
#ifdef ESCARGOT_DEBUGGER
|
||||
if (state->context()->debuggerEnabled()) {
|
||||
if (state->context()->debugger()->getRestart()) {
|
||||
return Value(true);
|
||||
}
|
||||
}
|
||||
#endif /* ESCARGOT_DEBUGGER */
|
||||
|
||||
ADD_PROGRAM_COUNTER(Call);
|
||||
NEXT_INSTRUCTION();
|
||||
}
|
||||
|
|
@ -997,6 +1007,16 @@ Value Interpreter::interpret(ExecutionState* state, ByteCodeBlock* byteCodeBlock
|
|||
NEXT_INSTRUCTION();
|
||||
}
|
||||
|
||||
DEFINE_OPCODE(ToPropertyKey)
|
||||
:
|
||||
{
|
||||
ToPropertyKey* code = (ToPropertyKey*)programCounter;
|
||||
const Value& val = registerFile[code->m_srcIndex];
|
||||
registerFile[code->m_dstIndex] = val.toPropertyKey(*state);
|
||||
ADD_PROGRAM_COUNTER(ToPropertyKey);
|
||||
NEXT_INSTRUCTION();
|
||||
}
|
||||
|
||||
DEFINE_OPCODE(BindingCalleeIntoRegister)
|
||||
:
|
||||
{
|
||||
|
|
@ -1556,6 +1576,14 @@ Value Interpreter::interpret(ExecutionState* state, ByteCodeBlock* byteCodeBlock
|
|||
NEXT_INSTRUCTION();
|
||||
}
|
||||
|
||||
// Rarely-used; see InterpreterSlowPath::setExecutionStateInStrictModeOperation.
|
||||
DEFINE_OPCODE(SetExecutionStateInStrictMode)
|
||||
:
|
||||
{
|
||||
InterpreterSlowPath::setExecutionStateInStrictModeOperation(*state, registerFile, programCounter);
|
||||
NEXT_INSTRUCTION();
|
||||
}
|
||||
|
||||
#if defined(ENABLE_TCO)
|
||||
// Tail recursion
|
||||
DEFINE_OPCODE(TailRecursion)
|
||||
|
|
@ -1705,7 +1733,10 @@ Value Interpreter::interpret(ExecutionState* state, ByteCodeBlock* byteCodeBlock
|
|||
:
|
||||
{
|
||||
if (state->context()->debuggerEnabled()) {
|
||||
state->context()->debugger()->processDisabledBreakpoint(byteCodeBlock, (uint32_t)(programCounter - (size_t)byteCodeBlock->m_code.data()), state);
|
||||
bool restart = state->context()->debugger()->processDisabledBreakpoint(byteCodeBlock, (uint32_t)(programCounter - (size_t)byteCodeBlock->m_code.data()), state);
|
||||
if (restart) {
|
||||
return Value(true);
|
||||
}
|
||||
}
|
||||
|
||||
ADD_PROGRAM_COUNTER(BreakpointDisabled);
|
||||
|
|
@ -1716,7 +1747,10 @@ Value Interpreter::interpret(ExecutionState* state, ByteCodeBlock* byteCodeBlock
|
|||
:
|
||||
{
|
||||
if (state->context()->debuggerEnabled()) {
|
||||
state->context()->debugger()->stopAtBreakpoint(byteCodeBlock, (uint32_t)(programCounter - (size_t)byteCodeBlock->m_code.data()), state);
|
||||
bool restart = state->context()->debugger()->stopAtBreakpoint(byteCodeBlock, (uint32_t)(programCounter - (size_t)byteCodeBlock->m_code.data()), state);
|
||||
if (restart) {
|
||||
return Value(true);
|
||||
}
|
||||
}
|
||||
|
||||
ADD_PROGRAM_COUNTER(BreakpointEnabled);
|
||||
|
|
@ -2570,7 +2604,8 @@ NEVER_INLINE void InterpreterSlowPath::getObjectPrecomputedCaseOperation(Executi
|
|||
}
|
||||
|
||||
auto& newItem = inlineCache->m_cache[0];
|
||||
code->m_inlineCacheProtoTraverseMaxIndex = std::max(cachedhiddenClassChain.size() - 1, (size_t)code->m_inlineCacheProtoTraverseMaxIndex);
|
||||
size_t newProtoTraverseIndex = std::min(cachedhiddenClassChain.size() - 1, SetObjectPreComputedCase::inlineCacheProtoTraverseMaxCount - 1);
|
||||
code->m_inlineCacheProtoTraverseMaxIndex = std::max(newProtoTraverseIndex, (size_t)code->m_inlineCacheProtoTraverseMaxIndex);
|
||||
|
||||
newItem.m_cachedhiddenClassChainLength = cachedhiddenClassChain.size();
|
||||
block->m_inlineCacheDataSize += sizeof(size_t) * cachedhiddenClassChain.size();
|
||||
|
|
@ -2634,7 +2669,7 @@ ALWAYS_INLINE void InterpreterSlowPath::setObjectPreComputedCaseOperation(Execut
|
|||
return;
|
||||
}
|
||||
}
|
||||
} else if (setObjectPreComputedCaseOperationSlowCase(state, originalObject, willBeObject, value, code, block)) {
|
||||
} else if (code->m_inlineCacheProtoTraverseMaxIndex > 0 && code->m_inlineCacheProtoTraverseMaxIndex < SetObjectPreComputedCase::inlineCacheProtoTraverseMaxCount && setObjectPreComputedCaseOperationSlowCase(state, originalObject, willBeObject, value, code, block)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
|
@ -2651,8 +2686,9 @@ NEVER_INLINE bool InterpreterSlowPath::setObjectPreComputedCaseOperationSlowCase
|
|||
Object* objChain[SetObjectPreComputedCase::inlineCacheProtoTraverseMaxCount];
|
||||
ObjectStructure* objStructures[SetObjectPreComputedCase::inlineCacheProtoTraverseMaxCount];
|
||||
const auto& maxIndex = code->m_inlineCacheProtoTraverseMaxIndex;
|
||||
const size_t safeMaxIndex = std::min((size_t)maxIndex, (size_t)(SetObjectPreComputedCase::inlineCacheProtoTraverseMaxCount - 1));
|
||||
size_t fillCount;
|
||||
for (fillCount = 0; fillCount <= maxIndex && obj; fillCount++) {
|
||||
for (fillCount = 0; fillCount <= safeMaxIndex && obj; fillCount++) {
|
||||
objChain[fillCount] = obj;
|
||||
objStructures[fillCount] = obj->structure();
|
||||
obj = obj->Object::getPrototypeObject(state);
|
||||
|
|
@ -2665,6 +2701,9 @@ NEVER_INLINE bool InterpreterSlowPath::setObjectPreComputedCaseOperationSlowCase
|
|||
const auto& item = cacheData[currentCacheIndex];
|
||||
const auto& cSiz = item.m_cachedhiddenClassChainLength;
|
||||
ASSERT(cSiz > 0);
|
||||
if (UNLIKELY(cSiz >= SetObjectPreComputedCase::inlineCacheProtoTraverseMaxCount)) {
|
||||
continue;
|
||||
}
|
||||
bool ok = true;
|
||||
for (size_t i = 0; i < cSiz; i++) {
|
||||
if (objStructures[i] != item.m_cachedHiddenClassChainData[i]) {
|
||||
|
|
@ -2861,7 +2900,8 @@ NEVER_INLINE void InterpreterSlowPath::setObjectPreComputedCaseOperationCacheMis
|
|||
inlineCache->m_cache[i].m_cachedHiddenClassChainData[0] = oldClass;
|
||||
}
|
||||
}
|
||||
code->m_inlineCacheProtoTraverseMaxIndex = std::max(cachedhiddenClassChain.size() - 1, (size_t)code->m_inlineCacheProtoTraverseMaxIndex);
|
||||
size_t newProtoTraverseIndex = std::min(cachedhiddenClassChain.size() - 1, SetObjectPreComputedCase::inlineCacheProtoTraverseMaxCount - 1);
|
||||
code->m_inlineCacheProtoTraverseMaxIndex = std::max(newProtoTraverseIndex, (size_t)code->m_inlineCacheProtoTraverseMaxIndex);
|
||||
}
|
||||
|
||||
// finally, insert a valid new cache item at the end
|
||||
|
|
@ -3049,11 +3089,18 @@ NEVER_INLINE void InterpreterSlowPath::createObjectOperation(ExecutionState& sta
|
|||
}
|
||||
}
|
||||
obj->m_values.reset(data->m_values.takeBuffer());
|
||||
|
||||
if (UNLIKELY(data->m_needsToUseBigPropertyFilterOnInterpreter)) {
|
||||
if (data->m_bigFilter) {
|
||||
GC_FREE(data->m_bigFilter.value());
|
||||
}
|
||||
}
|
||||
|
||||
if (isAsyncOrGenerator) {
|
||||
GC_FREE(data);
|
||||
} else {
|
||||
// reset creation area prevent leak from stack
|
||||
memset(data, 0, sizeof(CreateObjectPrepare::CreateObjectData));
|
||||
memset(reinterpret_cast<void*>(data), 0, sizeof(CreateObjectPrepare::CreateObjectData));
|
||||
}
|
||||
} else {
|
||||
registerFile[code->m_registerIndex] = new Object(state);
|
||||
|
|
@ -3084,22 +3131,36 @@ static Value createObjectPropertyFunctionName(ExecutionState& state, const Value
|
|||
NEVER_INLINE void InterpreterSlowPath::createObjectPrepareOperation(ExecutionState& state, CreateObjectPrepare* code, ByteCodeBlock* byteCodeBlock, Value* registerFile)
|
||||
{
|
||||
if (code->m_stage == CreateObjectPrepare::Init) {
|
||||
void* ptr;
|
||||
uint8_t* ptr;
|
||||
if (byteCodeBlock->codeBlock()->isAsyncOrGenerator()) {
|
||||
// async or generator uses ValueVector alloctor for allocating registerFile
|
||||
// ValueVector allocator cannot track CreateObjectData if there is CreateObjectData on registerFile
|
||||
ptr = GC_MALLOC(sizeof(CreateObjectPrepare::CreateObjectData));
|
||||
ptr = reinterpret_cast<uint8_t*>(GC_MALLOC(sizeof(CreateObjectPrepare::CreateObjectData)));
|
||||
registerFile[code->m_dataRegisterIndex] = Value(Value::FromPayload, reinterpret_cast<intptr_t>(ptr));
|
||||
} else {
|
||||
ptr = ®isterFile[code->m_dataRegisterIndex];
|
||||
ptr = reinterpret_cast<uint8_t*>(®isterFile[code->m_dataRegisterIndex]);
|
||||
}
|
||||
CreateObjectPrepare::CreateObjectData* data = new (ptr) CreateObjectPrepare::CreateObjectData(
|
||||
code->m_allPrecomputed, code->m_cachedObjectStructure.hasValue(), code->m_allPrecomputed,
|
||||
code->m_needsToUsePropertyFilterOnInterpreter, code->m_needsToUseBigPropertyFilterOnInterpreter,
|
||||
code->m_propertyReserveSize, new Object(state), code);
|
||||
registerFile[code->m_objectIndex] = data->m_target;
|
||||
if (data->m_wasStructureComputed) {
|
||||
data->m_target->m_structure = code->m_cachedObjectStructure.value();
|
||||
}
|
||||
if (UNLIKELY(data->m_needsToUsePropertyFilterOnInterpreter)) {
|
||||
if (byteCodeBlock->codeBlock()->isAsyncOrGenerator()) {
|
||||
data->m_filter = new (PointerFreeGC) CreateObjectPrepare::CreateObjectPropertyFilter();
|
||||
} else {
|
||||
size_t diff = sizeof(CreateObjectPrepare::CreateObjectData);
|
||||
if (diff % sizeof(Value)) {
|
||||
diff += (sizeof(Value) - (diff % sizeof(Value)));
|
||||
}
|
||||
data->m_filter = new (reinterpret_cast<void*>(ptr + diff)) CreateObjectPrepare::CreateObjectPropertyFilter;
|
||||
}
|
||||
} else if (UNLIKELY(data->m_needsToUseBigPropertyFilterOnInterpreter)) {
|
||||
data->m_bigFilter = new (PointerFreeGC) CreateObjectPrepare::CreateObjectBigPropertyFilter();
|
||||
}
|
||||
} else {
|
||||
ASSERT(code->m_stage == CreateObjectPrepare::FillKeyValue || code->m_stage == CreateObjectPrepare::DefineGetterSetter);
|
||||
CreateObjectPrepare::CreateObjectData* data;
|
||||
|
|
@ -3136,15 +3197,33 @@ NEVER_INLINE void InterpreterSlowPath::createObjectPrepareOperation(ExecutionSta
|
|||
return;
|
||||
}
|
||||
|
||||
bool needsPropertySearch = true;
|
||||
size_t lastPropertyCount = data->m_properties.size();
|
||||
size_t targetIndex = lastPropertyCount;
|
||||
bool updateProperty = false;
|
||||
for (size_t i = 0; i < lastPropertyCount; i++) {
|
||||
if (data->m_properties[i].m_propertyName == propertyName) {
|
||||
targetIndex = i;
|
||||
updateProperty = true;
|
||||
data->m_canStoreStructureOnCode = false;
|
||||
break;
|
||||
|
||||
if (UNLIKELY(data->m_needsToUsePropertyFilterOnInterpreter)) {
|
||||
auto newPropertyNameHash = propertyName.hashValue();
|
||||
if (!data->m_filter->mayContain(newPropertyNameHash)) {
|
||||
data->m_filter->add(newPropertyNameHash);
|
||||
needsPropertySearch = false;
|
||||
}
|
||||
} else if (UNLIKELY(data->m_needsToUseBigPropertyFilterOnInterpreter)) {
|
||||
auto newPropertyNameHash = propertyName.hashValue() * 13;
|
||||
if (!data->m_bigFilter->mayContain(newPropertyNameHash)) {
|
||||
data->m_bigFilter->add(newPropertyNameHash);
|
||||
needsPropertySearch = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (LIKELY(needsPropertySearch)) {
|
||||
for (size_t i = 0; i < lastPropertyCount; i++) {
|
||||
if (data->m_properties[i].m_propertyName == propertyName) {
|
||||
targetIndex = i;
|
||||
updateProperty = true;
|
||||
data->m_canStoreStructureOnCode = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -3840,6 +3919,9 @@ NEVER_INLINE void InterpreterSlowPath::replaceBlockLexicalEnvironmentOperation(E
|
|||
|
||||
NEVER_INLINE Value InterpreterSlowPath::blockOperation(ExecutionState*& state, BlockOperation* code, size_t& programCounter, ByteCodeBlock* byteCodeBlock, Value* registerFile)
|
||||
{
|
||||
// Push the null sentinel BEFORE sharing the vector with newState.
|
||||
// ensureControlFlowRecordVector() creates the vector if it doesn't exist yet,
|
||||
// so the subsequent setControlFlowRecordVector() always receives a non-null pointer.
|
||||
state->rareData()->ensureControlFlowRecordVector()->push_back(nullptr);
|
||||
size_t newPc = programCounter + sizeof(BlockOperation);
|
||||
uint8_t* codeBuffer = byteCodeBlock->m_code.data();
|
||||
|
|
@ -3901,7 +3983,13 @@ NEVER_INLINE Value InterpreterSlowPath::blockOperation(ExecutionState*& state, B
|
|||
newState->rareData()->setControlFlowRecordVector(state->rareData()->controlFlowRecordVector());
|
||||
}
|
||||
|
||||
Interpreter::interpret(newState, byteCodeBlock, newPc, registerFile);
|
||||
// Track the byte offset of the first instruction in this block so we can
|
||||
// detect when a NeedsJump destination falls within our own block range.
|
||||
size_t blockStartOffset = newPc - (size_t)codeBuffer;
|
||||
size_t innerPc = newPc;
|
||||
|
||||
interpretBlock:
|
||||
Interpreter::interpret(newState, byteCodeBlock, innerPc, registerFile);
|
||||
|
||||
if (newState->inExecutionStopState() || (inPauserResumeProcess && newState->parent()->inExecutionStopState())) {
|
||||
return Value();
|
||||
|
|
@ -3924,6 +4012,15 @@ NEVER_INLINE Value InterpreterSlowPath::blockOperation(ExecutionState*& state, B
|
|||
if (record->reason() == ControlFlowRecord::NeedsJump) {
|
||||
size_t pos = record->wordValue();
|
||||
record->m_count--;
|
||||
// When the destination falls inside this block's range, handle it
|
||||
// locally by re-running the inner interpreter from that position.
|
||||
// This must be checked before the normal propagation/final-hop logic
|
||||
// because the destination may be within this block regardless of count.
|
||||
if (!inPauserResumeProcess && pos >= blockStartOffset && pos < code->m_blockEndPosition) {
|
||||
innerPc = jumpTo(codeBuffer, pos);
|
||||
state->rareData()->controlFlowRecordVector()->push_back(nullptr);
|
||||
goto interpretBlock;
|
||||
}
|
||||
if (record->count() && (record->outerLimitCount() < record->count())) {
|
||||
state->rareData()->controlFlowRecordVector()->back() = record;
|
||||
return Value();
|
||||
|
|
@ -4270,9 +4367,14 @@ NEVER_INLINE void InterpreterSlowPath::spreadFunctionArguments(ExecutionState& s
|
|||
Value arg = argv[i];
|
||||
if (arg.isObject() && arg.asObject()->isSpreadArray()) {
|
||||
ArrayObject* spreadArray = arg.asObject()->asArrayObject();
|
||||
ASSERT(spreadArray->isFastModeArray());
|
||||
for (size_t i = 0; i < spreadArray->arrayLength(state); i++) {
|
||||
argVector.push_back(spreadArray->m_fastModeData[i]);
|
||||
if (spreadArray->isFastModeArray()) {
|
||||
for (size_t i = 0; i < spreadArray->arrayLength(state); i++) {
|
||||
argVector.push_back(spreadArray->m_fastModeData[i]);
|
||||
}
|
||||
} else {
|
||||
for (size_t i = 0; i < spreadArray->arrayLength(state); i++) {
|
||||
argVector.push_back(spreadArray->getOwnProperty(state, ObjectPropertyName(state, i)).value(state, spreadArray));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
argVector.push_back(arg);
|
||||
|
|
@ -4553,7 +4655,34 @@ NEVER_INLINE void InterpreterSlowPath::arrayDefineOwnPropertyBySpreadElementOper
|
|||
|
||||
size_t newLength = baseIndex + elementLength;
|
||||
arr->setArrayLength(state, newLength);
|
||||
ASSERT(arr->isFastModeArray());
|
||||
|
||||
// Check if the array is still in fast mode after setArrayLength
|
||||
// setArrayLength can convert the array to non-fast mode when length exceeds thresholds
|
||||
if (UNLIKELY(!arr->isFastModeArray())) {
|
||||
// Array was converted to non-fast mode, use slow path
|
||||
size_t elementIndex = 0;
|
||||
for (size_t i = 0; i < code->m_count; i++) {
|
||||
if (LIKELY(code->m_loadRegisterIndexs[i] != REGISTER_LIMIT)) {
|
||||
Value element = registerFile[code->m_loadRegisterIndexs[i]];
|
||||
if (element.isObject() && element.asObject()->isSpreadArray()) {
|
||||
ArrayObject* spreadArray = element.asObject()->asArrayObject();
|
||||
ASSERT(spreadArray->isFastModeArray());
|
||||
Value spreadElement;
|
||||
for (size_t spreadIndex = 0; spreadIndex < spreadArray->arrayLength(state); spreadIndex++) {
|
||||
spreadElement = spreadArray->m_fastModeData[spreadIndex];
|
||||
arr->defineOwnProperty(state, ObjectPropertyName(state, baseIndex + elementIndex), ObjectPropertyDescriptor(spreadElement, ObjectPropertyDescriptor::AllPresent));
|
||||
elementIndex++;
|
||||
}
|
||||
} else {
|
||||
arr->defineOwnProperty(state, ObjectPropertyName(state, baseIndex + elementIndex), ObjectPropertyDescriptor(element, ObjectPropertyDescriptor::AllPresent));
|
||||
elementIndex++;
|
||||
}
|
||||
} else {
|
||||
elementIndex++;
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
size_t elementIndex = 0;
|
||||
for (size_t i = 0; i < code->m_count; i++) {
|
||||
|
|
@ -4592,10 +4721,9 @@ NEVER_INLINE void InterpreterSlowPath::arrayDefineOwnPropertyBySpreadElementOper
|
|||
Value element = registerFile[loadRegisterIndexs[i]];
|
||||
if (element.isObject() && element.asObject()->isSpreadArray()) {
|
||||
ArrayObject* spreadArray = element.asObject()->asArrayObject();
|
||||
ASSERT(spreadArray->isFastModeArray());
|
||||
Value spreadElement;
|
||||
for (size_t spreadIndex = 0; spreadIndex < spreadArray->arrayLength(state); spreadIndex++) {
|
||||
spreadElement = spreadArray->m_fastModeData[spreadIndex];
|
||||
spreadElement = spreadArray->getOwnProperty(state, ObjectPropertyName(state, spreadIndex)).value(state, spreadArray);
|
||||
arr->defineOwnProperty(state, ObjectPropertyName(state, baseIndex + elementIndex), ObjectPropertyDescriptor(spreadElement, ObjectPropertyDescriptor::AllPresent));
|
||||
elementIndex++;
|
||||
}
|
||||
|
|
@ -4765,6 +4893,23 @@ NEVER_INLINE void InterpreterSlowPath::initializeDisposable(ExecutionState& stat
|
|||
ADD_PROGRAM_COUNTER(InitializeDisposable);
|
||||
}
|
||||
|
||||
// A class definition is always strict mode code. This rarely-used opcode forces
|
||||
// the running execution state into strict mode while a class's heritage expression
|
||||
// and computed property names are evaluated, then restores the previous strict flag.
|
||||
// Kept out-of-line (and away from the hot opcodes) on purpose to keep it out of the
|
||||
// interpreter's hot path.
|
||||
NEVER_INLINE void InterpreterSlowPath::setExecutionStateInStrictModeOperation(ExecutionState& state, Value* registerFile, size_t& programCounter)
|
||||
{
|
||||
SetExecutionStateInStrictMode* code = (SetExecutionStateInStrictMode*)programCounter;
|
||||
if (code->m_enter) {
|
||||
registerFile[code->m_savedStrictRegisterIndex] = Value(state.inStrictMode());
|
||||
state.setInStrictMode(true);
|
||||
} else {
|
||||
state.setInStrictMode(registerFile[code->m_savedStrictRegisterIndex].asBoolean());
|
||||
}
|
||||
ADD_PROGRAM_COUNTER(SetExecutionStateInStrictMode);
|
||||
}
|
||||
|
||||
static void finalizeDisposableAwaitOperation(ExecutionState& state, ByteCodeBlock* byteCodeBlock, DisposableResourceRecord* data,
|
||||
const Value& awaitValue, size_t programCounter, FinalizeDisposable* code, size_t nextStage)
|
||||
{
|
||||
|
|
@ -4955,19 +5100,28 @@ NEVER_INLINE void InterpreterSlowPath::iteratorOperation(ExecutionState& state,
|
|||
bool exceptionWasThrown = state.rareData()->controlFlowRecordVector() && state.rareData()->controlFlowRecordVector()->back() && state.rareData()->controlFlowRecordVector()->back()->reason() == ControlFlowRecord::NeedsThrow;
|
||||
IteratorRecord* iteratorRecord = registerFile[code->m_iteratorCloseData.m_iterRegisterIndex].asPointerValue()->asIteratorRecord();
|
||||
Object* iterator = iteratorRecord->m_iterator;
|
||||
Value returnFunction = iterator->get(state, ObjectPropertyName(state.context()->staticStrings().stringReturn)).value(state, iterator);
|
||||
if (returnFunction.isUndefined()) {
|
||||
Value returnFunction;
|
||||
Value innerResult;
|
||||
bool innerResultHasException = false;
|
||||
try {
|
||||
returnFunction = iterator->get(state, ObjectPropertyName(state.context()->staticStrings().stringReturn)).value(state, iterator);
|
||||
} catch (const Value& e) {
|
||||
innerResult = e;
|
||||
innerResultHasException = true;
|
||||
}
|
||||
|
||||
if (!innerResultHasException && returnFunction.isUndefined()) {
|
||||
ADD_PROGRAM_COUNTER(IteratorOperation);
|
||||
return;
|
||||
}
|
||||
|
||||
Value innerResult;
|
||||
bool innerResultHasException = false;
|
||||
try {
|
||||
innerResult = Object::call(state, returnFunction, iterator, 0, nullptr);
|
||||
} catch (const Value& e) {
|
||||
innerResult = e;
|
||||
innerResultHasException = true;
|
||||
if (!innerResultHasException) {
|
||||
try {
|
||||
innerResult = Object::call(state, returnFunction, iterator, 0, nullptr);
|
||||
} catch (const Value& e) {
|
||||
innerResult = e;
|
||||
innerResultHasException = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (exceptionWasThrown) {
|
||||
|
|
|
|||
|
|
@ -49,6 +49,7 @@
|
|||
#include "runtime/Value.h"
|
||||
#include "runtime/Object.h"
|
||||
#include "runtime/ArrayObject.h"
|
||||
#include "runtime/DateObject.h"
|
||||
#include "runtime/VMInstance.h"
|
||||
#include "Intl.h"
|
||||
#include "IntlLocale.h"
|
||||
|
|
@ -2336,6 +2337,69 @@ static String* removeUnicodeLocaleExtension(ExecutionState& state, String* local
|
|||
return builder.finalize();
|
||||
}
|
||||
|
||||
void Intl::availableTimeZones(const std::function<void(const char* buf, size_t len)>& callback, bool includeNonCanonical)
|
||||
{
|
||||
UErrorCode status = U_ZERO_ERROR;
|
||||
|
||||
LocalResourcePointer<UEnumeration> tzs(ucal_openTimeZoneIDEnumeration(includeNonCanonical ? UCAL_ZONE_TYPE_ANY : UCAL_ZONE_TYPE_CANONICAL_LOCATION, nullptr, nullptr, &status),
|
||||
[](UEnumeration* fmt) { uenum_close(fmt); });
|
||||
|
||||
if (!U_SUCCESS(status)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const char* buffer;
|
||||
int32_t bufferLength = 0;
|
||||
while ((buffer = uenum_next(tzs.get(), &bufferLength, &status)) && U_SUCCESS(status)) {
|
||||
std::string id(buffer, bufferLength);
|
||||
if (!includeNonCanonical && (id == "UTC" || (id.find("Etc/GMT") != std::string::npos))) {
|
||||
continue;
|
||||
}
|
||||
callback(id.data(), id.length());
|
||||
}
|
||||
if (!includeNonCanonical) {
|
||||
callback("UTC", 3);
|
||||
|
||||
for (int i = 1; i <= 12; i++) {
|
||||
std::string s;
|
||||
s += "Etc/GMT+" + std::to_string(i);
|
||||
callback(s.data(), s.length());
|
||||
s = "";
|
||||
s += "Etc/GMT-" + std::to_string(i);
|
||||
callback(s.data(), s.length());
|
||||
}
|
||||
|
||||
callback("Etc/GMT-13", 10);
|
||||
callback("Etc/GMT-14", 10);
|
||||
}
|
||||
}
|
||||
|
||||
UTF16StringDataNonGCStd Intl::canonicalTimeZoneID(String* timezoneId)
|
||||
{
|
||||
if (timezoneId->equals("Etc/GMT0") || timezoneId->equals("Etc/UTC") || timezoneId->equals("GMT") || timezoneId->equals("Etc/GMT")) {
|
||||
return u"Etc/UTC";
|
||||
}
|
||||
auto u16String = timezoneId->toUTF16StringData();
|
||||
UTF16StringDataNonGCStd buffer;
|
||||
buffer.resize(u16String.length());
|
||||
UBool isSystemID = false;
|
||||
UErrorCode status = U_ZERO_ERROR;
|
||||
auto canonicalLength = ucal_getCanonicalTimeZoneID(u16String.data(), u16String.length(), (UChar*)buffer.data(), u16String.length(), &isSystemID, &status);
|
||||
if (status == U_BUFFER_OVERFLOW_ERROR) {
|
||||
buffer.resize(canonicalLength);
|
||||
isSystemID = false;
|
||||
status = U_ZERO_ERROR;
|
||||
ucal_getCanonicalTimeZoneID(u16String.data(), u16String.length(), (UChar*)buffer.data(), canonicalLength, &isSystemID, &status);
|
||||
ASSERT(U_SUCCESS(status));
|
||||
} else if (U_SUCCESS(status)) {
|
||||
buffer.resize(canonicalLength);
|
||||
} else {
|
||||
auto u16 = timezoneId->toUTF16StringData();
|
||||
buffer = UTF16StringDataNonGCStd(u16.data(), u16.size());
|
||||
}
|
||||
return buffer;
|
||||
}
|
||||
|
||||
ValueVector Intl::canonicalizeLocaleList(ExecutionState& state, Value locales)
|
||||
{
|
||||
// http://www.ecma-international.org/ecma-402/1.0/index.html#sec-9.2.1
|
||||
|
|
@ -2848,7 +2912,18 @@ Value Intl::supportedLocales(ExecutionState& state, const Vector<String*, GCUtil
|
|||
return Object::createArrayFromList(state, subset);
|
||||
}
|
||||
|
||||
Value Intl::getOption(ExecutionState& state, Object* options, Value property, Intl::OptionValueType type, Value* values, size_t valuesLength, const Value& fallback)
|
||||
Optional<Object*> Intl::getOptionsObject(ExecutionState& state, const Value& options)
|
||||
{
|
||||
Optional<Object*> resolvedOptions;
|
||||
if (options.isObject()) {
|
||||
resolvedOptions = options.asObject();
|
||||
} else if (!options.isUndefined()) {
|
||||
ErrorObject::throwBuiltinError(state, ErrorCode::TypeError, "Invalid options value");
|
||||
}
|
||||
return resolvedOptions;
|
||||
}
|
||||
|
||||
Value Intl::getOption(ExecutionState& state, Object* options, Value property, Intl::OptionValueType type, Value* values, size_t valuesLength, Optional<Value> fallback)
|
||||
{
|
||||
// http://www.ecma-international.org/ecma-402/1.0/index.html#sec-9.2.9
|
||||
// Let value be the result of calling the [[Get]] internal method of options with argument property.
|
||||
|
|
@ -2880,8 +2955,13 @@ Value Intl::getOption(ExecutionState& state, Object* options, Value property, In
|
|||
// Return value.
|
||||
return value;
|
||||
} else {
|
||||
// Else return fallback.
|
||||
return fallback;
|
||||
// If default is required, throw a RangeError exception.
|
||||
if (!fallback) {
|
||||
ErrorObject::throwBuiltinError(state, ErrorCode::RangeError, "got invalid value");
|
||||
}
|
||||
|
||||
// return fallback.
|
||||
return fallback.value();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -2993,9 +3073,13 @@ std::string Intl::canonicalizeCalendarTag(const std::string& s)
|
|||
std::string ret = s;
|
||||
std::transform(ret.begin(), ret.end(), ret.begin(),
|
||||
[](unsigned char c) { return std::tolower(c); });
|
||||
if (ret == "islamicc") {
|
||||
ret = "islamic-civil";
|
||||
#define DEFINE_FIELD(name, string, icuString, fullName, alias) \
|
||||
if (alias && ret == string) { \
|
||||
ret = fullName; \
|
||||
}
|
||||
CALENDAR_ID_RECORDS(DEFINE_FIELD)
|
||||
#undef DEFINE_FIELD
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
|
@ -3442,8 +3526,8 @@ void Intl::initNumberFormatSkeleton(ExecutionState& state, const Intl::SetNumber
|
|||
}
|
||||
|
||||
if (!formatResult.minimumFractionDigits.isUndefined()) {
|
||||
double mnfd = formatResult.minimumSignificantDigits.asNumber();
|
||||
double mxfd = formatResult.maximumSignificantDigits.asNumber();
|
||||
double mnfd = formatResult.minimumFractionDigits.asNumber();
|
||||
double mxfd = formatResult.maximumFractionDigits.asNumber();
|
||||
|
||||
skeleton += '.';
|
||||
|
||||
|
|
@ -3600,6 +3684,764 @@ void Intl::initNumberFormatSkeleton(ExecutionState& state, const Intl::SetNumber
|
|||
}
|
||||
}
|
||||
|
||||
#define CHECK_ICU_CALENDAR() \
|
||||
if (U_FAILURE(status)) { \
|
||||
ErrorObject::throwBuiltinError(state, ErrorCode::TypeError, "failed to get value from ICU calendar"); \
|
||||
}
|
||||
|
||||
// https://tc39.es/proposal-intl-era-monthcode/#table-eras
|
||||
/*
|
||||
"buddhist" "be" -∞ +∞ epoch
|
||||
"coptic" "am" -∞ +∞ epoch
|
||||
"ethioaa" "aa" -∞ +∞ epoch
|
||||
"ethiopic" "am" 1 +∞ epoch
|
||||
"ethiopic" "aa" -∞ 5500 offset -5499
|
||||
"gregory" "ce" "ad" 1 +∞ epoch
|
||||
"gregory" "bce" "bc" 1 +∞ negative
|
||||
"hebrew" "am" -∞ +∞ epoch
|
||||
"indian" "shaka" -∞ +∞ epoch
|
||||
"islamic-civil" "ah" 1 +∞ epoch
|
||||
"islamic-civil" "bh" 1 +∞ negative
|
||||
"islamic-tbla" "ah" 1 +∞ epoch
|
||||
"islamic-tbla" "bh" 1 +∞ negative
|
||||
"islamic-umalqura" "ah" 1 +∞ epoch
|
||||
"islamic-umalqura" "bh" 1 +∞ negative
|
||||
"japanese" "reiwa" 1 +∞ offset 2019
|
||||
"japanese" "heisei" 1 31 offset 1989
|
||||
"japanese" "showa" 1 64 offset 1926
|
||||
"japanese" "taisho" 1 15 offset 1912
|
||||
"japanese" "meiji" 1 45 offset 1868
|
||||
"japanese" "ce" "ad" 1 1868 epoch
|
||||
"japanese" "bce" "bc" 1 +∞ negative
|
||||
"persian" "ap" -∞ +∞ epoch
|
||||
"roc" "roc" 1 +∞ epoch
|
||||
"roc" "broc" 1 +∞ negative
|
||||
*/
|
||||
|
||||
void Calendar::lookupICUEra(ExecutionState& state, const std::function<bool(size_t idx, const std::string& icuEra)>& fn) const
|
||||
{
|
||||
// non-canonical form
|
||||
if (id() == ID::Gregorian) {
|
||||
if (fn(0, "bce")) {
|
||||
return;
|
||||
}
|
||||
if (fn(1, "ce")) {
|
||||
return;
|
||||
}
|
||||
if (fn(0, "bc")) {
|
||||
return;
|
||||
}
|
||||
if (fn(1, "ad")) {
|
||||
return;
|
||||
}
|
||||
return;
|
||||
} else if (id() == ID::Ethiopian) {
|
||||
// for old-icu(~77)
|
||||
if (fn(0, "aa")) {
|
||||
return;
|
||||
}
|
||||
if (fn(1, "am")) {
|
||||
return;
|
||||
}
|
||||
return;
|
||||
} else if (id() >= ID::Islamic && id() <= ID::IslamicUmmAlQura) {
|
||||
// for old-icu(~77)
|
||||
if (fn(0, "ah")) {
|
||||
return;
|
||||
}
|
||||
if (fn(0, "bh")) {
|
||||
return;
|
||||
}
|
||||
return;
|
||||
} else if (id() == ID::Coptic) {
|
||||
// for old-icu(~77)
|
||||
// 0 is not AM
|
||||
if (fn(1, "am")) {
|
||||
return;
|
||||
}
|
||||
return;
|
||||
} else if (id() == ID::EthiopianAmeteAlem) {
|
||||
// for old-icu(~77)
|
||||
if (fn(0, "aa")) {
|
||||
return;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
std::string s = "root/calendar/";
|
||||
s += toICUString();
|
||||
s += "/eras/abbreviated";
|
||||
UErrorCode status = U_ZERO_ERROR;
|
||||
|
||||
LocalResourcePointer<UResourceBundle> bundle(ures_findResource(s.data(), nullptr, &status), [](UResourceBundle* res) {
|
||||
ures_close(res);
|
||||
});
|
||||
|
||||
// skip before meiji for japanese
|
||||
bool skipEraName = id() == ID::Japanese;
|
||||
int meijiKey = std::numeric_limits<int>::max();
|
||||
|
||||
UResourceBundle* res = nullptr;
|
||||
while (true) {
|
||||
res = ures_getNextResource(bundle.get(), res, &status);
|
||||
if (res == nullptr || U_FAILURE(status)) {
|
||||
break;
|
||||
}
|
||||
|
||||
auto key = ures_getKey(res);
|
||||
|
||||
int32_t length = 0;
|
||||
const UChar* data = ures_getStringByKey(bundle.get(), key, &length, &status);
|
||||
UTF16StringFromExternalMemory u16Str(data, length);
|
||||
|
||||
// for old icu(~77)
|
||||
if (id() == ID::ROC) {
|
||||
if (u16Str.equals("Before R.O.C.")) {
|
||||
u16Str = u"broc";
|
||||
} else if (u16Str.equals("R.O.C.")) {
|
||||
u16Str = u"roc";
|
||||
}
|
||||
}
|
||||
|
||||
const UNormalizer2* normalizer = unorm2_getNFDInstance(&status);
|
||||
if (!normalizer || U_FAILURE(status)) {
|
||||
ErrorObject::throwBuiltinError(state, ErrorCode::TypeError, "normalization fails");
|
||||
}
|
||||
int32_t normalizedStringLength = unorm2_normalize(normalizer, u16Str.bufferAccessData().bufferAs16Bit, u16Str.length(), nullptr, 0, &status);
|
||||
|
||||
if (U_FAILURE(status) && status != U_BUFFER_OVERFLOW_ERROR) {
|
||||
// when normalize fails.
|
||||
ErrorObject::throwBuiltinError(state, ErrorCode::TypeError, "normalization fails");
|
||||
}
|
||||
UTF16StringData ret;
|
||||
ret.resizeWithUninitializedValues(normalizedStringLength);
|
||||
status = U_ZERO_ERROR;
|
||||
unorm2_normalize(normalizer, u16Str.bufferAccessData().bufferAs16Bit, u16Str.length(), (UChar*)ret.data(), normalizedStringLength, &status);
|
||||
|
||||
std::string icuString;
|
||||
for (int32_t i = 0; i < normalizedStringLength; i++) {
|
||||
if (ret[i] < 128) {
|
||||
icuString.push_back(tolower(ret[i]));
|
||||
}
|
||||
if (ret[i] == ' ') {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (id() == ID::Indian) {
|
||||
if (icuString == "saka") {
|
||||
icuString = "shaka";
|
||||
}
|
||||
}
|
||||
|
||||
int i;
|
||||
try {
|
||||
i = std::stoi(key);
|
||||
} catch (...) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (skipEraName && i <= meijiKey) {
|
||||
if (icuString == "meiji") {
|
||||
meijiKey = i;
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (fn(i, icuString)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool Calendar::isEraRelated() const
|
||||
{
|
||||
switch (m_id) {
|
||||
case ID::Buddhist:
|
||||
case ID::Coptic:
|
||||
case ID::Ethiopian:
|
||||
case ID::EthiopianAmeteAlem:
|
||||
case ID::EthiopianAmeteAlemAlias:
|
||||
case ID::Gregorian:
|
||||
case ID::Hebrew:
|
||||
case ID::Indian:
|
||||
case ID::Islamic:
|
||||
case ID::IslamicCivil:
|
||||
case ID::IslamicCivilAlias:
|
||||
case ID::IslamicTabular:
|
||||
case ID::IslamicUmmAlQura:
|
||||
case ID::Japanese:
|
||||
case ID::Persian:
|
||||
case ID::ROC:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool Calendar::shouldUseICUExtendedYear() const
|
||||
{
|
||||
if (id() == Calendar::ID::ISO8601) {
|
||||
return true;
|
||||
}
|
||||
if (sameAsGregoryExceptHandlingEraAndYear()) {
|
||||
return false;
|
||||
}
|
||||
if (id() == Calendar::ID::EthiopianAmeteAlem) {
|
||||
return false;
|
||||
}
|
||||
if (id() == Calendar::ID::Dangi || id() == Calendar::ID::Chinese) {
|
||||
return true;
|
||||
}
|
||||
if (isEraRelated()) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Calendar::hasLeapMonths() const
|
||||
{
|
||||
switch (m_id) {
|
||||
case ID::Chinese:
|
||||
case ID::Dangi:
|
||||
case ID::Hebrew:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool Calendar::hasEpagomenalMonths() const
|
||||
{
|
||||
switch (m_id) {
|
||||
case ID::Coptic:
|
||||
case ID::Ethiopian:
|
||||
case ID::EthiopianAmeteAlem:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool Calendar::sameAsGregoryExceptHandlingEraAndYear() const
|
||||
{
|
||||
switch (m_id) {
|
||||
case ID::ROC:
|
||||
case ID::Buddhist:
|
||||
case ID::Japanese:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// https://tc39.es/proposal-intl-era-monthcode/#table-epoch-years
|
||||
int32_t Calendar::epochISOYear() const
|
||||
{
|
||||
switch (m_id) {
|
||||
case ID::Buddhist:
|
||||
return -543;
|
||||
case ID::Coptic:
|
||||
return 283;
|
||||
case ID::EthiopianAmeteAlem:
|
||||
return -5493;
|
||||
case ID::Ethiopian:
|
||||
return 7;
|
||||
case ID::Hebrew:
|
||||
return -3761;
|
||||
case ID::Indian:
|
||||
return 78;
|
||||
case ID::IslamicCivil:
|
||||
case ID::IslamicTabular:
|
||||
case ID::IslamicUmmAlQura:
|
||||
return 621;
|
||||
case ID::Persian:
|
||||
return 621;
|
||||
case ID::ROC:
|
||||
return 1911;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
int Calendar::diffYearDueToICU4CAndSpecDiffer() const
|
||||
{
|
||||
#if defined(ENABLE_RUNTIME_ICU_BINDER) || (defined(U_ICU_VERSION_MAJOR_NUM) && U_ICU_VERSION_MAJOR_NUM < 74)
|
||||
UVersionInfo versionArray;
|
||||
u_getVersion(versionArray);
|
||||
// https://unicode-org.atlassian.net/browse/ICU-23167
|
||||
if (versionArray[0] < 78) {
|
||||
switch (m_id) {
|
||||
case ID::Chinese:
|
||||
return 2637;
|
||||
case ID::Dangi:
|
||||
return 2333;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
return 0;
|
||||
}
|
||||
|
||||
void Calendar::setYear(ExecutionState& state, UCalendar* icuCalendar, int32_t year)
|
||||
{
|
||||
UErrorCode status = U_ZERO_ERROR;
|
||||
if (sameAsGregoryExceptHandlingEraAndYear()) {
|
||||
if (id() == ID::Buddhist) {
|
||||
year += -543;
|
||||
} else if (id() == ID::ROC) {
|
||||
year += 1911;
|
||||
}
|
||||
ucal_set(icuCalendar, UCAL_YEAR, year);
|
||||
} else {
|
||||
if (shouldUseICUExtendedYear()) {
|
||||
ucal_set(icuCalendar, UCAL_EXTENDED_YEAR, year + diffYearDueToICU4CAndSpecDiffer());
|
||||
} else {
|
||||
ucal_set(icuCalendar, UCAL_YEAR, year + diffYearDueToICU4CAndSpecDiffer());
|
||||
}
|
||||
}
|
||||
CHECK_ICU_CALENDAR();
|
||||
}
|
||||
|
||||
void Calendar::setYear(ExecutionState& state, UCalendar* icuCalendar, const String* era, int32_t year)
|
||||
{
|
||||
ASSERT(isEraRelated());
|
||||
UCalendar* targetCalendar = icuCalendar;
|
||||
LocalResourcePointer<UCalendar> newCal(nullptr, [](UCalendar* r) {
|
||||
ucal_close(r);
|
||||
});
|
||||
|
||||
UErrorCode status = U_ZERO_ERROR;
|
||||
if (sameAsGregoryExceptHandlingEraAndYear()) {
|
||||
if (id() == Calendar::ID::Japanese && (era->equals("ad") || era->equals("bc") || era->equals("ce") || era->equals("bce"))) {
|
||||
Calendar(ID::Gregorian).setYear(state, icuCalendar, era, year);
|
||||
return;
|
||||
} else if (id() == Calendar::ID::Buddhist && era->equals("be")) {
|
||||
Calendar(ID::ISO8601).setYear(state, icuCalendar, year + epochISOYear());
|
||||
return;
|
||||
}
|
||||
newCal.reset(createICUCalendar(state, "en@calendar=" + toICUString()));
|
||||
icuCalendar = newCal.get();
|
||||
}
|
||||
|
||||
Optional<int32_t> eraIdx;
|
||||
auto fieldEraValue = era->toNonGCUTF8StringData();
|
||||
lookupICUEra(state, [&](size_t idx, const std::string& s) -> bool {
|
||||
if (s == fieldEraValue) {
|
||||
eraIdx = idx;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
if (!eraIdx) {
|
||||
ErrorObject::throwBuiltinError(state, ErrorCode::RangeError, "Invalid era value");
|
||||
}
|
||||
|
||||
if (id() >= ID::Islamic && id() <= ID::IslamicUmmAlQura) {
|
||||
// for old-icu(~77)
|
||||
ucal_set(icuCalendar, UCAL_ERA, 0);
|
||||
if (era->equals("ah")) {
|
||||
ucal_set(icuCalendar, UCAL_YEAR, year);
|
||||
} else {
|
||||
ucal_set(icuCalendar, UCAL_YEAR, (-year + 1));
|
||||
}
|
||||
} else {
|
||||
ucal_set(icuCalendar, UCAL_ERA, eraIdx.value());
|
||||
ucal_set(icuCalendar, UCAL_YEAR, year + diffYearDueToICU4CAndSpecDiffer());
|
||||
}
|
||||
|
||||
if (sameAsGregoryExceptHandlingEraAndYear()) {
|
||||
auto isoDate = Calendar::computeISODate(state, icuCalendar);
|
||||
ucal_set(targetCalendar, UCAL_YEAR, isoDate.year());
|
||||
}
|
||||
}
|
||||
|
||||
int32_t Calendar::year(ExecutionState& state, UCalendar* icuCalendar)
|
||||
{
|
||||
UErrorCode status = U_ZERO_ERROR;
|
||||
int32_t y;
|
||||
if (sameAsGregoryExceptHandlingEraAndYear()) {
|
||||
y = Calendar::computeISODate(state, icuCalendar).year() - diffYearDueToICU4CAndSpecDiffer();
|
||||
if (id() == ID::Buddhist) {
|
||||
y -= -543;
|
||||
} else if (id() == ID::ROC) {
|
||||
y -= 1911;
|
||||
}
|
||||
} else if (id() == ID::EthiopianAmeteAlem) {
|
||||
// exceptional cases
|
||||
y = ucal_get(icuCalendar, UCAL_YEAR, &status) - diffYearDueToICU4CAndSpecDiffer();
|
||||
} else {
|
||||
if (shouldUseICUExtendedYear()) {
|
||||
y = ucal_get(icuCalendar, UCAL_EXTENDED_YEAR, &status) - diffYearDueToICU4CAndSpecDiffer();
|
||||
} else {
|
||||
y = ucal_get(icuCalendar, UCAL_YEAR, &status) - diffYearDueToICU4CAndSpecDiffer();
|
||||
}
|
||||
}
|
||||
|
||||
CHECK_ICU_CALENDAR();
|
||||
|
||||
return y;
|
||||
}
|
||||
|
||||
int32_t Calendar::eraYear(ExecutionState& state, UCalendar* icuCalendar)
|
||||
{
|
||||
ASSERT(isEraRelated());
|
||||
int32_t y;
|
||||
if (sameAsGregoryExceptHandlingEraAndYear()) {
|
||||
UErrorCode status = U_ZERO_ERROR;
|
||||
auto epochTime = ucal_getMillis(icuCalendar, &status);
|
||||
CHECK_ICU_CALENDAR()
|
||||
if (id() == Calendar::ID::Japanese) {
|
||||
auto meijiStart = ISO8601::ExactTime::fromPlainDate(ISO8601::PlainDate(1868, 10, 23)).epochMilliseconds();
|
||||
if (epochTime < meijiStart) {
|
||||
auto isoYear = Calendar::computeISODate(state, icuCalendar).year();
|
||||
if (isoYear <= 0) {
|
||||
return -isoYear + 1;
|
||||
} else {
|
||||
return isoYear;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
LocalResourcePointer<UCalendar> newCal(createICUCalendar(state, "en@calendar=" + toICUString()), [](UCalendar* r) {
|
||||
ucal_close(r);
|
||||
});
|
||||
ucal_setMillis(newCal.get(), epochTime, &status);
|
||||
y = ucal_get(newCal.get(), UCAL_YEAR, &status);
|
||||
CHECK_ICU_CALENDAR();
|
||||
} else if (id() >= ID::Islamic && id() <= ID::IslamicUmmAlQura) {
|
||||
// for old-icu(~77)
|
||||
UErrorCode status = U_ZERO_ERROR;
|
||||
y = ucal_get(icuCalendar, UCAL_YEAR, &status);
|
||||
CHECK_ICU_CALENDAR();
|
||||
if (y < 1) {
|
||||
y = -(y - 1);
|
||||
}
|
||||
} else if (id() == ID::Coptic) {
|
||||
UErrorCode status = U_ZERO_ERROR;
|
||||
y = ucal_get(icuCalendar, UCAL_YEAR, &status);
|
||||
CHECK_ICU_CALENDAR();
|
||||
auto isoYear = Calendar::computeISODate(state, icuCalendar).year();
|
||||
if (isoYear <= epochISOYear()) {
|
||||
y = -(y - 1);
|
||||
}
|
||||
} else {
|
||||
UErrorCode status = U_ZERO_ERROR;
|
||||
y = ucal_get(icuCalendar, UCAL_YEAR, &status);
|
||||
CHECK_ICU_CALENDAR();
|
||||
}
|
||||
return y;
|
||||
}
|
||||
|
||||
String* Calendar::era(ExecutionState& state, UCalendar* icuCalendar)
|
||||
{
|
||||
ASSERT(isEraRelated());
|
||||
UErrorCode status = U_ZERO_ERROR;
|
||||
|
||||
LocalResourcePointer<UCalendar> newCal(nullptr, [](UCalendar* r) {
|
||||
ucal_close(r);
|
||||
});
|
||||
|
||||
if (sameAsGregoryExceptHandlingEraAndYear()) {
|
||||
if (id() == Calendar::ID::Japanese) {
|
||||
auto meijiStart = ISO8601::ExactTime::fromPlainDate(ISO8601::PlainDate(1868, 10, 23)).epochMilliseconds();
|
||||
auto epochTime = ucal_getMillis(icuCalendar, &status);
|
||||
if (epochTime < meijiStart) {
|
||||
auto isoYear = Calendar::computeISODate(state, icuCalendar).year();
|
||||
if (isoYear <= 0) {
|
||||
return new ASCIIStringFromExternalMemory("bce");
|
||||
} else {
|
||||
return new ASCIIStringFromExternalMemory("ce");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
newCal.reset(createICUCalendar(state, "en@calendar=" + toICUString()));
|
||||
auto epochTime = ucal_getMillis(icuCalendar, &status);
|
||||
CHECK_ICU_CALENDAR()
|
||||
ucal_setMillis(newCal.get(), epochTime, &status);
|
||||
CHECK_ICU_CALENDAR();
|
||||
icuCalendar = newCal.get();
|
||||
} else if (id() >= ID::Islamic && id() <= ID::IslamicUmmAlQura) {
|
||||
// for old-icu(~77)
|
||||
auto y = ucal_get(icuCalendar, UCAL_YEAR, &status);
|
||||
CHECK_ICU_CALENDAR();
|
||||
if (y < 1) {
|
||||
return new ASCIIStringFromExternalMemory("bh");
|
||||
} else {
|
||||
return new ASCIIStringFromExternalMemory("ah");
|
||||
}
|
||||
} else if (id() == ID::Coptic) {
|
||||
// for old-icu(~77)
|
||||
return new ASCIIStringFromExternalMemory("am");
|
||||
} else if (id() == ID::EthiopianAmeteAlem) {
|
||||
// for old-icu(~77)
|
||||
return new ASCIIStringFromExternalMemory("aa");
|
||||
}
|
||||
|
||||
auto ucalEra = ucal_get(icuCalendar, UCAL_ERA, &status);
|
||||
CHECK_ICU_CALENDAR()
|
||||
|
||||
Optional<std::string> result;
|
||||
lookupICUEra(state, [&](size_t idx, const std::string& s) -> bool {
|
||||
if (size_t(ucalEra) == idx) {
|
||||
result = s;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
if (!result) {
|
||||
ErrorObject::throwBuiltinError(state, ErrorCode::RangeError, "Invalid date value");
|
||||
}
|
||||
|
||||
return String::fromASCII(result.value().data(), result.value().length());
|
||||
}
|
||||
|
||||
void Calendar::setOrdinalMonth(ExecutionState& state, UCalendar* icuCalendar, int32_t month)
|
||||
{
|
||||
if (id() == Calendar::ID::Chinese || id() == Calendar::ID::Dangi) {
|
||||
// there is some bugs compute UCAL_ORDINAL_MONTH in icu4c :(
|
||||
UErrorCode status = U_ZERO_ERROR;
|
||||
LocalResourcePointer<UCalendar> calHolder(ucal_clone(icuCalendar, &status), [](UCalendar* r) {
|
||||
ucal_close(r);
|
||||
});
|
||||
CHECK_ICU_CALENDAR();
|
||||
int32_t monthCount = 0;
|
||||
MonthCode lastMatchedMonth;
|
||||
for (unsigned i = 1; i <= 13; i++) {
|
||||
MonthCode mc;
|
||||
mc.monthNumber = i;
|
||||
mc.isLeapMonth = false;
|
||||
|
||||
setMonth(calHolder.get(), mc);
|
||||
if (mc == monthCode(state, calHolder.get())) {
|
||||
monthCount++;
|
||||
lastMatchedMonth = mc;
|
||||
if (monthCount == month) {
|
||||
setMonth(icuCalendar, mc);
|
||||
return;
|
||||
}
|
||||
}
|
||||
mc.isLeapMonth = true;
|
||||
setMonth(calHolder.get(), mc);
|
||||
if (mc == monthCode(state, calHolder.get())) {
|
||||
monthCount++;
|
||||
lastMatchedMonth = mc;
|
||||
if (monthCount == month) {
|
||||
setMonth(icuCalendar, mc);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
setMonth(icuCalendar, lastMatchedMonth);
|
||||
return;
|
||||
}
|
||||
ucal_set(icuCalendar, UCAL_ORDINAL_MONTH, month - 1);
|
||||
}
|
||||
|
||||
void Calendar::setMonth(UCalendar* icuCalendar, MonthCode mc)
|
||||
{
|
||||
if (id() == ID::Hebrew) {
|
||||
if (mc.isLeapMonth) {
|
||||
if (mc.monthNumber == 5) {
|
||||
ucal_set(icuCalendar, UCAL_MONTH, 5);
|
||||
} else {
|
||||
// set error value
|
||||
ucal_set(icuCalendar, UCAL_MONTH, 13);
|
||||
}
|
||||
} else if (mc.monthNumber <= 5) {
|
||||
ucal_set(icuCalendar, UCAL_MONTH, mc.monthNumber - 1);
|
||||
} else {
|
||||
ucal_set(icuCalendar, UCAL_MONTH, mc.monthNumber);
|
||||
}
|
||||
} else {
|
||||
ucal_set(icuCalendar, UCAL_MONTH, mc.monthNumber - 1);
|
||||
if (hasLeapMonths()) {
|
||||
ucal_set(icuCalendar, UCAL_IS_LEAP_MONTH, mc.isLeapMonth);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int32_t Calendar::ordinalMonth(ExecutionState& state, UCalendar* icuCalendar)
|
||||
{
|
||||
if (id() == Calendar::ID::Chinese || id() == Calendar::ID::Dangi) {
|
||||
// there is some bugs compute UCAL_ORDINAL_MONTH in icu4c :(
|
||||
auto target = monthCode(state, icuCalendar);
|
||||
|
||||
UErrorCode status = U_ZERO_ERROR;
|
||||
LocalResourcePointer<UCalendar> calHolder(ucal_clone(icuCalendar, &status), [](UCalendar* r) {
|
||||
ucal_close(r);
|
||||
});
|
||||
CHECK_ICU_CALENDAR();
|
||||
int32_t monthCount = 0;
|
||||
for (unsigned i = 1; i <= 13; i++) {
|
||||
MonthCode mc;
|
||||
mc.monthNumber = i;
|
||||
mc.isLeapMonth = false;
|
||||
|
||||
setMonth(calHolder.get(), mc);
|
||||
if (mc == monthCode(state, calHolder.get())) {
|
||||
monthCount++;
|
||||
if (mc == target) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
mc.isLeapMonth = true;
|
||||
setMonth(calHolder.get(), mc);
|
||||
if (mc == monthCode(state, calHolder.get())) {
|
||||
monthCount++;
|
||||
if (mc == target) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return monthCount;
|
||||
}
|
||||
UErrorCode status = U_ZERO_ERROR;
|
||||
unsigned m = ucal_get(icuCalendar, UCAL_ORDINAL_MONTH, &status);
|
||||
CHECK_ICU_CALENDAR();
|
||||
return m + 1;
|
||||
}
|
||||
|
||||
MonthCode Calendar::monthCode(ExecutionState& state, UCalendar* icuCalendar)
|
||||
{
|
||||
MonthCode mc;
|
||||
if (id() == ID::Hebrew) {
|
||||
UErrorCode status = U_ZERO_ERROR;
|
||||
unsigned m = ucal_get(icuCalendar, UCAL_MONTH, &status);
|
||||
CHECK_ICU_CALENDAR();
|
||||
if (m < 5) {
|
||||
mc.monthNumber = m + 1;
|
||||
mc.isLeapMonth = false;
|
||||
} else if (m == 5) {
|
||||
mc.monthNumber = 5;
|
||||
mc.isLeapMonth = true;
|
||||
} else {
|
||||
mc.monthNumber = m;
|
||||
mc.isLeapMonth = false;
|
||||
}
|
||||
} else {
|
||||
UErrorCode status = U_ZERO_ERROR;
|
||||
unsigned m = ucal_get(icuCalendar, UCAL_MONTH, &status);
|
||||
CHECK_ICU_CALENDAR();
|
||||
unsigned leap = ucal_get(icuCalendar, UCAL_IS_LEAP_MONTH, &status);
|
||||
CHECK_ICU_CALENDAR();
|
||||
mc.monthNumber = m + 1;
|
||||
mc.isLeapMonth = leap;
|
||||
}
|
||||
return mc;
|
||||
}
|
||||
|
||||
bool Calendar::inLeapMonth(ExecutionState& state, UCalendar* icuCalendar)
|
||||
{
|
||||
UErrorCode status = U_ZERO_ERROR;
|
||||
if (id() == ID::Hebrew) {
|
||||
unsigned m = ucal_get(icuCalendar, UCAL_MONTH, &status);
|
||||
CHECK_ICU_CALENDAR();
|
||||
return m == 5;
|
||||
} else {
|
||||
unsigned testLeap = ucal_get(icuCalendar, UCAL_IS_LEAP_MONTH, &status);
|
||||
CHECK_ICU_CALENDAR();
|
||||
return testLeap;
|
||||
}
|
||||
}
|
||||
|
||||
Optional<Calendar> Calendar::fromString(const std::string& id, bool shouldAllowIslamicAndIslamicRGSA)
|
||||
{
|
||||
auto u = id;
|
||||
std::transform(u.begin(), u.end(), u.begin(), tolower);
|
||||
|
||||
Optional<Calendar> ret;
|
||||
|
||||
if (false) {}
|
||||
#define DEFINE_FIELD(name, string, icuString, fullName, alias) \
|
||||
else if (u == string || u == icuString) \
|
||||
{ \
|
||||
ret = Calendar(ID::name); \
|
||||
}
|
||||
CALENDAR_ID_RECORDS(DEFINE_FIELD)
|
||||
#undef DEFINE_FIELD
|
||||
|
||||
if (ret && (ret.value() == ID::Islamic || ret.value() == ID::IslamicRGSA) && !shouldAllowIslamicAndIslamicRGSA) {
|
||||
ret = NullOption;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
Optional<Calendar> Calendar::fromString(String* str, bool shouldAllowIslamicAndIslamicRGSA)
|
||||
{
|
||||
return fromString(str->toNonGCUTF8StringData(), shouldAllowIslamicAndIslamicRGSA);
|
||||
}
|
||||
|
||||
String* Calendar::toString() const
|
||||
{
|
||||
switch (m_id) {
|
||||
#define DEFINE_FIELD(name, string, icuString, fullName, alias) \
|
||||
case ID::name: \
|
||||
return new ASCIIStringFromExternalMemory(fullName);
|
||||
CALENDAR_ID_RECORDS(DEFINE_FIELD)
|
||||
#undef DEFINE_FIELD
|
||||
default:
|
||||
ASSERT_NOT_REACHED();
|
||||
}
|
||||
return new ASCIIStringFromExternalMemory("iso8601");
|
||||
}
|
||||
|
||||
std::string Calendar::toICUString() const
|
||||
{
|
||||
switch (m_id) {
|
||||
#define DEFINE_FIELD(name, string, icuString, fullName, alias) \
|
||||
case ID::name: \
|
||||
return icuString;
|
||||
CALENDAR_ID_RECORDS(DEFINE_FIELD)
|
||||
#undef DEFINE_FIELD
|
||||
default:
|
||||
ASSERT_NOT_REACHED();
|
||||
}
|
||||
return "iso8601";
|
||||
}
|
||||
|
||||
UCalendar* Calendar::createICUCalendar(ExecutionState& state, const std::string& name)
|
||||
{
|
||||
UErrorCode status = U_ZERO_ERROR;
|
||||
auto icuCalendar = ucal_open(u"GMT", 3, name.data(), UCalendarType::UCAL_DEFAULT, &status);
|
||||
if (U_FAILURE(status)) {
|
||||
ErrorObject::throwBuiltinError(state, ErrorCode::TypeError, "failed to initialize ICU calendar");
|
||||
}
|
||||
ucal_setMillis(icuCalendar, 0, &status);
|
||||
// ignore unsupported error here
|
||||
ucal_setGregorianChange(icuCalendar, -8.64E15, &status);
|
||||
|
||||
return icuCalendar;
|
||||
}
|
||||
|
||||
UCalendar* Calendar::createICUCalendar(ExecutionState& state)
|
||||
{
|
||||
std::string calName;
|
||||
if (sameAsGregoryExceptHandlingEraAndYear()) {
|
||||
calName = "en@calendar=gregorian";
|
||||
} else {
|
||||
calName = "en@calendar=" + toICUString();
|
||||
}
|
||||
return createICUCalendar(state, calName);
|
||||
}
|
||||
|
||||
ISO8601::PlainDate Calendar::computeISODate(ExecutionState& state, UCalendar* ucal)
|
||||
{
|
||||
UErrorCode status = U_ZERO_ERROR;
|
||||
auto epochTime = ucal_getMillis(ucal, &status);
|
||||
CHECK_ICU_CALENDAR()
|
||||
DateObject::DateTimeInfo timeInfo;
|
||||
DateObject::computeTimeInfoFromEpoch(epochTime, timeInfo);
|
||||
return ISO8601::PlainDate(timeInfo.year, timeInfo.month + 1, timeInfo.mday);
|
||||
}
|
||||
|
||||
} // namespace Escargot
|
||||
|
||||
#endif
|
||||
|
|
|
|||
123
src/intl/Intl.h
123
src/intl/Intl.h
|
|
@ -21,8 +21,120 @@
|
|||
#ifndef __EscargotIntlObject__
|
||||
#define __EscargotIntlObject__
|
||||
|
||||
#include "util/ISO8601.h"
|
||||
|
||||
namespace Escargot {
|
||||
|
||||
struct MonthCode {
|
||||
unsigned monthNumber = 0;
|
||||
bool isLeapMonth = false;
|
||||
bool operator==(const MonthCode& src) const
|
||||
{
|
||||
return monthNumber == src.monthNumber && isLeapMonth == src.isLeapMonth;
|
||||
}
|
||||
|
||||
bool operator!=(const MonthCode& src) const
|
||||
{
|
||||
return !operator==(src);
|
||||
}
|
||||
};
|
||||
|
||||
// https://github.com/tc39/proposal-intl-era-monthcode
|
||||
class Calendar {
|
||||
public:
|
||||
#define CALENDAR_ID_RECORDS(F) \
|
||||
F(ISO8601, "iso8601", "iso8601", "iso8601", false) \
|
||||
F(Buddhist, "buddhist", "buddhist", "buddhist", false) \
|
||||
F(Chinese, "chinese", "chinese", "chinese", false) \
|
||||
F(Coptic, "coptic", "coptic", "coptic", false) \
|
||||
F(Dangi, "dangi", "dangi", "dangi", false) \
|
||||
F(Ethiopian, "ethiopic", "ethiopic", "ethiopic", false) \
|
||||
F(EthiopianAmeteAlem, "ethioaa", "ethiopic-amete-alem", "ethioaa", false) \
|
||||
F(EthiopianAmeteAlemAlias, "ethiopic-amete-alem", "ethiopic-amete-alem", "ethioaa", true) \
|
||||
F(Gregorian, "gregory", "gregorian", "gregory", false) \
|
||||
F(Hebrew, "hebrew", "hebrew", "hebrew", false) \
|
||||
F(Indian, "indian", "indian", "indian", false) \
|
||||
F(Islamic, "islamic", "islamic", "islamic", false) \
|
||||
F(IslamicCivil, "islamic-civil", "islamic-civil", "islamic-civil", false) \
|
||||
F(IslamicCivilAlias, "islamicc", "islamic-civil", "islamic-civil", true) \
|
||||
F(IslamicRGSA, "islamic-rgsa", "islamic-rgsa", "islamic-rgsa", false) \
|
||||
F(IslamicTabular, "islamic-tbla", "islamic-tbla", "islamic-tbla", false) \
|
||||
F(IslamicUmmAlQura, "islamic-umalqura", "islamic-umalqura", "islamic-umalqura", false) \
|
||||
F(Japanese, "japanese", "japanese", "japanese", false) \
|
||||
F(Persian, "persian", "persian", "persian", false) \
|
||||
F(ROC, "roc", "roc", "roc", false)
|
||||
|
||||
enum class ID : int32_t {
|
||||
#define DEFINE_FIELD(name, string, icuString, fullName, alias) name,
|
||||
CALENDAR_ID_RECORDS(DEFINE_FIELD)
|
||||
#undef DEFINE_FIELD
|
||||
};
|
||||
|
||||
Calendar(ID id = ID::ISO8601)
|
||||
: m_id(id)
|
||||
{
|
||||
}
|
||||
|
||||
bool operator==(const Calendar& c) const
|
||||
{
|
||||
return toICUString() == c.toICUString();
|
||||
}
|
||||
|
||||
bool operator!=(const Calendar& c) const
|
||||
{
|
||||
return !operator==(c);
|
||||
}
|
||||
|
||||
bool isISO8601() const
|
||||
{
|
||||
return m_id == ID::ISO8601;
|
||||
}
|
||||
|
||||
ID id() const
|
||||
{
|
||||
return m_id;
|
||||
}
|
||||
|
||||
bool isEraRelated() const;
|
||||
bool shouldUseICUExtendedYear() const;
|
||||
bool hasLeapMonths() const;
|
||||
bool hasEpagomenalMonths() const;
|
||||
bool sameAsGregoryExceptHandlingEraAndYear() const;
|
||||
|
||||
// https://tc39.es/proposal-intl-era-monthcode/#table-epoch-years
|
||||
int32_t epochISOYear() const;
|
||||
|
||||
// icu4c base year of chinese, dangi, roc are differ with icu4x
|
||||
int diffYearDueToICU4CAndSpecDiffer() const;
|
||||
|
||||
static Optional<Calendar> fromString(const std::string&, bool shouldAllowIslamicAndIslamicRGSA = false);
|
||||
static Optional<Calendar> fromString(String* str, bool shouldAllowIslamicAndIslamicRGSA = false);
|
||||
String* toString() const;
|
||||
std::string toICUString() const;
|
||||
UCalendar* createICUCalendar(ExecutionState& state);
|
||||
void lookupICUEra(ExecutionState& state, const std::function<bool(size_t idx, const std::string& icuEra)>& fn) const;
|
||||
|
||||
void setYear(ExecutionState& state, UCalendar* calendar, int32_t year);
|
||||
void setYear(ExecutionState& state, UCalendar* calendar, const String* era, int32_t year);
|
||||
|
||||
int32_t year(ExecutionState& state, UCalendar* calendar);
|
||||
int32_t eraYear(ExecutionState& state, UCalendar* calendar);
|
||||
String* era(ExecutionState& state, UCalendar* calendar);
|
||||
|
||||
void setOrdinalMonth(ExecutionState& state, UCalendar* calendar, int32_t month);
|
||||
void setMonth(UCalendar* calendar, MonthCode mc);
|
||||
|
||||
int32_t ordinalMonth(ExecutionState& state, UCalendar* calendar);
|
||||
MonthCode monthCode(ExecutionState& state, UCalendar* calendar);
|
||||
bool inLeapMonth(ExecutionState& state, UCalendar* calendar);
|
||||
|
||||
static ISO8601::PlainDate computeISODate(ExecutionState& state, UCalendar* ucal);
|
||||
|
||||
private:
|
||||
UCalendar* createICUCalendar(ExecutionState& state, const std::string& name);
|
||||
ID m_id;
|
||||
};
|
||||
|
||||
class Intl {
|
||||
public:
|
||||
typedef std::vector<std::string> (*LocaleDataImplFunction)(String* locale, size_t keyIndex);
|
||||
|
|
@ -39,14 +151,21 @@ public:
|
|||
size_t extensionIndex;
|
||||
};
|
||||
|
||||
static void availableTimeZones(const std::function<void(const char* buf, size_t len)>& callback, bool includeNonCanonical = false);
|
||||
static UTF16StringDataNonGCStd canonicalTimeZoneID(String* timezoneId);
|
||||
static ValueVector canonicalizeLocaleList(ExecutionState& state, Value locales);
|
||||
static StringMap resolveLocale(ExecutionState& state, const Vector<String*, GCUtil::gc_malloc_allocator<String*>>& availableLocales, const ValueVector& requestedLocales, const StringMap& options, const char* const relevantExtensionKeys[], size_t relevantExtensionKeyCount, LocaleDataImplFunction localeData);
|
||||
static Value supportedLocales(ExecutionState& state, const Vector<String*, GCUtil::gc_malloc_allocator<String*>>& availableLocales, const ValueVector& requestedLocales, Value options);
|
||||
static Optional<Object*> getOptionsObject(ExecutionState& state, const Value& options); // https://tc39.es/proposal-temporal/#sec-getoptionsobject
|
||||
enum OptionValueType {
|
||||
StringValue,
|
||||
BooleanValue,
|
||||
};
|
||||
static Value getOption(ExecutionState& state, Object* options, Value property, OptionValueType type, Value* values, size_t valuesLength, const Value& fallback);
|
||||
static Value getOption(ExecutionState& state, Object* options, Value property, OptionValueType type, Value* values, size_t valuesLength, Optional<Value> fallback);
|
||||
static Value getOption(ExecutionState& state, Object* options, Value property, OptionValueType type, Value* values, size_t valuesLength, Value fallback)
|
||||
{
|
||||
return getOption(state, options, property, type, values, valuesLength, Optional<Value>(fallback));
|
||||
}
|
||||
template <typename T>
|
||||
static T getNumberOption(ExecutionState& state, Optional<Object*> options, String* property, double minimum, double maximum, const T& fallback);
|
||||
static std::string preferredLanguage(const std::string& language);
|
||||
|
|
@ -66,7 +185,7 @@ public:
|
|||
static bool isUnicodeExtensionKey(const std::string& src);
|
||||
static Optional<std::string> languageTagForLocaleID(const char* localeID);
|
||||
|
||||
static std::vector<std::string> calendarsForLocale(String* locale);
|
||||
static std::vector<std::string> calendarsForLocale(String* locale, bool includeAlias);
|
||||
static std::vector<std::string> numberingSystemsForLocale(String* locale);
|
||||
struct CanonicalizedLangunageTag {
|
||||
Optional<String*> canonicalizedTag;
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -28,19 +28,20 @@ namespace Escargot {
|
|||
|
||||
class IntlDateTimeFormatObject : public DerivedObject {
|
||||
public:
|
||||
IntlDateTimeFormatObject(ExecutionState& state, Value locales, Value options);
|
||||
IntlDateTimeFormatObject(ExecutionState& state, Object* proto, Value locales, Value options);
|
||||
IntlDateTimeFormatObject(ExecutionState& state, Value locales, Value options, Optional<String*> toLocaleStringTimeZone = NullOption);
|
||||
IntlDateTimeFormatObject(ExecutionState& state, Object* proto, Value locales, Value options, Optional<String*> toLocaleStringTimeZone = NullOption);
|
||||
|
||||
virtual bool isIntlDateTimeFormatObject() const override
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
UTF16StringDataNonGCStd format(ExecutionState& state, Value x, bool allowZonedDateTime = false);
|
||||
UTF16StringDataNonGCStd format(ExecutionState& state, double x);
|
||||
ArrayObject* formatToParts(ExecutionState& state, double x);
|
||||
UTF16StringDataNonGCStd formatRange(ExecutionState& state, double startDate, double endDate);
|
||||
ArrayObject* formatRangeToParts(ExecutionState& state, double startDate, double endDate);
|
||||
static Value toDateTimeOptions(ExecutionState& state, Value options, Value required, Value defaults);
|
||||
ArrayObject* formatToParts(ExecutionState& state, Value x);
|
||||
UTF16StringDataNonGCStd formatRange(ExecutionState& state, Value startDate, Value endDate);
|
||||
ArrayObject* formatRangeToParts(ExecutionState& state, Value startDate, Value endDate);
|
||||
static std::pair<Value, bool> toDateTimeOptions(ExecutionState& state, Value options, Value required, Value defaults);
|
||||
static std::string readHourCycleFromPattern(const UTF16StringDataNonGCStd& patternString);
|
||||
String* locale() const
|
||||
{
|
||||
|
|
@ -137,12 +138,31 @@ public:
|
|||
return m_timeStyle;
|
||||
}
|
||||
|
||||
UDateFormat* icuDateFormat()
|
||||
{
|
||||
return m_icuDateFormat;
|
||||
}
|
||||
|
||||
bool allOptionsUndefined();
|
||||
|
||||
protected:
|
||||
String* initDateTimeFormatMainHelper(ExecutionState& state, StringMap& opt, const Value& options, const Value& hour12, std::function<void(String* prop, Value* values, size_t valuesSize)>& doTable4, StringBuilder& skeletonBuilder);
|
||||
void initDateTimeFormatOtherHelper(ExecutionState& state, const Value& dataLocale, const Value& dateStyle, const Value& timeStyle, const Value& hourCycle, const Value& hour12, String* hour, const StringMap& opt, std::string& dataLocaleWithExtensions, StringBuilder& skeletonBuilder);
|
||||
std::tuple<double, LocalResourcePointer<UDateFormat>> icuFormatTemporalHelper(ExecutionState& state, Value value, bool allowZonedDateTime);
|
||||
static String* initDateTimeFormatMainHelper(ExecutionState& state, StringMap& opt, Object* options, const Value& hour12, StringBuilder& skeletonBuilder);
|
||||
struct DateTimeFormatOtherHelperResult {
|
||||
Optional<UDateFormat*> icuDateFormat;
|
||||
Value newHourCycle;
|
||||
Optional<String*> timeZoneICU;
|
||||
};
|
||||
static DateTimeFormatOtherHelperResult initDateTimeFormatOtherHelper(ExecutionState& state, Optional<IntlDateTimeFormatObject*> dateObject, const Value& dataLocale, String* timeZone, const Value& dateStyle, const Value& timeStyle, const Value& computedHourCycle, const Value& hourCycle, const Value& hour12, String* hour, const StringMap& opt, StringBuilder& skeletonBuilder, bool ignoreDay = false, bool ignoreYear = false, bool ignoreTimeZone = false);
|
||||
void setDateFromPattern(ExecutionState& state, UTF16StringDataNonGCStd& patternBuffer, bool hasHourOption);
|
||||
void initICUIntervalFormatIfNecessary(ExecutionState& state);
|
||||
std::tuple<double, double, UCalendar*, UDateIntervalFormat*, LocalResourcePointer<UCalendar>, LocalResourcePointer<UDateIntervalFormat>> prepareFormatRangeArguments(ExecutionState& state, Value startDateInput, Value endDateInput);
|
||||
UTF16StringDataNonGCStd format(ExecutionState& state, UDateFormat* dateFormat, double x);
|
||||
|
||||
bool m_wasThereNoFormatOption;
|
||||
|
||||
String* m_locale;
|
||||
String* m_dataLocale;
|
||||
String* m_calendar;
|
||||
String* m_numberingSystem;
|
||||
String* m_timeZone;
|
||||
|
|
@ -155,6 +175,7 @@ protected:
|
|||
EncodedValue m_weekday;
|
||||
EncodedValue m_day;
|
||||
EncodedValue m_dayPeriod;
|
||||
EncodedValue m_dayPeriodInput;
|
||||
EncodedValue m_hour;
|
||||
EncodedValue m_hourCycle;
|
||||
EncodedValue m_minute;
|
||||
|
|
|
|||
|
|
@ -219,18 +219,17 @@ static String* canonicalCodeForDisplayNames(ExecutionState& state, String* type,
|
|||
// a. If code does not match the Unicode Locale Identifier type nonterminal, throw a RangeError exception.
|
||||
// b. Let code be the result of mapping code to lower case as described in 6.1.
|
||||
// c. Return code.
|
||||
std::string s = code->toNonGCUTF8StringData();
|
||||
|
||||
if (code->equals("islamicc")) {
|
||||
code = String::fromASCII("islamic-civil", sizeof("islamic-civil") - 1);
|
||||
} else if (code->equals("ethioaa")) {
|
||||
code = String::fromASCII("ethiopic-amete-alem", sizeof("ethiopic-amete-alem") - 1);
|
||||
auto mayID = Calendar::fromString(s);
|
||||
if (mayID) {
|
||||
s = mayID.value().toString()->toNonGCUTF8StringData();
|
||||
}
|
||||
|
||||
if (!Intl::isValidUnicodeLocaleIdentifier(code)) {
|
||||
ErrorObject::throwBuiltinError(state, ErrorCode::RangeError, "Invalid calendar code");
|
||||
}
|
||||
|
||||
std::string s = code->toNonGCUTF8StringData();
|
||||
for (size_t i = 0; i < s.length(); i++) {
|
||||
s[i] = tolower(s[i]);
|
||||
}
|
||||
|
|
@ -291,6 +290,12 @@ Value IntlDisplayNamesObject::of(ExecutionState& state, const Value& codeInput)
|
|||
} else if (m_type->equals("calendar")) {
|
||||
auto icuKey = Intl::convertBCP47KeywordToICUCalendarKeywordIfNeeds(code->toNonGCUTF8StringData());
|
||||
result = INTL_ICU_STRING_BUFFER_OPERATION(uldn_keyValueDisplayName, m_icuLocaleDisplayNames, "calendar", icuKey.data());
|
||||
if (U_SUCCESS(result.first)) {
|
||||
auto mayID = Calendar::fromString(icuKey);
|
||||
if (!mayID) {
|
||||
result.first = U_ILLEGAL_ARGUMENT_ERROR;
|
||||
}
|
||||
}
|
||||
} else if (m_type->equals("currency")) {
|
||||
UCurrNameStyle style = UCURR_LONG_NAME;
|
||||
if (m_style->equals("long")) {
|
||||
|
|
|
|||
|
|
@ -54,6 +54,11 @@
|
|||
#include "IntlDurationFormat.h"
|
||||
#include "IntlNumberFormat.h"
|
||||
|
||||
#if defined(ENABLE_TEMPORAL)
|
||||
#include "runtime/TemporalObject.h"
|
||||
#include "runtime/TemporalDurationObject.h"
|
||||
#endif
|
||||
|
||||
#if defined(ENABLE_INTL_DURATIONFORMAT)
|
||||
|
||||
namespace Escargot {
|
||||
|
|
@ -336,10 +341,10 @@ IntlDurationFormatObject::IntlDurationFormatObject(ExecutionState& state, Object
|
|||
std::pair<String*, String*> IntlDurationFormatObject::data(size_t index)
|
||||
{
|
||||
switch (index) {
|
||||
#define DEFINE_GETTER(name, Name, index) \
|
||||
case index: \
|
||||
return std::make_pair(m_##name##Style, m_##name##Display);
|
||||
FOR_EACH_DURATION_RECORD(DEFINE_GETTER)
|
||||
#define DEFINE_GETTER(name, Name, names, Names, index, category) \
|
||||
case index: \
|
||||
return std::make_pair(m_##names##Style, m_##names##Display);
|
||||
PLAIN_DATETIME_UNITS(DEFINE_GETTER)
|
||||
#undef DEFINE_GETTER
|
||||
default:
|
||||
ASSERT_NOT_REACHED();
|
||||
|
|
@ -355,21 +360,10 @@ Object* IntlDurationFormatObject::resolvedOptions(ExecutionState& state)
|
|||
options->directDefineOwnProperty(state, ObjectPropertyName(ss.lazyNumberingSystem()), ObjectPropertyDescriptor(m_numberingSystem, ObjectPropertyDescriptor::AllPresent));
|
||||
options->directDefineOwnProperty(state, ObjectPropertyName(ss.lazyStyle()), ObjectPropertyDescriptor(m_style, ObjectPropertyDescriptor::AllPresent));
|
||||
|
||||
#define ADD_PROPERTY(name) \
|
||||
options->directDefineOwnProperty(state, ObjectPropertyName(state, String::fromASCII(#name "s")), ObjectPropertyDescriptor(m_##name##sStyle, ObjectPropertyDescriptor::AllPresent)); \
|
||||
options->directDefineOwnProperty(state, ObjectPropertyName(state, String::fromASCII(#name "sDisplay")), ObjectPropertyDescriptor(m_##name##sDisplay, ObjectPropertyDescriptor::AllPresent));
|
||||
|
||||
ADD_PROPERTY(year)
|
||||
ADD_PROPERTY(month)
|
||||
ADD_PROPERTY(week)
|
||||
ADD_PROPERTY(day)
|
||||
ADD_PROPERTY(hour)
|
||||
ADD_PROPERTY(minute)
|
||||
ADD_PROPERTY(second)
|
||||
ADD_PROPERTY(millisecond)
|
||||
ADD_PROPERTY(microsecond)
|
||||
ADD_PROPERTY(nanosecond)
|
||||
|
||||
#define ADD_PROPERTY(name, Name, names, Names, index, category) \
|
||||
options->directDefineOwnProperty(state, ObjectPropertyName(state, String::fromASCII(#names)), ObjectPropertyDescriptor(m_##names##Style, ObjectPropertyDescriptor::AllPresent)); \
|
||||
options->directDefineOwnProperty(state, ObjectPropertyName(state, String::fromASCII(#names "Display")), ObjectPropertyDescriptor(m_##names##Display, ObjectPropertyDescriptor::AllPresent));
|
||||
PLAIN_DATETIME_UNITS(ADD_PROPERTY)
|
||||
#undef ADD_PROPERTY
|
||||
|
||||
if (!m_fractionalDigits.isUndefined()) {
|
||||
|
|
@ -378,90 +372,19 @@ Object* IntlDurationFormatObject::resolvedOptions(ExecutionState& state)
|
|||
return options;
|
||||
}
|
||||
|
||||
// https://tc39.es/ecma402/#sec-tointegerifintegral
|
||||
static double toIntegerIfIntergral(ExecutionState& state, const Value& argument)
|
||||
{
|
||||
// Let number be ? ToNumber(argument).
|
||||
double number = argument.toNumber(state) + 0.0;
|
||||
// If number is not an integral Number, throw a RangeError exception.
|
||||
if (std::trunc(number) != number) {
|
||||
ErrorObject::throwBuiltinError(state, ErrorCode::RangeError, "Invalid input for ToDurationRecord");
|
||||
}
|
||||
// Return ℝ(number).
|
||||
return number;
|
||||
}
|
||||
|
||||
static bool isValidDurationWork(double v, int& sign)
|
||||
{
|
||||
// If 𝔽(v) is not finite, return false.
|
||||
if (!std::isfinite(v)) {
|
||||
return false;
|
||||
}
|
||||
// If v < 0, then
|
||||
if (v < 0) {
|
||||
// If sign > 0, return false.
|
||||
if (sign > 0) {
|
||||
return false;
|
||||
}
|
||||
// Set sign to -1.
|
||||
sign = -1;
|
||||
} else if (v > 0) {
|
||||
// Else if v > 0, then
|
||||
// If sign < 0, return false.
|
||||
if (sign < 0) {
|
||||
return false;
|
||||
}
|
||||
// Set sign to 1.
|
||||
sign = 1;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// https://tc39.es/ecma402/#sec-isvalidduration
|
||||
static bool isValidDuration(const DurationRecord& record)
|
||||
{
|
||||
// Let sign be 0.
|
||||
int sign = 0;
|
||||
// For each value v of « years, months, weeks, days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds », do
|
||||
for (double v : record) {
|
||||
if (!isValidDurationWork(v, sign)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// If abs(years) ≥ 2**32, return false.
|
||||
if (std::abs(record.years()) >= (1ULL << 32)) {
|
||||
return false;
|
||||
}
|
||||
// If abs(months) ≥ 2**32, return false.
|
||||
if (std::abs(record.months()) >= (1ULL << 32)) {
|
||||
return false;
|
||||
}
|
||||
// If abs(weeks) ≥ 2**32, return false.
|
||||
if (std::abs(record.weeks()) >= (1ULL << 32)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Let normalizedSeconds be days × 86,400 + hours × 3600 + minutes × 60 + seconds + ℝ(𝔽(milliseconds)) × 10**-3 + ℝ(𝔽(microseconds)) × 10**-6 + ℝ(𝔽(nanoseconds)) × 10**-9.
|
||||
// NOTE: The above step cannot be implemented directly using floating-point arithmetic. Multiplying by 10**-3, 10**-6, and 10**-9 respectively may be imprecise when milliseconds, microseconds, or nanoseconds is an unsafe integer. This multiplication can be implemented in C++ with an implementation of std::remquo() with sufficient bits in the quotient. String manipulation will also give an exact result, since the multiplication is by a power of 10.
|
||||
BigIntData normalizedNanoSeconds = record.totalNanoseconds(DurationRecord::Type::Years);
|
||||
// If abs(normalizedSeconds) ≥ 2**53, return false.
|
||||
BigIntData limit(int64_t(1ULL << 53));
|
||||
limit.multiply(1000000000ULL);
|
||||
if (normalizedNanoSeconds.greaterThanEqual(limit)) {
|
||||
return false;
|
||||
}
|
||||
limit = BigIntData(int64_t(1ULL << 53));
|
||||
limit.multiply(-1000000000ULL);
|
||||
if (normalizedNanoSeconds.lessThanEqual(limit)) {
|
||||
return false;
|
||||
}
|
||||
// Return true.
|
||||
return true;
|
||||
}
|
||||
|
||||
static DurationRecord toDurationRecord(ExecutionState& state, const Value& input)
|
||||
{
|
||||
#if defined(ENABLE_TEMPORAL)
|
||||
// https://tc39.es/proposal-temporal/#sec-temporal-totemporalduration
|
||||
if (input.isObject() && input.asObject()->isTemporalDurationObject()) {
|
||||
auto t = input.asObject()->asTemporalDurationObject();
|
||||
return t->duration();
|
||||
} else if (input.isString()) {
|
||||
auto t = Temporal::toTemporalDuration(state, input);
|
||||
return t->duration();
|
||||
}
|
||||
#endif
|
||||
|
||||
// If input is not an Object, then
|
||||
if (!input.isObject()) {
|
||||
// If input is a String, throw a RangeError exception.
|
||||
|
|
@ -471,13 +394,14 @@ static DurationRecord toDurationRecord(ExecutionState& state, const Value& input
|
|||
// Throw a TypeError exception.
|
||||
ErrorObject::throwBuiltinError(state, ErrorCode::TypeError, "Invalid input for ToDurationRecord");
|
||||
}
|
||||
|
||||
// Let result be a new Duration Record with each field set to 0.
|
||||
DurationRecord result;
|
||||
|
||||
#define GET_ONCE(name, Name) \
|
||||
Value name = input.asObject()->get(state, state.context()->staticStrings().lazy##Name()).value(state, input); \
|
||||
if (!name.isUndefined()) { \
|
||||
result.set##Name(toIntegerIfIntergral(state, name)); \
|
||||
result.set##Name(name.toIntegerIfIntergral(state)); \
|
||||
}
|
||||
|
||||
// Let days be ? Get(input, "days").
|
||||
|
|
@ -518,7 +442,7 @@ static DurationRecord toDurationRecord(ExecutionState& state, const Value& input
|
|||
ErrorObject::throwBuiltinError(state, ErrorCode::TypeError, "Invalid input for ToDurationRecord");
|
||||
}
|
||||
// If IsValidDuration( result.[[Years]], result.[[Months]], result.[[Weeks]], result.[[Days]], result.[[Hours]], result.[[Minutes]], result.[[Seconds]], result.[[Milliseconds]], result.[[Microseconds]], result.[[Nanoseconds]]) is false, then
|
||||
if (!isValidDuration(result)) {
|
||||
if (!result.isValid()) {
|
||||
// Throw a RangeError exception.
|
||||
ErrorObject::throwBuiltinError(state, ErrorCode::RangeError, "Invalid input for ToDurationRecord");
|
||||
}
|
||||
|
|
@ -555,70 +479,6 @@ static DurationSignType getDurationSign(const DurationRecord& duration)
|
|||
return DurationSignType::Zero;
|
||||
}
|
||||
|
||||
String* DurationRecord::typeName(ExecutionState& state, Type t)
|
||||
{
|
||||
switch (t) {
|
||||
#define DEFINE_GETTER(name, Name, index) \
|
||||
case Type::Name: \
|
||||
return state.context()->staticStrings().lazy##Name().string();
|
||||
FOR_EACH_DURATION_RECORD(DEFINE_GETTER)
|
||||
#undef DEFINE_GETTER
|
||||
default:
|
||||
break;
|
||||
}
|
||||
ASSERT_NOT_REACHED();
|
||||
return String::emptyString();
|
||||
}
|
||||
|
||||
BigIntData DurationRecord::totalNanoseconds(DurationRecord::Type unit) const
|
||||
{
|
||||
BigIntData resultNs;
|
||||
|
||||
constexpr int64_t nanoMultiplier = 1000000000ULL;
|
||||
constexpr int64_t milliMultiplier = 1000000ULL;
|
||||
constexpr int64_t microMultiplier = 1000ULL;
|
||||
|
||||
if (unit <= DurationRecord::Type::Days) {
|
||||
BigIntData s(days());
|
||||
s.multiply(86400);
|
||||
s.multiply(nanoMultiplier);
|
||||
resultNs.addition(s);
|
||||
}
|
||||
if (unit <= DurationRecord::Type::Hours) {
|
||||
BigIntData s(hours());
|
||||
s.multiply(3600);
|
||||
s.multiply(nanoMultiplier);
|
||||
resultNs.addition(s);
|
||||
}
|
||||
if (unit <= DurationRecord::Type::Minutes) {
|
||||
BigIntData s(minutes());
|
||||
s.multiply(60);
|
||||
s.multiply(nanoMultiplier);
|
||||
resultNs.addition(s);
|
||||
}
|
||||
if (unit <= DurationRecord::Type::Seconds) {
|
||||
BigIntData s(seconds());
|
||||
s.multiply(nanoMultiplier);
|
||||
resultNs.addition(s);
|
||||
}
|
||||
if (unit <= DurationRecord::Type::Milliseconds) {
|
||||
BigIntData s(milliseconds());
|
||||
s.multiply(milliMultiplier);
|
||||
resultNs.addition(s);
|
||||
}
|
||||
if (unit <= DurationRecord::Type::Microseconds) {
|
||||
BigIntData s(microseconds());
|
||||
s.multiply(microMultiplier);
|
||||
resultNs.addition(s);
|
||||
}
|
||||
if (unit <= DurationRecord::Type::Nanoseconds) {
|
||||
BigIntData s(nanoseconds());
|
||||
resultNs.addition(s);
|
||||
}
|
||||
|
||||
return resultNs;
|
||||
}
|
||||
|
||||
inline double purifyNaN(double value)
|
||||
{
|
||||
uint64_t PNaNAsBits{ 0x7ff8000000000000ll };
|
||||
|
|
@ -629,37 +489,37 @@ inline double purifyNaN(double value)
|
|||
return value;
|
||||
}
|
||||
|
||||
static std::string buildDecimalFormat(DurationRecord::Type unit, BigIntData ns)
|
||||
static std::string buildDecimalFormat(ISO8601::DateTimeUnit unit, Int128 ns)
|
||||
{
|
||||
ASSERT(unit == DurationRecord::Type::Seconds || unit == DurationRecord::Type::Milliseconds || unit == DurationRecord::Type::Microseconds);
|
||||
ASSERT(unit == ISO8601::DateTimeUnit::Second || unit == ISO8601::DateTimeUnit::Millisecond || unit == ISO8601::DateTimeUnit::Microsecond);
|
||||
|
||||
int flactionalDigits = 0;
|
||||
int64_t exponent = 0;
|
||||
if (unit == DurationRecord::Type::Seconds) {
|
||||
if (unit == ISO8601::DateTimeUnit::Second) {
|
||||
flactionalDigits = 9;
|
||||
exponent = 1000000000;
|
||||
} else if (unit == DurationRecord::Type::Milliseconds) {
|
||||
} else if (unit == ISO8601::DateTimeUnit::Millisecond) {
|
||||
flactionalDigits = 6;
|
||||
exponent = 1000000;
|
||||
} else {
|
||||
ASSERT(unit == DurationRecord::Type::Microseconds);
|
||||
ASSERT(unit == ISO8601::DateTimeUnit::Microsecond);
|
||||
flactionalDigits = 3;
|
||||
exponent = 1000;
|
||||
}
|
||||
|
||||
BigIntData integerPart = ns;
|
||||
integerPart.division(exponent);
|
||||
Int128 integerPart = ns;
|
||||
integerPart /= exponent;
|
||||
|
||||
BigIntData fractionalPart(ns);
|
||||
fractionalPart.remainder(exponent);
|
||||
Int128 fractionalPart = ns;
|
||||
fractionalPart %= exponent;
|
||||
|
||||
StringBuilder builder;
|
||||
|
||||
auto integerPartString = integerPart.toNonGCStdString();
|
||||
auto integerPartString = std::to_string(integerPart);
|
||||
builder.appendString(integerPartString.data(), integerPartString.size());
|
||||
builder.appendChar('.');
|
||||
|
||||
auto fractionalString = fractionalPart.toNonGCStdString();
|
||||
auto fractionalString = std::to_string(fractionalPart);
|
||||
if (fractionalString.size() && fractionalString[0] == '-') {
|
||||
fractionalString.erase(fractionalString.begin());
|
||||
}
|
||||
|
|
@ -722,36 +582,36 @@ std::vector<IntlDurationFormatObject::Element> IntlDurationFormatObject::collect
|
|||
std::vector<Element> elements;
|
||||
Optional<DurationSignType> durationSign;
|
||||
for (uint8_t index = 0; index < 10 && !done; ++index) {
|
||||
DurationRecord::Type unit = static_cast<DurationRecord::Type>(index);
|
||||
ISO8601::DateTimeUnit unit = static_cast<ISO8601::DateTimeUnit>(index);
|
||||
auto unitData = durationFormat->data(index);
|
||||
double value = duration[unit];
|
||||
Optional<BigIntData> totalNanosecondsValue;
|
||||
Optional<Int128> totalNanosecondsValue;
|
||||
|
||||
UTF16StringDataNonGCStd skeletonBuilder;
|
||||
|
||||
switch (unit) {
|
||||
// 3.j. If unit is "seconds", "milliseconds", or "microseconds", then
|
||||
case DurationRecord::Type::Seconds:
|
||||
case DurationRecord::Type::Milliseconds:
|
||||
case DurationRecord::Type::Microseconds: {
|
||||
case ISO8601::DateTimeUnit::Second:
|
||||
case ISO8601::DateTimeUnit::Millisecond:
|
||||
case ISO8601::DateTimeUnit::Microsecond: {
|
||||
skeletonBuilder = u"rounding-mode-down";
|
||||
String* nextStyle = state.context()->staticStrings().lazyLong().string();
|
||||
if (unit == DurationRecord::Type::Seconds)
|
||||
nextStyle = durationFormat->data(static_cast<unsigned>(DurationRecord::Type::Milliseconds)).first;
|
||||
else if (unit == DurationRecord::Type::Milliseconds)
|
||||
nextStyle = durationFormat->data(static_cast<unsigned>(DurationRecord::Type::Microseconds)).first;
|
||||
if (unit == ISO8601::DateTimeUnit::Second)
|
||||
nextStyle = durationFormat->data(static_cast<unsigned>(ISO8601::DateTimeUnit::Millisecond)).first;
|
||||
else if (unit == ISO8601::DateTimeUnit::Millisecond)
|
||||
nextStyle = durationFormat->data(static_cast<unsigned>(ISO8601::DateTimeUnit::Microsecond)).first;
|
||||
else {
|
||||
ASSERT(unit == DurationRecord::Type::Microseconds);
|
||||
nextStyle = durationFormat->data(static_cast<unsigned>(DurationRecord::Type::Nanoseconds)).first;
|
||||
ASSERT(unit == ISO8601::DateTimeUnit::Microsecond);
|
||||
nextStyle = durationFormat->data(static_cast<unsigned>(ISO8601::DateTimeUnit::Nanosecond)).first;
|
||||
}
|
||||
if (nextStyle->equals("numeric")) {
|
||||
if (unit == DurationRecord::Type::Seconds) {
|
||||
totalNanosecondsValue = duration.totalNanoseconds(DurationRecord::Type::Seconds);
|
||||
} else if (unit == DurationRecord::Type::Milliseconds) {
|
||||
totalNanosecondsValue = duration.totalNanoseconds(DurationRecord::Type::Milliseconds);
|
||||
if (unit == ISO8601::DateTimeUnit::Second) {
|
||||
totalNanosecondsValue = duration.totalNanoseconds(ISO8601::DateTimeUnit::Second);
|
||||
} else if (unit == ISO8601::DateTimeUnit::Millisecond) {
|
||||
totalNanosecondsValue = duration.totalNanoseconds(ISO8601::DateTimeUnit::Millisecond);
|
||||
} else {
|
||||
ASSERT(unit == DurationRecord::Type::Microseconds);
|
||||
totalNanosecondsValue = duration.totalNanoseconds(DurationRecord::Type::Microseconds);
|
||||
ASSERT(unit == ISO8601::DateTimeUnit::Microsecond);
|
||||
totalNanosecondsValue = duration.totalNanoseconds(ISO8601::DateTimeUnit::Microsecond);
|
||||
}
|
||||
ASSERT(totalNanosecondsValue);
|
||||
|
||||
|
|
@ -848,7 +708,7 @@ std::vector<IntlDurationFormatObject::Element> IntlDurationFormatObject::collect
|
|||
|
||||
auto formatIntl128AsDecimal = [&](const UTF16StringDataNonGCStd& skeleton) -> LocalResourcePointer<UFormattedNumber> {
|
||||
ASSERT(totalNanosecondsValue);
|
||||
ASSERT(unit == DurationRecord::Type::Seconds || unit == DurationRecord::Type::Milliseconds || unit == DurationRecord::Type::Microseconds);
|
||||
ASSERT(unit == ISO8601::DateTimeUnit::Second || unit == ISO8601::DateTimeUnit::Millisecond || unit == ISO8601::DateTimeUnit::Microsecond);
|
||||
|
||||
UErrorCode status = U_ZERO_ERROR;
|
||||
LocalResourcePointer<UNumberFormatter> numberFormatter(unumf_openForSkeletonAndLocale(skeleton.data(), skeleton.length(), durationFormat->m_dataLocaleWithExtensions->toNonGCUTF8StringData().data(), &status),
|
||||
|
|
@ -880,19 +740,19 @@ std::vector<IntlDurationFormatObject::Element> IntlDurationFormatObject::collect
|
|||
// 3.l.i. If style is "2-digit" or "numeric", then
|
||||
if (style->equals("2-digit") || style->equals("numeric")) {
|
||||
// https://tc39.es/proposal-intl-duration-format/#sec-formatnumericunits
|
||||
ASSERT(unit == DurationRecord::Type::Hours || unit == DurationRecord::Type::Minutes || unit == DurationRecord::Type::Seconds);
|
||||
ASSERT(unit == ISO8601::DateTimeUnit::Hour || unit == ISO8601::DateTimeUnit::Minute || unit == ISO8601::DateTimeUnit::Second);
|
||||
|
||||
double secondsValue = duration[DurationRecord::Type::Seconds];
|
||||
if (durationFormat->data(static_cast<unsigned>(DurationRecord::Type::Milliseconds)).first->equals("numeric")) {
|
||||
secondsValue = secondsValue + duration[DurationRecord::Type::Milliseconds] / 1000.0 + duration[DurationRecord::Type::Microseconds] / 1000000.0 + duration[DurationRecord::Type::Nanoseconds] / 1000000000.0;
|
||||
double secondsValue = duration[ISO8601::DateTimeUnit::Second];
|
||||
if (durationFormat->data(static_cast<unsigned>(ISO8601::DateTimeUnit::Millisecond)).first->equals("numeric")) {
|
||||
secondsValue = secondsValue + duration[ISO8601::DateTimeUnit::Millisecond] / 1000.0 + duration[ISO8601::DateTimeUnit::Microsecond] / 1000000.0 + duration[ISO8601::DateTimeUnit::Nanosecond] / 1000000000.0;
|
||||
}
|
||||
|
||||
bool needsFormatHours = duration[DurationRecord::Type::Hours] || !durationFormat->data(static_cast<unsigned>(DurationRecord::Type::Hours)).second->equals("auto");
|
||||
bool needsFormatSeconds = secondsValue || !durationFormat->data(static_cast<unsigned>(DurationRecord::Type::Seconds)).second->equals("auto");
|
||||
bool needsFormatMinutes = (needsFormatHours && needsFormatSeconds) || duration[DurationRecord::Type::Minutes] || !durationFormat->data(static_cast<unsigned>(DurationRecord::Type::Minutes)).second->equals("auto");
|
||||
bool needsFormatHours = duration[ISO8601::DateTimeUnit::Hour] || !durationFormat->data(static_cast<unsigned>(ISO8601::DateTimeUnit::Hour)).second->equals("auto");
|
||||
bool needsFormatSeconds = secondsValue || !durationFormat->data(static_cast<unsigned>(ISO8601::DateTimeUnit::Second)).second->equals("auto");
|
||||
bool needsFormatMinutes = (needsFormatHours && needsFormatSeconds) || duration[ISO8601::DateTimeUnit::Minute] || !durationFormat->data(static_cast<unsigned>(ISO8601::DateTimeUnit::Minute)).second->equals("auto");
|
||||
|
||||
bool needsFormat = (unit == DurationRecord::Type::Hours && needsFormatHours) || (unit == DurationRecord::Type::Minutes && needsFormatMinutes) || (unit == DurationRecord::Type::Seconds && needsFormatSeconds);
|
||||
bool needsSeparator = (unit == DurationRecord::Type::Hours && needsFormatHours && needsFormatMinutes) || (unit == DurationRecord::Type::Minutes && needsFormatSeconds);
|
||||
bool needsFormat = (unit == ISO8601::DateTimeUnit::Hour && needsFormatHours) || (unit == ISO8601::DateTimeUnit::Minute && needsFormatMinutes) || (unit == ISO8601::DateTimeUnit::Second && needsFormatSeconds);
|
||||
bool needsSeparator = (unit == ISO8601::DateTimeUnit::Hour && needsFormatHours && needsFormatMinutes) || (unit == ISO8601::DateTimeUnit::Minute && needsFormatSeconds);
|
||||
|
||||
if (needsFormat) {
|
||||
adjustSignDisplay();
|
||||
|
|
|
|||
|
|
@ -24,57 +24,12 @@
|
|||
|
||||
#include "runtime/Object.h"
|
||||
#include "runtime/BigInt.h"
|
||||
#include "util/ISO8601.h"
|
||||
|
||||
namespace Escargot {
|
||||
|
||||
// https://tc39.es/ecma402/#sec-todurationrecord
|
||||
class DurationRecord {
|
||||
#define FOR_EACH_DURATION_RECORD(F) \
|
||||
F(years, Years, 0) \
|
||||
F(months, Months, 1) \
|
||||
F(weeks, Weeks, 2) \
|
||||
F(days, Days, 3) \
|
||||
F(hours, Hours, 4) \
|
||||
F(minutes, Minutes, 5) \
|
||||
F(seconds, Seconds, 6) \
|
||||
F(milliseconds, Milliseconds, 7) \
|
||||
F(microseconds, Microseconds, 8) \
|
||||
F(nanoseconds, Nanoseconds, 9)
|
||||
|
||||
std::array<double, 10> m_data;
|
||||
|
||||
public:
|
||||
enum class Type : uint8_t {
|
||||
#define DEFINE_TYPE(name, Name, index) Name,
|
||||
FOR_EACH_DURATION_RECORD(DEFINE_TYPE)
|
||||
#undef DEFINE_TYPE
|
||||
};
|
||||
|
||||
DurationRecord()
|
||||
{
|
||||
m_data.fill(0);
|
||||
}
|
||||
|
||||
static String* typeName(ExecutionState& state, Type t);
|
||||
BigIntData totalNanoseconds(DurationRecord::Type type) const;
|
||||
|
||||
double operator[](DurationRecord::Type idx) const
|
||||
{
|
||||
return m_data[static_cast<unsigned>(idx)];
|
||||
}
|
||||
std::array<double, 10>::const_iterator begin() const { return m_data.begin(); }
|
||||
std::array<double, 10>::const_iterator end() const { return m_data.end(); }
|
||||
|
||||
#define DEFINE_GETTER(name, Name, index) \
|
||||
double name() const { return m_data[index]; }
|
||||
FOR_EACH_DURATION_RECORD(DEFINE_GETTER)
|
||||
#undef DEFINE_GETTER
|
||||
|
||||
#define DEFINE_SETTER(name, Name, index) \
|
||||
void set##Name(double v) { m_data[index] = v; }
|
||||
FOR_EACH_DURATION_RECORD(DEFINE_SETTER)
|
||||
#undef DEFINE_SETTER
|
||||
};
|
||||
using DurationRecord = ISO8601::Duration;
|
||||
|
||||
class IntlDurationFormatObject : public DerivedObject {
|
||||
public:
|
||||
|
|
@ -101,7 +56,7 @@ protected:
|
|||
struct Element {
|
||||
ElementType m_type;
|
||||
bool m_valueSignBit;
|
||||
DurationRecord::Type m_unit;
|
||||
ISO8601::DateTimeUnit m_unit;
|
||||
UTF16StringDataNonGCStd m_string;
|
||||
LocalResourcePointer<UFormattedNumber> m_formattedNumber;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -254,7 +254,7 @@ IntlLocaleObject::IntlLocaleObject(ExecutionState& state, Object* proto, String*
|
|||
Value calendar = Intl::getOption(state, options, state.context()->staticStrings().lazyCalendar().string(), Intl::StringValue, nullptr, 0, Value());
|
||||
if (!calendar.isUndefined()) {
|
||||
std::string s = calendar.asString()->toNonGCUTF8StringData();
|
||||
if (!Intl::isUnicodeLocaleIdentifierType(s) || !localeID.setKeywordValue("calendar", s)) {
|
||||
if (!Intl::isUnicodeLocaleIdentifierType(s) || !localeID.setKeywordValue("calendar", Intl::canonicalizeCalendarTag(s))) {
|
||||
ErrorObject::throwBuiltinError(state, ErrorCode::RangeError, "calendar is not a well-formed calendar value");
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -394,6 +394,10 @@ void InterpretedCodeBlock::recordFunctionParsingInfo(ASTScopeContext* scopeCtx,
|
|||
}
|
||||
}
|
||||
|
||||
#ifndef ESCARGOT_DEBUGGER
|
||||
m_parameterUsed = scopeCtx->m_parameterUsed;
|
||||
#endif
|
||||
|
||||
m_canUseIndexedVariableStorage = !m_hasEval && !m_isEvalCode && !m_hasWith;
|
||||
m_canAllocateEnvironmentOnStack = m_canUseIndexedVariableStorage && !m_isGenerator && !m_isAsync;
|
||||
m_canAllocateVariablesOnStack = true;
|
||||
|
|
@ -419,7 +423,7 @@ void InterpretedCodeBlock::recordFunctionParsingInfo(ASTScopeContext* scopeCtx,
|
|||
void InterpretedCodeBlock::captureArguments(bool needToAllocateOnStack)
|
||||
{
|
||||
AtomicString arguments = m_context->staticStrings().arguments;
|
||||
ASSERT(!hasParameterName(arguments));
|
||||
ASSERT(!isParameterName(arguments));
|
||||
ASSERT(!isGlobalCodeBlock() && !isArrowFunctionExpression() && isKindOfFunction());
|
||||
|
||||
if (m_usesArgumentsObject) {
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@ struct ASTScopeContext;
|
|||
struct ByteCodeGenerateContext;
|
||||
|
||||
typedef HashMap<AtomicString, StorePositiveNumberAsOddNumber, std::hash<AtomicString>, std::equal_to<AtomicString>,
|
||||
GCUtil::gc_malloc_allocator<std::pair<AtomicString const, StorePositiveNumberAsOddNumber>>>
|
||||
GCUtil::gc_malloc_atomic_allocator<std::pair<AtomicString const, size_t>>>
|
||||
FunctionContextVarMap;
|
||||
|
||||
// length of argv is same with NativeFunctionInfo.m_argumentCount
|
||||
|
|
@ -542,6 +542,13 @@ public:
|
|||
return m_parameterNames;
|
||||
}
|
||||
|
||||
#ifndef ESCARGOT_DEBUGGER
|
||||
uint16_t parameterUsed() const
|
||||
{
|
||||
return m_parameterUsed;
|
||||
}
|
||||
#endif
|
||||
|
||||
const IdentifierInfoVector& identifierInfos() const
|
||||
{
|
||||
return m_identifierInfos;
|
||||
|
|
@ -918,7 +925,7 @@ public:
|
|||
return findVarName(name) != SIZE_MAX;
|
||||
}
|
||||
|
||||
bool hasParameterName(const AtomicString& name)
|
||||
bool isParameterName(const AtomicString& name)
|
||||
{
|
||||
for (size_t i = 0; i < parameterNamesCount(); i++) {
|
||||
if (m_parameterNames[i] == name) {
|
||||
|
|
@ -928,16 +935,6 @@ public:
|
|||
return false;
|
||||
}
|
||||
|
||||
bool isParameterName(const AtomicString& name)
|
||||
{
|
||||
size_t r = findVarName(name);
|
||||
if (r != SIZE_MAX) {
|
||||
return m_identifierInfos[r].m_isParameterName;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void markHeapAllocatedEnvironmentFromHere(LexicalBlockIndex blockIndex = 0, InterpretedCodeBlock* to = nullptr);
|
||||
|
||||
void setConstructedObjectPropertyCount(size_t s)
|
||||
|
|
@ -982,6 +979,10 @@ protected:
|
|||
ExtendedNodeLOC m_bodyEndLOC;
|
||||
#endif
|
||||
|
||||
#ifndef ESCARGOT_DEBUGGER
|
||||
uint16_t m_parameterUsed : 16; // 0xFFFF means all parameters are used or parameter is more than 16
|
||||
#endif
|
||||
|
||||
uint16_t m_functionLength : 16;
|
||||
uint16_t m_parameterCount : 16; // number of parameter elements
|
||||
|
||||
|
|
|
|||
|
|
@ -189,7 +189,7 @@ NEVER_INLINE bool EscargotLexer::isWhiteSpaceSlowCase(char16_t ch)
|
|||
static NEVER_INLINE bool isIdentifierPartSlow(char32_t ch)
|
||||
{
|
||||
int bottom = 0;
|
||||
int top = (EscargotLexer::basic_plane_length / sizeof(uint16_t)) - 1;
|
||||
int top = (EscargotLexer::basicPlaneLength / sizeof(uint16_t)) - 1;
|
||||
|
||||
while (true) {
|
||||
int middle = (bottom + top) >> 1;
|
||||
|
|
@ -219,7 +219,7 @@ static NEVER_INLINE bool isIdentifierPartSlow(char32_t ch)
|
|||
static NEVER_INLINE bool isIdentifierPartSlowSupplementary(char32_t ch)
|
||||
{
|
||||
int bottom = 0;
|
||||
int top = (EscargotLexer::supplementary_plane_length / sizeof(uint32_t)) - 1;
|
||||
int top = (EscargotLexer::supplementaryPlaneLength / sizeof(uint32_t)) - 1;
|
||||
|
||||
while (true) {
|
||||
int middle = (bottom + top) >> 1;
|
||||
|
|
@ -755,7 +755,7 @@ void Scanner::skipSingleLine()
|
|||
++this->index;
|
||||
|
||||
if (isLineTerminator(ch)) {
|
||||
if (ch == 13 && this->peekCharWithoutEOF() == 10) {
|
||||
if (ch == 13 && !this->eof() && this->peekCharWithoutEOF() == 10) {
|
||||
++this->index;
|
||||
}
|
||||
++this->lineNumber;
|
||||
|
|
@ -772,7 +772,7 @@ void Scanner::skipSingleLineComment(void)
|
|||
++this->index;
|
||||
|
||||
if (isLineTerminator(ch)) {
|
||||
if (ch == 13 && this->peekCharWithoutEOF() == 10) {
|
||||
if (ch == 13 && !this->eof() && this->peekCharWithoutEOF() == 10) {
|
||||
++this->index;
|
||||
}
|
||||
++this->lineNumber;
|
||||
|
|
@ -790,12 +790,12 @@ void Scanner::skipMultiLineComment(void)
|
|||
++this->index;
|
||||
|
||||
if (isLineTerminator(ch)) {
|
||||
if (ch == 0x0D && this->peekCharWithoutEOF() == 0x0A) {
|
||||
if (ch == 0x0D && !this->eof() && this->peekCharWithoutEOF() == 0x0A) {
|
||||
++this->index;
|
||||
}
|
||||
++this->lineNumber;
|
||||
this->lineStart = this->index;
|
||||
} else if (ch == 0x2A && this->peekCharWithoutEOF() == 0x2F) {
|
||||
} else if (ch == 0x2A && !this->eof() && this->peekCharWithoutEOF() == 0x2F) {
|
||||
// Block comment ends with '*/'.
|
||||
++this->index;
|
||||
return;
|
||||
|
|
|
|||
|
|
@ -385,6 +385,7 @@ Value Script::execute(ExecutionState& state, bool isExecuteOnEvalFunction, bool
|
|||
newState = new ExtendedExecutionState(context(), nullptr, nullptr, 0, nullptr, false);
|
||||
}
|
||||
ExecutionState* codeExecutionState = newState;
|
||||
codeExecutionState->setParent(&state);
|
||||
|
||||
EnvironmentRecord* globalRecord = new GlobalEnvironmentRecord(state, m_topCodeBlock, context()->globalObject(), context()->globalDeclarativeRecord(), context()->globalDeclarativeStorage());
|
||||
LexicalEnvironment* globalLexicalEnvironment = new LexicalEnvironment(globalRecord, nullptr);
|
||||
|
|
@ -1123,6 +1124,10 @@ Script::ModuleExecutionResult Script::moduleExecute(ExecutionState& state, Optio
|
|||
|
||||
if (LIKELY(!m_topCodeBlock->isAsync())) {
|
||||
try {
|
||||
#ifdef ESCARGOT_DEBUGGER
|
||||
// set the next(first) breakpoint to be stopped in a newer script execution
|
||||
context()->setAsAlwaysStopState();
|
||||
#endif
|
||||
Interpreter::interpret(newState, byteCodeBlock, reinterpret_cast<size_t>(byteCodeBlock->m_code.data()), registerFile);
|
||||
} catch (const Value& e) {
|
||||
resultValue = e;
|
||||
|
|
|
|||
|
|
@ -186,7 +186,7 @@ InterpretedCodeBlock* ScriptParser::generateCodeBlockTreeFromASTWalker(Context*
|
|||
bool needToCaptureArguments = false;
|
||||
InterpretedCodeBlock* argumentsObjectHolder = codeBlock;
|
||||
while (argumentsObjectHolder && !argumentsObjectHolder->isGlobalCodeBlock()) {
|
||||
if (UNLIKELY(argumentsObjectHolder->hasParameterName(arguments))) {
|
||||
if (UNLIKELY(argumentsObjectHolder->isParameterName(arguments))) {
|
||||
// no need to create arguments object since it has a parameter of which name is `arguments`
|
||||
break;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,57 +0,0 @@
|
|||
/* * Copyright (c) 2020-present Samsung Electronics Co., Ltd
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 2.1 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301
|
||||
* USA
|
||||
*/
|
||||
|
||||
|
||||
/* This file is automatically generated by the gen_unicode.py script
|
||||
* from https://www.unicode.org/Public/<version>/ucd/DerivedCoreProperties.txt
|
||||
* DO NOT EDIT!
|
||||
*/
|
||||
|
||||
#include "UnicodeIdentifierTables.h"
|
||||
|
||||
namespace Escargot {
|
||||
namespace EscargotLexer {
|
||||
|
||||
/* Starting codepoints of identifier ranges. */
|
||||
const uint16_t identRangeStart[532] = {
|
||||
170, 181, 186, 192, 216, 248, 444, 710, 736, 748, 768, 890, 891, 895, 902, 903, 904, 908, 931, 1015, 1155, 1162, 1329, 1369, 1376, 1425, 1471, 1476, 1479, 1488, 1519, 1552, 1568, 1601, 1646, 1649, 1749, 1750, 1759, 1770, 1791, 1808, 1809, 1810, 1869, 1984, 2042, 2045, 2048, 2075, 2085, 2089, 2112, 2144, 2160, 2185, 2199, 2250, 2275, 2308, 2363, 2364, 2365, 2366, 2382, 2385, 2406, 2417, 2418, 2434, 2437, 2447, 2451, 2474, 2482, 2486, 2492, 2493, 2494, 2503, 2507, 2510, 2519, 2524, 2527, 2534, 2556, 2559, 2575, 2579, 2602, 2610, 2613, 2616, 2620, 2631, 2635, 2641, 2649, 2654, 2662, 2689, 2703, 2707, 2730, 2738, 2741, 2748, 2749, 2750, 2759, 2768, 2784, 2790, 2809, 2810, 2817, 2818, 2821, 2831, 2835, 2858, 2866, 2869, 2876, 2877, 2878, 2879, 2880, 2881, 2887, 2891, 2899, 2908, 2911, 2918, 2929, 2946, 2947, 2958, 2962, 2969, 2972, 2979, 2984, 2990, 3006, 3009, 3014, 3018, 3024, 3031, 3046, 3072, 3073, 3077, 3086, 3090, 3114, 3132, 3133, 3134, 3142, 3146, 3157, 3160, 3164, 3168, 3174, 3200, 3201, 3202, 3205, 3214, 3218, 3242, 3253, 3260, 3261, 3262, 3263, 3264, 3270, 3271, 3274, 3285, 3292, 3296, 3302, 3313, 3328, 3342, 3346, 3390, 3398, 3402, 3406, 3412, 3423, 3430, 3450, 3457, 3458, 3461, 3482, 3507, 3517, 3520, 3530, 3535, 3542, 3558, 3570, 3585, 3634, 3648, 3655, 3664, 3713, 3716, 3724, 3749, 3762, 3776, 3782, 3792, 3804, 3840, 3864, 3872, 3893, 3902, 3913, 3953, 3968, 3974, 3993, 4038, 4096, 4146, 4153, 4160, 4176, 4194, 4227, 4238, 4239, 4240, 4256, 4295, 4301, 4304, 4348, 4349, 4682, 4688, 4696, 4704, 4746, 4752, 4786, 4792, 4800, 4808, 4824, 4882, 4888, 4957, 4969, 4992, 5024, 5112, 5121, 5743, 5761, 5792, 5870, 5888, 5919, 5952, 5984, 5998, 6002, 6016, 6071, 6087, 6103, 6108, 6109, 6112, 6155, 6159, 6160, 6176, 6212, 6272, 6314, 6320, 6400, 6432, 6448, 6451, 6470, 6512, 6528, 6576, 6608, 6656, 6688, 6742, 6743, 6744, 6752, 6753, 6754, 6755, 6783, 6784, 6800, 6823, 6832, 6847, 6880, 6912, 6917, 6965, 6966, 6972, 6973, 6979, 6992, 7019, 7040, 7043, 7074, 7083, 7143, 7144, 7150, 7151, 7168, 7232, 7245, 7296, 7312, 7357, 7376, 7380, 7394, 7406, 7413, 7416, 7424, 7545, 7960, 7968, 8008, 8016, 8025, 8064, 8118, 8126, 8130, 8134, 8144, 8150, 8160, 8178, 8182, 8204, 8205, 8255, 8276, 8305, 8319, 8336, 8400, 8417, 8421, 8450, 8455, 8458, 8469, 8472, 8473, 8484, 8495, 8508, 8517, 8526, 8544, 11264, 11499, 11520, 11559, 11565, 11568, 11631, 11647, 11648, 11680, 11688, 11696, 11704, 11712, 11720, 11728, 11736, 11744, 12293, 12294, 12295, 12321, 12337, 12344, 12348, 12353, 12441, 12540, 12549, 12593, 12704, 12784, 13312, 19968, 40982, 42192, 42240, 42512, 42560, 42607, 42612, 42623, 42624, 42775, 42786, 42865, 42891, 42896, 42993, 43000, 43003, 43011, 43015, 43020, 43052, 43072, 43136, 43216, 43232, 43259, 43264, 43312, 43360, 43392, 43396, 43444, 43471, 43472, 43488, 43494, 43495, 43520, 43584, 43588, 43597, 43600, 43616, 43633, 43642, 43643, 43644, 43645, 43646, 43697, 43698, 43713, 43714, 43739, 43744, 43756, 43762, 43763, 43766, 43777, 43785, 43793, 43808, 43816, 43824, 43868, 43888, 44006, 44009, 44012, 44013, 44016, 44032, 55216, 55243, 63744, 64112, 64256, 64275, 64285, 64286, 64287, 64298, 64312, 64318, 64323, 64326, 64467, 64848, 64914, 65008, 65024, 65056, 65075, 65101, 65136, 65142, 65296, 65313, 65343, 65381, 65382, 65393, 65474, 65482, 65490, 65498
|
||||
};
|
||||
|
||||
/* Lengths of identifier ranges. */
|
||||
const uint16_t identRangeLength[532] = {
|
||||
1, 3, 1, 22, 30, 196, 205, 11, 4, 3, 119, 1, 2, 1, 1, 1, 2, 21, 82, 138, 4, 165, 37, 1, 40, 44, 3, 1, 1, 26, 3, 10, 33, 40, 3, 98, 1, 6, 9, 18, 1, 1, 1, 56, 101, 53, 1, 1, 27, 10, 4, 4, 27, 10, 23, 6, 51, 23, 33, 55, 1, 1, 1, 16, 3, 18, 9, 1, 16, 1, 7, 1, 21, 6, 1, 3, 1, 1, 6, 1, 3, 1, 1, 1, 4, 11, 3, 11, 1, 21, 6, 1, 1, 1, 6, 1, 2, 1, 3, 1, 16, 12, 2, 21, 6, 1, 4, 1, 1, 7, 7, 1, 3, 9, 1, 5, 1, 1, 7, 1, 21, 6, 1, 4, 1, 1, 1, 1, 1, 3, 1, 3, 5, 1, 4, 9, 1, 1, 7, 2, 3, 1, 3, 1, 2, 11, 3, 1, 2, 4, 1, 1, 9, 1, 4, 7, 2, 22, 15, 1, 1, 6, 2, 3, 1, 2, 1, 3, 9, 1, 1, 1, 7, 2, 22, 9, 4, 1, 1, 1, 1, 4, 1, 1, 3, 1, 2, 3, 9, 3, 12, 2, 44, 6, 2, 4, 1, 4, 4, 9, 5, 1, 1, 17, 23, 8, 1, 6, 1, 5, 9, 9, 1, 49, 8, 7, 7, 9, 1, 6, 23, 13, 12, 4, 8, 9, 3, 1, 1, 9, 5, 9, 35, 15, 4, 17, 35, 1, 50, 7, 7, 9, 18, 33, 11, 1, 1, 14, 37, 1, 1, 42, 1, 202, 3, 6, 5, 40, 3, 32, 3, 6, 5, 14, 56, 3, 66, 2, 8, 15, 85, 5, 201, 16, 25, 74, 10, 22, 22, 19, 12, 2, 1, 55, 16, 12, 1, 1, 1, 9, 2, 1, 9, 36, 52, 42, 1, 69, 30, 11, 3, 8, 39, 4, 43, 25, 11, 28, 54, 1, 1, 6, 1, 1, 1, 25, 1, 9, 9, 1, 13, 30, 11, 5, 48, 1, 6, 1, 6, 9, 9, 8, 3, 31, 9, 60, 1, 6, 1, 4, 55, 9, 48, 10, 42, 2, 2, 14, 12, 7, 3, 3, 121, 209, 5, 37, 5, 7, 36, 52, 6, 1, 2, 6, 3, 5, 12, 2, 6, 1, 1, 1, 1, 1, 1, 12, 12, 1, 11, 1, 1, 9, 1, 1, 4, 11, 11, 3, 4, 1, 40, 203, 8, 37, 1, 1, 55, 1, 1, 22, 6, 6, 6, 6, 6, 6, 6, 6, 31, 1, 1, 1, 14, 4, 4, 1, 85, 99, 4, 42, 93, 31, 15, 204, 211, 200, 45, 207, 27, 47, 1, 9, 1, 113, 8, 79, 24, 5, 76, 7, 3, 8, 4, 5, 28, 1, 51, 69, 9, 23, 5, 45, 35, 28, 4, 48, 12, 1, 9, 6, 1, 23, 54, 4, 9, 1, 9, 17, 5, 1, 1, 1, 1, 51, 1, 15, 1, 1, 3, 12, 3, 1, 3, 1, 5, 5, 5, 6, 6, 42, 14, 118, 3, 1, 1, 1, 9, 208, 22, 48, 210, 105, 6, 4, 1, 1, 9, 12, 4, 3, 1, 107, 206, 63, 53, 11, 15, 15, 1, 2, 4, 134, 9, 25, 27, 1, 11, 77, 5, 5, 5, 2
|
||||
};
|
||||
|
||||
/* Lengths of identifier ranges greater than LEXER IDENT_RANGE_LONG. */
|
||||
const uint16_t identRangeLongLength[12] = {
|
||||
1142, 619, 331, 228, 6591, 261, 362, 269, 11171, 412, 365, 21014
|
||||
};
|
||||
|
||||
/* Identifier starting codepoints for the supplementary plane */
|
||||
const uint32_t identRangeStartSupplementaryPlane[446] = {
|
||||
65536, 65549, 65576, 65596, 65599, 65616, 65664, 65856, 66045, 66176, 66208, 66272, 66304, 66349, 66370, 66384, 66432, 66464, 66504, 66513, 66560, 66720, 66736, 66776, 66816, 66864, 66928, 66940, 66956, 66964, 66967, 66979, 66995, 67003, 67008, 67072, 67392, 67424, 67456, 67463, 67506, 67584, 67592, 67639, 67644, 67647, 67680, 67712, 67808, 67828, 67840, 67872, 67904, 67968, 68030, 68096, 68097, 68101, 68108, 68117, 68121, 68152, 68159, 68192, 68224, 68288, 68297, 68352, 68416, 68448, 68480, 68608, 68736, 68800, 68864, 68912, 68928, 68943, 68944, 68969, 68975, 68976, 69248, 69291, 69296, 69314, 69318, 69370, 69415, 69424, 69488, 69552, 69600, 69632, 69633, 69634, 69635, 69734, 69745, 69759, 69763, 69826, 69840, 69872, 69888, 69933, 69942, 69956, 69957, 69968, 70006, 70016, 70019, 70089, 70094, 70095, 70096, 70144, 70163, 70197, 70198, 70206, 70207, 70272, 70280, 70287, 70303, 70320, 70368, 70384, 70400, 70405, 70415, 70419, 70442, 70450, 70453, 70459, 70462, 70465, 70471, 70475, 70480, 70487, 70493, 70502, 70512, 70528, 70539, 70542, 70583, 70584, 70594, 70597, 70604, 70607, 70608, 70609, 70610, 70611, 70625, 70656, 70726, 70727, 70736, 70750, 70751, 70784, 70842, 70843, 70850, 70855, 70864, 71040, 71096, 71103, 71128, 71168, 71230, 71231, 71236, 71248, 71296, 71340, 71341, 71342, 71351, 71352, 71360, 71376, 71424, 71453, 71454, 71455, 71456, 71463, 71472, 71488, 71680, 71737, 71840, 71935, 71945, 71948, 71957, 71960, 71991, 71995, 71998, 71999, 72000, 72001, 72002, 72003, 72016, 72096, 72106, 72154, 72161, 72164, 72192, 72193, 72250, 72251, 72263, 72272, 72273, 72344, 72349, 72368, 72544, 72545, 72546, 72550, 72551, 72640, 72688, 72704, 72714, 72752, 72760, 72767, 72768, 72784, 72818, 72850, 72873, 72874, 72882, 72885, 72960, 72968, 72971, 73018, 73023, 73031, 73040, 73056, 73063, 73066, 73104, 73107, 73110, 73111, 73112, 73120, 73136, 73178, 73184, 73440, 73472, 73475, 73476, 73490, 73534, 73537, 73538, 73552, 73648, 73728, 74752, 74880, 77712, 77824, 78912, 78913, 78944, 82944, 90368, 92160, 92736, 92768, 92784, 92864, 92880, 92912, 92928, 92992, 93008, 93027, 93053, 93504, 93552, 93568, 93593, 93600, 93760, 93856, 93883, 93952, 94031, 94032, 94033, 94095, 94176, 94179, 94180, 94192, 94208, 101631, 101760, 110576, 110581, 110589, 110592, 110898, 110928, 110933, 110948, 110960, 113664, 113776, 113792, 113808, 113821, 118000, 118528, 118576, 119141, 119149, 119163, 119173, 119210, 119362, 119808, 119894, 119966, 119970, 119973, 119977, 119982, 119995, 120005, 120071, 120077, 120086, 120094, 120123, 120128, 120134, 120138, 120146, 120488, 120514, 120540, 120572, 120598, 120630, 120656, 120688, 120714, 120746, 120772, 120782, 121344, 121403, 121461, 121476, 121499, 121505, 122624, 122635, 122661, 122880, 122888, 122907, 122915, 122918, 122928, 123023, 123136, 123184, 123200, 123214, 123536, 123584, 124112, 124140, 124368, 124401, 124608, 124640, 124644, 124647, 124670, 124671, 124896, 124904, 124909, 124912, 124928, 125136, 125184, 125264, 126464, 126469, 126497, 126500, 126503, 126516, 126521, 126530, 126535, 126545, 126548, 126551, 126564, 126567, 126572, 126580, 126585, 126590, 126603, 126625, 126629, 126635, 130032, 131072, 173824, 178208, 183984, 191472, 194560, 196608, 201552, 917760
|
||||
};
|
||||
|
||||
/* Lengths of identifier ranges for the supplementary plane */
|
||||
const uint16_t identRangeLengthSupplementaryPlane[446] = {
|
||||
11, 25, 18, 1, 14, 13, 122, 52, 1, 28, 48, 1, 31, 21, 9, 42, 29, 35, 7, 4, 157, 9, 35, 35, 39, 51, 10, 14, 6, 1, 10, 14, 6, 1, 51, 310, 21, 7, 5, 41, 8, 5, 45, 1, 1, 22, 22, 30, 18, 1, 21, 25, 25, 55, 1, 1, 2, 1, 7, 2, 28, 2, 1, 28, 28, 7, 29, 53, 21, 18, 17, 72, 50, 50, 39, 9, 15, 1, 21, 4, 1, 21, 41, 1, 1, 4, 1, 34, 1, 32, 21, 20, 22, 1, 1, 1, 67, 11, 5, 4, 55, 1, 24, 9, 45, 7, 9, 1, 3, 36, 1, 3, 65, 3, 1, 1, 13, 17, 34, 1, 1, 1, 3, 6, 5, 14, 9, 48, 10, 9, 3, 7, 1, 21, 6, 1, 4, 3, 3, 3, 1, 2, 1, 1, 6, 6, 4, 9, 1, 39, 1, 8, 1, 5, 3, 1, 1, 1, 1, 1, 1, 70, 1, 3, 9, 1, 2, 58, 1, 7, 3, 1, 9, 53, 7, 1, 5, 62, 1, 1, 1, 9, 44, 1, 1, 9, 1, 1, 9, 19, 26, 1, 1, 1, 7, 4, 9, 6, 57, 1, 73, 7, 1, 7, 1, 29, 1, 3, 1, 1, 1, 1, 1, 1, 9, 7, 45, 7, 3, 1, 1, 57, 1, 3, 1, 1, 71, 1, 1, 72, 1, 1, 4, 1, 1, 32, 9, 8, 38, 6, 7, 1, 1, 9, 29, 21, 1, 8, 3, 1, 6, 1, 43, 3, 8, 1, 9, 5, 1, 36, 1, 3, 1, 1, 1, 9, 42, 1, 9, 22, 3, 1, 12, 40, 3, 1, 1, 11, 1, 921, 110, 195, 96, 1071, 1, 20, 3994, 582, 57, 568, 30, 9, 78, 9, 29, 4, 54, 3, 9, 20, 18, 44, 9, 25, 5, 9, 63, 24, 24, 74, 1, 1, 54, 16, 1, 1, 1, 6, 7381, 31, 114, 3, 6, 1, 290, 1, 2, 1, 3, 395, 106, 12, 8, 9, 1, 9, 45, 22, 4, 5, 7, 6, 3, 2, 84, 70, 1, 1, 1, 3, 11, 8, 64, 3, 7, 6, 27, 3, 4, 1, 6, 339, 24, 24, 30, 24, 30, 24, 30, 24, 30, 24, 7, 49, 54, 49, 1, 1, 4, 14, 11, 19, 5, 6, 16, 6, 1, 4, 61, 1, 44, 13, 9, 1, 31, 57, 28, 13, 33, 9, 30, 4, 3, 15, 1, 1, 6, 3, 1, 14, 196, 6, 76, 9, 3, 26, 1, 1, 11, 3, 3, 1, 8, 1, 1, 11, 1, 3, 6, 3, 3, 11, 16, 2, 4, 16, 9, 42719, 4381, 5773, 7472, 621, 541, 4938, 8489, 239
|
||||
};
|
||||
const uint16_t basic_plane_length = sizeof(identRangeStart);
|
||||
const uint16_t supplementary_plane_length = sizeof(identRangeStartSupplementaryPlane);
|
||||
} // namespace EscargotLexer
|
||||
} // namespace Escargot
|
||||
|
|
@ -16,12 +16,6 @@
|
|||
* USA
|
||||
*/
|
||||
|
||||
|
||||
/* This file is automatically generated by the gen_unicode.py script
|
||||
* from https://www.unicode.org/Public/13.0.0/ucd/DerivedCoreProperties.txt
|
||||
* DO NOT EDIT!
|
||||
*/
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#ifndef __EscargotUnicodeIdentifierTables__
|
||||
|
|
@ -34,8 +28,9 @@ extern const uint16_t identRangeLength[];
|
|||
extern const uint16_t identRangeLongLength[];
|
||||
extern const uint32_t identRangeStartSupplementaryPlane[];
|
||||
extern const uint16_t identRangeLengthSupplementaryPlane[];
|
||||
extern const uint16_t basic_plane_length;
|
||||
extern const uint16_t supplementary_plane_length;
|
||||
extern const uint16_t basicPlaneLength;
|
||||
extern const uint16_t supplementaryPlaneLength;
|
||||
|
||||
} // namespace EscargotLexer
|
||||
} // namespace Escargot
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -311,11 +311,17 @@ struct ASTScopeContext {
|
|||
bool m_allowSuperProperty : 1;
|
||||
bool m_allowArguments : 1;
|
||||
bool m_needRareData : 1;
|
||||
#ifndef ESCARGOT_DEBUGGER
|
||||
bool m_hasStringArguments : 1;
|
||||
#endif
|
||||
unsigned int m_nodeType : 2; // it is actually NodeType but used on FunctionExpression, ArrowFunctionExpression and FunctionDeclaration only
|
||||
unsigned int m_functionLength : 16; // represent the number of consecutive identifier parameters from the start of parameter list (function length)
|
||||
unsigned int m_parameterCount : 16; // represent the number of parameter element nodes
|
||||
LexicalBlockIndex m_functionBodyBlockIndex : 16;
|
||||
LexicalBlockIndex m_lexicalBlockIndexFunctionLocatedIn : 16;
|
||||
#ifndef ESCARGOT_DEBUGGER
|
||||
uint16_t m_parameterUsed : 16;
|
||||
#endif
|
||||
ASTScopeContextNameInfoVector m_varNames;
|
||||
FunctionContextVarMap *m_varNamesMap;
|
||||
AtomicStringTightVector *m_classPrivateNames; // this is needed for direct eval in class & nested class
|
||||
|
|
@ -696,11 +702,17 @@ struct ASTScopeContext {
|
|||
, m_allowSuperProperty(false)
|
||||
, m_allowArguments(true)
|
||||
, m_needRareData(false)
|
||||
#ifndef ESCARGOT_DEBUGGER
|
||||
, m_hasStringArguments(false)
|
||||
#endif
|
||||
, m_nodeType(ASTNodeType::Program)
|
||||
, m_functionLength(0)
|
||||
, m_parameterCount(0)
|
||||
, m_functionBodyBlockIndex(0)
|
||||
, m_lexicalBlockIndexFunctionLocatedIn(LEXICAL_BLOCK_INDEX_MAX)
|
||||
#ifndef ESCARGOT_DEBUGGER
|
||||
, m_parameterUsed(0)
|
||||
#endif
|
||||
, m_varNamesMap(nullptr)
|
||||
, m_classPrivateNames(nullptr)
|
||||
, m_firstChild(nullptr)
|
||||
|
|
|
|||
|
|
@ -55,6 +55,17 @@ public:
|
|||
context->m_classInfo.m_prototypeIndex = context->getRegister();
|
||||
context->m_classInfo.m_superIndex = hasSuper ? context->getRegister() : SIZE_MAX;
|
||||
|
||||
// A class definition is always strict mode code. When the surrounding code
|
||||
// is not strict, force strict mode while the heritage expression and the
|
||||
// computed property names are evaluated. The save register is allocated last
|
||||
// (on top of the register stack) so the completion-value register is untouched.
|
||||
bool needStrictModeSwitch = !codeBlock->m_codeBlock->isStrict();
|
||||
ByteCodeRegisterIndex savedStrictRegister = REGISTER_LIMIT;
|
||||
if (needStrictModeSwitch) {
|
||||
savedStrictRegister = context->getRegister();
|
||||
codeBlock->pushCode(SetExecutionStateInStrictMode(ByteCodeLOC(m_loc.index), true, savedStrictRegister), context, this->m_loc.index);
|
||||
}
|
||||
|
||||
// add class name property if there is no 'name' static member
|
||||
if (m_class.classBody()->hasStaticMemberName(codeBlock->m_codeBlock->context()->staticStrings().name)) {
|
||||
context->m_classInfo.m_name = Optional<AtomicString>();
|
||||
|
|
@ -104,6 +115,11 @@ public:
|
|||
classIdent->generateStoreByteCode(codeBlock, context, classIndex, true);
|
||||
ASSERT(!context->m_isLexicallyDeclaredBindingInitialization);
|
||||
|
||||
if (needStrictModeSwitch) {
|
||||
codeBlock->pushCode(SetExecutionStateInStrictMode(ByteCodeLOC(m_loc.index), false, savedStrictRegister), context, this->m_loc.index);
|
||||
context->giveUpRegister(); // for drop savedStrictRegister (allocated last)
|
||||
}
|
||||
|
||||
if (context->m_classInfo.m_superIndex != SIZE_MAX) {
|
||||
context->giveUpRegister(); // for drop m_superIndex
|
||||
}
|
||||
|
|
|
|||
|
|
@ -48,6 +48,16 @@ public:
|
|||
Node* classIdent = m_class.id();
|
||||
bool hasSuper = m_class.superClass();
|
||||
|
||||
// A class definition is always strict mode code. When the surrounding code
|
||||
// is not strict, force strict mode while the heritage expression and the
|
||||
// computed property names are evaluated.
|
||||
bool needStrictModeSwitch = !codeBlock->m_codeBlock->isStrict();
|
||||
ByteCodeRegisterIndex savedStrictRegister = REGISTER_LIMIT;
|
||||
if (needStrictModeSwitch) {
|
||||
savedStrictRegister = context->getRegister();
|
||||
codeBlock->pushCode(SetExecutionStateInStrictMode(ByteCodeLOC(m_loc.index), true, savedStrictRegister), context, this->m_loc.index);
|
||||
}
|
||||
|
||||
const ClassContextInformation classInfoBefore = context->m_classInfo;
|
||||
context->m_classInfo.m_constructorIndex = dstIndex;
|
||||
context->m_classInfo.m_prototypeIndex = context->getRegister();
|
||||
|
|
@ -107,6 +117,11 @@ public:
|
|||
context->giveUpRegister(); // for drop m_bodyIndex
|
||||
|
||||
context->m_classInfo = classInfoBefore;
|
||||
|
||||
if (needStrictModeSwitch) {
|
||||
codeBlock->pushCode(SetExecutionStateInStrictMode(ByteCodeLOC(m_loc.index), false, savedStrictRegister), context, this->m_loc.index);
|
||||
context->giveUpRegister(); // for drop savedStrictRegister
|
||||
}
|
||||
}
|
||||
|
||||
virtual void iterateChildren(const std::function<void(Node* node)>& fn) override
|
||||
|
|
|
|||
|
|
@ -42,6 +42,8 @@ public:
|
|||
#endif /* ESCARGOT_DEBUGGER */
|
||||
|
||||
ByteCodeGenerateContext newContext(*context);
|
||||
// Do not let nested loops inherit this loop's labels. - Issue #1571/#1577
|
||||
newContext.m_currentLoopLabels.clear();
|
||||
|
||||
size_t doStart = codeBlock->currentCodeSize();
|
||||
if (context->shouldCareScriptExecutionResult()) {
|
||||
|
|
@ -66,6 +68,7 @@ public:
|
|||
|
||||
size_t doEnd = codeBlock->currentCodeSize();
|
||||
newContext.consumeContinuePositions(codeBlock, testPos, newContext.tryCatchWithBlockStatementCount());
|
||||
|
||||
newContext.consumeBreakPositions(codeBlock, doEnd, newContext.tryCatchWithBlockStatementCount());
|
||||
newContext.m_positionToContinue = testPos;
|
||||
newContext.propagateInformationTo(*context);
|
||||
|
|
|
|||
|
|
@ -162,6 +162,10 @@ public:
|
|||
}
|
||||
|
||||
ByteCodeGenerateContext newContext(*context);
|
||||
// The labels of this loop (collected in `context`) must not be inherited by
|
||||
// nested loops generated while emitting the head/body, otherwise an inner loop
|
||||
// would wrongly consume a `continue <label>` that targets this loop. - Issue #1571/#1577
|
||||
newContext.m_currentLoopLabels.clear();
|
||||
|
||||
newContext.getRegister(); // ExecutionResult of m_right should not overwrite any reserved value
|
||||
if (m_left->type() == ASTNodeType::VariableDeclaration) {
|
||||
|
|
@ -373,6 +377,16 @@ public:
|
|||
newContext.consumeContinuePositions(codeBlock, continuePosition, newContext.tryCatchWithBlockStatementCount());
|
||||
newContext.m_positionToContinue = continuePosition;
|
||||
|
||||
// A `continue <label>` whose label targets THIS loop is semantically identical
|
||||
// to an unlabeled `continue`, so resolve it to `continuePosition` here, before the
|
||||
// iterator-cleanup try block of for-of is registered as a complex case. Otherwise
|
||||
// the labeled continue would be morphed into a JumpComplexCase that unwinds the
|
||||
// for-of try (wrongly closing the iterator and corrupting the result register),
|
||||
// which crashed `L: for (const v of [...]) { continue L; }`. - Issue #1571/#1577
|
||||
for (size_t i = 0; i < context->m_currentLoopLabels.size(); i++) {
|
||||
newContext.consumeLabelledContinuePositions(codeBlock, continuePosition, context->m_currentLoopLabels[i], newContext.tryCatchWithBlockStatementCount());
|
||||
}
|
||||
|
||||
if (!m_forIn) {
|
||||
TryStatementNode::generateTryStatementBodyEndByteCode(codeBlock, &newContext, this, forOfTryStatementContext);
|
||||
TryStatementNode::generateTryFinalizerStatementStartByteCode(codeBlock, &newContext, this, forOfTryStatementContext, true);
|
||||
|
|
|
|||
|
|
@ -64,6 +64,8 @@ public:
|
|||
}
|
||||
|
||||
ByteCodeGenerateContext newContext(*context);
|
||||
// Do not let nested loops inherit this loop's labels. - Issue #1571/#1577
|
||||
newContext.m_currentLoopLabels.clear();
|
||||
|
||||
newContext.getRegister(); // ExeuctionResult of m_body should not be overwritten by m_test
|
||||
|
||||
|
|
|
|||
|
|
@ -107,7 +107,7 @@ public:
|
|||
context->addLexicallyDeclaredNames(m_name);
|
||||
}
|
||||
|
||||
if (context->m_inParameterInitialization && codeBlock->m_codeBlock->hasParameterName(m_name)) {
|
||||
if (context->m_inParameterInitialization && codeBlock->m_codeBlock->isParameterName(m_name)) {
|
||||
context->addInitializedParameterNames(m_name);
|
||||
}
|
||||
|
||||
|
|
@ -205,7 +205,7 @@ public:
|
|||
codeBlock->pushCode(EnsureArgumentsObject(ByteCodeLOC(m_loc.index)), context, this->m_loc.index);
|
||||
}
|
||||
|
||||
if (context->m_inParameterInitialization && codeBlock->m_codeBlock->hasParameterName(m_name)) {
|
||||
if (context->m_inParameterInitialization && codeBlock->m_codeBlock->isParameterName(m_name)) {
|
||||
addParameterReferenceErrorIfNeeds(codeBlock, context);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -38,7 +38,12 @@ public:
|
|||
{
|
||||
size_t start = codeBlock->currentCodeSize();
|
||||
context->m_positionToContinue = start;
|
||||
// Make this label visible to the loop that this labeled statement (possibly
|
||||
// through a chain of labels) directly wraps, so the loop can resolve a
|
||||
// `continue <label>` targeting itself just like an unlabeled continue. - Issue #1571/#1577
|
||||
context->m_currentLoopLabels.push_back(m_label);
|
||||
m_statementNode->generateStatementByteCode(codeBlock, context);
|
||||
context->m_currentLoopLabels.pop_back();
|
||||
size_t end = codeBlock->currentCodeSize();
|
||||
context->consumeLabelledBreakPositions(codeBlock, end, m_label, context->tryCatchWithBlockStatementCount());
|
||||
context->consumeLabelledContinuePositions(codeBlock, context->m_positionToContinue, m_label, context->tryCatchWithBlockStatementCount());
|
||||
|
|
|
|||
|
|
@ -54,6 +54,7 @@ class ImportNamespaceSpecifierNode;
|
|||
class ExportSpecifierNode;
|
||||
class ArrowParameterPlaceHolderNode;
|
||||
class VariableDeclarationNode;
|
||||
class RestElementNode;
|
||||
|
||||
class Node {
|
||||
protected:
|
||||
|
|
@ -164,6 +165,17 @@ public:
|
|||
return (ObjectExpressionNode*)this;
|
||||
}
|
||||
|
||||
bool isRestElement()
|
||||
{
|
||||
return type() == ASTNodeType::RestElement;
|
||||
}
|
||||
|
||||
ALWAYS_INLINE RestElementNode* asRestElement()
|
||||
{
|
||||
ASSERT(isRestElement());
|
||||
return (RestElementNode*)this;
|
||||
}
|
||||
|
||||
bool isProperty()
|
||||
{
|
||||
return type() == ASTNodeType::Property;
|
||||
|
|
|
|||
|
|
@ -60,6 +60,16 @@ public:
|
|||
size_t objectCreationDataRegisterSize = 0;
|
||||
size_t initCodePosition = codeBlock->currentCodeSize();
|
||||
|
||||
bool needsToUseBigPropertyFilterOnInterpreter = m_properties.size() >= 512;
|
||||
bool needsToUseNameFilterOnInterpreter = !needsToUseBigPropertyFilterOnInterpreter && m_properties.size() >= 6;
|
||||
if (needsToUseNameFilterOnInterpreter || needsToUseBigPropertyFilterOnInterpreter) {
|
||||
for (SentinelNode* property = m_properties.begin(); property != m_properties.end(); property = property->next()) {
|
||||
if (!property->astNode()->isProperty()) {
|
||||
needsToUseBigPropertyFilterOnInterpreter = needsToUseNameFilterOnInterpreter = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (m_properties.size()) {
|
||||
objectCreationDataIndex = context->getRegister();
|
||||
objectCreationDataRegisterSize = 1;
|
||||
|
|
@ -70,9 +80,17 @@ public:
|
|||
objectCreationDataRegisterSize++;
|
||||
context->getRegister();
|
||||
}
|
||||
if (needsToUseNameFilterOnInterpreter) {
|
||||
for (size_t i = 0; i < sizeof(CreateObjectPrepare::CreateObjectPropertyFilter); i += sizeof(Value)) {
|
||||
objectCreationDataRegisterSize++;
|
||||
context->getRegister();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
codeBlock->pushCode(CreateObjectPrepare(ByteCodeLOC(m_loc.index), objectCreationDataIndex, dstRegister), context, this->m_loc.index);
|
||||
codeBlock->pushCode(CreateObjectPrepare(ByteCodeLOC(m_loc.index), objectCreationDataIndex, dstRegister,
|
||||
needsToUseNameFilterOnInterpreter, needsToUseBigPropertyFilterOnInterpreter),
|
||||
context, this->m_loc.index);
|
||||
}
|
||||
|
||||
bool allPrecomputed = true;
|
||||
|
|
|
|||
|
|
@ -101,14 +101,18 @@ public:
|
|||
size_t valueIndex = context->getRegister();
|
||||
size_t propertyIndex = m_key->getRegister(codeBlock, context);
|
||||
m_key->generateExpressionByteCode(codeBlock, context, propertyIndex);
|
||||
codeBlock->pushCode(GetObject(ByteCodeLOC(m_loc.index), srcRegister, propertyIndex, valueIndex), context, this->m_loc.index);
|
||||
size_t toPropertyKeyIndex = context->getRegister();
|
||||
codeBlock->pushCode(ToPropertyKey(ByteCodeLOC(m_loc.index), propertyIndex, toPropertyKeyIndex), context, this->m_loc.index);
|
||||
m_value->generateResolveAddressByteCode(codeBlock, context);
|
||||
codeBlock->pushCode(GetObject(ByteCodeLOC(m_loc.index), srcRegister, toPropertyKeyIndex, valueIndex), context, this->m_loc.index);
|
||||
m_value->generateStoreByteCode(codeBlock, context, valueIndex, false);
|
||||
|
||||
|
||||
if (enumDataIndex != REGISTER_LIMIT) {
|
||||
codeBlock->pushCode(MarkEnumerateKey(ByteCodeLOC(m_loc.index), enumDataIndex, propertyIndex), context, this->m_loc.index);
|
||||
codeBlock->pushCode(MarkEnumerateKey(ByteCodeLOC(m_loc.index), enumDataIndex, toPropertyKeyIndex), context, this->m_loc.index);
|
||||
}
|
||||
context->giveUpRegister(); // for drop propertyIndex
|
||||
context->giveUpRegister(); // for drop toPropertyIndex
|
||||
context->giveUpRegister(); // for drop valueIndex
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -53,6 +53,11 @@ public:
|
|||
m_discriminant->generateExpressionByteCode(codeBlock, &newContext, rIndex0);
|
||||
newContext.m_canSkipCopyToRegister = canSkipCopyToRegister;
|
||||
|
||||
// Whether the discriminant occupies its own freshly-allocated register (rather than
|
||||
// reusing an existing variable's register). Captured here, before pushLexicalBlock
|
||||
// may allocate a disposable-record register (for `using`) on top of it. - Issue #1577
|
||||
bool discriminantHasOwnRegister = (rIndex0 == newContext.getLastRegisterIndex());
|
||||
|
||||
if (firstRegister == 0 && context->shouldCareScriptExecutionResult()) {
|
||||
codeBlock->pushCode(LoadLiteral(ByteCodeLOC(m_loc.index), 0, Value()), context, this->m_loc.index);
|
||||
}
|
||||
|
|
@ -65,6 +70,14 @@ public:
|
|||
blockContext = codeBlock->pushLexicalBlock(&newContext, bi, this);
|
||||
}
|
||||
|
||||
// When the switch block declares a `using` variable, pushLexicalBlock allocates a
|
||||
// disposable-record register that lives until finalizeLexicalBlock. It sits on the
|
||||
// register stack above the discriminant temporaries, so those temporaries can only be
|
||||
// released (LIFO) after the disposable register is popped in finalizeLexicalBlock.
|
||||
// Releasing them earlier (the default path below) would free the disposable register
|
||||
// itself, letting a statement in a case body clobber it. - Issue #1577
|
||||
bool hasUsingBlock = (m_lexicalBlockIndex != LEXICAL_BLOCK_INDEX_MAX) && (blockContext.usingBlockTryStartPosition != SIZE_MAX);
|
||||
|
||||
std::vector<size_t> jumpCodePerCaseNodePosition;
|
||||
StatementNode* nd = m_casesB->firstChild();
|
||||
while (nd) {
|
||||
|
|
@ -95,9 +108,11 @@ public:
|
|||
nd = nd->nextSibling();
|
||||
}
|
||||
|
||||
newContext.giveUpRegister();
|
||||
if (registerWasSame) {
|
||||
if (!hasUsingBlock) {
|
||||
newContext.giveUpRegister();
|
||||
if (registerWasSame) {
|
||||
newContext.giveUpRegister();
|
||||
}
|
||||
}
|
||||
|
||||
size_t jmpToDefault = SIZE_MAX;
|
||||
|
|
@ -134,6 +149,16 @@ public:
|
|||
newContext.m_lexicalBlockIndex = lexicalBlockIndexBefore;
|
||||
}
|
||||
|
||||
// Deferred release of the discriminant temporaries for the `using` case (see the
|
||||
// comment where hasUsingBlock is computed). finalizeLexicalBlock above has already
|
||||
// popped the disposable-record register, so these are now back on top. - Issue #1577
|
||||
if (hasUsingBlock) {
|
||||
newContext.giveUpRegister();
|
||||
if (discriminantHasOwnRegister) {
|
||||
newContext.giveUpRegister();
|
||||
}
|
||||
}
|
||||
|
||||
newContext.propagateInformationTo(*context);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -48,6 +48,8 @@ public:
|
|||
}
|
||||
|
||||
ByteCodeGenerateContext newContext(*context);
|
||||
// Do not let nested loops inherit this loop's labels. - Issue #1571/#1577
|
||||
newContext.m_currentLoopLabels.clear();
|
||||
|
||||
newContext.getRegister(); // ExeuctionResult of m_body should not be overwritten by m_test
|
||||
|
||||
|
|
@ -80,6 +82,7 @@ public:
|
|||
|
||||
codeBlock->pushCode(Jump(ByteCodeLOC(m_loc.index), whileStart), &newContext, this->m_loc.index);
|
||||
newContext.consumeContinuePositions(codeBlock, whileStart, newContext.tryCatchWithBlockStatementCount());
|
||||
|
||||
size_t whileEnd = codeBlock->currentCodeSize();
|
||||
newContext.consumeBreakPositions(codeBlock, whileEnd, newContext.tryCatchWithBlockStatementCount());
|
||||
if (testPos != SIZE_MAX)
|
||||
|
|
|
|||
|
|
@ -81,6 +81,7 @@ public:
|
|||
static constexpr const char* ParameterAfterRestParameter = "Rest parameter must be last formal parameter";
|
||||
static constexpr const char* DefaultRestParameter = "Unexpected token =";
|
||||
static constexpr const char* ObjectPatternAsRestParameter = "Unexpected token {";
|
||||
static constexpr const char* RestElementMustBeFollowedByIdentifier = "`...` must be followed by an identifier in declaration contexts";
|
||||
static constexpr const char* DuplicateProtoProperty = "Duplicate __proto__ fields are not allowed in object literals";
|
||||
static constexpr const char* ConstructorSpecialMethod = "Class constructor may not be an accessor";
|
||||
static constexpr const char* ConstructorGenerator = "Class constructor may not be a generator";
|
||||
|
|
|
|||
|
|
@ -317,8 +317,46 @@ public:
|
|||
}
|
||||
}
|
||||
|
||||
#ifndef ESCARGOT_DEBUGGER
|
||||
void checkUsedParameters(ASTScopeContext* scopeCtx)
|
||||
{
|
||||
AtomicStringTightVector& parameters = this->currentScopeContext->m_parameters;
|
||||
|
||||
for (size_t i = 0; i < parameters.size(); i++) {
|
||||
AtomicString& name = parameters[i];
|
||||
for (size_t j = 0; j < scopeCtx->m_childBlockScopes.size(); j++) {
|
||||
ASTBlockContext& blockCtx = *scopeCtx->m_childBlockScopes[j];
|
||||
if (VectorUtil::findInVector(blockCtx.m_usingNames, name) != VectorUtil::invalidIndex) {
|
||||
this->currentScopeContext->m_parameterUsed |= (1 << i);
|
||||
break;
|
||||
} else if (UNLIKELY(scopeCtx->m_parameterUsed == DISABLE_PARAM_CHECK)) {
|
||||
this->currentScopeContext->m_parameterUsed = DISABLE_PARAM_CHECK;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// check nested functions
|
||||
size_t childCount = scopeCtx->childCount();
|
||||
if (childCount > 0) {
|
||||
ASTScopeContext* child = scopeCtx->firstChild();
|
||||
for (size_t i = 0; i < childCount; i++) {
|
||||
checkUsedParameters(child);
|
||||
child = child->nextSibling();
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
ASTScopeContext* popScopeContext(ASTScopeContext* lastPushedScopeContext)
|
||||
{
|
||||
#ifndef ESCARGOT_DEBUGGER
|
||||
if (UNLIKELY(this->currentScopeContext->m_hasEval || this->currentScopeContext->m_hasStringArguments || this->currentScopeContext->m_parameters.size() > 16)) {
|
||||
this->currentScopeContext->m_parameterUsed = DISABLE_PARAM_CHECK;
|
||||
} else {
|
||||
checkUsedParameters(this->currentScopeContext);
|
||||
}
|
||||
#endif
|
||||
auto ret = this->currentScopeContext;
|
||||
this->lastUsingName = AtomicString();
|
||||
this->lastPoppedScopeContext = ret;
|
||||
|
|
@ -1040,6 +1078,12 @@ public:
|
|||
}
|
||||
}
|
||||
|
||||
#ifndef ESCARGOT_DEBUGGER
|
||||
if (UNLIKELY(ret->asIdentifier()->name() == this->stringArguments)) {
|
||||
this->currentScopeContext->m_hasStringArguments = true;
|
||||
}
|
||||
#endif
|
||||
|
||||
if (this->trackUsingNames) {
|
||||
this->insertUsingName(ret->asIdentifier()->name());
|
||||
}
|
||||
|
|
@ -1307,10 +1351,15 @@ public:
|
|||
}
|
||||
|
||||
template <class ASTBuilder>
|
||||
ASTNode parseBindingRestElement(ASTBuilder& builder, SmallScannerResultVector& params, KeywordKind kind = KeywordKindEnd, bool isExplicitVariableDeclaration = false)
|
||||
ASTNode parseBindingRestElement(ASTBuilder& builder, SmallScannerResultVector& params, KeywordKind kind = KeywordKindEnd, bool isExplicitVariableDeclaration = false, bool restrictToIdentifier = false)
|
||||
{
|
||||
MetaNode node = this->createNode();
|
||||
this->nextToken();
|
||||
// BindingRestProperty (object binding pattern rest) only accepts a BindingIdentifier,
|
||||
// unlike BindingRestElement (array binding pattern rest) which also accepts a BindingPattern.
|
||||
if (restrictToIdentifier && (this->match(LeftBrace) || this->match(LeftSquareBracket))) {
|
||||
this->throwError(Messages::RestElementMustBeFollowedByIdentifier);
|
||||
}
|
||||
ASTNode arg = this->parsePattern(builder, params, kind, isExplicitVariableDeclaration);
|
||||
return this->finalize(node, builder.createRestElementNode(arg));
|
||||
}
|
||||
|
|
@ -1415,7 +1464,7 @@ public:
|
|||
while (!this->match(RightBrace)) {
|
||||
if (this->match(PeriodPeriodPeriod)) {
|
||||
hasRestElement = true;
|
||||
properties.append(this->allocator, this->parseBindingRestElement(builder, params, kind, isExplicitVariableDeclaration));
|
||||
properties.append(this->allocator, this->parseBindingRestElement(builder, params, kind, isExplicitVariableDeclaration, true));
|
||||
break;
|
||||
} else {
|
||||
properties.append(this->allocator, this->parsePropertyPattern(builder, params, kind, isExplicitVariableDeclaration));
|
||||
|
|
@ -2525,6 +2574,16 @@ public:
|
|||
|
||||
bool hasOptional = false;
|
||||
while (true) {
|
||||
// If the just-parsed base carries a pending CoverInitializedName (e.g. `{ a = 0 }`)
|
||||
// and we are about to apply a member access / call / computed access / tagged
|
||||
// template, the base is being used as a real value and can no longer be refined
|
||||
// into a destructuring pattern. This is an early error (e.g. `({ a = 0 }.b = 1)`).
|
||||
if (UNLIKELY(static_cast<bool>(this->context->firstCoverInitializedNameError))) {
|
||||
if (this->match(GuessDot) || this->match(LeftParenthesis) || this->match(LeftSquareBracket) || this->match(Period)
|
||||
|| (this->lookahead.type == Token::TemplateToken && this->lookahead.valueTemplate->head)) {
|
||||
this->throwUnexpectedToken(this->context->firstCoverInitializedNameError);
|
||||
}
|
||||
}
|
||||
bool optional = false;
|
||||
if (this->match(GuessDot)) {
|
||||
Marker startMarker = this->startMarker;
|
||||
|
|
@ -2697,6 +2756,16 @@ public:
|
|||
this->throwUnexpectedToken(this->lookahead);
|
||||
}
|
||||
|
||||
// A pending CoverInitializedName (e.g. `{ a = 0 }`) cannot be the base of a
|
||||
// member access / computed access / tagged template: the base is used as a
|
||||
// real value, so the CoverInitializedName is an early error here.
|
||||
if (UNLIKELY(static_cast<bool>(this->context->firstCoverInitializedNameError))) {
|
||||
if (this->match(LeftSquareBracket) || this->match(Period)
|
||||
|| (this->lookahead.type == Token::TemplateToken && this->lookahead.valueTemplate->head)) {
|
||||
this->throwUnexpectedToken(this->context->firstCoverInitializedNameError);
|
||||
}
|
||||
}
|
||||
|
||||
if (this->match(LeftSquareBracket)) {
|
||||
this->context->isBindingElement = false;
|
||||
this->context->isAssignmentTarget = false;
|
||||
|
|
@ -5032,7 +5101,18 @@ public:
|
|||
Node* param = this->parseFormalParameter(builder, options);
|
||||
|
||||
switch (param->type()) {
|
||||
case Identifier:
|
||||
case Identifier: {
|
||||
#ifndef ESCARGOT_DEBUGGER
|
||||
if (this->codeBlock->parameterUsed() & (1 << paramIndex) || this->codeBlock->parameterUsed() == DISABLE_PARAM_CHECK) {
|
||||
#endif
|
||||
Node* init = this->finalize(node, builder.createInitializeParameterExpressionNode(param, paramIndex));
|
||||
Node* statement = this->finalize(node, builder.createExpressionStatementNode(init));
|
||||
container->appendChild(statement);
|
||||
#ifndef ESCARGOT_DEBUGGER
|
||||
}
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
case AssignmentPattern:
|
||||
case ArrayPattern:
|
||||
case ObjectPattern: {
|
||||
|
|
@ -5042,8 +5122,14 @@ public:
|
|||
break;
|
||||
}
|
||||
case RestElement: {
|
||||
Node* statement = this->finalize(node, builder.createExpressionStatementNode(param));
|
||||
container->appendChild(statement);
|
||||
#ifndef ESCARGOT_DEBUGGER
|
||||
if (this->codeBlock->parameterUsed() & (1 << paramIndex) || this->codeBlock->parameterUsed() == DISABLE_PARAM_CHECK || param->asRestElement()->argument()->type() != Identifier) {
|
||||
#endif
|
||||
Node* statement = this->finalize(node, builder.createExpressionStatementNode(param));
|
||||
container->appendChild(statement);
|
||||
#ifndef ESCARGOT_DEBUGGER
|
||||
}
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
|
|
@ -6811,7 +6897,7 @@ public:
|
|||
this->throwUnexpectedToken(this->lookahead);
|
||||
}
|
||||
this->consumeSemicolon();
|
||||
} else if (this->lookahead.type == Token::KeywordToken) {
|
||||
} else if (this->lookahead.type == Token::KeywordToken || this->matchAsyncFunction()) {
|
||||
// export var f = 1;
|
||||
auto oldNameCallback = this->nameDeclaredCallback;
|
||||
AtomicStringVector declaredNames;
|
||||
|
|
@ -6827,19 +6913,24 @@ public:
|
|||
}
|
||||
AtomicStringVector declaredName;
|
||||
ASTNode declaration = nullptr;
|
||||
switch (this->lookahead.valueKeywordKind) {
|
||||
case KeywordKind::LetKeyword:
|
||||
case KeywordKind::ConstKeyword:
|
||||
declaration = this->parseLexicalDeclaration(builder, false);
|
||||
break;
|
||||
case KeywordKind::VarKeyword:
|
||||
case KeywordKind::ClassKeyword:
|
||||
case KeywordKind::FunctionKeyword:
|
||||
declaration = this->parseStatementListItem(builder);
|
||||
break;
|
||||
default:
|
||||
this->throwUnexpectedToken(this->lookahead);
|
||||
break;
|
||||
|
||||
if (this->lookahead.type == Token::KeywordToken) {
|
||||
switch (this->lookahead.valueKeywordKind) {
|
||||
case KeywordKind::LetKeyword:
|
||||
case KeywordKind::ConstKeyword:
|
||||
declaration = this->parseLexicalDeclaration(builder, false);
|
||||
break;
|
||||
case KeywordKind::VarKeyword:
|
||||
case KeywordKind::ClassKeyword:
|
||||
case KeywordKind::FunctionKeyword:
|
||||
declaration = this->parseStatementListItem(builder);
|
||||
break;
|
||||
default:
|
||||
this->throwUnexpectedToken(this->lookahead);
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
declaration = this->parseFunctionDeclaration(builder);
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < declaredNames.size(); i++) {
|
||||
|
|
@ -6852,9 +6943,6 @@ public:
|
|||
}
|
||||
exportDeclaration = this->finalize(node, builder.createExportNamedDeclarationNode(declaration, ASTNodeList(), nullptr));
|
||||
this->nameDeclaredCallback = oldNameCallback;
|
||||
} else if (this->matchAsyncFunction()) {
|
||||
ASTNode declaration = this->parseFunctionDeclaration(builder);
|
||||
exportDeclaration = this->finalize(node, builder.createExportNamedDeclarationNode(declaration, ASTNodeList(), nullptr));
|
||||
} else {
|
||||
ASTNodeList specifiers;
|
||||
ASTNode source = nullptr;
|
||||
|
|
|
|||
|
|
@ -55,6 +55,9 @@ struct Error : public gc {
|
|||
};
|
||||
|
||||
#define ESPRIMA_RECURSIVE_LIMIT 1024
|
||||
#ifndef ESCARGOT_DEBUGGER
|
||||
#define DISABLE_PARAM_CHECK 0xFFFF
|
||||
#endif
|
||||
|
||||
ProgramNode* parseProgram(::Escargot::Context* ctx, StringView source, ASTClassInfo* outerClassInfo,
|
||||
bool isModule, bool strictFromOutside, bool inWith, bool allowSuperCallFromOutside,
|
||||
|
|
|
|||
|
|
@ -49,6 +49,7 @@ public:
|
|||
explicit ArrayBuffer(ExecutionState& state, Object* proto)
|
||||
: DerivedObject(state, proto, ESCARGOT_OBJECT_BUILTIN_PROPERTY_NUMBER)
|
||||
, BufferAddressObserverManager<ArrayBuffer>()
|
||||
, m_isResizable(false)
|
||||
{
|
||||
}
|
||||
|
||||
|
|
@ -92,15 +93,17 @@ public:
|
|||
|
||||
ALWAYS_INLINE bool isDetachedBuffer()
|
||||
{
|
||||
return (data() == nullptr);
|
||||
return !m_backingStore.hasValue();
|
||||
}
|
||||
|
||||
// https://tc39.es/ecma262/#sec-isfixedlengtharraybuffer
|
||||
ALWAYS_INLINE bool isFixedLengthArrayBuffer() const
|
||||
{
|
||||
return !isResizableArrayBuffer();
|
||||
}
|
||||
ALWAYS_INLINE bool isResizableArrayBuffer() const
|
||||
{
|
||||
if (LIKELY(m_backingStore)) {
|
||||
return m_backingStore->isResizable();
|
||||
}
|
||||
return false;
|
||||
return m_isResizable;
|
||||
}
|
||||
|
||||
ALWAYS_INLINE void throwTypeErrorIfDetached(ExecutionState& state)
|
||||
|
|
@ -128,11 +131,21 @@ protected:
|
|||
if (m_backingStore) {
|
||||
m_backingStore->addObserver(this, backingStoreObserver);
|
||||
bufferUpdated(m_backingStore->data(), m_backingStore->byteLength());
|
||||
m_isResizable = m_backingStore->isResizable();
|
||||
} else {
|
||||
bufferUpdated(nullptr, 0);
|
||||
// no m_isResizable update here
|
||||
}
|
||||
}
|
||||
|
||||
// We needs m_isResizable because
|
||||
// IsResizableArrayBuffer ( arrayBuffer )
|
||||
// 1. Assert: Type(arrayBuffer) is Object and arrayBuffer has an
|
||||
// [[ArrayBufferData]] internal slot.
|
||||
// 2. If buffer has an [[ArrayBufferMaxByteLength]] internal slot, return true.
|
||||
// 3. Return false.
|
||||
|
||||
bool m_isResizable;
|
||||
Optional<BackingStore*> m_backingStore;
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -158,21 +158,26 @@ Value AsyncGeneratorObject::asyncGeneratorResumeNext(ExecutionState& state, Asyn
|
|||
// Set generator.[[AsyncGeneratorState]] to "awaiting-return".
|
||||
generator->m_asyncGeneratorState = AsyncGeneratorObject::AwaitingReturn;
|
||||
// Let promise be ? PromiseResolve(%Promise%, « completion.[[Value]] »).
|
||||
auto promise = PromiseObject::promiseResolve(state, state.context()->globalObject()->promise(), next.m_value)->asPromiseObject();
|
||||
// Let stepsFulfilled be the algorithm steps defined in AsyncGeneratorResumeNext Return Processor Fulfilled Functions.
|
||||
// Let onFulfilled be CreateBuiltinFunction(stepsFulfilled, « [[Generator]] »).
|
||||
// Set onFulfilled.[[Generator]] to generator.
|
||||
ExtendedNativeFunctionObject* onFulfilled = new ExtendedNativeFunctionObjectImpl<1>(state, NativeFunctionInfo(AtomicString(), asyncGeneratorResumeNextReturnProcessorFulfilledFunction, 1));
|
||||
onFulfilled->setInternalSlot(AsyncGeneratorObject::BuiltinFunctionSlot::Generator, generator);
|
||||
// Let stepsRejected be the algorithm steps defined in AsyncGeneratorResumeNext Return Processor Rejected Functions.
|
||||
// Let onRejected be CreateBuiltinFunction(stepsRejected, « [[Generator]] »).
|
||||
// Set onRejected.[[Generator]] to generator.
|
||||
ExtendedNativeFunctionObject* onRejected = new ExtendedNativeFunctionObjectImpl<1>(state, NativeFunctionInfo(AtomicString(), asyncGeneratorResumeNextReturnProcessorRejectedFunction, 1));
|
||||
onRejected->setInternalSlot(AsyncGeneratorObject::BuiltinFunctionSlot::Generator, generator);
|
||||
// Perform ! PerformPromiseThen(promise, onFulfilled, onRejected).
|
||||
promise->then(state, onFulfilled, onRejected);
|
||||
// Return undefined.
|
||||
return Value();
|
||||
try {
|
||||
auto promise = PromiseObject::promiseResolve(state, state.context()->globalObject()->promise(), next.m_value)->asPromiseObject();
|
||||
// Let stepsFulfilled be the algorithm steps defined in AsyncGeneratorResumeNext Return Processor Fulfilled Functions.
|
||||
// Let onFulfilled be CreateBuiltinFunction(stepsFulfilled, « [[Generator]] »).
|
||||
// Set onFulfilled.[[Generator]] to generator.
|
||||
ExtendedNativeFunctionObject* onFulfilled = new ExtendedNativeFunctionObjectImpl<1>(state, NativeFunctionInfo(AtomicString(), asyncGeneratorResumeNextReturnProcessorFulfilledFunction, 1));
|
||||
onFulfilled->setInternalSlot(AsyncGeneratorObject::BuiltinFunctionSlot::Generator, generator);
|
||||
// Let stepsRejected be the algorithm steps defined in AsyncGeneratorResumeNext Return Processor Rejected Functions.
|
||||
// Let onRejected be CreateBuiltinFunction(stepsRejected, « [[Generator]] »).
|
||||
// Set onRejected.[[Generator]] to generator.
|
||||
ExtendedNativeFunctionObject* onRejected = new ExtendedNativeFunctionObjectImpl<1>(state, NativeFunctionInfo(AtomicString(), asyncGeneratorResumeNextReturnProcessorRejectedFunction, 1));
|
||||
onRejected->setInternalSlot(AsyncGeneratorObject::BuiltinFunctionSlot::Generator, generator);
|
||||
// Perform ! PerformPromiseThen(promise, onFulfilled, onRejected).
|
||||
promise->then(state, onFulfilled, onRejected);
|
||||
// Return undefined.
|
||||
return Value();
|
||||
} catch (const Value& error) {
|
||||
AsyncGeneratorObject::asyncGeneratorReject(state, generator, error);
|
||||
return Value();
|
||||
}
|
||||
} else {
|
||||
// Assert: completion.[[Type]] is throw.
|
||||
// Perform ! AsyncGeneratorReject(generator, completion.[[Value]]).
|
||||
|
|
|
|||
|
|
@ -29,6 +29,12 @@ BigIntData::~BigIntData()
|
|||
bf_delete(&m_data);
|
||||
}
|
||||
|
||||
BigIntData::BigIntData(BigInt* src)
|
||||
{
|
||||
bf_init(ThreadLocal::bfContext(), &m_data);
|
||||
bf_set(&m_data, src->bf());
|
||||
}
|
||||
|
||||
BigIntData::BigIntData(BigIntData&& src)
|
||||
{
|
||||
bf_init(src.m_data.ctx, &m_data);
|
||||
|
|
@ -66,6 +72,17 @@ BigIntData::BigIntData(const uint64_t& d)
|
|||
bf_set_ui(&m_data, d);
|
||||
}
|
||||
|
||||
BigIntData::BigIntData(const Int128& d)
|
||||
{
|
||||
if (d < std::numeric_limits<int64_t>::max() && d > std::numeric_limits<int64_t>::min()) {
|
||||
bf_init(ThreadLocal::bfContext(), &m_data);
|
||||
bf_set_si(&m_data, int64_t(d));
|
||||
} else {
|
||||
auto s = std::to_string(d);
|
||||
init(s.data(), s.length(), 10);
|
||||
}
|
||||
}
|
||||
|
||||
BigIntData::BigIntData(String* src, int radix)
|
||||
{
|
||||
const auto& bd = src->bufferAccessData();
|
||||
|
|
@ -131,81 +148,68 @@ void BigIntData::init(const char* buf, size_t length, int radix)
|
|||
}
|
||||
}
|
||||
|
||||
void BigIntData::addition(const int64_t& d)
|
||||
BigIntData BigIntData::addition(const int64_t& d) const
|
||||
{
|
||||
bf_t r;
|
||||
bf_init(ThreadLocal::bfContext(), &r);
|
||||
int ret = bf_add_si(&r, &m_data, d, BF_PREC_INF, BF_RNDZ);
|
||||
BigIntData result;
|
||||
int ret = bf_add_si(&result.m_data, &m_data, d, BF_PREC_INF, BF_RNDZ);
|
||||
if (UNLIKELY(ret)) {
|
||||
RELEASE_ASSERT_NOT_REACHED();
|
||||
}
|
||||
bf_set(&m_data, &r);
|
||||
bf_delete(&r);
|
||||
return result;
|
||||
}
|
||||
|
||||
void BigIntData::addition(const BigIntData& src)
|
||||
BigIntData BigIntData::addition(const BigIntData& src) const
|
||||
{
|
||||
bf_t r;
|
||||
bf_init(ThreadLocal::bfContext(), &r);
|
||||
int ret = bf_add(&r, &m_data, &src.m_data, BF_PREC_INF, BF_RNDZ);
|
||||
BigIntData result;
|
||||
int ret = bf_add(&result.m_data, &m_data, &src.m_data, BF_PREC_INF, BF_RNDZ);
|
||||
if (UNLIKELY(ret)) {
|
||||
RELEASE_ASSERT_NOT_REACHED();
|
||||
}
|
||||
bf_set(&m_data, &r);
|
||||
bf_delete(&r);
|
||||
return result;
|
||||
}
|
||||
|
||||
void BigIntData::multiply(const int64_t& d)
|
||||
BigIntData BigIntData::multiply(const int64_t& d) const
|
||||
{
|
||||
bf_t r;
|
||||
bf_init(ThreadLocal::bfContext(), &r);
|
||||
int ret = bf_mul_si(&r, &m_data, d, BF_PREC_INF, BF_RNDZ);
|
||||
BigIntData result;
|
||||
int ret = bf_mul_si(&result.m_data, &m_data, d, BF_PREC_INF, BF_RNDZ);
|
||||
if (UNLIKELY(ret)) {
|
||||
RELEASE_ASSERT_NOT_REACHED();
|
||||
}
|
||||
bf_set(&m_data, &r);
|
||||
bf_delete(&r);
|
||||
return result;
|
||||
}
|
||||
|
||||
void BigIntData::division(const int64_t& d)
|
||||
BigIntData BigIntData::division(const int64_t& d, size_t prec) const
|
||||
{
|
||||
int64_t a;
|
||||
if (bf_get_int64(&a, &m_data, BF_GET_INT_MOD) == 0) {
|
||||
bf_set_si(&m_data, a / d);
|
||||
return;
|
||||
}
|
||||
BigIntData result;
|
||||
|
||||
bf_t src;
|
||||
bf_t r;
|
||||
bf_init(ThreadLocal::bfContext(), &r);
|
||||
bf_init(ThreadLocal::bfContext(), &src);
|
||||
bf_set_si(&src, d);
|
||||
|
||||
int ret = bf_div(&r, &m_data, &src, BF_PREC_INF, BF_RNDZ);
|
||||
int ret = bf_div(&result.m_data, &m_data, &src, prec, BF_RNDZ);
|
||||
if (UNLIKELY(ret) && UNLIKELY(ret != BF_ST_INEXACT)) {
|
||||
RELEASE_ASSERT_NOT_REACHED();
|
||||
}
|
||||
bf_set(&m_data, &r);
|
||||
bf_delete(&r);
|
||||
bf_delete(&src);
|
||||
return result;
|
||||
}
|
||||
|
||||
void BigIntData::remainder(const int64_t& d)
|
||||
BigIntData BigIntData::remainder(const int64_t& d, size_t prec) const
|
||||
{
|
||||
bf_t r, src;
|
||||
bf_init(ThreadLocal::bfContext(), &r);
|
||||
BigIntData result;
|
||||
|
||||
bf_t src;
|
||||
bf_init(ThreadLocal::bfContext(), &src);
|
||||
bf_set_si(&src, d);
|
||||
|
||||
int ret = bf_rem(&r, &m_data, &src, BF_PREC_INF, BF_RNDZ,
|
||||
int ret = bf_rem(&result.m_data, &m_data, &src, prec, BF_RNDZ,
|
||||
BF_RNDZ)
|
||||
& BF_ST_INVALID_OP;
|
||||
if (UNLIKELY(ret)) {
|
||||
RELEASE_ASSERT_NOT_REACHED();
|
||||
}
|
||||
bf_set(&m_data, &r);
|
||||
bf_delete(&r);
|
||||
bf_delete(&src);
|
||||
return result;
|
||||
}
|
||||
|
||||
bool BigIntData::lessThan(BigInt* b) const
|
||||
|
|
@ -218,6 +222,11 @@ bool BigIntData::lessThanEqual(BigInt* b) const
|
|||
return bf_cmp_le(&m_data, &b->m_bf);
|
||||
}
|
||||
|
||||
bool BigIntData::lessThan(const BigIntData& b) const
|
||||
{
|
||||
return bf_cmp_lt(&m_data, &b.m_data);
|
||||
}
|
||||
|
||||
bool BigIntData::lessThanEqual(const BigIntData& b) const
|
||||
{
|
||||
return bf_cmp_le(&m_data, &b.m_data);
|
||||
|
|
@ -233,21 +242,40 @@ bool BigIntData::greaterThanEqual(BigInt* b) const
|
|||
return bf_cmp(&m_data, &b->m_bf) >= 0;
|
||||
}
|
||||
|
||||
bool BigIntData::greaterThan(const BigIntData& src) const
|
||||
{
|
||||
return bf_cmp(&m_data, &src.m_data) > 0;
|
||||
}
|
||||
|
||||
bool BigIntData::greaterThanEqual(const BigIntData& src) const
|
||||
{
|
||||
return bf_cmp(&m_data, &src.m_data) >= 0;
|
||||
}
|
||||
|
||||
bool BigIntData::isNaN()
|
||||
bool BigIntData::isNaN() const
|
||||
{
|
||||
return bf_is_nan(&m_data);
|
||||
}
|
||||
|
||||
bool BigIntData::isInfinity()
|
||||
bool BigIntData::isInfinity() const
|
||||
{
|
||||
return !bf_is_finite(&m_data);
|
||||
}
|
||||
|
||||
int64_t BigIntData::toInt64() const
|
||||
{
|
||||
int64_t d;
|
||||
bf_get_int64(&d, &m_data, BF_GET_INT_MOD);
|
||||
return d;
|
||||
}
|
||||
|
||||
double BigIntData::toDouble() const
|
||||
{
|
||||
double d;
|
||||
bf_get_float64(&m_data, &d, bf_rnd_t::BF_RNDN);
|
||||
return d;
|
||||
}
|
||||
|
||||
std::string BigIntData::toNonGCStdString()
|
||||
{
|
||||
int savedSign = m_data.sign;
|
||||
|
|
@ -749,4 +777,39 @@ bool BigInt::isNegative() const
|
|||
return m_bf.sign;
|
||||
}
|
||||
|
||||
Optional<Int128> BigInt::toInt128()
|
||||
{
|
||||
auto s = std::to_string(std::numeric_limits<Int128>::min());
|
||||
BigIntData test(s.data(), s.length());
|
||||
if (lessThan(test)) {
|
||||
return NullOption;
|
||||
}
|
||||
|
||||
s = std::to_string(std::numeric_limits<Int128>::max());
|
||||
BigIntData test2(s.data(), s.length());
|
||||
if (greaterThan(test2)) {
|
||||
return NullOption;
|
||||
}
|
||||
|
||||
BigIntData big(this);
|
||||
s = big.toNonGCStdString();
|
||||
|
||||
bool sign = s.length() && s[0] == '-';
|
||||
if (sign) {
|
||||
s.erase(s.begin());
|
||||
}
|
||||
|
||||
Int128 ret = 0;
|
||||
for (char c : s) {
|
||||
ret *= 10;
|
||||
ret += c - '0';
|
||||
}
|
||||
|
||||
if (sign) {
|
||||
ret *= -1;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
} // namespace Escargot
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@
|
|||
#define __EscargotBigInt__
|
||||
|
||||
#include "runtime/PointerValue.h"
|
||||
#include "util/Int128.h"
|
||||
|
||||
namespace Escargot {
|
||||
|
||||
|
|
@ -30,9 +31,11 @@ class BigIntData {
|
|||
friend class BigInt;
|
||||
|
||||
public:
|
||||
BigIntData(BigInt* src);
|
||||
BigIntData(const uint64_t& d = 0);
|
||||
BigIntData(const int64_t& d);
|
||||
BigIntData(const double& d);
|
||||
BigIntData(const Int128& d);
|
||||
BigIntData(String* src, int radix = 10);
|
||||
BigIntData(const char* buf, size_t length, int radix = 10);
|
||||
BigIntData(BigIntData&& src);
|
||||
|
|
@ -40,23 +43,27 @@ public:
|
|||
BigIntData& operator=(const BigIntData& src);
|
||||
~BigIntData();
|
||||
|
||||
void addition(const int64_t& d);
|
||||
void addition(const BigIntData& src);
|
||||
void multiply(const int64_t& d);
|
||||
void division(const int64_t& d);
|
||||
void remainder(const int64_t& d);
|
||||
BigIntData addition(const int64_t& d) const;
|
||||
BigIntData addition(const BigIntData& src) const;
|
||||
BigIntData multiply(const int64_t& d) const;
|
||||
BigIntData division(const int64_t& d, size_t prec = BF_PREC_MAX) const;
|
||||
BigIntData remainder(const int64_t& d, size_t prec = BF_PREC_MAX) const;
|
||||
|
||||
bool lessThan(BigInt* b) const;
|
||||
bool lessThanEqual(BigInt* b) const;
|
||||
bool lessThan(const BigIntData& b) const;
|
||||
bool lessThanEqual(const BigIntData& b) const;
|
||||
bool greaterThan(BigInt* b) const;
|
||||
bool greaterThanEqual(BigInt* b) const;
|
||||
bool greaterThan(const BigIntData& src) const;
|
||||
bool greaterThanEqual(const BigIntData& src) const;
|
||||
|
||||
bool isNaN();
|
||||
bool isInfinity();
|
||||
bool isNaN() const;
|
||||
bool isInfinity() const;
|
||||
|
||||
std::string toNonGCStdString();
|
||||
int64_t toInt64() const;
|
||||
double toDouble() const;
|
||||
|
||||
private:
|
||||
void init(const char* buf, size_t length, int radix);
|
||||
|
|
@ -123,6 +130,8 @@ public:
|
|||
BigInt* negativeValue(ExecutionState& state);
|
||||
BigInt* negativeValue();
|
||||
|
||||
Optional<Int128> toInt128();
|
||||
|
||||
bool isZero() const;
|
||||
bool isNaN() const;
|
||||
bool isInfinity() const;
|
||||
|
|
|
|||
|
|
@ -74,6 +74,7 @@ Context::Context(VMInstance* instance)
|
|||
, m_defaultStructureForBoundFunctionObject(instance->m_defaultStructureForBoundFunctionObject)
|
||||
, m_defaultStructureForClassConstructorFunctionObject(instance->m_defaultStructureForClassConstructorFunctionObject)
|
||||
, m_defaultStructureForClassConstructorFunctionObjectWithName(instance->m_defaultStructureForClassConstructorFunctionObjectWithName)
|
||||
, m_defaultStructureForWrappedFunctionObject(instance->m_defaultStructureForWrappedFunctionObject)
|
||||
, m_defaultStructureForStringObject(instance->m_defaultStructureForStringObject)
|
||||
, m_defaultStructureForRegExpObject(instance->m_defaultStructureForRegExpObject)
|
||||
, m_defaultStructureForMappedArgumentsObject(instance->m_defaultStructureForMappedArgumentsObject)
|
||||
|
|
@ -157,14 +158,14 @@ ASTAllocator& Context::astAllocator()
|
|||
|
||||
#ifdef ESCARGOT_DEBUGGER
|
||||
|
||||
bool Context::initDebuggerRemote(const char* options)
|
||||
bool Context::initDebugger(const char* options)
|
||||
{
|
||||
if (debuggerEnabled()) {
|
||||
// debugger cannot be re-initialized
|
||||
return false;
|
||||
}
|
||||
|
||||
Debugger::createDebuggerRemote(options, this);
|
||||
Debugger::createDebugger(options, this);
|
||||
return m_debugger != nullptr;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -25,6 +25,10 @@
|
|||
#include "runtime/RegExpObject.h"
|
||||
#include "runtime/StaticStrings.h"
|
||||
|
||||
#if defined(ESCARGOT_DEBUGGER)
|
||||
#include "debugger/HeapSnapshot.h"
|
||||
#endif
|
||||
|
||||
namespace Escargot {
|
||||
|
||||
class VMInstance;
|
||||
|
|
@ -212,6 +216,11 @@ public:
|
|||
return m_defaultStructureForClassConstructorFunctionObjectWithName;
|
||||
}
|
||||
|
||||
ObjectStructure* defaultStructureForWrappedFunctionObject()
|
||||
{
|
||||
return m_defaultStructureForWrappedFunctionObject;
|
||||
}
|
||||
|
||||
ObjectStructure* defaultStructureForStringObject()
|
||||
{
|
||||
return m_defaultStructureForStringObject;
|
||||
|
|
@ -318,7 +327,7 @@ public:
|
|||
return m_debugger;
|
||||
}
|
||||
|
||||
bool initDebuggerRemote(const char* options);
|
||||
bool initDebugger(const char* options);
|
||||
void initDebugger(Debugger* debugger);
|
||||
void removeDebugger();
|
||||
bool debuggerEnabled() const;
|
||||
|
|
@ -359,6 +368,7 @@ private:
|
|||
ObjectStructure* m_defaultStructureForBoundFunctionObject;
|
||||
ObjectStructure* m_defaultStructureForClassConstructorFunctionObject;
|
||||
ObjectStructure* m_defaultStructureForClassConstructorFunctionObjectWithName;
|
||||
ObjectStructure* m_defaultStructureForWrappedFunctionObject;
|
||||
ObjectStructure* m_defaultStructureForStringObject;
|
||||
ObjectStructure* m_defaultStructureForRegExpObject;
|
||||
ObjectStructure* m_defaultStructureForMappedArgumentsObject;
|
||||
|
|
|
|||
|
|
@ -90,8 +90,8 @@ public:
|
|||
ErrorObject::throwBuiltinError(state, ErrorCode::RangeError, state.context()->staticStrings().DataView.string(), false, String::emptyString(), ErrorObject::Messages::GlobalObject_InvalidArrayBufferOffset);
|
||||
}
|
||||
|
||||
// Perform coercion first before any buffer state checks
|
||||
auto numericValue = val.toNumeric(state);
|
||||
UNUSED_VARIABLE(numericValue);
|
||||
|
||||
bool isLittleEndian = _isLittleEndian.toBoolean();
|
||||
throwTypeErrorIfDetached(state);
|
||||
|
|
@ -105,7 +105,8 @@ public:
|
|||
}
|
||||
|
||||
size_t bufferIndex = numberIndex + viewOffset;
|
||||
buffer()->setValueInBuffer(state, bufferIndex, type, val, isLittleEndian);
|
||||
// Pass the already-coerced numeric value to prevent re-coercion in setValueInBuffer
|
||||
buffer()->setValueInBuffer(state, bufferIndex, type, numericValue.first, isLittleEndian);
|
||||
}
|
||||
};
|
||||
} // namespace Escargot
|
||||
|
|
|
|||
|
|
@ -74,6 +74,11 @@
|
|||
#include "DateObject.h"
|
||||
#include "Context.h"
|
||||
#include "runtime/VMInstance.h"
|
||||
|
||||
#if defined(ENABLE_TEMPORAL)
|
||||
#include "runtime/TemporalInstantObject.h"
|
||||
#endif
|
||||
|
||||
#include <time.h>
|
||||
|
||||
namespace Escargot {
|
||||
|
|
@ -785,6 +790,7 @@ static char* parseES5DatePortion(const char* currentPosition, int& year, long& m
|
|||
{
|
||||
char* postParsePosition;
|
||||
|
||||
bool minusFirst = *currentPosition == '-';
|
||||
// This is a bit more lenient on the year string than ES5 specifies:
|
||||
// instead of restricting to 4 digits (or 6 digits with mandatory +/-),
|
||||
// it accepts any integer value. Consider this an implementation fallback.
|
||||
|
|
@ -792,6 +798,11 @@ static char* parseES5DatePortion(const char* currentPosition, int& year, long& m
|
|||
return 0;
|
||||
}
|
||||
|
||||
// The year 0 is considered positive and must be prefixed with a + sign.
|
||||
if (minusFirst && year == 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Check for presence of -MM portion.
|
||||
if (*postParsePosition != '-') {
|
||||
return postParsePosition;
|
||||
|
|
@ -1213,7 +1224,7 @@ inline bool DateObject::inLeapYear(int year)
|
|||
}
|
||||
|
||||
|
||||
void DateObject::getYMDFromTime(time64_t t, struct timeinfo& cachedLocal)
|
||||
void DateObject::computeTimeInfoFromEpoch(time64_t t, struct DateTimeInfo& cachedLocal)
|
||||
{
|
||||
int estimate = floor(t / TimeConstant::MsPerDay / 365.2425) + 1970;
|
||||
time64_t yearAsMs = daysToMs(estimate, 0, 1);
|
||||
|
|
@ -1239,6 +1250,19 @@ void DateObject::getYMDFromTime(time64_t t, struct timeinfo& cachedLocal)
|
|||
}
|
||||
|
||||
cachedLocal.mday = dayWithinYear + 1 - firstDayOfMonth[leap][cachedLocal.month];
|
||||
|
||||
int days = daysFromTime(t);
|
||||
int timeInDay = static_cast<int>(t - days * TimeConstant::MsPerDay);
|
||||
|
||||
ASSERT(timeInDay >= 0);
|
||||
|
||||
int weekday = (days + 4) % TimeConstant::DaysPerWeek;
|
||||
cachedLocal.wday = weekday >= 0 ? weekday : weekday + TimeConstant::DaysPerWeek;
|
||||
// Do not cast TimeConstant::MsPer[Hour|Minute|Second] into double
|
||||
cachedLocal.hour = timeInDay / TimeConstant::MsPerHour;
|
||||
cachedLocal.min = (timeInDay / TimeConstant::MsPerMinute) % TimeConstant::MinutesPerHour;
|
||||
cachedLocal.sec = (timeInDay / TimeConstant::MsPerSecond) % TimeConstant::SecondsPerMinute;
|
||||
cachedLocal.millisec = (timeInDay) % TimeConstant::MsPerSecond;
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -1285,25 +1309,10 @@ void DateObject::resolveCache(ExecutionState& state)
|
|||
t += (stdOffset + dstOffset);
|
||||
t -= msBetweenYears;
|
||||
|
||||
getYMDFromTime(t, m_cachedLocal);
|
||||
|
||||
int days = daysFromTime(t);
|
||||
int timeInDay = static_cast<int>(t - days * TimeConstant::MsPerDay);
|
||||
|
||||
ASSERT(timeInDay >= 0);
|
||||
|
||||
int weekday = (days + 4) % TimeConstant::DaysPerWeek;
|
||||
m_cachedLocal.wday = weekday >= 0 ? weekday : weekday + TimeConstant::DaysPerWeek;
|
||||
// Do not cast TimeConstant::MsPer[Hour|Minute|Second] into double
|
||||
m_cachedLocal.hour = timeInDay / TimeConstant::MsPerHour;
|
||||
m_cachedLocal.min = (timeInDay / TimeConstant::MsPerMinute) % TimeConstant::MinutesPerHour;
|
||||
m_cachedLocal.sec = (timeInDay / TimeConstant::MsPerSecond) % TimeConstant::SecondsPerMinute;
|
||||
m_cachedLocal.millisec = (timeInDay) % TimeConstant::MsPerSecond;
|
||||
|
||||
computeTimeInfoFromEpoch(t, m_cachedLocal);
|
||||
m_isCacheDirty = false;
|
||||
}
|
||||
|
||||
|
||||
time64_t DateObject::daysToMs(int year, int month, int date)
|
||||
{
|
||||
ASSERT(0 <= month && month < 12);
|
||||
|
|
@ -1311,7 +1320,6 @@ time64_t DateObject::daysToMs(int year, int month, int date)
|
|||
return t + (date - 1) * TimeConstant::MsPerDay;
|
||||
}
|
||||
|
||||
|
||||
String* DateObject::toDateString(ExecutionState& state)
|
||||
{
|
||||
RESOLVECACHE(state);
|
||||
|
|
@ -1568,6 +1576,22 @@ DECLARE_DATE_UTC_GETTER(Minutes);
|
|||
DECLARE_DATE_UTC_GETTER(Month);
|
||||
DECLARE_DATE_UTC_GETTER(Seconds);
|
||||
|
||||
#if defined(ENABLE_TEMPORAL)
|
||||
TemporalInstantObject* DateObject::toTemporalInstant(ExecutionState& state)
|
||||
{
|
||||
// Let t be dateObject.[[DateValue]].
|
||||
// Let ns be ? NumberToBigInt(t) × ℤ(10**6).
|
||||
double val = primitiveValue();
|
||||
if (std::trunc(val) != val) {
|
||||
ErrorObject::throwBuiltinError(state, ErrorCode::RangeError, "Date primitiveValue is not intergral");
|
||||
}
|
||||
Int128 n = int64_t(val);
|
||||
Int128 ns = n * 1000000;
|
||||
// Return ! CreateTemporalInstant(ns).
|
||||
return new TemporalInstantObject(state, state.context()->globalObject()->temporalInstantPrototype(), ns);
|
||||
}
|
||||
#endif
|
||||
|
||||
void* DateObject::operator new(size_t size)
|
||||
{
|
||||
ASSERT(size == sizeof(DateObject));
|
||||
|
|
|
|||
|
|
@ -142,12 +142,16 @@ public:
|
|||
int getUTCMonth(ExecutionState& state);
|
||||
int getUTCSeconds(ExecutionState& state);
|
||||
|
||||
#if defined(ENABLE_TEMPORAL)
|
||||
// https://tc39.es/proposal-temporal/#sec-date.prototype.totemporalinstant
|
||||
TemporalInstantObject* toTemporalInstant(ExecutionState& state);
|
||||
#endif
|
||||
|
||||
void* operator new(size_t size);
|
||||
void* operator new[](size_t size) = delete;
|
||||
|
||||
protected:
|
||||
struct timeinfo {
|
||||
timeinfo()
|
||||
struct DateTimeInfo {
|
||||
DateTimeInfo()
|
||||
: year(0)
|
||||
, month(0)
|
||||
, mday(0)
|
||||
|
|
@ -174,9 +178,11 @@ protected:
|
|||
int isdst;
|
||||
// int yday;
|
||||
};
|
||||
static void computeTimeInfoFromEpoch(time64_t t, struct DateTimeInfo& dst);
|
||||
|
||||
protected:
|
||||
time64_t m_primitiveValue; // 1LL << 63 is reserved for represent NaN
|
||||
struct timeinfo m_cachedLocal;
|
||||
struct DateTimeInfo m_cachedLocal;
|
||||
bool m_isCacheDirty : 1;
|
||||
|
||||
void resolveCache(ExecutionState& state);
|
||||
|
|
@ -193,7 +199,6 @@ protected:
|
|||
static int daysFromTime(time64_t t); // return the number of days after 1970.1.1
|
||||
static time64_t daysToMs(int year, int month, int date);
|
||||
static time64_t timeFromYear(int year) { return TimeConstant::MsPerDay * daysFromYear(year); }
|
||||
static void getYMDFromTime(time64_t t, struct timeinfo& cachedLocal);
|
||||
static bool inLeapYear(int year);
|
||||
};
|
||||
} // namespace Escargot
|
||||
|
|
|
|||
|
|
@ -223,6 +223,11 @@ public:
|
|||
m_data.payload = (intptr_t)v;
|
||||
}
|
||||
|
||||
bool isUndefined() const
|
||||
{
|
||||
return m_data.payload == (intptr_t)(ValueUndefined);
|
||||
}
|
||||
|
||||
bool isStoredInHeap() const
|
||||
{
|
||||
if (HAS_SMI_TAG(m_data.payload)) {
|
||||
|
|
|
|||
|
|
@ -17,8 +17,8 @@
|
|||
* USA
|
||||
*/
|
||||
|
||||
#ifndef __Escargot_Environment_h
|
||||
#define __Escargot_Environment_h
|
||||
#ifndef __Escargot_Environment__
|
||||
#define __Escargot_Environment__
|
||||
|
||||
#include "runtime/AtomicString.h"
|
||||
#include "runtime/String.h"
|
||||
|
|
|
|||
|
|
@ -294,7 +294,8 @@ private:
|
|||
// http://www.ecma-international.org/ecma-262/6.0/index.html#sec-global-environment-records
|
||||
class GlobalEnvironmentRecord : public EnvironmentRecord {
|
||||
#ifdef ESCARGOT_DEBUGGER
|
||||
friend class DebuggerRemote;
|
||||
friend class DebuggerEscargot;
|
||||
friend class DebuggerDevtools;
|
||||
friend class DebuggerAPI;
|
||||
#endif /* ESCARGOT_DEBUGGER */
|
||||
public:
|
||||
|
|
@ -406,6 +407,8 @@ public:
|
|||
|
||||
template <const bool onHeap>
|
||||
class DeclarativeEnvironmentRecordIndexedImpl : public DeclarativeEnvironmentRecord {
|
||||
friend class DebuggerDevtools;
|
||||
|
||||
public:
|
||||
DeclarativeEnvironmentRecordIndexedImpl(ExecutionState& state, InterpretedCodeBlock::BlockInfo* blockInfo)
|
||||
: DeclarativeEnvironmentRecord()
|
||||
|
|
@ -587,7 +590,8 @@ public:
|
|||
// DeclarativeEnvironmentRecordNotIndexed record does not create binding self likes FunctionEnvironmentRecord
|
||||
class DeclarativeEnvironmentRecordNotIndexed : public DeclarativeEnvironmentRecord {
|
||||
#ifdef ESCARGOT_DEBUGGER
|
||||
friend class DebuggerRemote;
|
||||
friend class DebuggerEscargot;
|
||||
friend class DebuggerDevtools;
|
||||
friend class DebuggerAPI;
|
||||
#endif /* ESCARGOT_DEBUGGER */
|
||||
public:
|
||||
|
|
@ -640,6 +644,11 @@ public:
|
|||
|
||||
virtual void initializeBinding(ExecutionState& state, const AtomicString& name, const Value& V) override;
|
||||
|
||||
IdentifierRecordVector getIdentifierRecordVector()
|
||||
{
|
||||
return m_recordVector;
|
||||
}
|
||||
|
||||
void* operator new(size_t size)
|
||||
{
|
||||
static MAY_THREAD_LOCAL bool typeInited = false;
|
||||
|
|
@ -1202,8 +1211,10 @@ public:
|
|||
|
||||
class ModuleEnvironmentRecord : public DeclarativeEnvironmentRecord {
|
||||
#ifdef ESCARGOT_DEBUGGER
|
||||
friend class DebuggerRemote;
|
||||
friend class DebuggerEscargot;
|
||||
friend class DebuggerDevtools;
|
||||
friend class DebuggerAPI;
|
||||
friend class HeapSnapshot;
|
||||
#endif /* ESCARGOT_DEBUGGER */
|
||||
public:
|
||||
struct ModuleBindingRecord {
|
||||
|
|
|
|||
|
|
@ -135,7 +135,6 @@ public:
|
|||
static constexpr const char* CanNotReadPrivateMember = "Cannot read private member %s from an object whose class did not declare it";
|
||||
static constexpr const char* CanNotWritePrivateMember = "Cannot write private member %s from an object whose class did not declare it";
|
||||
static constexpr const char* CanNotRedefinePrivateMember = "Cannot add private field %s with same name twice";
|
||||
static constexpr const char* TemporalError = "Error related with Temporal Operation";
|
||||
#if defined(ENABLE_CODE_CACHE)
|
||||
static constexpr const char* CodeCache_Loaded_StaticError = "[CodeCache] Default Error Message of ThrowStaticError: %s";
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@
|
|||
#include "FunctionObject.h"
|
||||
#include "NativeFunctionObject.h"
|
||||
#include "ScriptClassConstructorFunctionObject.h"
|
||||
#include "../src/debugger/HeapSnapshot.h"
|
||||
|
||||
namespace Escargot {
|
||||
|
||||
|
|
@ -127,7 +128,7 @@ Optional<LexicalEnvironment*> ExecutionState::mostNearestHeapAllocatedLexicalEnv
|
|||
return nullptr;
|
||||
}
|
||||
|
||||
Optional<Object*> ExecutionState::mostNearestHomeObject()
|
||||
Optional<Object*> ExecutionState::mostNearestHomeObject(size_t skipCount)
|
||||
{
|
||||
LexicalEnvironment* env = m_lexicalEnvironment;
|
||||
|
||||
|
|
@ -136,7 +137,11 @@ Optional<Object*> ExecutionState::mostNearestHomeObject()
|
|||
if (rec->isDeclarativeEnvironmentRecord() && rec->asDeclarativeEnvironmentRecord()->isFunctionEnvironmentRecord()) {
|
||||
auto homeObject = rec->asDeclarativeEnvironmentRecord()->asFunctionEnvironmentRecord()->homeObject();
|
||||
if (homeObject) {
|
||||
return homeObject;
|
||||
if (skipCount) {
|
||||
skipCount--;
|
||||
} else {
|
||||
return homeObject;
|
||||
}
|
||||
}
|
||||
}
|
||||
env = env->outerEnvironment();
|
||||
|
|
@ -154,11 +159,24 @@ Object* ExecutionState::convertHomeObjectIntoPrivateMemberContextObject(Object*
|
|||
|
||||
Object* ExecutionState::findPrivateMemberContextObject()
|
||||
{
|
||||
auto o = mostNearestHomeObject();
|
||||
size_t skipCount = 0;
|
||||
Optional<Object*> o;
|
||||
while (true) {
|
||||
auto test = mostNearestHomeObject(skipCount);
|
||||
if (!test) {
|
||||
break;
|
||||
}
|
||||
if (test->isScriptClassConstructorPrototypeObject() || test->isScriptClassConstructorFunctionObject()) {
|
||||
o = test;
|
||||
break;
|
||||
}
|
||||
skipCount++;
|
||||
}
|
||||
if (!o) {
|
||||
ErrorObject::throwBuiltinError(*this, ErrorCode::TypeError, "Cannot read/write private member here");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return convertHomeObjectIntoPrivateMemberContextObject(o.value());
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -36,6 +36,7 @@ class Script;
|
|||
class GeneratorObject;
|
||||
class FunctionObject;
|
||||
class NativeFunctionObject;
|
||||
class HeapSnapshot;
|
||||
|
||||
typedef VectorWithInlineStorage<2, ControlFlowRecord*, GCUtil::gc_malloc_allocator<ControlFlowRecord*>> ControlFlowRecordVector;
|
||||
|
||||
|
|
@ -225,6 +226,13 @@ public:
|
|||
return m_inStrictMode;
|
||||
}
|
||||
|
||||
// Used to evaluate class heritage and computed property names as strict mode
|
||||
// code regardless of the surrounding code (a class definition is always strict).
|
||||
void setInStrictMode(bool inStrictMode)
|
||||
{
|
||||
m_inStrictMode = inStrictMode;
|
||||
}
|
||||
|
||||
bool inExecutionStopState() const
|
||||
{
|
||||
return m_inExecutionStopState;
|
||||
|
|
@ -301,7 +309,7 @@ public:
|
|||
LexicalEnvironment* mostNearestFunctionLexicalEnvironment();
|
||||
Optional<LexicalEnvironment*> mostNearestHeapAllocatedLexicalEnvironment();
|
||||
|
||||
Optional<Object*> mostNearestHomeObject();
|
||||
Optional<Object*> mostNearestHomeObject(size_t skipCount = 0);
|
||||
static Object* convertHomeObjectIntoPrivateMemberContextObject(Object* o);
|
||||
Object* findPrivateMemberContextObject();
|
||||
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@
|
|||
#include "interpreter/ByteCode.h"
|
||||
#include "parser/CodeBlock.h"
|
||||
#include "util/Util.h"
|
||||
#include "api/EscargotPublic.h"
|
||||
|
||||
namespace Escargot {
|
||||
|
||||
|
|
@ -55,6 +56,9 @@ FinalizationRegistryObject::FinalizationRegistryObject(ExecutionState& state, Ob
|
|||
cell->unregisterToken->removeFinalizer(finalizerUnregisterToken, cell);
|
||||
}
|
||||
}
|
||||
// Set source to nullptr to indicate that FinalizationRegistryObject is being collected.
|
||||
// This prevents the weakRefTarget's finalizer from accessing an invalid source pointer.
|
||||
cell->source = nullptr;
|
||||
}
|
||||
s->m_cells.clear();
|
||||
s->m_deletedCellCount = 0;
|
||||
|
|
@ -185,7 +189,14 @@ void FinalizationRegistryObject::finalizer(PointerValue* self, void* data)
|
|||
UNUSED_PARAMETER(self);
|
||||
FinalizationRegistryObjectItem* item = (FinalizationRegistryObjectItem*)data;
|
||||
|
||||
ASSERT(!!item->source && !!item->source->m_cleanupCallback);
|
||||
// Check if FinalizationRegistryObject is already being collected by GC.
|
||||
// GC finalizers are not ordered, so the FinalizationRegistryObject may be collected
|
||||
// before its registered targets. In that case, source will be nullptr.
|
||||
if (!item->source) {
|
||||
return;
|
||||
}
|
||||
|
||||
ASSERT(!!item->source->m_cleanupCallback);
|
||||
bool wasCallbackDeleted = false;
|
||||
auto callback = item->source->m_cleanupCallback;
|
||||
if (callback->isScriptFunctionObject()) {
|
||||
|
|
@ -197,7 +208,7 @@ void FinalizationRegistryObject::finalizer(PointerValue* self, void* data)
|
|||
}
|
||||
}
|
||||
|
||||
if (!wasCallbackDeleted) {
|
||||
if (!wasCallbackDeleted && Globals::isInitialized()) {
|
||||
try {
|
||||
ExecutionState tempState(item->source->m_realm);
|
||||
Value argv = item->heldValue;
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue