| /* |
| * 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" |
| |
| 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 << text; |
| Colors::magenta(o); |
| o << "]"; |
| Colors::normal(o); |
| } |
| |
| void SourceMapReader::readHeader(Module& wasm) { |
| assert(pos == 0); |
| if (buffer.empty()) { |
| return; |
| } |
| |
| auto skipWhitespace = [&]() { |
| while (pos < buffer.size() && (buffer[pos] == ' ' || buffer[pos] == '\n')) { |
| ++pos; |
| } |
| }; |
| |
| auto findField = [&](const char* name) { |
| bool matching = false; |
| size_t len = strlen(name); |
| size_t index = 0; |
| while (1) { |
| char ch = get(); |
| if (ch == '\"') { |
| if (matching) { |
| if (index == len) { |
| // We matched a terminating quote. |
| break; |
| } |
| matching = false; |
| } else { |
| // Beginning of a new potential match. |
| matching = true; |
| index = 0; |
| } |
| } else if (matching && name[index] == ch) { |
| ++index; |
| } else if (matching) { |
| matching = false; |
| } |
| } |
| skipWhitespace(); |
| expect(':'); |
| skipWhitespace(); |
| return true; |
| }; |
| |
| auto readString = [&](std::string& str) { |
| std::vector<char> vec; |
| skipWhitespace(); |
| expect('\"'); |
| while (1) { |
| if (maybeGet('\"')) { |
| break; |
| } |
| vec.push_back(get()); |
| } |
| skipWhitespace(); |
| str = std::string(vec.begin(), vec.end()); |
| }; |
| |
| if (!findField("sources")) { |
| throw MapParseException("cannot find the 'sources' field in map"); |
| } |
| |
| skipWhitespace(); |
| expect('['); |
| if (!maybeGet(']')) { |
| do { |
| std::string file; |
| readString(file); |
| wasm.debugInfoFileNames.push_back(file); |
| } while (maybeGet(',')); |
| expect(']'); |
| } |
| |
| if (findField("names")) { |
| skipWhitespace(); |
| expect('['); |
| if (!maybeGet(']')) { |
| do { |
| std::string symbol; |
| readString(symbol); |
| wasm.debugInfoSymbolNames.push_back(symbol); |
| } while (maybeGet(',')); |
| expect(']'); |
| } |
| } |
| |
| if (!findField("mappings")) { |
| throw MapParseException("cannot find the 'mappings' field in map"); |
| } |
| |
| expect('\"'); |
| if (maybeGet('\"')) { |
| // 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) { |
| if (pos >= buffer.size()) { |
| return std::nullopt; |
| } |
| |
| 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 == ',' || next == '\"') { |
| hasSymbol = false; |
| break; |
| } |
| |
| hasSymbol = true; |
| symbol += readBase64VLQ(); |
| |
| } while (false); |
| |
| // Check whether there is another record to read the position for. |
| char next = get(); |
| if (next == '\"') { |
| // End of records. |
| location = 0; |
| break; |
| } |
| if (next != ',') { |
| 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 |