| /* | |
| * Copyright 2024 WebAssembly Community Group participants | |
| * | |
| * Licensed under the Apache License, Version 2.0 (the "License"); | |
| * you may not use this file except in compliance with the License. | |
| * You may obtain a copy of the License at | |
| * | |
| * http://www.apache.org/licenses/LICENSE-2.0 | |
| * | |
| * Unless required by applicable law or agreed to in writing, software | |
| * distributed under the License is distributed on an "AS IS" BASIS, | |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| * See the License for the specific language governing permissions and | |
| * limitations under the License. | |
| */ | |
| #include "source-map.h" | |
| #include "support/colors.h" | |
| #include "support/json.h" | |
| namespace wasm { | |
| std::vector<char> defaultEmptySourceMap; | |
| void MapParseException::dump(std::ostream& o) const { | |
| Colors::magenta(o); | |
| o << "["; | |
| Colors::red(o); | |
| o << "map parse exception: "; | |
| Colors::green(o); | |
| o << errorText; | |
| Colors::magenta(o); | |
| o << "]"; | |
| Colors::normal(o); | |
| } | |
| void SourceMapReader::parse(Module& wasm) { | |
| if (buffer.empty()) { | |
| return; | |
| } | |
| json::Value json; | |
| try { | |
| json.parse(buffer.data(), json::Value::ASCII); | |
| } catch (json::JsonParseException jx) { | |
| throw MapParseException(jx); | |
| } | |
| if (!json.isObject()) { | |
| throw MapParseException("Source map is not valid JSON"); | |
| } | |
| if (!(json.has("version") && json["version"]->isNumber() && | |
| json["version"]->getInteger() == 3)) { | |
| throw MapParseException("Source map version missing or is not 3"); | |
| } | |
| if (!(json.has("sources") && json["sources"]->isArray())) { | |
| throw MapParseException("Source map sources missing or not an array"); | |
| } | |
| json::Ref s = json["sources"]; | |
| for (size_t i = 0; i < s->size(); i++) { | |
| json::Ref v = s[i]; | |
| if (!(s[i]->isString())) { | |
| throw MapParseException("Source map sources contains non-string"); | |
| } | |
| wasm.debugInfoFileNames.push_back(v->getCString()); | |
| } | |
| if (json.has("sourcesContent")) { | |
| json::Ref sc = json["sourcesContent"]; | |
| if (!sc->isArray()) { | |
| throw MapParseException("Source map sourcesContent is not an array"); | |
| } | |
| for (size_t i = 0; i < sc->size(); i++) { | |
| wasm.debugInfoSourcesContent.push_back(sc[i]->getCString()); | |
| } | |
| } | |
| if (json.has("names")) { | |
| json::Ref n = json["names"]; | |
| if (!n->isArray()) { | |
| throw MapParseException("Source map names is not an array"); | |
| } | |
| for (size_t i = 0; i < n->size(); i++) { | |
| json::Ref v = n[i]; | |
| if (!v->isString()) { | |
| throw MapParseException("Source map names contains non-string"); | |
| } | |
| wasm.debugInfoSymbolNames.push_back(v->getCString()); | |
| } | |
| } | |
| if (json.has("sourceRoot")) { | |
| json::Ref sr = json["sourceRoot"]; | |
| if (!sr->isString()) { | |
| throw MapParseException("Source map sourceRoot is not a string"); | |
| } | |
| wasm.debugInfoSourceRoot = sr->getCString(); | |
| } | |
| if (json.has("file")) { | |
| json::Ref f = json["file"]; | |
| if (!f->isString()) { | |
| throw MapParseException("Source map file is not a string"); | |
| } | |
| wasm.debugInfoFile = f->getCString(); | |
| } | |
| if (!json.has("mappings")) { | |
| throw MapParseException("Source map mappings missing"); | |
| } | |
| json::Ref m = json["mappings"]; | |
| if (!m->isString()) { | |
| throw MapParseException("Source map mappings is not a string"); | |
| } | |
| mappings = m->getCString(); | |
| if (mappings.empty()) { | |
| // There are no mappings. | |
| location = 0; | |
| return; | |
| } | |
| // Read the location of the first debug location. | |
| location = readBase64VLQ(); | |
| } | |
| std::optional<Function::DebugLocation> | |
| SourceMapReader::readDebugLocationAt(size_t currLocation) { | |
| while (location && location <= currLocation) { | |
| do { | |
| char next = peek(); | |
| if (next == ',' || next == '\"') { | |
| // This is a 1-length entry, so the next location has no debug info. | |
| hasInfo = false; | |
| break; | |
| } | |
| hasInfo = true; | |
| file += readBase64VLQ(); | |
| line += readBase64VLQ(); | |
| col += readBase64VLQ(); | |
| next = peek(); | |
| if (next == ';') { | |
| // Generated JS files can have multiple lines, and mappings for each | |
| // line are separated by ';'. Wasm files do not have lines, so there | |
| // should be only one generated "line". | |
| throw MapParseException("Unexpected mapping for 2nd generated line"); | |
| } | |
| if (next == ',' || next == '\"') { | |
| hasSymbol = false; | |
| break; | |
| } | |
| hasSymbol = true; | |
| symbol += readBase64VLQ(); | |
| } while (false); | |
| // Check whether there is another record to read the position for. | |
| if (peek() == '\"') { | |
| // End of records. | |
| location = 0; | |
| break; | |
| } | |
| if (get() != ',') { | |
| throw MapParseException("Expected delimiter"); | |
| } | |
| // Set up for the next record. | |
| location += readBase64VLQ(); | |
| } | |
| if (!hasInfo) { | |
| return std::nullopt; | |
| } | |
| auto sym = hasSymbol ? symbol : std::optional<uint32_t>{}; | |
| return Function::DebugLocation{file, line, col, sym}; | |
| } | |
| int32_t SourceMapReader::readBase64VLQ() { | |
| uint32_t value = 0; | |
| uint32_t shift = 0; | |
| while (1) { | |
| auto ch = get(); | |
| if ((ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch < 'g')) { | |
| // last number digit | |
| uint32_t digit = ch < 'a' ? ch - 'A' : ch - 'a' + 26; | |
| value |= digit << shift; | |
| break; | |
| } | |
| if (!(ch >= 'g' && ch <= 'z') && !(ch >= '0' && ch <= '9') && ch != '+' && | |
| ch != '/') { | |
| throw MapParseException("invalid VLQ digit"); | |
| } | |
| uint32_t digit = | |
| ch > '9' ? ch - 'g' : (ch >= '0' ? ch - '0' + 20 : (ch == '+' ? 30 : 31)); | |
| value |= digit << shift; | |
| shift += 5; | |
| if (shift >= 32) { | |
| throw MapParseException("VLQ value too large"); | |
| } | |
| } | |
| return value & 1 ? -int32_t(value >> 1) : int32_t(value >> 1); | |
| } | |
| } // namespace wasm |