/src/bloaty/src/source_map.cc
Line | Count | Source (jump to first uncovered line) |
1 | | // Copyright 2024 Google Inc. All Rights Reserved. |
2 | | // |
3 | | // Licensed under the Apache License, Version 2.0 (the "License"); |
4 | | // you may not use this file except in compliance with the License. |
5 | | // You may obtain a copy of the License at |
6 | | // |
7 | | // http://www.apache.org/licenses/LICENSE-2.0 |
8 | | // |
9 | | // Unless required by applicable law or agreed to in writing, software |
10 | | // distributed under the License is distributed on an "AS IS" BASIS, |
11 | | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
12 | | // See the License for the specific language governing permissions and |
13 | | // limitations under the License. |
14 | | |
15 | | #include <cstdint> |
16 | | #include <memory> |
17 | | #include <string> |
18 | | #include <string_view> |
19 | | #include <utility> |
20 | | #include <vector> |
21 | | |
22 | | #include "bloaty.h" |
23 | | #include "source_map.h" |
24 | | #include "util.h" |
25 | | |
26 | | namespace bloaty { |
27 | | namespace sourcemap { |
28 | | |
29 | 0 | static bool ReadOpeningBrace(std::string_view* data) { |
30 | 0 | return ReadFixed<char>(data) == '{'; |
31 | 0 | } |
32 | | |
33 | 0 | static std::string_view ReadQuotedString(std::string_view* data) { |
34 | 0 | RequireChar(data, '\"'); |
35 | | // Simply read until the next '\"'. We currently do not handle escaped |
36 | | // characters. Field names never contain quotes and file names are unlikely to |
37 | | // contain quotes. |
38 | 0 | return ReadUntilConsuming(data, '\"'); |
39 | 0 | } |
40 | | |
41 | | // Finds the field with the given name in the source map. Any fields encountered |
42 | | // before the field are skipped. |
43 | 0 | static void FindField(std::string_view* data, const char* name) { |
44 | 0 | while (!data->empty()) { |
45 | 0 | SkipWhitespace(data); |
46 | 0 | auto field_name = ReadQuotedString(data); |
47 | 0 | if (field_name == name) { |
48 | 0 | SkipWhitespace(data); |
49 | 0 | RequireChar(data, ':'); |
50 | 0 | SkipWhitespace(data); |
51 | 0 | return; |
52 | 0 | } |
53 | | |
54 | | // Skip until the next quote. We don't expect any structures involving |
55 | | // quotes in the fields that we skip. |
56 | 0 | ReadUntil(data, '\"'); |
57 | 0 | } |
58 | 0 | THROWF("field \"$0\" not found in source map", name); |
59 | 0 | } |
60 | | |
61 | 0 | static int32_t ReadBase64VLQ(std::string_view* data) { |
62 | 0 | uint32_t value = 0; |
63 | 0 | uint32_t shift = 0; |
64 | 0 | const char* ptr = data->data(); |
65 | 0 | const char* limit = ptr + data->size(); |
66 | 0 | while (ptr < limit) { |
67 | 0 | auto ch = *(ptr++); |
68 | | // Base64 characters A-Z, a-f do not have the continuation bit set and are |
69 | | // the last digit. |
70 | 0 | if ((ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch < 'g')) { |
71 | 0 | uint32_t digit = ch < 'a' ? ch - 'A' : ch - 'a' + 26; |
72 | 0 | value |= digit << shift; |
73 | 0 | data->remove_prefix(ptr - data->data()); |
74 | 0 | return value & 1 |
75 | 0 | ? -static_cast<int32_t>(value >> 1) |
76 | 0 | : static_cast<int32_t>(value >> 1); |
77 | 0 | } |
78 | 0 | if (!(ch >= 'g' && ch <= 'z') && !(ch >= '0' && ch <= '9') && ch != '+' && |
79 | 0 | ch != '/') { |
80 | 0 | THROWF("Invalid Base64VLQ digit $0", ch); |
81 | 0 | } |
82 | | // Base64 characters g-z, 0-9, + and / have the continuation bit set and |
83 | | // must be followed by another digit. |
84 | 0 | uint32_t digit = |
85 | 0 | ch > '9' ? ch - 'g' : (ch >= '0' ? ch - '0' + 20 : (ch == '+' ? 30 : 31)); |
86 | 0 | value |= digit << shift; |
87 | 0 | shift += 5; |
88 | 0 | } |
89 | | |
90 | 0 | THROW("Unterminated Base64VLQ"); |
91 | 0 | } |
92 | | |
93 | 0 | static bool IsBase64Digit(char ch) { |
94 | 0 | return (ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z') || |
95 | 0 | (ch >= '0' && ch <= '9') || ch == '+' || ch == '/'; |
96 | 0 | } |
97 | | |
98 | 0 | static int ReadBase64VLQSegment(std::string_view* data, int32_t (&values)[5]) { |
99 | 0 | for (int i = 0; i < 5; i++) { |
100 | 0 | values[i] = ReadBase64VLQ(data); |
101 | 0 | if (data->empty() || !IsBase64Digit(data->front())) { |
102 | 0 | if (i != 0 && i != 3 && i != 4) { |
103 | 0 | THROWF("Invalid source map VLQ segment length $0", i + 1); |
104 | 0 | } |
105 | 0 | return i + 1; |
106 | 0 | } |
107 | 0 | } |
108 | 0 | THROW("Unterminated Base64VLQ segment"); |
109 | 0 | } |
110 | | |
111 | | class VlqSegment { |
112 | | public: |
113 | | int32_t col; |
114 | | int32_t length; |
115 | | |
116 | | std::string source_file; |
117 | | int32_t source_line; |
118 | | int32_t source_col; |
119 | | |
120 | | VlqSegment(int32_t col, int32_t length, |
121 | | std::string_view source_file, |
122 | | int32_t source_line, int32_t source_col) |
123 | 0 | : col(col), length(length), |
124 | 0 | source_file(source_file), |
125 | 0 | source_line(source_line), source_col(source_col) {} |
126 | | |
127 | 0 | void addToSink(RangeSink* sink) const { |
128 | 0 | auto name = sink->data_source() == DataSource::kInlines |
129 | 0 | ? source_file + ":" + std::to_string(source_line) |
130 | 0 | : source_file; |
131 | 0 | sink->AddFileRange("sourcemap", name, col, length); |
132 | 0 | } |
133 | | }; |
134 | | |
135 | | template <class Func> |
136 | | void ForEachVLQSegment(std::string_view* data, |
137 | | const std::vector<std::string_view>& sources, |
138 | 0 | Func&& segment_func) { |
139 | 0 | if (data->empty() || data->front() == '\"') { |
140 | 0 | return; |
141 | 0 | } |
142 | | |
143 | | // Read the first segment. We don't generate the `VlqSegment` until the next |
144 | | // one is encountered. This one only points to a particular byte. The next |
145 | | // segment is required to determine the length. |
146 | 0 | int32_t values[5]; |
147 | 0 | int values_count = ReadBase64VLQSegment(data, values); |
148 | 0 | if (values_count < 4) { |
149 | 0 | THROW("Source file info expected in first VLQ segment"); |
150 | 0 | } |
151 | 0 | int32_t col = values[0]; |
152 | 0 | int32_t source_file = values[1]; |
153 | 0 | int32_t source_line = values[2]; |
154 | 0 | int32_t source_col = values[3]; |
155 | |
|
156 | 0 | while (!data->empty() && data->front() != '\"') { |
157 | 0 | if (data->front() == ',') { |
158 | 0 | data->remove_prefix(1); |
159 | 0 | continue; |
160 | 0 | } |
161 | | |
162 | | // We don't support line separators in the source map for now. |
163 | 0 | if (data->front() == ';') { |
164 | 0 | THROW("Unsupported line separator in source map"); |
165 | 0 | } |
166 | | |
167 | 0 | int new_values_count = ReadBase64VLQSegment(data, values); |
168 | 0 | if (values_count >= 4) { |
169 | 0 | segment_func(VlqSegment(col, values[0], |
170 | 0 | sources[source_file], source_line, source_col)); |
171 | 0 | } |
172 | 0 | values_count = new_values_count; |
173 | 0 | col += values[0]; |
174 | 0 | if (values_count >= 4) { |
175 | 0 | source_file += values[1]; |
176 | 0 | source_line += values[2]; |
177 | 0 | source_col += values[3]; |
178 | 0 | } |
179 | 0 | } |
180 | 0 | } |
181 | | |
182 | 0 | static void ProcessToSink(std::string_view data, RangeSink* sink) { |
183 | 0 | ReadOpeningBrace(&data); |
184 | |
|
185 | 0 | std::vector<std::string_view> sources; |
186 | 0 | FindField(&data, "sources"); |
187 | 0 | RequireChar(&data, '['); |
188 | 0 | while (!data.empty()) { |
189 | 0 | SkipWhitespace(&data); |
190 | 0 | if (data.empty()) { |
191 | 0 | break; |
192 | 0 | } |
193 | 0 | if (data.front() == ']') { |
194 | 0 | data.remove_prefix(1); |
195 | 0 | break; |
196 | 0 | } |
197 | 0 | if (data.front() == ',') { |
198 | 0 | data.remove_prefix(1); |
199 | 0 | } |
200 | 0 | auto source = ReadQuotedString(&data); |
201 | 0 | sources.push_back(source); |
202 | 0 | } |
203 | 0 | SkipWhitespace(&data); |
204 | 0 | RequireChar(&data, ','); |
205 | |
|
206 | 0 | FindField(&data, "mappings"); |
207 | 0 | RequireChar(&data, '\"'); |
208 | |
|
209 | 0 | ForEachVLQSegment(&data, sources, [&sink](const VlqSegment& segment) { |
210 | 0 | segment.addToSink(sink); |
211 | 0 | }); |
212 | |
|
213 | 0 | RequireChar(&data, '\"'); |
214 | 0 | } |
215 | | |
216 | 0 | void SourceMapObjectFile::ProcessFileToSink(RangeSink* sink) const { |
217 | 0 | if (sink->data_source() != DataSource::kCompileUnits |
218 | 0 | && sink->data_source() != DataSource::kInlines) { |
219 | 0 | THROW("Source map doesn't support this data source"); |
220 | 0 | } |
221 | | |
222 | 0 | ProcessToSink(file_data().data(), sink); |
223 | 0 | } |
224 | | |
225 | | } // namespace sourcemap |
226 | | |
227 | | std::unique_ptr<ObjectFile> TryOpenSourceMapFile( |
228 | 0 | std::unique_ptr<InputFile>& file, std::string build_id) { |
229 | 0 | std::string_view data = file->data(); |
230 | 0 | if (sourcemap::ReadOpeningBrace(&data)) { |
231 | 0 | return std::unique_ptr<ObjectFile>( |
232 | 0 | new sourcemap::SourceMapObjectFile(std::move(file), build_id)); |
233 | 0 | } |
234 | | |
235 | 0 | return nullptr; |
236 | 0 | } |
237 | | |
238 | | } // namespace bloaty |
239 | | |