| /* |
| * 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; |
| } |
| return value & 1 ? -int32_t(value >> 1) : int32_t(value >> 1); |
| } |
| |
| } // namespace wasm |