Coverage Report

Created: 2025-06-13 06:15

/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