/* 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 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "mozilla/RefPtr.h" // RefPtr #include "mozilla/ScopeExit.h" // MakeScopeExit #include "mozilla/Utf8.h" // mozilla::Utf8Unit #include "frontend/CompilationStencil.h" // JS::Stencil #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 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 buf16; CHECK(buf16.init(cx, src_16.data(), src_16.length(), JS::SourceOwnership::Borrowed)); JS::CompilationStorage compileStorage; RefPtr stencil = CompileGlobalScriptToStencil(fc, options, buf16, compileStorage); CHECK(stencil); CHECK(stencil->scriptExtra.size() == 1); CHECK(stencil->scriptExtra[0].extent.sourceStart == 0); CHECK(stencil->scriptExtra[0].extent.sourceEnd == 3); CHECK(stencil->scriptData.size() == 1); CHECK(stencil->scriptData[0].hasSharedData()); // has generated bytecode CHECK(stencil->scriptData[0].gcThingsLength == 1); CHECK(compileStorage.hasInput()); } { // 8-bit characters JS::SourceText buf8; CHECK( buf8.init(cx, src.data(), src.length(), JS::SourceOwnership::Borrowed)); JS::CompilationStorage compileStorage; RefPtr stencil = CompileGlobalScriptToStencil(fc, options, buf8, compileStorage); CHECK(stencil); CHECK(stencil->scriptExtra.size() == 1); CHECK(stencil->scriptExtra[0].extent.sourceStart == 0); CHECK(stencil->scriptExtra[0].extent.sourceEnd == 3); CHECK(stencil->scriptData.size() == 1); CHECK(stencil->scriptData[0].hasSharedData()); // has generated bytecode CHECK(stencil->scriptData[0].gcThingsLength == 1); CHECK(compileStorage.hasInput()); } { // propagates failures static constexpr std::string_view badSrc = "{ a: 1, b: 3\n"; JS::SourceText srcBuf; CHECK(srcBuf.init(cx, badSrc.data(), badSrc.length(), JS::SourceOwnership::Borrowed)); JS::CompilationStorage compileStorage; RefPtr stencil = CompileGlobalScriptToStencil(fc, options, srcBuf, compileStorage); 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 == 9); } return true; } bool testNonsyntacticCompile() { const char* chars = "function f() { return x; }" "f();"; JS::SourceText 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); }); JS::CompilationStorage compileStorage; RefPtr stencil = CompileGlobalScriptToStencil(fc, options, srcBuf, compileStorage); 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 buf16; CHECK(buf16.init(cx, src_16.data(), src_16.length(), JS::SourceOwnership::Borrowed)); JS::CompilationStorage compileStorage; RefPtr stencil = CompileModuleScriptToStencil(fc, options, buf16, compileStorage); CHECK(stencil); CHECK(stencil->isModule()); CHECK(stencil->scriptExtra.size() == 1); CHECK(stencil->scriptExtra[0].extent.sourceStart == 0); CHECK(stencil->scriptExtra[0].extent.sourceEnd == 15); CHECK(stencil->scriptData.size() == 1); CHECK(stencil->scriptData[0].hasSharedData()); // has generated bytecode CHECK(stencil->scriptData[0].gcThingsLength == 1); CHECK(compileStorage.hasInput()); } { // 8-bit characters JS::SourceText buf8; CHECK( buf8.init(cx, src.data(), src.length(), JS::SourceOwnership::Borrowed)); JS::CompilationStorage compileStorage; RefPtr stencil = CompileModuleScriptToStencil(fc, options, buf8, compileStorage); CHECK(stencil); CHECK(stencil->scriptExtra.size() == 1); CHECK(stencil->scriptExtra[0].extent.sourceStart == 0); CHECK(stencil->scriptExtra[0].extent.sourceEnd == 15); CHECK(stencil->scriptData.size() == 1); CHECK(stencil->scriptData[0].hasSharedData()); // has generated bytecode CHECK(stencil->scriptData[0].gcThingsLength == 1); CHECK(compileStorage.hasInput()); } { // propagates failures static constexpr std::string_view badSrc = "{ a: 1, b: 3\n"; JS::SourceText srcBuf; CHECK(srcBuf.init(cx, badSrc.data(), badSrc.length(), JS::SourceOwnership::Borrowed)); JS::CompilationStorage compileStorage; RefPtr stencil = CompileModuleScriptToStencil(fc, options, srcBuf, compileStorage); 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 == 9); } 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 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); }); JS::CompilationStorage compileStorage; RefPtr stencil = CompileGlobalScriptToStencil(fc, options, buf16, compileStorage); CHECK(stencil); CHECK(stencil->scriptData.size() == 2); CHECK(stencil->scopeData.size() == 1); // function f CHECK(stencil->parserAtomData.size() == 1); // 'field' CHECK(compileStorage.hasInput()); CHECK(compileStorage.getInput().atomCache.empty()); JS::InstantiationStorage storage; CHECK(JS::PrepareForInstantiate(fc, compileStorage, *stencil, storage)); CHECK(compileStorage.getInput().atomCache.size() == 1); // 'field' 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);