| // Copyright 2024 Google Inc. All Rights Reserved. |
| // |
| // 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 <cstdint> |
| #include <memory> |
| #include <string> |
| #include <string_view> |
| #include <utility> |
| #include <vector> |
| |
| #include "bloaty.h" |
| #include "source_map.h" |
| #include "util.h" |
| |
| namespace bloaty { |
| namespace sourcemap { |
| |
| static bool ReadOpeningBrace(std::string_view* data) { |
| return ReadFixed<char>(data) == '{'; |
| } |
| |
| static std::string_view ReadQuotedString(std::string_view* data) { |
| RequireChar(data, '\"'); |
| // Simply read until the next '\"'. We currently do not handle escaped |
| // characters. Field names never contain quotes and file names are unlikely to |
| // contain quotes. |
| return ReadUntilConsuming(data, '\"'); |
| } |
| |
| // Finds the field with the given name in the source map. Any fields encountered |
| // before the field are skipped. |
| static void FindField(std::string_view* data, const char* name) { |
| while (!data->empty()) { |
| SkipWhitespace(data); |
| auto field_name = ReadQuotedString(data); |
| if (field_name == name) { |
| SkipWhitespace(data); |
| RequireChar(data, ':'); |
| SkipWhitespace(data); |
| return; |
| } |
| |
| // Skip until the next quote. We don't expect any structures involving |
| // quotes in the fields that we skip. |
| ReadUntil(data, '\"'); |
| } |
| THROWF("field \"$0\" not found in source map", name); |
| } |
| |
| static int32_t ReadBase64VLQ(std::string_view* data) { |
| uint32_t value = 0; |
| uint32_t shift = 0; |
| const char* ptr = data->data(); |
| const char* limit = ptr + data->size(); |
| while (ptr < limit) { |
| auto ch = *(ptr++); |
| // Base64 characters A-Z, a-f do not have the continuation bit set and are |
| // the last digit. |
| if ((ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch < 'g')) { |
| uint32_t digit = ch < 'a' ? ch - 'A' : ch - 'a' + 26; |
| value |= digit << shift; |
| data->remove_prefix(ptr - data->data()); |
| return value & 1 |
| ? -static_cast<int32_t>(value >> 1) |
| : static_cast<int32_t>(value >> 1); |
| } |
| if (!(ch >= 'g' && ch <= 'z') && !(ch >= '0' && ch <= '9') && ch != '+' && |
| ch != '/') { |
| THROWF("Invalid Base64VLQ digit $0", ch); |
| } |
| // Base64 characters g-z, 0-9, + and / have the continuation bit set and |
| // must be followed by another digit. |
| uint32_t digit = |
| ch > '9' ? ch - 'g' : (ch >= '0' ? ch - '0' + 20 : (ch == '+' ? 30 : 31)); |
| value |= digit << shift; |
| shift += 5; |
| } |
| |
| THROW("Unterminated Base64VLQ"); |
| } |
| |
| static bool IsBase64Digit(char ch) { |
| return (ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z') || |
| (ch >= '0' && ch <= '9') || ch == '+' || ch == '/'; |
| } |
| |
| static int ReadBase64VLQSegment(std::string_view* data, int32_t (&values)[5]) { |
| for (int i = 0; i < 5; i++) { |
| values[i] = ReadBase64VLQ(data); |
| if (data->empty() || !IsBase64Digit(data->front())) { |
| if (i != 0 && i != 3 && i != 4) { |
| THROWF("Invalid source map VLQ segment length $0", i + 1); |
| } |
| return i + 1; |
| } |
| } |
| THROW("Unterminated Base64VLQ segment"); |
| } |
| |
| class VlqSegment { |
| public: |
| int32_t col; |
| int32_t length; |
| |
| std::string source_file; |
| int32_t source_line; |
| int32_t source_col; |
| |
| VlqSegment(int32_t col, int32_t length, |
| std::string_view source_file, |
| int32_t source_line, int32_t source_col) |
| : col(col), length(length), |
| source_file(source_file), |
| source_line(source_line), source_col(source_col) {} |
| |
| void addToSink(RangeSink* sink) const { |
| auto name = sink->data_source() == DataSource::kInlines |
| ? source_file + ":" + std::to_string(source_line) |
| : source_file; |
| sink->AddFileRange("sourcemap", name, col, length); |
| } |
| }; |
| |
| template <class Func> |
| void ForEachVLQSegment(std::string_view* data, |
| const std::vector<std::string_view>& sources, |
| Func&& segment_func) { |
| if (data->empty() || data->front() == '\"') { |
| return; |
| } |
| |
| // Read the first segment. We don't generate the `VlqSegment` until the next |
| // one is encountered. This one only points to a particular byte. The next |
| // segment is required to determine the length. |
| int32_t values[5]; |
| int values_count = ReadBase64VLQSegment(data, values); |
| if (values_count < 4) { |
| THROW("Source file info expected in first VLQ segment"); |
| } |
| int32_t col = values[0]; |
| int32_t source_file = values[1]; |
| int32_t source_line = values[2]; |
| int32_t source_col = values[3]; |
| |
| while (!data->empty() && data->front() != '\"') { |
| if (data->front() == ',') { |
| data->remove_prefix(1); |
| continue; |
| } |
| |
| // We don't support line separators in the source map for now. |
| if (data->front() == ';') { |
| THROW("Unsupported line separator in source map"); |
| } |
| |
| int new_values_count = ReadBase64VLQSegment(data, values); |
| if (values_count >= 4) { |
| segment_func(VlqSegment(col, values[0], |
| sources[source_file], source_line, source_col)); |
| } |
| values_count = new_values_count; |
| col += values[0]; |
| if (values_count >= 4) { |
| source_file += values[1]; |
| source_line += values[2]; |
| source_col += values[3]; |
| } |
| } |
| } |
| |
| static void ProcessToSink(std::string_view data, RangeSink* sink) { |
| ReadOpeningBrace(&data); |
| |
| std::vector<std::string_view> sources; |
| FindField(&data, "sources"); |
| RequireChar(&data, '['); |
| while (!data.empty()) { |
| SkipWhitespace(&data); |
| if (data.empty()) { |
| break; |
| } |
| if (data.front() == ']') { |
| data.remove_prefix(1); |
| break; |
| } |
| if (data.front() == ',') { |
| data.remove_prefix(1); |
| } |
| auto source = ReadQuotedString(&data); |
| sources.push_back(source); |
| } |
| SkipWhitespace(&data); |
| RequireChar(&data, ','); |
| |
| FindField(&data, "mappings"); |
| RequireChar(&data, '\"'); |
| |
| ForEachVLQSegment(&data, sources, [&sink](const VlqSegment& segment) { |
| segment.addToSink(sink); |
| }); |
| |
| RequireChar(&data, '\"'); |
| } |
| |
| void SourceMapObjectFile::ProcessFileToSink(RangeSink* sink) const { |
| if (sink->data_source() != DataSource::kCompileUnits |
| && sink->data_source() != DataSource::kInlines) { |
| THROW("Source map doesn't support this data source"); |
| } |
| |
| ProcessToSink(file_data().data(), sink); |
| } |
| |
| } // namespace sourcemap |
| |
| std::unique_ptr<ObjectFile> TryOpenSourceMapFile( |
| std::unique_ptr<InputFile>& file, std::string build_id) { |
| std::string_view data = file->data(); |
| if (sourcemap::ReadOpeningBrace(&data)) { |
| return std::unique_ptr<ObjectFile>( |
| new sourcemap::SourceMapObjectFile(std::move(file), build_id)); |
| } |
| |
| return nullptr; |
| } |
| |
| } // namespace bloaty |
| |