Coverage Report

Created: 2025-06-13 06:49

/src/spirv-tools/source/text_handler.cpp
Line
Count
Source (jump to first uncovered line)
1
// Copyright (c) 2015-2016 The Khronos Group Inc.
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 "source/text_handler.h"
16
17
#include <algorithm>
18
#include <cassert>
19
#include <cstdlib>
20
#include <cstring>
21
#include <tuple>
22
23
#include "source/assembly_grammar.h"
24
#include "source/binary.h"
25
#include "source/ext_inst.h"
26
#include "source/instruction.h"
27
#include "source/opcode.h"
28
#include "source/text.h"
29
#include "source/util/bitutils.h"
30
#include "source/util/hex_float.h"
31
#include "source/util/parse_number.h"
32
#include "source/util/string_utils.h"
33
34
namespace spvtools {
35
namespace {
36
37
// Advances |text| to the start of the next line and writes the new position to
38
// |position|.
39
29.6k
spv_result_t advanceLine(spv_text text, spv_position position) {
40
783k
  while (true) {
41
783k
    if (position->index >= text->length) return SPV_END_OF_STREAM;
42
782k
    switch (text->str[position->index]) {
43
98
      case '\0':
44
98
        return SPV_END_OF_STREAM;
45
29.2k
      case '\n':
46
29.2k
        position->column = 0;
47
29.2k
        position->line++;
48
29.2k
        position->index++;
49
29.2k
        return SPV_SUCCESS;
50
753k
      default:
51
753k
        position->column++;
52
753k
        position->index++;
53
753k
        break;
54
782k
    }
55
782k
  }
56
29.6k
}
57
58
// Advances |text| to first non white space character and writes the new
59
// position to |position|.
60
// If a null terminator is found during the text advance, SPV_END_OF_STREAM is
61
// returned, SPV_SUCCESS otherwise. No error checking is performed on the
62
// parameters, its the users responsibility to ensure these are non null.
63
27.1M
spv_result_t advance(spv_text text, spv_position position) {
64
  // NOTE: Consume white space, otherwise don't advance.
65
45.6M
  while (true) {
66
45.6M
    if (position->index >= text->length) return SPV_END_OF_STREAM;
67
45.6M
    switch (text->str[position->index]) {
68
2.12k
      case '\0':
69
2.12k
        return SPV_END_OF_STREAM;
70
29.6k
      case ';':
71
29.6k
        if (spv_result_t error = advanceLine(text, position)) return error;
72
29.2k
        continue;
73
17.8M
      case ' ':
74
17.9M
      case '\t':
75
18.0M
      case '\r':
76
18.0M
        position->column++;
77
18.0M
        position->index++;
78
18.0M
        continue;
79
441k
      case '\n':
80
441k
        position->column = 0;
81
441k
        position->line++;
82
441k
        position->index++;
83
441k
        continue;
84
27.1M
      default:
85
27.1M
        return SPV_SUCCESS;
86
45.6M
    }
87
45.6M
  }
88
27.1M
}
89
90
// Fetches the next word from the given text stream starting from the given
91
// *position. On success, writes the decoded word into *word and updates
92
// *position to the location past the returned word.
93
//
94
// A word ends at the next comment or whitespace.  However, double-quoted
95
// strings remain intact, and a backslash always escapes the next character.
96
26.5M
spv_result_t getWord(spv_text text, spv_position position, std::string* word) {
97
26.5M
  if (!text->str || !text->length) return SPV_ERROR_INVALID_TEXT;
98
26.5M
  if (!position) return SPV_ERROR_INVALID_POINTER;
99
100
26.5M
  const size_t start_index = position->index;
101
102
26.5M
  bool quoting = false;
103
26.5M
  bool escaping = false;
104
105
  // NOTE: Assumes first character is not white space!
106
557M
  while (true) {
107
557M
    if (position->index >= text->length) {
108
60.0k
      word->assign(text->str + start_index, text->str + position->index);
109
60.0k
      return SPV_SUCCESS;
110
60.0k
    }
111
557M
    const char ch = text->str[position->index];
112
557M
    if (ch == '\\') {
113
23.1k
      escaping = !escaping;
114
557M
    } else {
115
557M
      switch (ch) {
116
350k
        case '"':
117
350k
          if (!escaping) quoting = !quoting;
118
350k
          break;
119
37.4M
        case ' ':
120
37.4M
        case ';':
121
37.5M
        case ',':
122
37.5M
        case '(':
123
37.5M
        case ')':
124
37.6M
        case '\t':
125
38.6M
        case '\n':
126
38.7M
        case '\r':
127
38.7M
          if (escaping || quoting) break;
128
26.5M
          word->assign(text->str + start_index, text->str + position->index);
129
26.5M
          return SPV_SUCCESS;
130
3.86k
        case '\0': {  // NOTE: End of word found!
131
3.86k
          word->assign(text->str + start_index, text->str + position->index);
132
3.86k
          return SPV_SUCCESS;
133
38.7M
        }
134
518M
        default:
135
518M
          break;
136
557M
      }
137
530M
      escaping = false;
138
530M
    }
139
140
530M
    position->column++;
141
530M
    position->index++;
142
530M
  }
143
26.5M
}
144
145
// Returns true if the characters in the text as position represent
146
// the start of an Opcode.
147
13.2M
bool startsWithOp(spv_text text, spv_position position) {
148
13.2M
  if (text->length < position->index + 3) return false;
149
13.2M
  char ch0 = text->str[position->index];
150
13.2M
  char ch1 = text->str[position->index + 1];
151
13.2M
  char ch2 = text->str[position->index + 2];
152
13.2M
  return ('O' == ch0 && 'p' == ch1 && ('A' <= ch2 && ch2 <= 'Z'));
153
13.2M
}
154
155
}  // namespace
156
157
const IdType kUnknownType = {0, false, IdTypeClass::kBottom};
158
159
// TODO(dneto): Reorder AssemblyContext definitions to match declaration order.
160
161
// This represents all of the data that is only valid for the duration of
162
// a single compilation.
163
1.44M
uint32_t AssemblyContext::spvNamedIdAssignOrGet(const char* textValue) {
164
1.44M
  if (!ids_to_preserve_.empty()) {
165
347k
    uint32_t id = 0;
166
347k
    if (spvtools::utils::ParseNumber(textValue, &id)) {
167
177k
      if (ids_to_preserve_.find(id) != ids_to_preserve_.end()) {
168
177k
        bound_ = std::max(bound_, id + 1);
169
177k
        return id;
170
177k
      }
171
177k
    }
172
347k
  }
173
174
1.26M
  const auto it = named_ids_.find(textValue);
175
1.26M
  if (it == named_ids_.end()) {
176
305k
    uint32_t id = next_id_++;
177
305k
    if (!ids_to_preserve_.empty()) {
178
44.4k
      while (ids_to_preserve_.find(id) != ids_to_preserve_.end()) {
179
9.77k
        id = next_id_++;
180
9.77k
      }
181
34.6k
    }
182
183
305k
    named_ids_.emplace(textValue, id);
184
305k
    bound_ = std::max(bound_, id + 1);
185
305k
    return id;
186
305k
  }
187
188
958k
  return it->second;
189
1.26M
}
190
191
13.1k
uint32_t AssemblyContext::getBound() const { return bound_; }
192
193
13.7M
spv_result_t AssemblyContext::advance() {
194
13.7M
  return spvtools::advance(text_, &current_position_);
195
13.7M
}
196
197
spv_result_t AssemblyContext::getWord(std::string* word,
198
13.4M
                                      spv_position next_position) {
199
13.4M
  *next_position = current_position_;
200
13.4M
  return spvtools::getWord(text_, next_position, word);
201
13.4M
}
202
203
1.04M
bool AssemblyContext::startsWithOp() {
204
1.04M
  return spvtools::startsWithOp(text_, &current_position_);
205
1.04M
}
206
207
12.1M
bool AssemblyContext::isStartOfNewInst() {
208
12.1M
  spv_position_t pos = current_position_;
209
12.1M
  if (spvtools::advance(text_, &pos)) return false;
210
12.1M
  if (spvtools::startsWithOp(text_, &pos)) return true;
211
212
11.9M
  std::string word;
213
11.9M
  pos = current_position_;
214
11.9M
  if (spvtools::getWord(text_, &pos, &word)) return false;
215
11.9M
  if ('%' != word.front()) return false;
216
217
1.14M
  if (spvtools::advance(text_, &pos)) return false;
218
1.14M
  if (spvtools::getWord(text_, &pos, &word)) return false;
219
1.14M
  if ("=" != word) return false;
220
221
112k
  if (spvtools::advance(text_, &pos)) return false;
222
112k
  if (spvtools::startsWithOp(text_, &pos)) return true;
223
82
  return false;
224
112k
}
225
226
795k
char AssemblyContext::peek() const {
227
795k
  return text_->str[current_position_.index];
228
795k
}
229
230
776k
bool AssemblyContext::hasText() const {
231
776k
  return text_->length > current_position_.index;
232
776k
}
233
234
210k
void AssemblyContext::seekForward(uint32_t size) {
235
210k
  current_position_.index += size;
236
210k
  current_position_.column += size;
237
210k
}
238
239
spv_result_t AssemblyContext::binaryEncodeU32(const uint32_t value,
240
10.9M
                                              spv_instruction_t* pInst) {
241
10.9M
  pInst->words.insert(pInst->words.end(), value);
242
10.9M
  return SPV_SUCCESS;
243
10.9M
}
244
245
spv_result_t AssemblyContext::binaryEncodeNumericLiteral(
246
    const char* val, spv_result_t error_code, const IdType& type,
247
10.7M
    spv_instruction_t* pInst) {
248
10.7M
  using spvtools::utils::EncodeNumberStatus;
249
  // Populate the NumberType from the IdType for parsing.
250
10.7M
  spvtools::utils::NumberType number_type;
251
10.7M
  switch (type.type_class) {
252
0
    case IdTypeClass::kOtherType:
253
0
      return diagnostic(SPV_ERROR_INTERNAL)
254
0
             << "Unexpected numeric literal type";
255
36.9k
    case IdTypeClass::kScalarIntegerType:
256
36.9k
      if (type.isSigned) {
257
6.08k
        number_type = {type.bitwidth, SPV_NUMBER_SIGNED_INT, type.encoding};
258
30.9k
      } else {
259
30.9k
        number_type = {type.bitwidth, SPV_NUMBER_UNSIGNED_INT, type.encoding};
260
30.9k
      }
261
36.9k
      break;
262
122k
    case IdTypeClass::kScalarFloatType:
263
122k
      number_type = {type.bitwidth, SPV_NUMBER_FLOATING, type.encoding};
264
122k
      break;
265
10.5M
    case IdTypeClass::kBottom:
266
      // kBottom means the type is unknown and we need to infer the type before
267
      // parsing the number. The rule is: If there is a decimal point, treat
268
      // the value as a floating point value, otherwise a integer value, then
269
      // if the first char of the integer text is '-', treat the integer as a
270
      // signed integer, otherwise an unsigned integer.
271
10.5M
      uint32_t bitwidth = static_cast<uint32_t>(assumedBitWidth(type));
272
10.5M
      if (strchr(val, '.')) {
273
26.7k
        number_type = {bitwidth, SPV_NUMBER_FLOATING, type.encoding};
274
10.5M
      } else if (type.isSigned || val[0] == '-') {
275
6.94k
        number_type = {bitwidth, SPV_NUMBER_SIGNED_INT, type.encoding};
276
10.5M
      } else {
277
10.5M
        number_type = {bitwidth, SPV_NUMBER_UNSIGNED_INT, type.encoding};
278
10.5M
      }
279
10.5M
      break;
280
10.7M
  }
281
282
10.7M
  std::string error_msg;
283
10.7M
  EncodeNumberStatus parse_status = ParseAndEncodeNumber(
284
10.7M
      val, number_type,
285
10.7M
      [this, pInst](uint32_t d) { this->binaryEncodeU32(d, pInst); },
286
10.7M
      &error_msg);
287
10.7M
  switch (parse_status) {
288
10.5M
    case EncodeNumberStatus::kSuccess:
289
10.5M
      return SPV_SUCCESS;
290
242k
    case EncodeNumberStatus::kInvalidText:
291
242k
      return diagnostic(error_code) << error_msg;
292
77
    case EncodeNumberStatus::kUnsupported:
293
77
      return diagnostic(SPV_ERROR_INTERNAL) << error_msg;
294
3
    case EncodeNumberStatus::kInvalidUsage:
295
3
      return diagnostic(SPV_ERROR_INVALID_TEXT) << error_msg;
296
10.7M
  }
297
  // This line is not reachable, only added to satisfy the compiler.
298
0
  return diagnostic(SPV_ERROR_INTERNAL)
299
0
         << "Unexpected result code from ParseAndEncodeNumber()";
300
10.7M
}
301
302
spv_result_t AssemblyContext::binaryEncodeString(const char* value,
303
61.2k
                                                 spv_instruction_t* pInst) {
304
61.2k
  const size_t length = strlen(value);
305
61.2k
  const size_t wordCount = (length / 4) + 1;
306
61.2k
  const size_t oldWordCount = pInst->words.size();
307
61.2k
  const size_t newWordCount = oldWordCount + wordCount;
308
309
  // TODO(dneto): We can just defer this check until later.
310
61.2k
  if (newWordCount > SPV_LIMIT_INSTRUCTION_WORD_COUNT_MAX) {
311
14
    return diagnostic() << "Instruction too long: more than "
312
14
                        << SPV_LIMIT_INSTRUCTION_WORD_COUNT_MAX << " words.";
313
14
  }
314
315
61.2k
  pInst->words.reserve(newWordCount);
316
61.2k
  spvtools::utils::AppendToVector(value, &pInst->words);
317
318
61.2k
  return SPV_SUCCESS;
319
61.2k
}
320
321
spv_result_t AssemblyContext::recordTypeDefinition(
322
25.1k
    const spv_instruction_t* pInst) {
323
25.1k
  uint32_t value = pInst->words[1];
324
25.1k
  if (types_.find(value) != types_.end()) {
325
52
    return diagnostic() << "Value " << value
326
52
                        << " has already been used to generate a type";
327
52
  }
328
329
25.1k
  if (pInst->opcode == spv::Op::OpTypeInt) {
330
2.23k
    if (pInst->words.size() != 4)
331
20
      return diagnostic() << "Invalid OpTypeInt instruction";
332
2.21k
    types_[value] = {pInst->words[2], pInst->words[3] != 0,
333
2.21k
                     IdTypeClass::kScalarIntegerType, SPV_FP_ENCODING_UNKNOWN};
334
22.8k
  } else if (pInst->opcode == spv::Op::OpTypeFloat) {
335
9.08k
    if ((pInst->words.size() != 3) && (pInst->words.size() != 4))
336
44
      return diagnostic() << "Invalid OpTypeFloat instruction";
337
9.04k
    spv_fp_encoding_t enc = SPV_FP_ENCODING_UNKNOWN;
338
9.04k
    if (pInst->words.size() >= 4) {
339
2.77k
      const spvtools::OperandDesc* desc;
340
2.77k
      spv_result_t status = spvtools::LookupOperand(SPV_OPERAND_TYPE_FPENCODING,
341
2.77k
                                                    pInst->words[3], &desc);
342
2.77k
      if (status == SPV_SUCCESS) {
343
2.67k
        enc = spvFPEncodingFromOperandFPEncoding(
344
2.67k
            static_cast<spv::FPEncoding>(desc->value));
345
2.67k
      } else {
346
100
        return diagnostic() << "Invalid OpTypeFloat encoding";
347
100
      }
348
2.77k
    }
349
8.94k
    types_[value] = {pInst->words[2], false, IdTypeClass::kScalarFloatType,
350
8.94k
                     enc};
351
13.7k
  } else {
352
13.7k
    types_[value] = {0, false, IdTypeClass::kOtherType,
353
13.7k
                     SPV_FP_ENCODING_UNKNOWN};
354
13.7k
  }
355
24.9k
  return SPV_SUCCESS;
356
25.1k
}
357
358
129k
IdType AssemblyContext::getTypeOfTypeGeneratingValue(uint32_t value) const {
359
129k
  auto type = types_.find(value);
360
129k
  if (type == types_.end()) {
361
330
    return kUnknownType;
362
330
  }
363
129k
  return std::get<1>(*type);
364
129k
}
365
366
5.53k
IdType AssemblyContext::getTypeOfValueInstruction(uint32_t value) const {
367
5.53k
  auto type_value = value_types_.find(value);
368
5.53k
  if (type_value == value_types_.end()) {
369
24
    return {0, false, IdTypeClass::kBottom};
370
24
  }
371
5.50k
  return getTypeOfTypeGeneratingValue(std::get<1>(*type_value));
372
5.53k
}
373
374
spv_result_t AssemblyContext::recordTypeIdForValue(uint32_t value,
375
363k
                                                   uint32_t type) {
376
363k
  bool successfully_inserted = false;
377
363k
  std::tie(std::ignore, successfully_inserted) =
378
363k
      value_types_.insert(std::make_pair(value, type));
379
363k
  if (!successfully_inserted)
380
177k
    return diagnostic() << "Value is being defined a second time";
381
185k
  return SPV_SUCCESS;
382
363k
}
383
384
spv_result_t AssemblyContext::recordIdAsExtInstImport(
385
6.54k
    uint32_t id, spv_ext_inst_type_t type) {
386
6.54k
  bool successfully_inserted = false;
387
6.54k
  std::tie(std::ignore, successfully_inserted) =
388
6.54k
      import_id_to_ext_inst_type_.insert(std::make_pair(id, type));
389
6.54k
  if (!successfully_inserted)
390
29
    return diagnostic() << "Import Id is being defined a second time";
391
6.51k
  return SPV_SUCCESS;
392
6.54k
}
393
394
20.1k
spv_ext_inst_type_t AssemblyContext::getExtInstTypeForId(uint32_t id) const {
395
20.1k
  auto type = import_id_to_ext_inst_type_.find(id);
396
20.1k
  if (type == import_id_to_ext_inst_type_.end()) {
397
38
    return SPV_EXT_INST_TYPE_NONE;
398
38
  }
399
20.1k
  return std::get<1>(*type);
400
20.1k
}
401
402
6.71k
std::set<uint32_t> AssemblyContext::GetNumericIds() const {
403
6.71k
  std::set<uint32_t> ids;
404
112k
  for (const auto& kv : named_ids_) {
405
112k
    uint32_t id;
406
112k
    if (spvtools::utils::ParseNumber(kv.first.c_str(), &id)) ids.insert(id);
407
112k
  }
408
6.71k
  return ids;
409
6.71k
}
410
411
}  // namespace spvtools