Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -55,4 +55,7 @@ v8_build
/project-template-ios/.build_env_vars.sh
/project-template-ios/__PROJECT_NAME__.xcodeproj/project.xcworkspace/xcshareddata/
/project-template-vision/.build_env_vars.sh
/project-template-vision/__PROJECT_NAME__.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
/project-template-vision/__PROJECT_NAME__.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist

# test262
dist-test/
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,6 @@
path = libffi
url = https://github.com/NativeScript/libffi.git
branch = darind/v8-ios
[submodule "TestRunner/app/tests/vendor/test262"]
path = TestRunner/app/tests/vendor/test262
url = https://github.com/tc39/test262.git
16 changes: 10 additions & 6 deletions NativeScript/inspector/JsV8InspectorClient.mm
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,13 @@

namespace v8_inspector {

namespace {
StringView Make8BitStringView(const std::string& value) {
return StringView(reinterpret_cast<const uint8_t*>(value.data()),
value.size());
}
} // namespace

#define NOTIFICATION(name) \
[[NSString stringWithFormat:@"%@:NativeScript.Debug.%s", \
[[NSBundle mainBundle] bundleIdentifier], name] UTF8String]
Expand Down Expand Up @@ -257,8 +264,7 @@
}

void JsV8InspectorClient::dispatchMessage(const std::string& message) {
std::vector<uint16_t> vector = tns::ToVector(message);
StringView messageView(vector.data(), vector.size());
StringView messageView = Make8BitStringView(message);
Isolate* isolate = isolate_;
v8::Locker locker(isolate);
Isolate::Scope isolate_scope(isolate);
Expand Down Expand Up @@ -343,8 +349,7 @@
auto returnString = GetReturnMessageFromDomainHandlerResult(result, requestId);

if (returnString.size() > 0) {
std::vector<uint16_t> vector = tns::ToVector(returnString);
StringView messageView(vector.data(), vector.size());
StringView messageView = Make8BitStringView(returnString);
auto msg = StringBuffer::create(messageView);
this->sendNotification(std::move(msg));
}
Expand Down Expand Up @@ -486,8 +491,7 @@
Local<v8::String> arg = args[0].As<v8::String>();
std::string message = tns::ToString(isolate, arg);

std::vector<uint16_t> vector = tns::ToVector(message);
StringView messageView(vector.data(), vector.size());
StringView messageView = Make8BitStringView(message);
auto msg = StringBuffer::create(messageView);
client->sendNotification(std::move(msg));
}
Expand Down
2 changes: 1 addition & 1 deletion NativeScript/inspector/src/inspector/string-16.h
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@

namespace v8_inspector {

using UChar = uint16_t;
using UChar = char16_t;

class String16 {
public:
Expand Down
4 changes: 2 additions & 2 deletions NativeScript/inspector/src/inspector/string-util.h
Original file line number Diff line number Diff line change
Expand Up @@ -30,13 +30,13 @@ class StringUtil {
}

static String fromUTF16LE(const uint16_t* data, size_t length) {
return String16::fromUTF16LE(data, length);
return String16::fromUTF16LE(reinterpret_cast<const UChar*>(data), length);
}

static const uint8_t* CharactersLatin1(const String& s) { return nullptr; }
static const uint8_t* CharactersUTF8(const String& s) { return nullptr; }
static const uint16_t* CharactersUTF16(const String& s) {
return s.characters16();
return reinterpret_cast<const uint16_t*>(s.characters16());
}
static size_t CharacterCount(const String& s) { return s.length(); }
};
Expand Down
9 changes: 4 additions & 5 deletions NativeScript/inspector/utils.mm
Original file line number Diff line number Diff line change
Expand Up @@ -35,17 +35,16 @@
}

std::string v8_inspector::ToStdString(const StringView& value) {
std::vector<uint16_t> buffer(value.length());
std::u16string value16;
value16.resize(value.length());
for (size_t i = 0; i < value.length(); i++) {
if (value.is8Bit()) {
buffer[i] = value.characters8()[i];
value16[i] = static_cast<char16_t>(value.characters8()[i]);
} else {
buffer[i] = value.characters16()[i];
value16[i] = static_cast<char16_t>(value.characters16()[i]);
}
}

std::u16string value16(buffer.begin(), buffer.end());

#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
// FIXME: std::codecvt_utf8_utf16 is deprecated
std::wstring_convert<std::codecvt_utf8_utf16<char16_t>, char16_t> convert;
Expand Down
4 changes: 2 additions & 2 deletions NativeScript/v8runtime/V8Runtime.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -431,7 +431,7 @@ std::shared_ptr<const jsi::PreparedJavaScript> V8Runtime::prepareJavaScript(

jsi::Value V8Runtime::evaluatePreparedJavaScript(
const std::shared_ptr<const jsi::PreparedJavaScript>& js) {
return evaluateJavaScript(nullptr, nullptr);
return evaluateJavaScript(nullptr, "");
}

void V8Runtime::queueMicrotask(const jsi::Function& callback) {
Expand Down Expand Up @@ -1586,4 +1586,4 @@ void V8Runtime::OnHostFuncionContainerCallback(
}
}

} // namespace rnv8
} // namespace rnv8
36 changes: 36 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,42 @@ Runtime initialization took 55ms

If all tests pass, everything is good! At this point you can make changes to the runtime, add breakpoints and step through with the debugger. In the next section we'll see how to attach the runtime to an existing NativeScript application allowing us to debug runtime issues in actual apps.

## Test262 integration

The `TestRunner` target can host a filtered subset of [Test262](https://github.com/tc39/test262) inside the same Jasmine and JUnit pipeline that the existing runtime tests already use.

The integration in this repo is intentionally phased:

1. Test262 cases are discovered ahead of time and written to `TestRunner/app/tests/Test262/generated-manifest.json`.
2. Each discovered case is executed in its own `Worker`, so it gets an isolated realm instead of sharing Jasmine's main global object.
3. The generated specs are reported through the existing `TestRunner` JUnit output, so they show up in the current Xcode and CI flow.

To wire in the upstream suite, add it as a submodule under the bundled test tree:

```bash
git submodule add https://github.com/tc39/test262.git TestRunner/app/tests/vendor/test262
git submodule update --init --recursive
```

Then enable and tune `TestRunner/app/tests/Test262/config.js`. The TestRunner build now regenerates the manifest automatically by running:

```bash
node ./scripts/generate-test262-manifest.js
```

By default, enabling the suite does not try to run the whole upstream corpus. It starts from the curated prefixes in `TestRunner/app/tests/Test262/curated-prefixes.json` and caps generation at 250 entries unless you override it.

Useful environment variables for local runs and CI sharding:

```bash
TEST262_FILTER=built-ins/Array
TEST262_LIMIT=250
TEST262_SHARD_COUNT=4
TEST262_SHARD_INDEX=0
```

The current adapter is deliberately conservative. It skips unsupported `module` and `raw` cases by default and is disabled by default so existing CI remains unchanged until you choose a supported subset.

# Attaching the runtime to a NativeScript app

In the existing app, we need to prepare the Xcode project using `ns prepare ios`. This will create a folder named `platforms/ios` and in there a `<appname>.xcworkspace` (or .xcodeproject but note the following...).
Expand Down
184 changes: 184 additions & 0 deletions TestRunner/app/tests/Test262/RunnerWorker.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
function serializeError(error) {
if (!error) {
return "Unknown error";
}

if (typeof error === "string") {
return error;
}

var parts = [];
if (error.name) {
parts.push(error.name);
}
if (error.message) {
parts.push(error.message);
}

var summary = parts.join(": ");
if (error.stack) {
summary += "\n" + error.stack;
}

return summary || String(error);
}

function readTextFile(path) {
var data = NSData.dataWithContentsOfFile(path);
if (!data) {
throw new Error("Unable to read file: " + path);
}

var text = NSString.alloc().initWithDataEncoding(data, NSUTF8StringEncoding);
if (!text) {
throw new Error("Unable to decode UTF-8 file: " + path);
}

return text.toString();
}

function withSourceURL(source, path) {
return source + "\n//# sourceURL=" + path.replace(/\\/g, "/");
}

function evalGlobal(source, path) {
var globalEval = (0, eval);
return globalEval(withSourceURL(source, path));
}

function compileScript(source, path) {
return Function(withSourceURL(source, path));
}

function expectedErrorMatches(error, negative) {
if (!negative || !negative.type) {
return true;
}

return error && (error.name === negative.type || (error.constructor && error.constructor.name === negative.type));
}

function makePrelude() {
globalThis.$262 = {
global: globalThis
};

globalThis.print = function () {
};

globalThis.reportCompare = function (expected, actual, message) {
if (expected !== actual) {
throw new Error(message || ("Expected " + expected + " but received " + actual));
}
};
}

function loadHarness(test262Root, includes) {
includes.forEach(function (includeName) {
var harnessPath = test262Root + "/harness/" + includeName;
evalGlobal(readTextFile(harnessPath), harnessPath);
});
}

function postPass() {
postMessage({ status: "passed" });
}

function postFailure(error) {
postMessage({
status: "failed",
error: serializeError(error)
});
}

function runCase(payload) {
var settled = false;
var test262Root = NSBundle.mainBundle.bundlePath + "/app/tests/" + payload.bundleSubmodulePath;
var testPath = test262Root + "/test/" + payload.relativePath;
var source = readTextFile(testPath);
var wrappedSource = payload.mode === "strict" ? "\"use strict\";\n" + source : source;

function completeWithPass() {
if (settled) {
return;
}

settled = true;
postPass();
}

function completeWithFailure(error) {
if (settled) {
return;
}

settled = true;
postFailure(error);
}

makePrelude();

if (payload.async) {
globalThis.$DONE = function (error) {
if (error === undefined || error === null) {
completeWithPass();
return;
}

completeWithFailure(error);
};
}

loadHarness(test262Root, payload.includes || []);

if (payload.negative && payload.negative.phase === "parse") {
try {
compileScript(wrappedSource, testPath);
} catch (error) {
if (expectedErrorMatches(error, payload.negative)) {
completeWithPass();
return;
}

completeWithFailure(error);
return;
}

completeWithFailure(new Error("Expected parse failure but compilation succeeded"));
return;
}

try {
evalGlobal(wrappedSource, testPath);
} catch (error) {
if (payload.negative) {
if (expectedErrorMatches(error, payload.negative)) {
completeWithPass();
return;
}

completeWithFailure(error);
return;
}

completeWithFailure(error);
return;
}

if (payload.negative) {
completeWithFailure(new Error("Expected runtime failure but test completed successfully"));
return;
}

if (!payload.async) {
completeWithPass();
}
}

onmessage = function (message) {
try {
runCase(message.data);
} catch (error) {
postFailure(error);
}
};
28 changes: 28 additions & 0 deletions TestRunner/app/tests/Test262/config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
module.exports = {
enabled: true,
suiteName: "Test262",
projectSubmodulePath: "TestRunner/app/tests/vendor/test262",
bundleSubmodulePath: "vendor/test262",
generatedManifestPath: "TestRunner/app/tests/Test262/generated-manifest.json",
curatedIncludePathsPath: "TestRunner/app/tests/Test262/curated-prefixes.json",
harnessDefaults: ["assert.js", "sta.js"],
unsupportedFlags: ["module", "raw"],
unsupportedIncludes: [
"agent.js",
"detachArrayBuffer.js",
"nondeterministic.js",
"shadowrealm.js",
"timer.js",
"workerHelper.js"
],
unsupportedFeatures: [
"Array.fromAsync",
"cross-realm",
"ShadowRealm"
],
includePaths: ["built-ins/Object"],
excludePaths: ["built-ins/Array/from/elements-deleted-after.js"],
timeoutMs: 4000,
defaultLimit: 100,
expandStrictVariants: true
};
Loading
Loading