Source code
Revision control
Copy as Markdown
Other Tools
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
#include "mozilla/RefPtr.h" // RefPtr
#include "mozilla/ScopeExit.h" // MakeScopeExit
#include "mozilla/Utf8.h" // mozilla::Utf8Unit
#include "frontend/CompilationStencil.h" // JS::Stencil, frontend::CompilationStencil
#include "js/CompileOptions.h" // JS::CompileOptions, JS::InstantiateOptions
#include "js/experimental/CompileScript.h" // JS::NewFrontendContext
#include "js/SourceText.h" // JS::Source{Ownership,Text}
#include "jsapi-tests/tests.h"
#include "vm/ErrorReporting.h"
#include "vm/JSONPrinter.h" // js::JSONPrinter
using namespace JS;
template <typename T>
static void dump(const T& data) {
js::Fprinter printer(stderr);
js::JSONPrinter json(printer, true);
data->dump(json);
printer.put("\n");
}
BEGIN_TEST(testCompileScript) {
CHECK(testCompile());
CHECK(testNonsyntacticCompile());
CHECK(testCompileModule());
CHECK(testPrepareForInstantiate());
return true;
}
bool testCompile() {
static constexpr std::string_view src = "42\n";
static constexpr std::u16string_view src_16 = u"42\n";
static_assert(src.length() == src_16.length(),
"Source buffers must be same length");
JS::CompileOptions options(cx);
JS::FrontendContext* fc = JS::NewFrontendContext();
CHECK(fc);
auto destroyFc =
mozilla::MakeScopeExit([fc] { JS::DestroyFrontendContext(fc); });
{ // 16-bit characters
JS::SourceText<char16_t> buf16;
CHECK(buf16.init(cx, src_16.data(), src_16.length(),
JS::SourceOwnership::Borrowed));
RefPtr<JS::Stencil> stencil =
CompileGlobalScriptToStencil(fc, options, buf16);
CHECK(stencil);
RefPtr<const js::frontend::CompilationStencil> initial =
stencil->getInitial();
CHECK(initial->scriptExtra.size() == 1);
CHECK(initial->scriptExtra[0].extent.sourceStart == 0);
CHECK(initial->scriptExtra[0].extent.sourceEnd == 3);
CHECK(initial->scriptData.size() == 1);
CHECK(initial->scriptData[0].hasSharedData()); // has generated bytecode
CHECK(initial->scriptData[0].gcThingsLength == 1);
}
{ // 8-bit characters
JS::SourceText<mozilla::Utf8Unit> buf8;
CHECK(
buf8.init(cx, src.data(), src.length(), JS::SourceOwnership::Borrowed));
RefPtr<JS::Stencil> stencil =
CompileGlobalScriptToStencil(fc, options, buf8);
CHECK(stencil);
RefPtr<const js::frontend::CompilationStencil> initial =
stencil->getInitial();
CHECK(initial->scriptExtra.size() == 1);
CHECK(initial->scriptExtra[0].extent.sourceStart == 0);
CHECK(initial->scriptExtra[0].extent.sourceEnd == 3);
CHECK(initial->scriptData.size() == 1);
CHECK(initial->scriptData[0].hasSharedData()); // has generated bytecode
CHECK(initial->scriptData[0].gcThingsLength == 1);
}
{ // propagates failures
static constexpr std::string_view badSrc = "{ a: 1, b: 3\n";
JS::SourceText<mozilla::Utf8Unit> srcBuf;
CHECK(srcBuf.init(cx, badSrc.data(), badSrc.length(),
JS::SourceOwnership::Borrowed));
RefPtr<JS::Stencil> stencil =
CompileGlobalScriptToStencil(fc, options, srcBuf);
CHECK(!stencil);
CHECK(fc->maybeError().isSome());
const js::CompileError& error = fc->maybeError().ref();
CHECK(JSEXN_SYNTAXERR == error.exnType);
CHECK(error.lineno == 1);
CHECK(error.column.oneOriginValue() == 10);
}
return true;
}
bool testNonsyntacticCompile() {
const char* chars =
"function f() { return x; }"
"f();";
JS::SourceText<mozilla::Utf8Unit> srcBuf;
CHECK(srcBuf.init(cx, chars, strlen(chars), JS::SourceOwnership::Borrowed));
JS::CompileOptions options(cx);
options.setNonSyntacticScope(true);
JS::FrontendContext* fc = JS::NewFrontendContext();
CHECK(fc);
auto destroyFc =
mozilla::MakeScopeExit([fc] { JS::DestroyFrontendContext(fc); });
RefPtr<JS::Stencil> stencil =
CompileGlobalScriptToStencil(fc, options, srcBuf);
CHECK(stencil);
JS::InstantiateOptions instantiateOptions(options);
JS::RootedScript script(
cx, JS::InstantiateGlobalStencil(cx, instantiateOptions, stencil));
CHECK(script);
CHECK(script->hasNonSyntacticScope());
return true;
}
bool testCompileModule() {
static constexpr std::string_view src = "import 'a'; 42\n";
static constexpr std::u16string_view src_16 = u"import 'a'; 42\n";
static_assert(src.length() == src_16.length(),
"Source buffers must be same length");
JS::CompileOptions options(cx);
JS::FrontendContext* fc = JS::NewFrontendContext();
CHECK(fc);
auto destroyFc =
mozilla::MakeScopeExit([fc] { JS::DestroyFrontendContext(fc); });
{ // 16-bit characters
JS::SourceText<char16_t> buf16;
CHECK(buf16.init(cx, src_16.data(), src_16.length(),
JS::SourceOwnership::Borrowed));
RefPtr<JS::Stencil> stencil =
CompileModuleScriptToStencil(fc, options, buf16);
CHECK(stencil);
RefPtr<const js::frontend::CompilationStencil> initial =
stencil->getInitial();
CHECK(initial->isModule());
CHECK(initial->scriptExtra.size() == 1);
CHECK(initial->scriptExtra[0].extent.sourceStart == 0);
CHECK(initial->scriptExtra[0].extent.sourceEnd == 15);
CHECK(initial->scriptData.size() == 1);
CHECK(initial->scriptData[0].hasSharedData()); // has generated bytecode
CHECK(initial->scriptData[0].gcThingsLength == 1);
}
{ // 8-bit characters
JS::SourceText<mozilla::Utf8Unit> buf8;
CHECK(
buf8.init(cx, src.data(), src.length(), JS::SourceOwnership::Borrowed));
RefPtr<JS::Stencil> stencil =
CompileModuleScriptToStencil(fc, options, buf8);
CHECK(stencil);
RefPtr<const js::frontend::CompilationStencil> initial =
stencil->getInitial();
CHECK(initial->scriptExtra.size() == 1);
CHECK(initial->scriptExtra[0].extent.sourceStart == 0);
CHECK(initial->scriptExtra[0].extent.sourceEnd == 15);
CHECK(initial->scriptData.size() == 1);
CHECK(initial->scriptData[0].hasSharedData()); // has generated bytecode
CHECK(initial->scriptData[0].gcThingsLength == 1);
}
{ // propagates failures
static constexpr std::string_view badSrc = "{ a: 1, b: 3\n";
JS::SourceText<mozilla::Utf8Unit> srcBuf;
CHECK(srcBuf.init(cx, badSrc.data(), badSrc.length(),
JS::SourceOwnership::Borrowed));
RefPtr<JS::Stencil> stencil =
CompileModuleScriptToStencil(fc, options, srcBuf);
CHECK(!stencil);
CHECK(fc->maybeError().isSome());
const js::CompileError& error = fc->maybeError().ref();
CHECK(JSEXN_SYNTAXERR == error.exnType);
CHECK(error.lineno == 1);
CHECK(error.column.oneOriginValue() == 10);
}
return true;
}
bool testPrepareForInstantiate() {
static constexpr std::u16string_view src_16 =
u"function f() { return {'field': 42};}; f()['field']\n";
JS::CompileOptions options(cx);
JS::SourceText<char16_t> buf16;
CHECK(buf16.init(cx, src_16.data(), src_16.length(),
JS::SourceOwnership::Borrowed));
JS::FrontendContext* fc = JS::NewFrontendContext();
CHECK(fc);
auto destroyFc =
mozilla::MakeScopeExit([fc] { JS::DestroyFrontendContext(fc); });
RefPtr<JS::Stencil> stencil =
CompileGlobalScriptToStencil(fc, options, buf16);
CHECK(stencil);
RefPtr<const js::frontend::CompilationStencil> initial =
stencil->getInitial();
CHECK(initial->scriptData.size() == 2);
CHECK(initial->scopeData.size() == 1); // function f
CHECK(initial->parserAtomData.size() == 1); // 'field'
JS::InstantiationStorage storage;
CHECK(JS::PrepareForInstantiate(fc, *stencil, storage));
CHECK(storage.isValid());
// TODO storage.gcOutput_ is private, so there isn't a good way to check the
// scriptData and scopeData capacities
return true;
}
END_TEST(testCompileScript);