Compare commits

..

205 commits

Author SHA1 Message Date
Seonghyun Kim
bab3a57975 Fix RELEASE_ASSERT_NOT_REACHED on CoverInitializedName used as a value
A CoverInitializedName such as `{ a = 0 }` (object shorthand with default) is
only valid when the enclosing object literal is refined into a destructuring
pattern. When such an object literal is instead consumed as a real value -- the
base of a member access, call, computed access, or tagged template, e.g.
`( {... { a = 0 }. b = 1 } )` -- the pending CoverInitializedName error was
discarded by a later assignment, so no SyntaxError was raised and the
AssignmentPattern property value reached bytecode generation, hitting
RELEASE_ASSERT_NOT_REACHED in Node::generateExpressionByteCode.

Report the pending CoverInitializedName as an early SyntaxError in the two
LeftHandSideExpression member-access loops the moment the base is consumed as a
value, since it can no longer be refined into a pattern.

Signed-off-by: Seonghyun Kim <sh8281.kim@samsung.com>
2026-06-18 19:02:02 +09:00
Seonghyun Kim
181019c0c3 Reject object binding pattern rest followed by a binding pattern (Issue #1334)
BindingRestProperty in an object binding pattern only accepts a
BindingIdentifier, unlike BindingRestElement in an array binding pattern
which also accepts a BindingPattern. Throw a SyntaxError when `...` is
followed by `{` or `[` in a declaration context.

Signed-off-by: Seonghyun Kim <sh8281.kim@samsung.com>
2026-06-18 16:09:00 +09:00
Seonghyun Kim
c37e2b4851 A class definition is always strict mode
Signed-off-by: Seonghyun Kim <sh8281.kim@samsung.com>
2026-06-18 10:05:18 +09:00
Seonghyun Kim
2dee22f5c7 Update test/vendortest with Issue #1577 regression tests
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: Seonghyun Kim <sh8281.kim@samsung.com>
2026-06-17 10:06:57 +09:00
Seonghyun Kim
5e3b91b052 Fix using declaration in a switch case clobbering its disposable record (Issue #1577 Crash #2)
A `using` declaration in a switch case preceded by another statement
aborted with Assertion `isDisposableResourceRecord()' failed / SEGV in
finalizeDisposable.

The switch releases its discriminant register before generating the
case bodies, but pushLexicalBlock had allocated the disposable-record
register on top of it. The early giveUpRegister therefore freed the
disposable register instead, and a statement in the case body (e.g.
`o.k = 1`) reused that register slot, clobbering the record;
Initialize/FinalizeDisposable then dereferenced a non-pointer value.

When the switch block contains a `using` declaration, defer releasing
the discriminant temporaries until after finalizeLexicalBlock has
popped the disposable register (preserving LIFO order). Switches
without `using` are unchanged.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: Seonghyun Kim <sh8281.kim@samsung.com>
2026-06-17 10:06:57 +09:00
Seonghyun Kim
e7221f4211 Fix labeled continue targeting a for-of loop (Issue #1577 Crash #1)
`L: for (const v of [...]) { continue L; }` aborted with
Assertion `!v.isEmpty()' failed, and `continue OUTER` from a nested
loop silently terminated the script.

A `continue <label>` whose label targets a for-of loop was left to be
resolved by LabelledStatementNode after the loop body, by which point
the for-of iterator-cleanup try block had registered the jump as a
complex case. It was then morphed into a JumpComplexCase that unwound
the try block, wrongly closing the iterator and leaving an empty Value
in the result register.

A previous per-loop attempt (8fd141b2) was reverted (60b1202a) because
a single m_currentLoopLabel leaked into nested loops and broke test262.

Track all labels directly targeting a loop (m_currentLoopLabels), clear
the list when entering each loop body so nested loops never inherit it,
and let for-of/for-in resolve continues for its own labels to
continuePosition (a plain jump, identical to an unlabeled continue)
before the try block is registered as a complex case.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: Seonghyun Kim <sh8281.kim@samsung.com>
2026-06-17 10:06:57 +09:00
Seonghyun Kim
b30b63fc63 Fix continue/break at first instruction of env-allocating block (Issue #1571)
A continue/break that is the first instruction inside a lexical block which
allocates an environment (e.g. `for(;c;){ continue; eval(); const x=1; }`) was
emitted as a plain Jump, skipping the block's CloseLexicalEnvironment. The
leaked environment then caused a subsequent outer-scope `const` to initialize
in the wrong environment, producing a spurious
`ReferenceError: Cannot access '...' before initialization`.

registerJumpPositionsToComplexCase compared jump positions against frontlimit
(= lexicalBlockStartPosition, the first body instruction) with strict `>`, so a
jump located exactly at the first body instruction was never morphed into a
JumpComplexCase and the block environment was left un-popped. Use `>=` for
break/continue/labelledBreak/labelledContinue.

With the environment now unwound correctly, the hasBinding guard band-aid in
InterpreterSlowPath::initializeByName is no longer needed and is removed.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Signed-off-by: Seonghyun Kim <sh8281.kim@samsung.com>
2026-06-16 15:35:01 +09:00
Seonghyun Kim
60b1202a72 Fix labeled continue regression in test262 tests
Remove the conditional labeled continue processing from loop statements.
The LabelledStatementNode correctly handles all labeled continues after the
labeled statement completes. Loops should only handle their own regular
(unlabeled) continues.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
Signed-off-by: Seonghyun Kim <sh8281.kim@samsung.com>
2026-06-16 15:35:01 +09:00
Seonghyun Kim
0a2fcaaf5e Add missing m_currentLoopLabel field and fix Crashes #1-2
- Add m_currentLoopLabel to ByteCodeGenerateContext for tracking labeled loop labels (Issue #1571)
- Fix Crash #1: Add bounds checking in inline cache proto traverse with std::min clamping
- Fix Crash #2: Check hasBinding before initializeBinding to prevent assertion on unreachable code paths

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
Signed-off-by: Seonghyun Kim <sh8281.kim@samsung.com>
2026-06-16 15:35:01 +09:00
Seonghyun Kim
09f0a10bba Fix DoWhileStatementNode labeled continue handling (Issue #1571)
Issue #1571: Labeled continue in do-while loops with allocated blocks
- Proper morphing for labeled continues crossing block boundaries
- Fixes environment record consistency in labeled loops
- Completes fix pattern across all loop statement types

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: Seonghyun Kim <sh8281.kim@samsung.com>
2026-06-16 15:35:01 +09:00
Seonghyun Kim
7e2b3292fd Fix WhileStatementNode labeled continue handling (Issue #1571)
Issue #1571: Labeled continue in while loops with allocated blocks
- Proper morphing for labeled continues crossing block boundaries
- Fixes environment record consistency in labeled loops
- Applies fix pattern to all loop statement types

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: Seonghyun Kim <sh8281.kim@samsung.com>
2026-06-16 15:35:01 +09:00
Seonghyun Kim
8fd141b29c Fix ForInOfStatementNode labeled continue handling (Issues #1571 Crashes #3-4)
Issue #1571 Crash #3: Labeled continue in for-of loop
- Iterator value issue when labeled continue triggered early
- Proper sequencing of iterator cleanup vs control flow

Issue #1571 Crash #4: With statement + labeled for-of
- Environment unwinding coordination with iterator cleanup
- CloseLexicalEnvironment called at correct time

Solution: Consume labeled continues with proper morphing
- Ensures iterator cleanup finalizer runs before unwinding
- Control flow record management stays consistent
- Both for-in and for-of (and for-await-of) properly handled

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: Seonghyun Kim <sh8281.kim@samsung.com>
2026-06-16 15:35:01 +09:00
Seonghyun Kim
c80623fc00 Fix ForStatementNode labeled continue handling (Issues #1571 Crashes #3-13)
Issue #1571 Crashes #3-4: Labeled continue in for loops
- Consume labeled continues targeting this loop with proper morphing
- Ensures iterator cleanup and environment unwinding work correctly

Issue #1571 Crashes #5-13: Environment record mismatch in labeled loops
- Proper morphing of labeled continues across allocated block boundaries
- Fixes crashes from scope-creating constructs in labeled loops
- Plain Jump path preserved for non-allocated blocks (zero overhead)

Solution: Call consumeLabelledContinuePositions with morphing enabled
- If no allocated block: plain Jump (fast path)
- If allocated block: JumpComplexCase with proper unwinding (correct path)
- Morphing is automatic via morphJumpPositionIntoComplexCase

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: Seonghyun Kim <sh8281.kim@samsung.com>
2026-06-16 15:35:01 +09:00
Seonghyun Kim
92ee65bc0c Update LabelledStatementNode to pass label to child loop
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signed-off-by: Seonghyun Kim <sh8281.kim@samsung.com>
2026-06-16 15:35:01 +09:00
Seonghyun Kim
07cdae7850 Update test cases
Signed-off-by: Seonghyun Kim <sh8281.kim@samsung.com>
2026-06-16 15:35:01 +09:00
Seonghyun Kim
ef525f337f Add programCount range check for edge case in blockOperation
Signed-off-by: Seonghyun Kim <sh8281.kim@samsung.com>
2026-06-15 11:50:30 +09:00
Ádám László Kulcsár
29fdbc741f Improve eval in devtools
Fix accidental deadlock possible inside the debugger and improve formatting when inspecting arrays.

Signed-off-by: Ádám László Kulcsár <adam.kulcsar@szteszoftver.hu>
2026-06-10 10:48:28 +09:00
Máté Tokodi
ebe3761308 Add support for scope variables and call stack in the Devtools Debugger
Signed-off-by: Máté Tokodi <mate.tokodi@szteszoftver.hu>
2026-06-10 10:44:10 +09:00
Ádám László Kulcsár
c423a4bfa0 Implement eval in Devtools debugger
Signed-off-by: Ádám László Kulcsár <adam.kulcsar@szteszoftver.hu>
2026-06-01 23:56:51 +09:00
epsilon
4b40f92aba Fix OOB read in string::rfind 2026-05-27 19:21:59 +09:00
Seonghyun Kim
779f6bedf5 Check stack overflow in ProxyObject::getPrototype, ProxyObject::getPrototypeObject
Signed-off-by: Seonghyun Kim <sh8281.kim@samsung.com>
2026-05-27 11:31:45 +09:00
Seonghyun Kim
d581b27af6 Check overflow when TypedArrayObject allocating for 32-bit systems
Signed-off-by: Seonghyun Kim <sh8281.kim@samsung.com>
2026-05-27 11:31:45 +09:00
Seonghyun Kim
3b43994a7d Don't assume spread element is fast mode
Signed-off-by: Seonghyun Kim <sh8281.kim@samsung.com>
2026-05-27 11:31:45 +09:00
Seonghyun Kim
299a7ff451 Fix crash in ArrayBuffer transfer
Signed-off-by: Seonghyun Kim <sh8281.kim@samsung.com>
2026-05-27 11:31:45 +09:00
Seonghyun Kim
36f5fb5836 Add size checking on ArrayBuffer.prototype.transfer
Signed-off-by: Seonghyun Kim <sh8281.kim@samsung.com>
2026-05-21 16:15:10 +09:00
Ádám László Kulcsár
d6aae0777f Fix bug with Devtools filenames
Fix bug where filenames could contain memory garbage.

Signed-off-by: Ádám László Kulcsár <adam.kulcsar@szteszoftver.hu>
2026-05-14 19:57:40 +09:00
Seonghyun Kim
590345cc62 Update vendor test
Signed-off-by: Seonghyun Kim <sh8281.kim@samsung.com>
2026-05-14 13:33:33 +09:00
Seonghyun Kim
c02c6595be Handle oom explicitly
Signed-off-by: Seonghyun Kim <sh8281.kim@samsung.com>
2026-05-14 13:33:33 +09:00
Seonghyun Kim
166fa7c66b Disable GC on c++ catch block w/ASAN
Signed-off-by: Seonghyun Kim <sh8281.kim@samsung.com>
2026-05-14 13:33:33 +09:00
Seonghyun Kim
3cf7d60b43 Fix memory error when FinalizationRegistry cleanup callback throws
Signed-off-by: Seonghyun Kim <sh8281.kim@samsung.com>
2026-05-14 13:33:33 +09:00
Seonghyun Kim
2bbd27caac Add stack overflow check in ProxyObject::ownPropertyKeys
Signed-off-by: Seonghyun Kim <sh8281.kim@samsung.com>
2026-05-14 13:33:33 +09:00
Seonghyun Kim
78e5e333b9 Compute ByteCodeLOC only !NDEBUG && ESCARGOT_DEBUGGER in ByteCodeBlock::pushCode
Signed-off-by: Seonghyun Kim <sh8281.kim@samsung.com>
2026-05-14 13:33:33 +09:00
Seonghyun Kim
22bedcec9e In Evaluator::EvaluatorResult::resultOrErrorToString error can be null even if the task was successful
Signed-off-by: Seonghyun Kim <sh8281.kim@samsung.com>
2026-05-14 13:33:33 +09:00
Seonghyun Kim
aa727d22a1 Use PointerFree allocatior for FunctionContextVarMap
Signed-off-by: Seonghyun Kim <sh8281.kim@samsung.com>
2026-05-14 13:33:33 +09:00
Seonghyun Kim
121d2fefca Fix StringObject::defineOwnProperty
* Check if this is an index property within the string length due to proxy object

Signed-off-by: Seonghyun Kim <sh8281.kim@samsung.com>
2026-05-14 13:33:33 +09:00
Seonghyun Kim
2e9a6393b9 In InterpreterSlowPath::arrayDefineOwnPropertyBySpreadElementOperation,
setArrayLength can convert the array to non-fast mode when length exceeds thresholds

Signed-off-by: Seonghyun Kim <sh8281.kim@samsung.com>
2026-05-14 13:33:33 +09:00
Seonghyun Kim
685a71c3d1 Check if the size exceeds the maximum allowed size for TypedArray construction
Signed-off-by: Seonghyun Kim <sh8281.kim@samsung.com>
2026-05-14 13:33:33 +09:00
Seonghyun Kim
78576a5af9 Update vendor test
Signed-off-by: Seonghyun Kim <sh8281.kim@samsung.com>
2026-05-14 13:33:33 +09:00
Seonghyun Kim
c8588c323c prevent stack overflow when parsing huge json array
Signed-off-by: Seonghyun Kim <sh8281.kim@samsung.com>
2026-05-14 13:33:33 +09:00
Seonghyun Kim
2cc649c97e Use correct index in DataViewObject::setViewValue
Signed-off-by: Seonghyun Kim <sh8281.kim@samsung.com>
2026-05-14 13:33:33 +09:00
Seonghyun Kim
98f54274d1 Fix buffer access bug in builtinTypedArrayCopyWithin
Signed-off-by: Seonghyun Kim <sh8281.kim@samsung.com>
2026-05-14 13:33:33 +09:00
SAY-5
0a79d6c1f7 Fix out-of-bounds read in lexer comment/hashbang skipping
skipSingleLine, skipSingleLineComment, and skipMultiLineComment incremented
the source index and then called peekCharWithoutEOF() without re-checking
eof(), causing a one-byte heap read past the source buffer when the input
ends with a bare \r or a trailing '*'. Guard each follow-up peek with eof().

Fixes #1568

Signed-off-by: SAY-5 <say.apm35@gmail.com>
2026-05-12 15:57:50 +09:00
Ádám László Kulcsár
634fe864d7 Add -Wno-maybe-uninitialized build option for GCC 16
Signed-off-by: Ádám László Kulcsár <adam.kulcsar@szteszoftver.hu>
2026-05-11 20:40:32 +09:00
Ádám László Kulcsár
475149426f Implement escargot debugger restart support
Implement restart in escargot and python debugger.
Also add debugger test.

Signed-off-by: Ádám László Kulcsár <adam.kulcsar@szteszoftver.hu>
2026-05-07 16:12:31 +09:00
Ádám László Kulcsár
7683468efb Add heap snapshots to devtools debugger
Signed-off-by: Ádám László Kulcsár <adam.kulcsar@szteszoftver.hu>
2026-04-29 09:37:05 +09:00
Máté Tokodi
48eb4b6af9 Add support for breakpoints in the Devtools Debugger
- Add, remove breakpoints
- Resume execution
- Step Into, Step Out, Step Over
- Deactivate/Reactivate all breakpoints

Signed-off-by: Máté Tokodi <mate.tokodi@szteszoftver.hu>
2026-04-28 16:43:39 +09:00
Ádám László Kulcsár
e9833cd791 Rework python debugger tester
Delete debugger_tester.sh script and rewrite it in python. Also add option to run individial tests.

Signed-off-by: Ádám László Kulcsár <adam.kulcsar@szteszoftver.hu>
2026-04-28 16:42:06 +09:00
Ádám László Kulcsár
769e86e32a Add ability to take heap snapshots with python debugger
Signed-off-by: Ádám László Kulcsár <adam.kulcsar@szteszoftver.hu>
2026-04-23 11:35:55 +09:00
Seonghyun Kim
ad3844437e Update ArrayBuffer::isDetachedBuffer check
Signed-off-by: Seonghyun Kim <sh8281.kim@samsung.com>
2026-04-23 11:35:23 +09:00
Ádám László Kulcsár
633fe63795 Add funcitonality to take heap snapshots
Signed-off-by: Ádám László Kulcsár <kuladam@inf.u-szeged.hu>
2026-04-16 15:42:23 +09:00
Seonghyun Kim
e52f0ce0cf Fix read private property on inner object method
Signed-off-by: Seonghyun Kim <sh8281.kim@samsung.com>
2026-04-16 09:25:12 +09:00
Seonghyun Kim
2624608567 Update clang compile option for old clang
Signed-off-by: Seonghyun Kim <sh8281.kim@samsung.com>
2026-04-16 09:25:12 +09:00
Ádám László Kulcsár
0ac274d1fc Add correct debugging to .mjs files
Add breakpoint on the start of the first source code line so that module file imports can be debugged.
Also extend debugger test script since Escargot uses realpaths with modules.

Signed-off-by: Ádám László Kulcsár <kuladam@inf.u-szeged.hu>
2026-04-14 21:35:29 +09:00
Máté Tokodi
ab7a13e089 Fix Devtools debugger websocket buffer handling
The type of `m_receiveBufferFill` was uint8_t causing it to roll over
when parsing longer messages from Devtools, causing message data to be
truncated incorrectly.

Signed-off-by: Máté Tokodi <mate.tokodi@szteszoftver.hu>
2026-04-09 12:27:01 +09:00
Seonghyun Kim
f25f05faca Validate input on CodeCacheReader
Signed-off-by: Seonghyun Kim <sh8281.kim@samsung.com>
2026-04-08 10:00:03 +09:00
Seonghyun Kim
a7f9695bf3 Check buffer size before reading buffer on CodeCacheReader
Signed-off-by: Seonghyun Kim <sh8281.kim@samsung.com>
2026-04-08 10:00:03 +09:00
Seonghyun Kim
50215a5ce8 Check wrong input in Serializer::deserializeFrom
Signed-off-by: Seonghyun Kim <sh8281.kim@samsung.com>
2026-04-08 10:00:03 +09:00
Seonghyun Kim
13e3a62312 Disable 32-bit pointer using with asan since it makes bdwgc related error
Signed-off-by: Seonghyun Kim <sh8281.kim@samsung.com>
2026-04-08 10:00:03 +09:00
Seonghyun Kim
2156cfa5b8 Fix compiler issue with int128(gcc-10 upper)
Signed-off-by: Seonghyun Kim <sh8281.kim@samsung.com>
2026-04-08 10:00:03 +09:00
Seonghyun Kim
b4f2b24e4a Fix compile error on clang-20
Signed-off-by: Seonghyun Kim <sh8281.kim@samsung.com>
2026-04-08 10:00:03 +09:00
Máté Tokodi
97e8115ab1 Add basics of Chrome Devtools debugger support
- Use routing table for request dispatch in DebuggerHttpRouter, for
  handling choosing which debugger stack to use based on the http
  upgrade request:
    - DebuggerEscargot for the python debugger and VSCode
    - DebuggerDevtools for connecting to Chrome Devtools
- Parse mesasges with 16bit message size
- Reply to the first few messages chrome sends
- Refactor Debugger:
    - Rename DebuggerRemote to DebuggerEscargot
    - DebuggerEscargot and DebuggerDevtools inherit from
      DebuggerTcp which inherits from Debugger
- Add debugger info to README.md

Signed-off-by: Máté Tokodi <mate.tokodi@szteszoftver.hu>
2026-03-28 13:08:51 +09:00
Hyukwoo Park
989e6922b6 Remove duplicated parameter-check methods
Signed-off-by: Hyukwoo Park <hyukwoo.park@jbnu.ac.kr>
2026-03-13 13:27:59 +09:00
Seonghyun Kim
6ebbd22c06 Optimize very big object expression through big bloom filter
Signed-off-by: Seonghyun Kim <sh8281.kim@samsung.com>
2026-03-10 16:01:03 +09:00
Seonghyun Kim
069fba1151 Optimize big object expression through bloom filter
Signed-off-by: Seonghyun Kim <sh8281.kim@samsung.com>
2026-03-10 16:01:03 +09:00
Máté Tokodi
9819a8de49 Debugger: add dedicated message for VSCode watch
Processing VSCode watches skips waitForResolvingPendingBreakpoints
(previously having multiple watches in VSC would call this when
processing the first watch, which would process pending messages while
trying to wait for break point changes while in eval mode (not in
waiting mode) and cause and Invalid message error and the closure of the
debgger connection when hitting the second watch message)

Allow '*.mjs' files in the python debugger

Add watches to the python debugger, add test

Signed-off-by: Máté Tokodi mate.tokodi@szteszoftver.hu
2026-03-09 08:19:57 +09:00
kwonjeomsim
f41ec3426b Change bottom-up to top-down check 2026-03-09 08:19:04 +09:00
kwonjeomsim
1b36b95006 Change location of optimization logic 2026-03-09 08:19:04 +09:00
kwonjeomsim
c69b8ada67 Add codes checking assignment pattern and rest parameter usage 2026-03-09 08:19:04 +09:00
kwonjeomsim
f6e0b04be4 Change location of removing unused parameters 2026-03-09 08:19:04 +09:00
kwonjeomsim
4d4cded5be Change AtomicStringMap allocation and deal with parameters over 16 2026-03-09 08:19:04 +09:00
kwonjeomsim
5c16ae5d84 Change m_parameterUsed to bit operation and Add hashset 2026-03-09 08:19:04 +09:00
kwonjeomsim
12a37ed4d1 Change unused parameter check point 2026-03-09 08:19:04 +09:00
kwonjeomsim
fb8b241655 Add InterpretedCodeBlock::ParameterUsed info to codecache 2026-03-09 08:19:04 +09:00
kwonjeomsim
339a5d1838 Add parsing process that remove unused function parameter bytecodes 2026-03-09 08:19:04 +09:00
Seonghyun Kim
bb00312798 Update gbs.conf to fix build error on ci
Signed-off-by: Seonghyun Kim <sh8281.kim@samsung.com>
2026-03-06 13:05:41 +09:00
Seonghyun Kim
c9f13d0730 Fix compile error on old gcc
Signed-off-by: Seonghyun Kim <sh8281.kim@samsung.com>
2026-03-05 17:20:30 +09:00
Seonghyun Kim
e35a8cb14d Add enconding option for python code generator
Signed-off-by: Seonghyun Kim <sh8281.kim@samsung.com>
2026-03-05 17:20:30 +09:00
Seonghyun Kim
af4c67a706 Implement VMInstanceRef::enqueueEvaluateJob
Signed-off-by: Seonghyun Kim <sh8281.kim@samsung.com>
2026-02-12 14:35:37 +09:00
Seonghyun Kim
c90e358e2f Generate YarrCanonicalizeUCS2.cpp from UnicodeData.txt
Signed-off-by: Seonghyun Kim <sh8281.kim@samsung.com>
2026-02-11 09:53:27 +09:00
Seonghyun Kim
17bdb07cc6 Update es-actions for clang
Signed-off-by: Seonghyun Kim <sh8281.kim@samsung.com>
2026-02-10 14:53:17 +09:00
Hyukwoo Park
eeea83ef3e Enable self-hosted runners only for the origin escargot repo
Signed-off-by: Hyukwoo Park <hyukwoo.park@jbnu.ac.kr>
2026-02-07 20:00:39 +09:00
Seonghyun Kim
d50bb8897a Update analysis-actions.yml
Signed-off-by: Seonghyun Kim <sh8281.kim@samsung.com>
2026-02-05 16:56:44 +09:00
Seonghyun Kim
a3abf7e40a Revise source generate from unicode data logic
Signed-off-by: Seonghyun Kim <sh8281.kim@samsung.com>
2026-02-05 16:56:44 +09:00
Seonghyun Kim
32281e3d22 Update processMemoryUsage function for posix
Signed-off-by: Seonghyun Kim <sh8281.kim@samsung.com>
2026-02-05 16:56:44 +09:00
Seonghyun Kim
ea826db76a Add thread memory usage test case
Signed-off-by: Seonghyun Kim <sh8281.kim@samsung.com>
2026-02-04 17:21:25 +09:00
Seonghyun Kim
7bd328b5df When thread exit, we should unmap mapped memory which is mapped from bdwgc
Signed-off-by: Seonghyun Kim <sh8281.kim@samsung.com>
2026-02-04 17:21:25 +09:00
Seonghyun Kim
20c7641b16 Implement tizen gbs build test on CI
Signed-off-by: Seonghyun Kim <sh8281.kim@samsung.com>
2026-02-03 12:48:04 +09:00
Seonghyun Kim
e44005a1d2 Android timezone data load for adb-shell
Signed-off-by: Seonghyun Kim <sh8281.kim@samsung.com>
2026-01-30 17:34:13 +09:00
Seonghyun Kim
a130b108a5 Implement Global::finalizeGC to prevent memory leak
Signed-off-by: Seonghyun Kim <sh8281.kim@samsung.com>
2026-01-30 09:54:22 +09:00
Máté Tokodi
fb2ad1eb04 Fix exporting async functions in ESM modules
Add mjs file matching to regression test script
2026-01-28 13:48:08 +09:00
Seonghyun Kim
32e9a72156 Fix typo in Intl::initNumberFormatSkeleton
Signed-off-by: Seonghyun Kim <sh8281.kim@samsung.com>
2026-01-27 12:57:45 +09:00
Seonghyun Kim
2a3447dc1c Add missing package for tizen build
Signed-off-by: Seonghyun Kim <sh8281.kim@samsung.com>
2026-01-08 19:25:39 +09:00
Seonghyun Kim
3b242a2e6e Update coverage-scan
Signed-off-by: Seonghyun Kim <sh8281.kim@samsung.com>
2026-01-07 12:20:48 +09:00
Seonghyun Kim
95036c339e Evaluate destructuring correctly
Signed-off-by: Seonghyun Kim <sh8281.kim@samsung.com>
2026-01-07 12:20:48 +09:00
Seonghyun Kim
50f1d7a2f1 When closing the iterator, handle exceptions that occur when retrieving the return function correctly.
Signed-off-by: Seonghyun Kim <sh8281.kim@samsung.com>
2026-01-07 12:20:48 +09:00
Seonghyun Kim
3f07b5696c Update TypedArray.prototype.toLocaleString to pass argv
Signed-off-by: Seonghyun Kim <sh8281.kim@samsung.com>
2026-01-06 16:17:04 +09:00
Seonghyun Kim
a138441b98 Use new Calendar functions in Intl + Fix timezone bug in Intl.DateTimeFormat
Signed-off-by: Seonghyun Kim <sh8281.kim@samsung.com>
2026-01-06 16:17:04 +09:00
Seonghyun Kim
a474595528 Update test262 driver
Signed-off-by: Seonghyun Kim <sh8281.kim@samsung.com>
2026-01-06 16:17:04 +09:00
Seonghyun Kim
75394413ac Fix ShadowRealm.prototype.importValue memory error
Signed-off-by: Seonghyun Kim <sh8281.kim@samsung.com>
2026-01-06 16:17:04 +09:00
Seonghyun Kim
1ab58f5470 Revise DecodeURI function to test max unicode codepoint
Signed-off-by: Seonghyun Kim <sh8281.kim@samsung.com>
2026-01-06 16:17:04 +09:00
Seonghyun Kim
a622791f6e Apply updated rules of TypedArray.[[Set]]
Signed-off-by: Seonghyun Kim <sh8281.kim@samsung.com>
2026-01-05 10:44:02 +09:00
Seonghyun Kim
142cf01ad4 In TypedArrayObject::integerIndexedElementSet check detached buffer correctly
Signed-off-by: Seonghyun Kim <sh8281.kim@samsung.com>
2026-01-05 10:44:02 +09:00
Seonghyun Kim
c231374c38 Update TypedArray.prototype.copyWithin to respect new ECMAScript spec
Signed-off-by: Seonghyun Kim <sh8281.kim@samsung.com>
2026-01-05 10:44:02 +09:00
Seonghyun Kim
ec0d3f1c61 Implement ShadowRealm.prototype.importValue
Signed-off-by: Seonghyun Kim <sh8281.kim@samsung.com>
2025-12-31 10:22:29 +09:00
Seonghyun Kim
982f15c83d Revise ShadowRealm
Signed-off-by: Seonghyun Kim <sh8281.kim@samsung.com>
2025-12-31 10:22:29 +09:00
Seonghyun Kim
23b88e0d3d When matchAll, 'v' is also unicode flag
Signed-off-by: Seonghyun Kim <sh8281.kim@samsung.com>
2025-12-31 10:22:29 +09:00
Seonghyun Kim
94abd9142c Apply updated spec of RegExp prototype functions
Signed-off-by: Seonghyun Kim <sh8281.kim@samsung.com>
2025-12-31 10:22:29 +09:00
Seonghyun Kim
bf4d55e27a Don't use error value on closeing iterator in Promise builtins
Signed-off-by: Seonghyun Kim <sh8281.kim@samsung.com>
2025-12-31 10:22:29 +09:00
Seonghyun Kim
f5ae276846 Disallow -0 year on DateObject parsing
Signed-off-by: Seonghyun Kim <sh8281.kim@samsung.com>
2025-12-31 10:22:29 +09:00
Seonghyun Kim
ef4b1ef414 Implement Iterator.concat method
Signed-off-by: Seonghyun Kim <sh8281.kim@samsung.com>
2025-12-30 14:53:22 +09:00
Seonghyun Kim
5537c312dc Replement DateView constructor to respect new ECMAScript spec
Signed-off-by: Seonghyun Kim <sh8281.kim@samsung.com>
2025-12-26 16:06:16 +09:00
Seonghyun Kim
b66f1f6678 Reject promise with broken promise in AsyncGenerator.prototype.return
Signed-off-by: Seonghyun Kim <sh8281.kim@samsung.com>
2025-12-26 16:06:16 +09:00
Seonghyun Kim
d49aece60c Fixup Date.prototype.setYear
* Read [[DateValue]] and then call ToNumber when stored time-value is valid

Signed-off-by: Seonghyun Kim <sh8281.kim@samsung.com>
2025-12-26 16:06:16 +09:00
Seonghyun Kim
be331e04c3 Use correct Realm on ScriptClassConstructorFunctionObjectReturnValueBinderWithConstruct
Signed-off-by: Seonghyun Kim <sh8281.kim@samsung.com>
2025-12-26 16:06:16 +09:00
Seonghyun Kim
c7a1b4154b Fixup resizable and detached check in ArrayBuffer
Signed-off-by: Seonghyun Kim <sh8281.kim@samsung.com>
2025-12-26 16:06:16 +09:00
Seonghyun Kim
e74404b9a2 Use tryToUseAsIndex in Array.prototype.join
Signed-off-by: Seonghyun Kim <sh8281.kim@samsung.com>
2025-12-24 09:37:03 +09:00
Seonghyun Kim
8aae441360 Update testing files
Signed-off-by: Seonghyun Kim <sh8281.kim@samsung.com>
2025-12-24 09:37:03 +09:00
Seonghyun Kim
c54390bf2e Generate unicode related files from raw unicode text file
* Generate UnicodeIdentifierTables.cpp from DerivedCoreProperties.txt
* Generate YarrCanonicalizeUnicode.cpp from CaseFolding.txt
* Generate UnicodePatternTables.h from UCD

Signed-off-by: Seonghyun Kim <sh8281.kim@samsung.com>
2025-12-24 09:37:03 +09:00
Seonghyun Kim
c599abdc60 Runs coverage tests on self-hosted runner
Signed-off-by: Seonghyun Kim <sh8281.kim@samsung.com>
2025-12-18 18:53:40 +09:00
Seonghyun Kim
8f24498310 Fix minor Temporal issues
Signed-off-by: Seonghyun Kim <sh8281.kim@samsung.com>
2025-12-18 18:03:08 +09:00
Seonghyun Kim
7bb1e520a5 Fix issues on Temporal::calendarResolveFields
Signed-off-by: Seonghyun Kim <sh8281.kim@samsung.com>
2025-12-18 18:03:08 +09:00
Seonghyun Kim
82924e7db5 Fix comptued eraYear check bug
Signed-off-by: Seonghyun Kim <sh8281.kim@samsung.com>
2025-12-18 18:03:08 +09:00
Seonghyun Kim
f541c5c63f Implement Temporal ISODateToFields method
Signed-off-by: Seonghyun Kim <sh8281.kim@samsung.com>
2025-12-18 18:03:08 +09:00
Seonghyun Kim
2f42f070f3 islamic and islamic-rgsa is not supported calendar for Temporal
Signed-off-by: Seonghyun Kim <sh8281.kim@samsung.com>
2025-12-16 16:45:19 +09:00
Seonghyun Kim
5c64d63fd1 Implement missing part of TemporalObjects.prototype.toLocaleString
Signed-off-by: Seonghyun Kim <sh8281.kim@samsung.com>
2025-12-16 16:45:19 +09:00
Seonghyun Kim
2b7a5657ef Extend time limit of analysis-action
Signed-off-by: Seonghyun Kim <sh8281.kim@samsung.com>
2025-12-16 16:45:19 +09:00
Seonghyun Kim
345a295cba Update indian era code for icu-78
Signed-off-by: Seonghyun Kim <sh8281.kim@samsung.com>
2025-12-16 16:45:19 +09:00
Seonghyun Kim
ce0d795280 Update Calendar::diffYearDueToICU4CAndSpecDiffer for icu-78
* see https://unicode-org.atlassian.net/browse/ICU-23167

Signed-off-by: Seonghyun Kim <sh8281.kim@samsung.com>
2025-12-16 16:45:19 +09:00
Seonghyun Kim
4e589b1a52 Use key of era data correctly for icu-78
Signed-off-by: Seonghyun Kim <sh8281.kim@samsung.com>
2025-12-16 16:45:19 +09:00
Seonghyun Kim
15d86aeeb9 Update test set to icu-78 and ubuntu24.04
Signed-off-by: Seonghyun Kim <sh8281.kim@samsung.com>
2025-12-15 18:43:33 +09:00
Seonghyun Kim
71706514f4 Fix extended year bug with iso8601 calendar
Signed-off-by: Seonghyun Kim <sh8281.kim@samsung.com>
2025-12-15 18:43:33 +09:00
Seonghyun Kim
ee64383623 Update Temporal methods
* Update Temporal::calendarDateUntil for non-iso 8601 calendars
* Update monthCode, monthsInYear logic for chinese, dangi calendar

Signed-off-by: Seonghyun Kim <sh8281.kim@samsung.com>
2025-12-15 18:43:33 +09:00
Seonghyun Kim
e6417b8ed8 Update Temporal
* Update calendarAdd method
* Use correct era code for ethioaa calendar
* Implement special path for hebrew calendar

Signed-off-by: Seonghyun Kim <sh8281.kim@samsung.com>
2025-12-15 18:43:33 +09:00
Seonghyun Kim
7667784d79 Consider single era correctly in Temporal
Signed-off-by: Seonghyun Kim <sh8281.kim@samsung.com>
2025-12-15 18:43:33 +09:00
Seonghyun Kim
b1dd07f05c Show correct era code and eraYear to islamic calendars
Signed-off-by: Seonghyun Kim <sh8281.kim@samsung.com>
2025-12-15 18:43:33 +09:00
Seonghyun Kim
2325f6fc64 Improve Temporal + intl402
* Update test262
* Apply basic of https://tc39.es/proposal-intl-era-monthcode/

Signed-off-by: Seonghyun Kim <sh8281.kim@samsung.com>
2025-12-15 18:43:33 +09:00
Seonghyun Kim
1175bb303b Implement Array.fromAsync
Signed-off-by: Seonghyun Kim <sh8281.kim@samsung.com>
2025-12-03 13:54:50 +09:00
Seonghyun Kim
d06a31a1a2 Update Intl.DateTimeFormat.formatRange, formatRangeToParts for Temporal
Signed-off-by: Seonghyun Kim <sh8281.kim@samsung.com>
2025-12-02 13:41:54 +09:00
Seonghyun Kim
369278e640 Implement overwrapping options of Temporal...toLocaleString
Signed-off-by: Seonghyun Kim <sh8281.kim@samsung.com>
2025-12-02 13:41:54 +09:00
Seonghyun Kim
7dd0c1821e Implement Temporal.Duration.prototype.toLocaleString
Signed-off-by: Seonghyun Kim <sh8281.kim@samsung.com>
2025-12-02 13:41:54 +09:00
Seonghyun Kim
33d32009da Implement Temporal.{PlainTime, PlainDateTime}.prototype.toLocaleString
Signed-off-by: Seonghyun Kim <sh8281.kim@samsung.com>
2025-12-02 13:41:54 +09:00
Seonghyun Kim
c3841a7176 Implement Temporal.{PlainYearMonth, PlainMonthDay}.prototype.toLocaleString
Signed-off-by: Seonghyun Kim <sh8281.kim@samsung.com>
2025-12-02 13:41:54 +09:00
Seonghyun Kim
0a2eec0e85 Skip Temporal test on arm32-linux
Signed-off-by: Seonghyun Kim <sh8281.kim@samsung.com>
2025-12-02 13:41:54 +09:00
Seonghyun Kim
75cdb470ba Implement Temporal.Instant.prototype.toLocaleString
Signed-off-by: Seonghyun Kim <sh8281.kim@samsung.com>
2025-12-02 13:41:54 +09:00
Seonghyun Kim
45313ea2ce Implement Temporal.ZonedDateTime.prototype.toLocaleString
Signed-off-by: Seonghyun Kim <sh8281.kim@samsung.com>
2025-12-02 13:41:54 +09:00
Seonghyun Kim
66e105e9f8 Treat timezones correctly
Signed-off-by: Seonghyun Kim <sh8281.kim@samsung.com>
2025-12-02 13:41:54 +09:00
Seonghyun Kim
4a4f8a6d7e Call GC_gcollect_and_unmap and GC_invoke_finalizers on Global::finalize
Signed-off-by: Seonghyun Kim <sh8281.kim@samsung.com>
2025-11-20 17:26:47 +09:00
Seonghyun Kim
ef5ef0b9b4 Update GCutil
Signed-off-by: Seonghyun Kim <sh8281.kim@samsung.com>
2025-11-20 14:25:16 +09:00
Seonghyun Kim
18d3f010a0 Date.prototype.toTemporalInstant
Signed-off-by: Seonghyun Kim <sh8281.kim@samsung.com>
2025-11-20 14:25:16 +09:00
Seonghyun Kim
297162133e Implement Temporal.Duration.compare
Signed-off-by: Seonghyun Kim <sh8281.kim@samsung.com>
2025-11-20 14:25:16 +09:00
Seonghyun Kim
ba05eaec99 Implement Temporal.Duration.total, round
Signed-off-by: Seonghyun Kim <sh8281.kim@samsung.com>
2025-11-20 14:25:16 +09:00
Seonghyun Kim
5bbfa73dcd Implement Temporal.Instant.toZonedDateTimeISO
Signed-off-by: Seonghyun Kim <sh8281.kim@samsung.com>
2025-11-20 14:25:16 +09:00
Seonghyun Kim
5240b9cbec Implement Temporal.Now.*ISO() functions
Signed-off-by: Seonghyun Kim <sh8281.kim@samsung.com>
2025-11-20 14:25:16 +09:00
Ivan Maidanski
72172f6c47 Update LICENSE.BOEHM-GC
The license file is copied from third_party/GCutil/LICENSE.

Signed-off-by: Ivan Maidanski <ivmai@mail.ru>
2025-11-20 10:40:24 +09:00
Ivan Maidanski
d017677d54 Change build scripts after move bdwgc files to gcutil repository root
Change `GCutil/bdwgc` to `GCutil` in escargot.spec, android CMakeLists.txt.
Remove `-I .../GCutil/bdwgc -I .../GCutil/bdwgc/include/gc`.

Signed-off-by:  Ivan Maidanski <ivmai@mail.ru>
2025-11-19 16:06:58 +09:00
Seonghyun Kim
98de57bc8a Fix windows CI
Signed-off-by: Seonghyun Kim <sh8281.kim@samsung.com>
2025-11-18 17:58:10 +09:00
Seonghyun Kim
fdac7ae1c3 Implement Temporal.ZonedDateTime.round, startOfDay, getTimeZoneTransition, valueOf, compare
Signed-off-by: Seonghyun Kim <sh8281.kim@samsung.com>
2025-11-17 11:17:29 +09:00
Seonghyun Kim
dccf2f9256 Implement Temporal.ZonedDateTime.since, until
Signed-off-by: Seonghyun Kim <sh8281.kim@samsung.com>
2025-11-17 11:17:29 +09:00
Seonghyun Kim
83c9e9a50a Implement Temporal.ZonedDateTime.add, subtract
Signed-off-by: Seonghyun Kim <sh8281.kim@samsung.com>
2025-11-17 11:17:29 +09:00
Seonghyun Kim
0c41944316 Implement Temporal.ZonedDateTime.with* functions
Signed-off-by: Seonghyun Kim <sh8281.kim@samsung.com>
2025-11-17 11:17:29 +09:00
Seonghyun Kim
3844dced3f Support more timezone names on Temporal
Signed-off-by: Seonghyun Kim <sh8281.kim@samsung.com>
2025-11-17 11:17:29 +09:00
Seonghyun Kim
0249b5efb5 Implement Temporal.ZonedDateTime.to* functions
Signed-off-by: Seonghyun Kim <sh8281.kim@samsung.com>
2025-11-17 11:17:29 +09:00
Seonghyun Kim
1c3248ce35 Implement Temporal.PlainDateTime.toZonedDateTime
Signed-off-by: Seonghyun Kim <sh8281.kim@samsung.com>
2025-11-17 11:17:29 +09:00
Seonghyun Kim
ef617652dc Implement Temporal.PlainDate.to* functions
Signed-off-by: Seonghyun Kim <sh8281.kim@samsung.com>
2025-11-17 11:17:29 +09:00
Seonghyun Kim
bb3c62e2cc Implement from, toString, equals, getter of Temporal.ZonedDateTimeObject
Signed-off-by: Seonghyun Kim <sh8281.kim@samsung.com>
2025-11-10 13:20:35 +09:00
Seonghyun Kim
acd242f7df Implement constructor of Temporal.ZonedDateTimeObject
Signed-off-by: Seonghyun Kim <sh8281.kim@samsung.com>
2025-11-10 13:20:35 +09:00
Seonghyun Kim
5618ae6f7b Implement Temporal.PlainDateTime.compare
Signed-off-by: Seonghyun Kim <sh8281.kim@samsung.com>
2025-10-21 15:10:15 +09:00
Seonghyun Kim
dc7640a152 Implement Temporal.PlainDateTime.{ toPlainTime, toPlainDate }
Signed-off-by: Seonghyun Kim <sh8281.kim@samsung.com>
2025-10-21 15:10:15 +09:00
Seonghyun Kim
5e5eb5d631 Implement Temporal.PlainDateTime.{ equals, round }
Signed-off-by: Seonghyun Kim <sh8281.kim@samsung.com>
2025-10-21 15:10:15 +09:00
Seonghyun Kim
fa3432f1d6 Implement Temporal.PlainDateTime.{ since, until }
Signed-off-by: Seonghyun Kim <sh8281.kim@samsung.com>
2025-10-21 15:10:15 +09:00
Seonghyun Kim
9d634be004 Implement Temporal.PlainDateTime.{ add, subtract }
Signed-off-by: Seonghyun Kim <sh8281.kim@samsung.com>
2025-10-21 15:10:15 +09:00
Seonghyun Kim
cb01142c4c Implement Temporal.PlainDateTime.{ with * }
Signed-off-by: Seonghyun Kim <sh8281.kim@samsung.com>
2025-10-21 15:10:15 +09:00
Seonghyun Kim
f9ca29d5cb Implement basic of Temporal.PlainDateTime
Signed-off-by: Seonghyun Kim <sh8281.kim@samsung.com>
2025-10-17 14:58:40 +09:00
Seonghyun Kim
c3c3bca85e Implement basic of Temporal.PlainMonthDay
Signed-off-by: Seonghyun Kim <sh8281.kim@samsung.com>
2025-10-17 14:58:40 +09:00
Seonghyun Kim
600fa1a906 Implement Temporal.PlainYearMonth.{ until, since }
Signed-off-by: Seonghyun Kim <sh8281.kim@samsung.com>
2025-10-14 14:21:15 +09:00
Seonghyun Kim
b9041e60b7 Implmenet Temporal.PlainYearMonth.{ add, subtract }
Signed-off-by: Seonghyun Kim <sh8281.kim@samsung.com>
2025-10-14 14:21:15 +09:00
Seonghyun Kim
70e0721082 Implement Temporal.PlainYearMonth.{ compare, with, toPlainDate }
Signed-off-by: Seonghyun Kim <sh8281.kim@samsung.com>
2025-10-14 14:21:15 +09:00
Seonghyun Kim
36e4562e68 Fix minor issues in PlainYearMonth
Signed-off-by: Seonghyun Kim <sh8281.kim@samsung.com>
2025-10-14 14:21:15 +09:00
2jaeheon
77a3b8554f ci(macos): add ad-hoc codesign for .dylib artifacts
Signed-off-by: 2jaeheon <jaeheon0826@jbnu.ac.kr>
Co-authored-by: M-SE0K <seg082911@gmail.com>
2025-10-02 13:10:02 +09:00
2Jaeheon
77741e8d13 ci: fix-windows build
Signed-off-by: 2Jaeheon <jaeheon0826@jbnu.ac.kr>
2025-09-26 16:06:22 +09:00
Hyukwoo Park
4f0a1cff00 Fix conflicts in actions
Signed-off-by: Hyukwoo Park <hyukwoo.park@jbnu.ac.kr>
2025-09-26 09:10:37 +09:00
2Jaeheon
9e414f3933 ci: add -DCMAKE_POLICY_VERSION_MINIMUM to builds
Signed-off-by: 2Jaeheon <jaeheon0826@jbnu.ac.kr>
2025-09-24 17:30:15 +09:00
Seonghyun Kim
641e3813c4 Implement basic of Temporal.PlainYearMonth
Signed-off-by: Seonghyun Kim <sh8281.kim@samsung.com>
2025-09-22 16:48:25 +09:00
Hyukwoo Park
8b39a2c3ff Enable workflow_dispatch in release action
Signed-off-by: Hyukwoo Park <hyukwoo.park@jbnu.ac.kr>
2025-09-20 18:35:42 +09:00
Seonghyun Kim
8e5636c198 Implement Temporal.PlainDate.{ since, until }
Signed-off-by: Seonghyun Kim <sh8281.kim@samsung.com>
2025-09-18 16:17:04 +09:00
Seonghyun Kim
5b74588aa1 Implement Temporal.PlainDate.{ add, subtract, equals, with, withCalendar }
Signed-off-by: Seonghyun Kim <sh8281.kim@samsung.com>
2025-09-18 16:17:04 +09:00
Seonghyun Kim
141797871f Implement basic of Temporal.PlainDate
Signed-off-by: Seonghyun Kim <sh8281.kim@samsung.com>
2025-09-18 16:17:04 +09:00
Seonghyun Kim
90d8da7fe8 Implement Temporal.PlainTime.{ since, until, round, equals }
Signed-off-by: Seonghyun Kim <sh8281.kim@samsung.com>
2025-09-18 16:17:04 +09:00
Seonghyun Kim
d7c2db8f3f Implement Temporal.PlainTime.{ toString, add, subtract, from }
Signed-off-by: Seonghyun Kim <sh8281.kim@samsung.com>
2025-09-18 16:17:04 +09:00
Seonghyun Kim
84a305785a Implement base of Temporal.PlainTime
Signed-off-by: Seonghyun Kim <sh8281.kim@samsung.com>
2025-09-18 16:17:04 +09:00
2Jaeheon
293729c869 implement: code review bot
Signed-off-by: 2Jaeheon <jaeheon0826@jbnu.ac.kr>
2025-09-18 16:15:47 +09:00
Hyukwoo Park
40e54ebead Fix cmake build error in macOS-actions
Signed-off-by: Hyukwoo Park <hyukwoo.park@jbnu.ac.kr>
2025-09-15 12:15:03 +09:00
Seonghyun Kim
8d140c3c0f Implement Temporal.Duration.{ add, subtract, with, toLocalString, toJSON }
Signed-off-by: Seonghyun Kim <sh8281.kim@samsung.com>
2025-09-09 11:38:59 +09:00
Seonghyun Kim
7135cbaefe Implement Temporal.Instant.{add, subtract, compare}
Signed-off-by: Seonghyun Kim <sh8281.kim@samsung.com>
2025-09-09 11:38:59 +09:00
Seonghyun Kim
34c2f0a20e Implement Temporal.Instant.{since, until}, Temporal.duration.{toString, negated}
Signed-off-by: Seonghyun Kim <sh8281.kim@samsung.com>
2025-09-09 11:38:59 +09:00
Seonghyun Kim
5711241b99 Implement Temporal.Instant.{ toLocalString, toJSON, round }
Signed-off-by: Seonghyun Kim <sh8281.kim@samsung.com>
2025-09-04 13:22:13 +09:00
Seonghyun Kim
96beab3416 Implement basic methods of Temporal.Instant
Signed-off-by: Seonghyun Kim <sh8281.kim@samsung.com>
2025-09-04 13:22:13 +09:00
Seonghyun Kim
06e356f15a Introduce Int128 library
Signed-off-by: Seonghyun Kim <sh8281.kim@samsung.com>
2025-09-04 13:22:13 +09:00
Seonghyun Kim
6175024ffc Implement basic of Temporal.Duration
Signed-off-by: Seonghyun Kim <sh8281.kim@samsung.com>
2025-09-04 13:22:13 +09:00
Seonghyun Kim
3c1ddaaa50 Implement basic of Temporal.Now and Temporal.Instant
Signed-off-by: Seonghyun Kim <sh8281.kim@samsung.com>
2025-09-04 13:22:13 +09:00
Seonghyun Kim
52bbc7a9bc Update README and CMakeFiles
* rename ESCARGOT_ENABLE_SHADOWREALM to ESCARGOT_SHADOWREALM in build stuff

Signed-off-by: Seonghyun Kim <sh8281.kim@samsung.com>
2025-08-26 17:00:40 +09:00
Seonghyun Kim
fc47134b6e Rename ESCARGOT_ENABLE_SHADOWREALM to ENABLE_SHADOWREALM in source code
Signed-off-by: Seonghyun Kim <sh8281.kim@samsung.com>
2025-08-25 12:38:24 +09:00
kwonjeomsim
2482f40fe2 Implement ShadowRealm Wrapped function 2025-08-22 14:21:26 +09:00
kwonjeomsim
25a5bf17c3 Implement ShadowRealm constructor and prototype.evaluate method 2025-08-22 14:21:26 +09:00
Seonghyun Kim
6cfdea8169 Update release.yml
Signed-off-by: Seonghyun Kim <sh8281.kim@samsung.com>
2025-08-22 14:21:01 +09:00
218 changed files with 113192 additions and 26261 deletions

View file

@ -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:

View file

@ -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
View 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));
}

View file

@ -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
View 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/

View file

@ -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
View file

@ -1,5 +1,5 @@
#error_report
escargot
/escargot
escargot.x*
.project
.cproject

View file

@ -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})

View file

@ -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.

View file

@ -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.

View file

@ -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})

View file

@ -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)

View file

@ -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})

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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;

View file

@ -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);

View file

@ -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();

View file

@ -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);

View file

@ -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)
{

View file

@ -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) \

View file

@ -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),

View file

@ -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;
}

View file

@ -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);

View file

@ -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);

View file

@ -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);

View 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

View file

@ -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

View file

@ -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);

View file

@ -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);

View file

@ -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

View file

@ -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

View file

@ -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 */

File diff suppressed because it is too large Load diff

View 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

View 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 */

View 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

File diff suppressed because it is too large Load diff

View 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

View 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 */

View 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

View file

@ -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()) {

View file

@ -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;

View 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
View 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

View file

@ -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();

View file

@ -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

View file

@ -573,6 +573,7 @@ void ByteCodeGenerator::relocateByteCode(ByteCodeBlock* block)
break;
}
case ToNumberOpcode:
case ToPropertyKeyOpcode:
case IncrementOpcode:
case DecrementOpcode:
case UnaryMinusOpcode:

View file

@ -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)

View file

@ -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, &registerFile[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 = &registerFile[code->m_dataRegisterIndex];
ptr = reinterpret_cast<uint8_t*>(&registerFile[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) {

View file

@ -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

View file

@ -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

View file

@ -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;

View file

@ -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")) {

View file

@ -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();

View file

@ -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;
};

View file

@ -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");
}
}

View file

@ -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) {

View file

@ -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

View file

@ -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;

View file

@ -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;

View file

@ -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;
}

View file

@ -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

View file

@ -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

View file

@ -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)

View file

@ -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
}

View file

@ -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

View file

@ -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);

View file

@ -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);

View file

@ -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

View file

@ -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);
}

View file

@ -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());

View file

@ -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;

View file

@ -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;

View file

@ -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
}
}

View file

@ -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);
}

View file

@ -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)

View file

@ -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";

View file

@ -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;

View file

@ -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,

View file

@ -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;
};

View file

@ -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]]).

View file

@ -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

View file

@ -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;

View file

@ -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;
}

View file

@ -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;

View file

@ -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

View file

@ -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));

View file

@ -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

View file

@ -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)) {

View file

@ -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"

View file

@ -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 {

View file

@ -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

View file

@ -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());
}

View file

@ -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();

View file

@ -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