Coverage Report

Created: 2026-10-17 06:38

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/processor_text_fuzzer.cc
Line
Count
Source
1
// Copyright 2026 Google LLC
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
// Fuzzer that loads a valid pre-built sentencepiece model (embedded in
16
// the binary as a byte array) and then fuzzes all encoding, decoding,
17
// normalization, and vocabulary operations with fuzz-derived text input.
18
19
#include <cstddef>
20
#include <cstdint>
21
#include <cstdlib>
22
#include <memory>
23
#include <string>
24
#include <vector>
25
26
#include <fuzzer/FuzzedDataProvider.h>
27
#include "sentencepiece_processor.h"
28
29
// Generated at build time by: xxd -i processor_text_fuzzer_model
30
#include "embedded_model.h"
31
32
static std::unique_ptr<sentencepiece::SentencePieceProcessor> g_processor;
33
34
2
extern "C" int LLVMFuzzerInitialize(int *argc, char ***argv) {
35
2
  g_processor = std::make_unique<sentencepiece::SentencePieceProcessor>();
36
37
  // Load the model from the embedded byte array
38
2
  std::string model_data(
39
2
      reinterpret_cast<const char *>(kEmbeddedModelData),
40
2
      kEmbeddedModelSize);
41
2
  auto status = g_processor->LoadFromSerializedProto(model_data);
42
2
  if (!status.ok()) {
43
0
    fprintf(stderr, "Failed to load embedded model: %s\n",
44
0
            status.ToString().c_str());
45
0
    abort();
46
0
  }
47
48
2
  return 0;
49
2
}
50
51
2.30k
extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
52
2.30k
  if (!g_processor || size < 2)
53
1
    return 0;
54
55
2.30k
  FuzzedDataProvider fdp(data, size);
56
2.30k
  uint8_t ops = fdp.ConsumeIntegral<uint8_t>();
57
2.30k
  std::string text = fdp.ConsumeRemainingBytesAsString();
58
59
  // === Core encoding operations ===
60
2.30k
  if (ops & 0x01) {
61
    // Encode to pieces (strings)
62
1.57k
    std::vector<std::string> pieces;
63
1.57k
    g_processor->Encode(text, &pieces);
64
65
    // Decode back from pieces
66
1.57k
    if (!pieces.empty()) {
67
1.54k
      std::string decoded;
68
1.54k
      g_processor->Decode(pieces, &decoded);
69
1.54k
    }
70
1.57k
  }
71
72
2.30k
  if (ops & 0x02) {
73
    // Encode to IDs
74
1.63k
    std::vector<int> ids;
75
1.63k
    g_processor->Encode(text, &ids);
76
77
    // Decode back from IDs
78
1.63k
    if (!ids.empty()) {
79
1.61k
      std::string decoded;
80
1.61k
      g_processor->Decode(ids, &decoded);
81
1.61k
    }
82
1.63k
  }
83
84
  // === Advanced encoding operations ===
85
2.30k
  if (ops & 0x04) {
86
    // NBest encoding - use small nbest to avoid being too slow
87
1.53k
    std::vector<std::vector<std::string>> nbest_pieces;
88
1.53k
    g_processor->NBestEncode(text, 3, &nbest_pieces);
89
90
    // NBest encode to IDs
91
1.53k
    std::vector<std::vector<int>> nbest_ids;
92
1.53k
    g_processor->NBestEncode(text, 3, &nbest_ids);
93
1.53k
  }
94
95
2.30k
  if (ops & 0x08) {
96
    // Sample encoding with various alpha values
97
1.41k
    std::vector<std::string> sampled;
98
1.41k
    g_processor->SampleEncode(text, -1, 0.5, &sampled);
99
100
1.41k
    std::vector<int> sampled_ids;
101
1.41k
    g_processor->SampleEncode(text, -1, 0.1, &sampled_ids);
102
1.41k
  }
103
104
2.30k
  if (ops & 0x10) {
105
    // Upstream removed the *AsSerializedProto family; the piece-returning
106
    // overloads reach the same encode paths. They are [[nodiscard]], so the
107
    // results are kept alive.
108
1.52k
    auto pieces = g_processor->EncodeAsPieces(text);
109
1.52k
    (void)pieces;
110
111
1.52k
    auto nb_pieces = g_processor->NBestEncodeAsPieces(text, 2);
112
1.52k
    (void)nb_pieces;
113
114
1.52k
    auto sample_pieces = g_processor->SampleEncodeAsPieces(text, -1, 0.5);
115
1.52k
    (void)sample_pieces;
116
1.52k
  }
117
118
  // === Normalization ===
119
2.30k
  if (ops & 0x20) {
120
1.70k
    std::string normalized;
121
1.70k
    g_processor->Normalize(text, &normalized);
122
123
    // Normalize with alignment info
124
1.70k
    std::string normalized2;
125
1.70k
    std::vector<size_t> norm_to_orig;
126
1.70k
    g_processor->Normalize(text, &normalized2, &norm_to_orig);
127
1.70k
  }
128
129
  // === Vocabulary operations ===
130
2.30k
  if (ops & 0x40) {
131
    // PieceToId with fuzz text
132
1.69k
    g_processor->PieceToId(text);
133
134
    // Try splitting text into substrings and looking them up
135
1.69k
    if (text.size() > 2) {
136
13.7k
      for (size_t i = 0; i < text.size() && i < 8; i++) {
137
12.0k
        std::string sub = text.substr(0, i + 1);
138
12.0k
        int id = g_processor->PieceToId(sub);
139
12.0k
        if (id >= 0 && id < g_processor->GetPieceSize()) {
140
12.0k
          g_processor->IdToPiece(id);
141
12.0k
          g_processor->GetScore(id);
142
12.0k
          g_processor->IsUnknown(id);
143
12.0k
          g_processor->IsControl(id);
144
12.0k
          g_processor->IsUnused(id);
145
12.0k
          g_processor->IsByte(id);
146
12.0k
        }
147
12.0k
      }
148
1.60k
    }
149
1.69k
  }
150
151
  // Upstream removed CalculateEntropy(). Exercise the sampling path it used
152
  // to sit on instead, so this branch still does work.
153
2.30k
  if (ops & 0x80) {
154
1.39k
    if (text.size() > 0 && text.size() < 256) {
155
848
      auto sampled_pieces = g_processor->SampleEncodeAsPieces(text, 8, 0.5);
156
848
      (void)sampled_pieces;
157
848
    }
158
1.39k
  }
159
160
2.30k
  return 0;
161
2.30k
}