/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- * vim: set ts=8 sts=2 et sw=2 tw=80: * 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 "frontend/Frontend2.h" #include "mozilla/Maybe.h" // mozilla::Maybe #include "mozilla/OperatorNewExtensions.h" // mozilla::KnownNotNull #include "mozilla/Range.h" // mozilla::Range #include "mozilla/Span.h" // mozilla::{Span, Span} #include "mozilla/Variant.h" // mozilla::AsVariant #include <stddef.h> // size_t #include <stdint.h> // uint8_t, uint32_t #include "jsapi.h" #include "frontend/AbstractScopePtr.h" // ScopeIndex #include "frontend/BytecodeSection.h" // EmitScriptThingsVector #include "frontend/CompilationInfo.h" // CompilationState, CompilationStencil #include "frontend/Parser.h" // NewEmptyLexicalScopeData, NewEmptyGlobalScopeData, NewEmptyVarScopeData, NewEmptyFunctionScopeData #include "frontend/ParserAtom.h" // ParserAtomsTable #include "frontend/ScriptIndex.h" // ScriptIndex #include "frontend/smoosh_generated.h" // CVec, Smoosh*, smoosh_* #include "frontend/SourceNotes.h" // SrcNote #include "frontend/Stencil.h" // ScopeStencil, RegExpIndex #include "frontend/TokenStream.h" // TokenStreamAnyChars #include "irregexp/RegExpAPI.h" // irregexp::CheckPatternSyntax #include "js/CharacterEncoding.h" // JS::UTF8Chars, UTF8CharsToNewTwoByteCharsZ #include "js/friend/ErrorMessages.h" // js::GetErrorMessage, JSMSG_* #include "js/GCAPI.h" // JS::AutoCheckCannotGC #include "js/HeapAPI.h" // JS::GCCellPtr #include "js/RegExpFlags.h" // JS::RegExpFlag, JS::RegExpFlags #include "js/RootingAPI.h" // JS::MutableHandle #include "js/UniquePtr.h" // js::UniquePtr #include "js/Utility.h" // JS::UniqueTwoByteChars, StringBufferArena #include "vm/JSScript.h" // JSScript #include "vm/ScopeKind.h" // ScopeKind #include "vm/SharedStencil.h" // ImmutableScriptData, ScopeNote, TryNote, GCThingIndex using mozilla::Utf8Unit; using namespace js::gc; using namespace js::frontend; using namespace js; namespace js { namespace frontend { // Given the result of SmooshMonkey's parser, Convert the list of atoms into // the list of ParserAtoms. bool ConvertAtoms(JSContext* cx, const SmooshResult& result, CompilationState& compilationState, Vector<const ParserAtom*>& allAtoms) { size_t numAtoms = result.all_atoms_len; if (!allAtoms.reserve(numAtoms)) { return false; } for (size_t i = 0; i < numAtoms; i++) { auto s = reinterpret_cast<const mozilla::Utf8Unit*>( smoosh_get_atom_at(result, i)); auto len = smoosh_get_atom_len_at(result, i); const ParserAtom* atom = compilationState.parserAtoms.internUtf8(cx, s, len); if (!atom) { return false; } atom->markUsedByStencil(); allAtoms.infallibleAppend(atom); } return true; } void CopyBindingNames(JSContext* cx, CVec<SmooshBindingName>& from, Vector<const ParserAtom*>& allAtoms, ParserBindingName* to) { // We're setting trailing array's content before setting its length. JS::AutoCheckCannotGC nogc(cx); size_t numBindings = from.len; for (size_t i = 0; i < numBindings; i++) { SmooshBindingName& name = from.data[i]; new (mozilla::KnownNotNull, &to[i]) ParserBindingName(allAtoms[name.name]->toIndex(), name.is_closed_over, name.is_top_level_function); } } void CopyBindingNames(JSContext* cx, CVec<COption<SmooshBindingName>>& from, Vector<const ParserAtom*>& allAtoms, ParserBindingName* to) { // We're setting trailing array's content before setting its length. JS::AutoCheckCannotGC nogc(cx); size_t numBindings = from.len; for (size_t i = 0; i < numBindings; i++) { COption<SmooshBindingName>& maybeName = from.data[i]; if (maybeName.IsSome()) { SmooshBindingName& name = maybeName.AsSome(); new (mozilla::KnownNotNull, &to[i]) ParserBindingName(allAtoms[name.name]->toIndex(), name.is_closed_over, name.is_top_level_function); } else { new (mozilla::KnownNotNull, &to[i]) ParserBindingName(TaggedParserAtomIndex::null(), false, false); } } } // Given the result of SmooshMonkey's parser, convert a list of scope data // into a list of ScopeStencil. bool ConvertScopeStencil(JSContext* cx, const SmooshResult& result, Vector<const ParserAtom*>& allAtoms, CompilationStencil& stencil, CompilationState& compilationState) { LifoAlloc& alloc = stencil.alloc; if (result.scopes.len > TaggedScriptThingIndex::IndexLimit) { ReportAllocationOverflow(cx); return false; } for (size_t i = 0; i < result.scopes.len; i++) { SmooshScopeData& scopeData = result.scopes.data[i]; ScopeIndex index; switch (scopeData.tag) { case SmooshScopeData::Tag::Global: { auto& global = scopeData.AsGlobal(); size_t numBindings = global.bindings.len; GlobalScope::ParserData* data = NewEmptyGlobalScopeData(cx, alloc, numBindings); if (!data) { return false; } CopyBindingNames(cx, global.bindings, allAtoms, data->trailingNames.start()); data->slotInfo.letStart = global.let_start; data->slotInfo.constStart = global.const_start; data->slotInfo.length = numBindings; if (!ScopeStencil::createForGlobalScope(cx, stencil, compilationState, ScopeKind::Global, data, &index)) { return false; } break; } case SmooshScopeData::Tag::Var: { auto& var = scopeData.AsVar(); size_t numBindings = var.bindings.len; VarScope::ParserData* data = NewEmptyVarScopeData(cx, alloc, numBindings); if (!data) { return false; } CopyBindingNames(cx, var.bindings, allAtoms, data->trailingNames.start()); // NOTE: data->slotInfo.nextFrameSlot is set in // ScopeStencil::createForVarScope. data->slotInfo.length = numBindings; uint32_t firstFrameSlot = var.first_frame_slot; ScopeIndex enclosingIndex(var.enclosing); if (!ScopeStencil::createForVarScope( cx, stencil, compilationState, ScopeKind::FunctionBodyVar, data, firstFrameSlot, var.function_has_extensible_scope, mozilla::Some(enclosingIndex), &index)) { return false; } break; } case SmooshScopeData::Tag::Lexical: { auto& lexical = scopeData.AsLexical(); size_t numBindings = lexical.bindings.len; LexicalScope::ParserData* data = NewEmptyLexicalScopeData(cx, alloc, numBindings); if (!data) { return false; } CopyBindingNames(cx, lexical.bindings, allAtoms, data->trailingNames.start()); // NOTE: data->slotInfo.nextFrameSlot is set in // ScopeStencil::createForLexicalScope. data->slotInfo.constStart = lexical.const_start; data->slotInfo.length = numBindings; uint32_t firstFrameSlot = lexical.first_frame_slot; ScopeIndex enclosingIndex(lexical.enclosing); if (!ScopeStencil::createForLexicalScope( cx, stencil, compilationState, ScopeKind::Lexical, data, firstFrameSlot, mozilla::Some(enclosingIndex), &index)) { return false; } break; } case SmooshScopeData::Tag::Function: { auto& function = scopeData.AsFunction(); size_t numBindings = function.bindings.len; FunctionScope::ParserData* data = NewEmptyFunctionScopeData(cx, alloc, numBindings); if (!data) { return false; } CopyBindingNames(cx, function.bindings, allAtoms, data->trailingNames.start()); // NOTE: data->slotInfo.nextFrameSlot is set in // ScopeStencil::createForFunctionScope. if (function.has_parameter_exprs) { data->slotInfo.setHasParameterExprs(); } data->slotInfo.nonPositionalFormalStart = function.non_positional_formal_start; data->slotInfo.varStart = function.var_start; data->slotInfo.length = numBindings; bool hasParameterExprs = function.has_parameter_exprs; bool needsEnvironment = function.non_positional_formal_start; ScriptIndex functionIndex = ScriptIndex(function.function_index); bool isArrow = function.is_arrow; ScopeIndex enclosingIndex(function.enclosing); if (!ScopeStencil::createForFunctionScope( cx, stencil, compilationState, data, hasParameterExprs, needsEnvironment, functionIndex, isArrow, mozilla::Some(enclosingIndex), &index)) { return false; } break; } } // `ConvertGCThings` depends on this condition. MOZ_ASSERT(index == i); } return true; } // Given the result of SmooshMonkey's parser, convert a list of RegExp data // into a list of RegExpStencil. bool ConvertRegExpData(JSContext* cx, const SmooshResult& result, CompilationStencil& stencil, CompilationState& compilationState) { auto len = result.regexps.len; if (len == 0) { return true; } if (len > TaggedScriptThingIndex::IndexLimit) { ReportAllocationOverflow(cx); return false; } auto* p = stencil.alloc.newArrayUninitialized<RegExpStencil>(len); if (!p) { js::ReportOutOfMemory(cx); return false; } stencil.regExpData = mozilla::Span(p, len); for (size_t i = 0; i < len; i++) { SmooshRegExpItem& item = result.regexps.data[i]; auto s = smoosh_get_slice_at(result, item.pattern); auto len = smoosh_get_slice_len_at(result, item.pattern); JS::RegExpFlags::Flag flags = JS::RegExpFlag::NoFlags; if (item.global) { flags |= JS::RegExpFlag::Global; } if (item.ignore_case) { flags |= JS::RegExpFlag::IgnoreCase; } if (item.multi_line) { flags |= JS::RegExpFlag::Multiline; } if (item.dot_all) { flags |= JS::RegExpFlag::DotAll; } if (item.sticky) { flags |= JS::RegExpFlag::Sticky; } if (item.unicode) { flags |= JS::RegExpFlag::Unicode; } // FIXME: This check should be done at parse time. size_t length; JS::UniqueTwoByteChars pattern( UTF8CharsToNewTwoByteCharsZ(cx, JS::UTF8Chars(s, len), &length, StringBufferArena) .get()); if (!pattern) { return false; } mozilla::Range<const char16_t> range(pattern.get(), length); TokenStreamAnyChars ts(cx, stencil.input.options, /* smg = */ nullptr); // See Parser<FullParseHandler, Unit>::newRegExp. LifoAllocScope allocScope(&cx->tempLifoAlloc()); if (!irregexp::CheckPatternSyntax(cx, ts, range, flags)) { return false; } const mozilla::Utf8Unit* sUtf8 = reinterpret_cast<const mozilla::Utf8Unit*>(s); const ParserAtom* atom = compilationState.parserAtoms.internUtf8(cx, sUtf8, len); if (!atom) { return false; } atom->markUsedByStencil(); new (mozilla::KnownNotNull, &stencil.regExpData[i]) RegExpStencil(atom->toIndex(), JS::RegExpFlags(flags)); } return true; } // Convert SmooshImmutableScriptData into ImmutableScriptData. UniquePtr<ImmutableScriptData> ConvertImmutableScriptData( JSContext* cx, const SmooshImmutableScriptData& smooshScriptData, bool isFunction) { Vector<ScopeNote, 0, SystemAllocPolicy> scopeNotes; if (!scopeNotes.resize(smooshScriptData.scope_notes.len)) { return nullptr; } for (size_t i = 0; i < smooshScriptData.scope_notes.len; i++) { SmooshScopeNote& scopeNote = smooshScriptData.scope_notes.data[i]; scopeNotes[i].index = GCThingIndex(scopeNote.index); scopeNotes[i].start = scopeNote.start; scopeNotes[i].length = scopeNote.length; scopeNotes[i].parent = scopeNote.parent; } return ImmutableScriptData::new_( cx, smooshScriptData.main_offset, smooshScriptData.nfixed, smooshScriptData.nslots, GCThingIndex(smooshScriptData.body_scope_index), smooshScriptData.num_ic_entries, isFunction, smooshScriptData.fun_length, mozilla::Span(smooshScriptData.bytecode.data, smooshScriptData.bytecode.len), mozilla::Span<const SrcNote>(), mozilla::Span<const uint32_t>(), scopeNotes, mozilla::Span<const TryNote>()); } // Given the result of SmooshMonkey's parser, convert a list of GC things // used by a script into ScriptThingsVector. bool ConvertGCThings(JSContext* cx, const SmooshResult& result, const SmooshScriptStencil& smooshScript, CompilationStencil& stencil, CompilationState& compilationState, Vector<const ParserAtom*>& allAtoms, ScriptIndex scriptIndex) { size_t ngcthings = smooshScript.gcthings.len; // If there are no things, avoid the allocation altogether. if (ngcthings == 0) { return true; } TaggedScriptThingIndex* cursor = nullptr; if (!compilationState.allocateGCThingsUninitialized(cx, scriptIndex, ngcthings, &cursor)) { return false; } for (size_t i = 0; i < ngcthings; i++) { SmooshGCThing& item = smooshScript.gcthings.data[i]; // Pointer to the uninitialized element. void* raw = &cursor[i]; switch (item.tag) { case SmooshGCThing::Tag::Null: { new (raw) TaggedScriptThingIndex(); break; } case SmooshGCThing::Tag::Atom: { new (raw) TaggedScriptThingIndex(allAtoms[item.AsAtom()]->toIndex()); break; } case SmooshGCThing::Tag::Function: { new (raw) TaggedScriptThingIndex(ScriptIndex(item.AsFunction())); break; } case SmooshGCThing::Tag::Scope: { new (raw) TaggedScriptThingIndex(ScopeIndex(item.AsScope())); break; } case SmooshGCThing::Tag::RegExp: { new (raw) TaggedScriptThingIndex(RegExpIndex(item.AsRegExp())); break; } } } return true; } // Given the result of SmooshMonkey's parser, convert a specific script // or function to a StencilScript, given a fixed set of source atoms. // // The StencilScript would then be in charge of handling the lifetime and // (until GC things gets removed from stencil) tracing API of the GC. bool ConvertScriptStencil(JSContext* cx, const SmooshResult& result, const SmooshScriptStencil& smooshScript, Vector<const ParserAtom*>& allAtoms, CompilationStencil& stencil, CompilationState& compilationState, ScriptIndex scriptIndex) { using ImmutableFlags = js::ImmutableScriptFlagsEnum; const JS::ReadOnlyCompileOptions& options = stencil.input.options; ScriptStencil& script = stencil.scriptData[scriptIndex]; ScriptStencilExtra& scriptExtra = stencil.scriptExtra[scriptIndex]; scriptExtra.immutableFlags = smooshScript.immutable_flags; // FIXME: The following flags should be set in jsparagus. scriptExtra.immutableFlags.setFlag(ImmutableFlags::SelfHosted, options.selfHostingMode); scriptExtra.immutableFlags.setFlag(ImmutableFlags::ForceStrict, options.forceStrictMode()); scriptExtra.immutableFlags.setFlag(ImmutableFlags::HasNonSyntacticScope, options.nonSyntacticScope); if (&smooshScript == &result.scripts.data[0]) { scriptExtra.immutableFlags.setFlag(ImmutableFlags::TreatAsRunOnce, options.isRunOnce); scriptExtra.immutableFlags.setFlag(ImmutableFlags::NoScriptRval, options.noScriptRval); } bool isFunction = scriptExtra.immutableFlags.hasFlag(ImmutableFlags::IsFunction); if (smooshScript.immutable_script_data.IsSome()) { auto index = smooshScript.immutable_script_data.AsSome(); auto immutableScriptData = ConvertImmutableScriptData( cx, result.script_data_list.data[index], isFunction); if (!immutableScriptData) { return false; } auto sharedData = SharedImmutableScriptData::createWith( cx, std::move(immutableScriptData)); if (!sharedData) { return false; } if (!stencil.sharedData.addAndShare(cx, scriptIndex, sharedData)) { return false; } script.setHasSharedData(); } scriptExtra.extent.sourceStart = smooshScript.extent.source_start; scriptExtra.extent.sourceEnd = smooshScript.extent.source_end; scriptExtra.extent.toStringStart = smooshScript.extent.to_string_start; scriptExtra.extent.toStringEnd = smooshScript.extent.to_string_end; scriptExtra.extent.lineno = smooshScript.extent.lineno; scriptExtra.extent.column = smooshScript.extent.column; if (isFunction) { if (smooshScript.fun_name.IsSome()) { script.functionAtom = allAtoms[smooshScript.fun_name.AsSome()]->toIndex(); } script.functionFlags = FunctionFlags(smooshScript.fun_flags); scriptExtra.nargs = smooshScript.fun_nargs; if (smooshScript.lazy_function_enclosing_scope_index.IsSome()) { script.setLazyFunctionEnclosingScopeIndex(ScopeIndex( smooshScript.lazy_function_enclosing_scope_index.AsSome())); } if (smooshScript.was_function_emitted) { script.setWasFunctionEmitted(); } } if (!ConvertGCThings(cx, result, smooshScript, stencil, compilationState, allAtoms, scriptIndex)) { return false; } return true; } // Free given SmooshResult on leaving scope. class AutoFreeSmooshResult { SmooshResult* result_; public: AutoFreeSmooshResult() = delete; explicit AutoFreeSmooshResult(SmooshResult* result) : result_(result) {} ~AutoFreeSmooshResult() { if (result_) { smoosh_free(*result_); } } }; // Free given SmooshParseResult on leaving scope. class AutoFreeSmooshParseResult { SmooshParseResult* result_; public: AutoFreeSmooshParseResult() = delete; explicit AutoFreeSmooshParseResult(SmooshParseResult* result) : result_(result) {} ~AutoFreeSmooshParseResult() { if (result_) { smoosh_free_parse_result(*result_); } } }; void InitSmoosh() { smoosh_init(); } void ReportSmooshCompileError(JSContext* cx, ErrorMetadata&& metadata, int errorNumber, ...) { va_list args; va_start(args, errorNumber); ReportCompileErrorUTF8(cx, std::move(metadata), /* notes = */ nullptr, errorNumber, &args); va_end(args); } /* static */ bool Smoosh::compileGlobalScriptToStencil(JSContext* cx, CompilationStencil& stencil, JS::SourceText<Utf8Unit>& srcBuf, bool* unimplemented) { // FIXME: check info members and return with *unimplemented = true // if any field doesn't match to smoosh_run. auto bytes = reinterpret_cast<const uint8_t*>(srcBuf.get()); size_t length = srcBuf.length(); const auto& options = stencil.input.options; SmooshCompileOptions compileOptions; compileOptions.no_script_rval = options.noScriptRval; SmooshResult result = smoosh_run(bytes, length, &compileOptions); AutoFreeSmooshResult afsr(&result); if (result.error.data) { *unimplemented = false; ErrorMetadata metadata; metadata.filename = "<unknown>"; metadata.lineNumber = 1; metadata.columnNumber = 0; metadata.isMuted = false; ReportSmooshCompileError(cx, std::move(metadata), JSMSG_SMOOSH_COMPILE_ERROR, reinterpret_cast<const char*>(result.error.data)); return false; } if (result.unimplemented) { *unimplemented = true; return false; } *unimplemented = false; LifoAllocScope allocScope(&cx->tempLifoAlloc()); Vector<const ParserAtom*> allAtoms(cx); CompilationState compilationState(cx, allocScope, stencil.input.options, stencil); if (!ConvertAtoms(cx, result, compilationState, allAtoms)) { return false; } if (!ConvertScopeStencil(cx, result, allAtoms, stencil, compilationState)) { return false; } if (!ConvertRegExpData(cx, result, stencil, compilationState)) { return false; } auto len = result.scripts.len; if (len == 0) { return true; } if (len > TaggedScriptThingIndex::IndexLimit) { ReportAllocationOverflow(cx); return false; } auto* pscript = stencil.alloc.newArrayUninitialized<ScriptStencil>(len); if (!pscript) { js::ReportOutOfMemory(cx); return false; } stencil.scriptData = mozilla::Span(pscript, len); auto* pextra = stencil.alloc.newArrayUninitialized<ScriptStencilExtra>(len); if (!pextra) { js::ReportOutOfMemory(cx); return false; } stencil.scriptExtra = mozilla::Span(pextra, len); // NOTE: Currently we don't support delazification or standalone function. // Once we support, fix the following loop to include 0-th item // and check if it's function. MOZ_ASSERT_IF(result.scripts.len > 0, result.scripts.data[0].fun_flags == 0); for (size_t i = 1; i < result.scripts.len; i++) { auto& script = result.scripts.data[i]; if (script.immutable_script_data.IsSome()) { compilationState.nonLazyFunctionCount++; } } stencil.prepareStorageFor(cx, compilationState); for (size_t i = 0; i < len; i++) { new (mozilla::KnownNotNull, &stencil.scriptData[i]) BaseCompilationStencil(); if (!ConvertScriptStencil(cx, result, result.scripts.data[i], allAtoms, stencil, compilationState, ScriptIndex(i))) { return false; } } if (!compilationState.finish(cx, stencil)) { return true; } return true; } /* static */ UniquePtr<CompilationStencil> Smoosh::compileGlobalScriptToStencil( JSContext* cx, const JS::ReadOnlyCompileOptions& options, JS::SourceText<Utf8Unit>& srcBuf, bool* unimplemented) { Rooted<UniquePtr<frontend::CompilationStencil>> stencil( cx, js_new<frontend::CompilationStencil>(cx, options)); if (!stencil) { ReportOutOfMemory(cx); return nullptr; } if (!stencil.get()->input.initForGlobal(cx)) { return nullptr; } if (!compileGlobalScriptToStencil(cx, *stencil.get().get(), srcBuf, unimplemented)) { return nullptr; } return std::move(stencil.get()); } /* static */ bool Smoosh::compileGlobalScript(JSContext* cx, CompilationStencil& stencil, JS::SourceText<Utf8Unit>& srcBuf, CompilationGCOutput& gcOutput, bool* unimplemented) { if (!compileGlobalScriptToStencil(cx, stencil, srcBuf, unimplemented)) { return false; } if (!CompilationStencil::instantiateStencils(cx, stencil, gcOutput)) { return false; } #if defined(DEBUG) || defined(JS_JITSPEW) Sprinter sprinter(cx); Rooted<JSScript*> script(cx, gcOutput.script); if (!sprinter.init()) { return false; } if (!Disassemble(cx, script, true, &sprinter, DisassembleSkeptically::Yes)) { return false; } printf("%s\n", sprinter.string()); if (!Disassemble(cx, script, true, &sprinter, DisassembleSkeptically::No)) { return false; } // (don't bother printing it) #endif return true; } bool SmooshParseScript(JSContext* cx, const uint8_t* bytes, size_t length) { SmooshParseResult result = smoosh_test_parse_script(bytes, length); AutoFreeSmooshParseResult afspr(&result); if (result.error.data) { JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, result.unimplemented ? JSMSG_SMOOSH_UNIMPLEMENTED : JSMSG_SMOOSH_COMPILE_ERROR, reinterpret_cast<const char*>(result.error.data)); return false; } return true; } bool SmooshParseModule(JSContext* cx, const uint8_t* bytes, size_t length) { SmooshParseResult result = smoosh_test_parse_module(bytes, length); AutoFreeSmooshParseResult afspr(&result); if (result.error.data) { JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, result.unimplemented ? JSMSG_SMOOSH_UNIMPLEMENTED : JSMSG_SMOOSH_COMPILE_ERROR, reinterpret_cast<const char*>(result.error.data)); return false; } return true; } } // namespace frontend } // namespace js