Coverage Report

Created: 2024-01-23 06:21

/src/bloaty/src/webassembly.cc
Line
Count
Source (jump to first uncovered line)
1
// Copyright 2018 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 "bloaty.h"
16
#include "util.h"
17
18
#include "absl/strings/substitute.h"
19
20
using absl::string_view;
21
22
namespace bloaty {
23
namespace wasm {
24
25
6.41M
uint64_t ReadLEB128Internal(bool is_signed, size_t size, string_view* data) {
26
6.41M
  uint64_t ret = 0;
27
6.41M
  int shift = 0;
28
6.41M
  int maxshift = 70;
29
6.41M
  const char* ptr = data->data();
30
6.41M
  const char* limit = ptr + data->size();
31
32
6.45M
  while (ptr < limit && shift < maxshift) {
33
6.45M
    char byte = *(ptr++);
34
6.45M
    ret |= static_cast<uint64_t>(byte & 0x7f) << shift;
35
6.45M
    shift += 7;
36
6.45M
    if ((byte & 0x80) == 0) {
37
6.41M
      data->remove_prefix(ptr - data->data());
38
6.41M
      if (is_signed && shift < size && (byte & 0x40)) {
39
0
        ret |= -(1ULL << shift);
40
0
      }
41
6.41M
      return ret;
42
6.41M
    }
43
6.45M
  }
44
45
6.41M
  THROW("corrupt wasm data, unterminated LEB128");
46
6.41M
}
47
48
2.40k
bool ReadVarUInt1(string_view* data) {
49
2.40k
  return static_cast<bool>(ReadLEB128Internal(false, 1, data));
50
2.40k
}
51
52
3.06M
uint8_t ReadVarUInt7(string_view* data) {
53
3.06M
  return static_cast<char>(ReadLEB128Internal(false, 7, data));
54
3.06M
}
55
56
3.34M
uint32_t ReadVarUInt32(string_view* data) {
57
3.34M
  return static_cast<uint32_t>(ReadLEB128Internal(false, 32, data));
58
3.34M
}
59
60
1.77k
int8_t ReadVarint7(string_view* data) {
61
1.77k
  return static_cast<int8_t>(ReadLEB128Internal(true, 7, data));
62
1.77k
}
63
64
6.37M
string_view ReadPiece(size_t bytes, string_view* data) {
65
6.37M
  if(data->size() < bytes) {
66
28.0k
    THROW("premature EOF reading variable-length DWARF data");
67
28.0k
  }
68
6.34M
  string_view ret = data->substr(0, bytes);
69
6.34M
  data->remove_prefix(bytes);
70
6.34M
  return ret;
71
6.37M
}
72
73
21.9M
bool ReadMagic(string_view* data) {
74
21.9M
  const uint32_t wasm_magic = 0x6d736100;
75
21.9M
  auto magic = ReadFixed<uint32_t>(data);
76
77
21.9M
  if (magic != wasm_magic) {
78
21.7M
    return false;
79
21.7M
  }
80
81
  // TODO(haberman): do we need to fail if this is >1?
82
193k
  auto version = ReadFixed<uint32_t>(data);
83
193k
  (void)version;
84
85
193k
  return true;
86
21.9M
}
87
88
class Section {
89
 public:
90
  uint32_t id;
91
  std::string name;
92
  string_view data;
93
  string_view contents;
94
95
3.06M
  static Section Read(string_view* data_param) {
96
3.06M
    Section ret;
97
3.06M
    string_view data = *data_param;
98
3.06M
    string_view section_data = data;
99
100
3.06M
    ret.id = ReadVarUInt7(&data);
101
3.06M
    uint32_t size = ReadVarUInt32(&data);
102
3.06M
    ret.contents = ReadPiece(size, &data);
103
3.06M
    size_t header_size = ret.contents.data() - section_data.data();
104
3.06M
    ret.data = ReadPiece(size + header_size, &section_data);
105
106
3.06M
    if (ret.id == 0) {
107
257k
      uint32_t name_len = ReadVarUInt32(&ret.contents);
108
257k
      ret.name = std::string(ReadPiece(name_len, &ret.contents));
109
2.81M
    } else if (ret.id <= 13) {
110
2.78M
      ret.name = names[ret.id];
111
2.78M
    } else {
112
29.3k
      THROWF("Unknown section id: $0", ret.id);
113
29.3k
    }
114
115
3.03M
    *data_param = data;
116
3.03M
    return ret;
117
3.06M
  }
118
119
  enum Name {
120
    kType      = 1,
121
    kImport    = 2,
122
    kFunction  = 3,
123
    kTable     = 4,
124
    kMemory    = 5,
125
    kGlobal    = 6,
126
    kExport    = 7,
127
    kStart     = 8,
128
    kElement   = 9,
129
    kCode      = 10,
130
    kData      = 11,
131
    kDataCount = 12,
132
    kEvent     = 13,
133
  };
134
135
  static const char* names[];
136
};
137
138
const char* Section::names[] = {
139
  "<none>",    // 0
140
  "Type",      // 1
141
  "Import",    // 2
142
  "Function",  // 3
143
  "Table",     // 4
144
  "Memory",    // 5
145
  "Global",    // 6
146
  "Export",    // 7
147
  "Start",     // 8
148
  "Element",   // 9
149
  "Code",      // 10
150
  "Data",      // 11
151
  "DataCount", // 12
152
  "Event",     // 13
153
};
154
155
struct ExternalKind {
156
  enum Kind {
157
    kFunction = 0,
158
    kTable = 1,
159
    kMemory = 2,
160
    kGlobal = 3,
161
  };
162
};
163
164
template <class Func>
165
92.0k
void ForEachSection(string_view file, Func&& section_func) {
166
92.0k
  string_view data = file;
167
92.0k
  ReadMagic(&data);
168
169
3.16M
  while (!data.empty()) {
170
3.06M
    Section section = Section::Read(&data);
171
3.06M
    section_func(section);
172
3.06M
  }
173
92.0k
}
webassembly.cc:void bloaty::wasm::ForEachSection<bloaty::wasm::ParseSections(bloaty::RangeSink*)::$_0>(std::__1::basic_string_view<char, std::__1::char_traits<char> >, bloaty::wasm::ParseSections(bloaty::RangeSink*)::$_0&&)
Line
Count
Source
165
57.5k
void ForEachSection(string_view file, Func&& section_func) {
166
57.5k
  string_view data = file;
167
57.5k
  ReadMagic(&data);
168
169
1.58M
  while (!data.empty()) {
170
1.52M
    Section section = Section::Read(&data);
171
1.52M
    section_func(section);
172
1.52M
  }
173
57.5k
}
webassembly.cc:void bloaty::wasm::ForEachSection<bloaty::wasm::ParseSymbols(bloaty::RangeSink*)::$_1>(std::__1::basic_string_view<char, std::__1::char_traits<char> >, bloaty::wasm::ParseSymbols(bloaty::RangeSink*)::$_1&&)
Line
Count
Source
165
3.34k
void ForEachSection(string_view file, Func&& section_func) {
166
3.34k
  string_view data = file;
167
3.34k
  ReadMagic(&data);
168
169
164k
  while (!data.empty()) {
170
160k
    Section section = Section::Read(&data);
171
160k
    section_func(section);
172
160k
  }
173
3.34k
}
webassembly.cc:void bloaty::wasm::ForEachSection<bloaty::wasm::ParseSymbols(bloaty::RangeSink*)::$_2>(std::__1::basic_string_view<char, std::__1::char_traits<char> >, bloaty::wasm::ParseSymbols(bloaty::RangeSink*)::$_2&&)
Line
Count
Source
165
3.34k
void ForEachSection(string_view file, Func&& section_func) {
166
3.34k
  string_view data = file;
167
3.34k
  ReadMagic(&data);
168
169
64.7k
  while (!data.empty()) {
170
61.4k
    Section section = Section::Read(&data);
171
61.4k
    section_func(section);
172
61.4k
  }
173
3.34k
}
webassembly.cc:void bloaty::wasm::ForEachSection<bloaty::wasm::AddWebAssemblyFallback(bloaty::RangeSink*)::$_3>(std::__1::basic_string_view<char, std::__1::char_traits<char> >, bloaty::wasm::AddWebAssemblyFallback(bloaty::RangeSink*)::$_3&&)
Line
Count
Source
165
27.7k
void ForEachSection(string_view file, Func&& section_func) {
166
27.7k
  string_view data = file;
167
27.7k
  ReadMagic(&data);
168
169
1.34M
  while (!data.empty()) {
170
1.32M
    Section section = Section::Read(&data);
171
1.32M
    section_func(section);
172
1.32M
  }
173
27.7k
}
174
175
57.5k
void ParseSections(RangeSink* sink) {
176
1.49M
  ForEachSection(sink->input_file().data(), [sink](const Section& section) {
177
1.49M
    sink->AddFileRange("wasm_sections", section.name, section.data);
178
1.49M
  });
179
57.5k
}
180
181
typedef std::unordered_map<int, std::string> IndexedNames;
182
183
void ReadNames(const Section& section, IndexedNames* func_names,
184
0
               IndexedNames* dataseg_names, RangeSink* sink) {
185
0
  enum class NameType {
186
0
    kModule = 0,
187
0
    kFunction = 1,
188
0
    kLocal = 2,
189
0
    kLabel = 3,
190
0
    kType = 4,
191
0
    kTable = 5,
192
0
    kMemory = 6,
193
0
    kGlobal = 7,
194
0
    kElemSegment = 8,
195
0
    kDataSegment = 9
196
0
  };
197
198
0
  string_view data = section.contents;
199
200
0
  while (!data.empty()) {
201
0
    NameType type = static_cast<NameType>(ReadVarUInt7(&data));
202
0
    uint32_t size = ReadVarUInt32(&data);
203
0
    string_view section = ReadPiece(size, &data);
204
205
0
    if (type == NameType::kFunction || type == NameType::kDataSegment) {
206
0
      uint32_t count = ReadVarUInt32(&section);
207
0
      for (uint32_t i = 0; i < count; i++) {
208
0
        string_view entry = section;
209
0
        uint32_t index = ReadVarUInt32(&section);
210
0
        uint32_t name_len = ReadVarUInt32(&section);
211
0
        string_view name = ReadPiece(name_len, &section);
212
0
        entry = StrictSubstr(entry, 0, name.data() - entry.data() + name.size());
213
0
        sink->AddFileRange("wasm_funcname", name, entry);
214
0
        IndexedNames *names = (type == NameType::kFunction ? func_names : dataseg_names);
215
0
        (*names)[index] = std::string(name);
216
0
      }
217
0
    }
218
0
  }
219
0
}
220
221
530
int ReadValueType(string_view* data) {
222
530
  return ReadVarint7(data);
223
530
}
224
225
1.24k
int ReadElemType(string_view* data) {
226
1.24k
  return ReadVarint7(data);
227
1.24k
}
228
229
1.91k
void ReadResizableLimits(string_view* data) {
230
1.91k
  auto flags = ReadVarUInt1(data);
231
1.91k
  ReadVarUInt32(data);
232
1.91k
  if (flags) {
233
1.41k
    ReadVarUInt32(data);
234
1.41k
  }
235
1.91k
}
236
237
530
void ReadGlobalType(string_view* data) {
238
530
  ReadValueType(data);
239
530
  ReadVarUInt1(data);
240
530
}
241
242
1.24k
void ReadTableType(string_view* data) {
243
1.24k
  ReadElemType(data);
244
1.24k
  ReadResizableLimits(data);
245
1.24k
}
246
247
729
void ReadMemoryType(string_view* data) {
248
729
  ReadResizableLimits(data);
249
729
}
250
251
3.25k
uint32_t GetNumFunctionImports(const Section& section) {
252
3.25k
  assert(section.id == Section::kImport);
253
0
  string_view data = section.contents;
254
255
3.25k
  uint32_t count = ReadVarUInt32(&data);
256
3.25k
  uint32_t func_count = 0;
257
258
7.02k
  for (uint32_t i = 0; i < count; i++) {
259
5.64k
    uint32_t module_len = ReadVarUInt32(&data);
260
5.64k
    ReadPiece(module_len, &data);
261
5.64k
    uint32_t field_len = ReadVarUInt32(&data);
262
5.64k
    ReadPiece(field_len, &data);
263
5.64k
    auto kind = ReadFixed<uint8_t>(&data);
264
265
5.64k
    switch (kind) {
266
1.82k
      case ExternalKind::kFunction:
267
1.82k
        func_count++;
268
1.82k
        ReadVarUInt32(&data);
269
1.82k
        break;
270
1.24k
      case ExternalKind::kTable:
271
1.24k
        ReadTableType(&data);
272
1.24k
        break;
273
729
      case ExternalKind::kMemory:
274
729
        ReadMemoryType(&data);
275
729
        break;
276
530
      case ExternalKind::kGlobal:
277
530
        ReadGlobalType(&data);
278
530
        break;
279
129
      default:
280
129
        THROWF("Unrecognized import kind: $0", kind);
281
5.64k
    }
282
5.64k
  }
283
284
1.38k
  return func_count;
285
3.25k
}
286
287
void ReadCodeSection(const Section& section, const IndexedNames& names,
288
358
                     uint32_t num_imports, RangeSink* sink) {
289
358
  string_view data = section.contents;
290
291
358
  uint32_t count = ReadVarUInt32(&data);
292
293
483
  for (uint32_t i = 0; i < count; i++) {
294
125
    string_view func = data;
295
125
    uint32_t size = ReadVarUInt32(&data);
296
125
    uint32_t total_size = size + (data.data() - func.data());
297
298
125
    func = StrictSubstr(func, 0, total_size);
299
125
    data = StrictSubstr(data, size);
300
301
125
    auto iter = names.find(num_imports + i);
302
303
125
    if (iter == names.end()) {
304
82
      std::string name = "func[" + std::to_string(i) + "]";
305
82
      sink->AddFileRange("wasm_function", name, func);
306
82
    } else {
307
43
      sink->AddFileRange("wasm_function", ItaniumDemangle(iter->second, sink->data_source()), func);
308
43
    }
309
125
  }
310
358
}
311
312
void ReadDataSection(const Section& section, const IndexedNames& names,
313
945
                     RangeSink* sink) {
314
945
  string_view data = section.contents;
315
945
  uint32_t count = ReadVarUInt32(&data);
316
317
1.02k
  for (uint32_t i = 0; i < count; i++) {
318
142
    string_view segment = data;
319
142
    uint8_t mode = ReadFixed<uint8_t>(&data);
320
142
    if (mode > 1) THROW("multi-memory extension isn't supported");
321
82
    if (mode == 0) { // Active segment
322
      // We will need to read the init expr.
323
      // For the extended const proposal, read instructions until end is reached
324
      // Otherwise, just read a constexpr inst (t.const or global.get)
325
      // For now, we just need to support passive segments.
326
48
      continue;
327
48
    }
328
    // else, a passive segment
329
330
34
    uint32_t segment_size = ReadVarUInt32(&data);
331
34
    uint32_t total_size = segment_size + (data.data() - segment.data());
332
333
34
    segment = StrictSubstr(segment, 0, total_size);
334
34
    data = StrictSubstr(data, segment_size);
335
336
34
    auto iter = names.find(i);
337
34
    if (iter == names.end()) {
338
8
      std::string name = "data[" + std::to_string(i) + "]";
339
8
      sink->AddFileRange("wasm_data", name, segment);
340
26
    } else {
341
26
      sink->AddFileRange("wasm_data", iter->second, segment);
342
26
    }
343
34
  }
344
945
}
345
346
347
3.34k
void ParseSymbols(RangeSink* sink) {
348
  // First pass: read the custom naming section to get function names.
349
3.34k
  std::unordered_map<int, std::string> func_names;
350
3.34k
  std::unordered_map<int, std::string> dataseg_names;
351
3.34k
  uint32_t num_imports = 0;
352
353
3.34k
  ForEachSection(sink->input_file().data(),
354
160k
                 [&func_names, &dataseg_names, sink](const Section& section) {
355
160k
                   if (section.name == "name") {
356
0
                     ReadNames(section, &func_names, &dataseg_names, sink);
357
0
                   }
358
160k
                 });
359
360
  // Second pass: read the function/code sections.
361
3.34k
  ForEachSection(sink->input_file().data(),
362
61.4k
                 [&func_names, &dataseg_names, &num_imports, sink](const Section& section) {
363
61.4k
                   if (section.id == Section::kImport) {
364
3.25k
                     num_imports = GetNumFunctionImports(section);
365
58.1k
                   } else if (section.id == Section::kCode) {
366
358
                     ReadCodeSection(section, func_names, num_imports, sink);
367
57.7k
                   } else if (section.id == Section::kData) {
368
945
                     ReadDataSection(section, dataseg_names, sink);
369
945
                   }
370
61.4k
                 });
371
3.34k
}
372
373
27.7k
void AddWebAssemblyFallback(RangeSink* sink) {
374
1.32M
  ForEachSection(sink->input_file().data(), [sink](const Section& section) {
375
1.32M
    std::string name2 =
376
1.32M
        std::string("[section ") + std::string(section.name) + std::string("]");
377
1.32M
    sink->AddFileRange("wasm_overhead", name2, section.data);
378
1.32M
  });
379
27.7k
  sink->AddFileRange("wasm_overhead", "[WASM Header]",
380
27.7k
                     StrictSubstr(sink->input_file().data(), 0, 8));
381
27.7k
}
382
383
class WebAssemblyObjectFile : public ObjectFile {
384
 public:
385
  WebAssemblyObjectFile(std::unique_ptr<InputFile> file_data)
386
101k
      : ObjectFile(std::move(file_data)) {}
387
388
101k
  std::string GetBuildId() const override {
389
    // TODO(haberman): does WebAssembly support this?
390
101k
    return std::string();
391
101k
  }
392
393
50.8k
  void ProcessFile(const std::vector<RangeSink*>& sinks) const override {
394
70.9k
    for (auto sink : sinks) {
395
70.9k
      switch (sink->data_source()) {
396
54.2k
        case DataSource::kSegments:
397
57.5k
        case DataSource::kSections:
398
57.5k
          ParseSections(sink);
399
57.5k
          break;
400
0
        case DataSource::kSymbols:
401
0
        case DataSource::kRawSymbols:
402
3.34k
        case DataSource::kShortSymbols:
403
3.34k
        case DataSource::kFullSymbols:
404
3.34k
          ParseSymbols(sink);
405
3.34k
          break;
406
3.34k
        case DataSource::kArchiveMembers:
407
6.69k
        case DataSource::kCompileUnits:
408
10.0k
        case DataSource::kInlines:
409
10.0k
        default:
410
10.0k
          THROW("WebAssembly doesn't support this data source");
411
70.9k
      }
412
27.7k
      AddWebAssemblyFallback(sink);
413
27.7k
    }
414
50.8k
  }
415
416
  bool GetDisassemblyInfo(absl::string_view /*symbol*/,
417
                          DataSource /*symbol_source*/,
418
0
                          DisassemblyInfo* /*info*/) const override {
419
0
    WARN("WebAssembly files do not support disassembly yet");
420
0
    return false;
421
0
  }
422
};
423
424
}  // namespace wasm
425
426
std::unique_ptr<ObjectFile> TryOpenWebAssemblyFile(
427
21.8M
    std::unique_ptr<InputFile>& file) {
428
21.8M
  string_view data = file->data();
429
21.8M
  if (wasm::ReadMagic(&data)) {
430
101k
    return std::unique_ptr<ObjectFile>(
431
101k
        new wasm::WebAssemblyObjectFile(std::move(file)));
432
101k
  }
433
434
21.7M
  return nullptr;
435
21.8M
}
436
437
}  // namespace bloaty