• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2014 The Chromium Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 // All data that is passed through a WebSocket with type "Text" needs to be
6 // validated as UTF8. Since this is done on the IO thread, it needs to be
7 // reasonably fast.
8 
9 // We are only interested in the performance on valid UTF8. Invalid UTF8 will
10 // result in a connection failure, so is unlikely to become a source of
11 // performance issues.
12 
13 #include "base/i18n/streaming_utf8_validator.h"
14 
15 #include <stddef.h>
16 
17 #include <string>
18 
19 #include "base/functional/bind.h"
20 #include "base/functional/callback.h"
21 #include "base/strings/string_util.h"
22 #include "base/strings/stringprintf.h"
23 #include "base/test/perf_time_logger.h"
24 #include "testing/gtest/include/gtest/gtest.h"
25 
26 namespace base {
27 namespace {
28 
29 // We want to test ranges of valid UTF-8 sequences. These ranges are inclusive.
30 // They are intended to be large enough that the validator needs to do
31 // meaningful work while being in some sense "realistic" (eg. control characters
32 // are not included).
33 const char kOneByteSeqRangeStart[] = " ";  // U+0020
34 const char kOneByteSeqRangeEnd[] = "~";    // U+007E
35 
36 const char kTwoByteSeqRangeStart[] = "\xc2\xa0";  // U+00A0 non-breaking space
37 const char kTwoByteSeqRangeEnd[] = "\xc9\x8f";    // U+024F small y with stroke
38 
39 const char kThreeByteSeqRangeStart[] = "\xe3\x81\x82";  // U+3042 Hiragana "a"
40 const char kThreeByteSeqRangeEnd[] = "\xe9\xbf\x83";    // U+9FC3 "to blink"
41 
42 const char kFourByteSeqRangeStart[] = "\xf0\xa0\x80\x8b";  // U+2000B
43 const char kFourByteSeqRangeEnd[] = "\xf0\xaa\x9a\xb2";    // U+2A6B2
44 
45 // The different lengths of strings to test.
46 const size_t kTestLengths[] = {1, 32, 256, 32768, 1 << 20};
47 
48 // Simplest possible byte-at-a-time validator, to provide a baseline
49 // for comparison. This is only tried on 1-byte UTF-8 sequences, as
50 // the results will not be meaningful with sequences containing
51 // top-bit-set bytes.
IsString7Bit(const std::string & s)52 bool IsString7Bit(const std::string& s) {
53   for (auto it : s) {
54     if (it & 0x80)
55       return false;
56   }
57   return true;
58 }
59 
60 // Assumes that |previous| is a valid UTF-8 sequence, and attempts to return
61 // the next one. Is just barely smart enough to iterate through the ranges
62 // defined about.
NextUtf8Sequence(const std::string & previous)63 std::string NextUtf8Sequence(const std::string& previous) {
64   DCHECK(StreamingUtf8Validator::Validate(previous));
65   std::string next = previous;
66   for (int i = static_cast<int>(previous.length() - 1); i >= 0; --i) {
67     // All bytes in a UTF-8 sequence except the first one are
68     // constrained to the range 0x80 to 0xbf, inclusive. When we
69     // increment past 0xbf, we carry into the previous byte.
70     if (i > 0 && next[i] == '\xbf') {
71       next[i] = '\x80';
72       continue;  // carry
73     }
74     ++next[i];
75     break;  // no carry
76   }
77   DCHECK(StreamingUtf8Validator::Validate(next))
78       << "Result \"" << next << "\" failed validation";
79   return next;
80 }
81 
82 typedef bool (*TestTargetType)(const std::string&);
83 
84 // Run fuction |target| over |test_string| |times| times, and report the results
85 // using |description|.
RunTest(const std::string & description,TestTargetType target,const std::string & test_string,int times)86 bool RunTest(const std::string& description,
87              TestTargetType target,
88              const std::string& test_string,
89              int times) {
90   base::PerfTimeLogger timer(description.c_str());
91   bool result = true;
92   for (int i = 0; i < times; ++i) {
93     result = target(test_string) && result;
94   }
95   timer.Done();
96   return result;
97 }
98 
99 // Construct a string by repeating |input| enough times to equal or exceed
100 // |length|.
ConstructRepeatedTestString(const std::string & input,size_t length)101 std::string ConstructRepeatedTestString(const std::string& input,
102                                         size_t length) {
103   std::string output = input;
104   while (output.length() * 2 < length) {
105     output += output;
106   }
107   if (output.length() < length) {
108     output += ConstructRepeatedTestString(input, length - output.length());
109   }
110   return output;
111 }
112 
113 // Construct a string by expanding the range of UTF-8 sequences
114 // between |input_start| and |input_end|, inclusive, and then
115 // repeating the resulting string until it equals or exceeds |length|
116 // bytes. |input_start| and |input_end| must be valid UTF-8
117 // sequences.
ConstructRangedTestString(const std::string & input_start,const std::string & input_end,size_t length)118 std::string ConstructRangedTestString(const std::string& input_start,
119                                       const std::string& input_end,
120                                       size_t length) {
121   std::string output = input_start;
122   std::string input = input_start;
123   while (output.length() < length && input != input_end) {
124     input = NextUtf8Sequence(input);
125     output += input;
126   }
127   if (output.length() < length) {
128     output = ConstructRepeatedTestString(output, length);
129   }
130   return output;
131 }
132 
133 struct TestFunctionDescription {
134   TestTargetType function;
135   const char* function_name;
136 };
137 
IsStringUTF8(const std::string & str)138 bool IsStringUTF8(const std::string& str) {
139   return base::IsStringUTF8(base::StringPiece(str));
140 }
141 
142 // IsString7Bit is intentionally placed last so it can be excluded easily.
143 const TestFunctionDescription kTestFunctions[] = {
144     {&StreamingUtf8Validator::Validate, "StreamingUtf8Validator"},
145     {&IsStringUTF8, "IsStringUTF8"}, {&IsString7Bit, "IsString7Bit"}};
146 
147 // Construct a test string from |construct_test_string| for each of the lengths
148 // in |kTestLengths| in turn. For each string, run each test in |test_functions|
149 // for a number of iterations such that the total number of bytes validated
150 // is around 16MB.
RunSomeTests(const char format[],base::RepeatingCallback<std::string (size_t length)> construct_test_string,const TestFunctionDescription * test_functions,size_t test_count)151 void RunSomeTests(
152     const char format[],
153     base::RepeatingCallback<std::string(size_t length)> construct_test_string,
154     const TestFunctionDescription* test_functions,
155     size_t test_count) {
156   for (auto length : kTestLengths) {
157     const std::string test_string = construct_test_string.Run(length);
158     const int real_length = static_cast<int>(test_string.length());
159     const int times = (1 << 24) / real_length;
160     for (size_t test_index = 0; test_index < test_count; ++test_index) {
161       EXPECT_TRUE(RunTest(StringPrintf(format,
162                                        test_functions[test_index].function_name,
163                                        real_length,
164                                        times),
165                           test_functions[test_index].function,
166                           test_string,
167                           times));
168     }
169   }
170 }
171 
TEST(StreamingUtf8ValidatorPerfTest,OneByteRepeated)172 TEST(StreamingUtf8ValidatorPerfTest, OneByteRepeated) {
173   RunSomeTests(
174       "%s: bytes=1 repeated length=%d repeat=%d",
175       base::BindRepeating(ConstructRepeatedTestString, kOneByteSeqRangeStart),
176       kTestFunctions, 3);
177 }
178 
TEST(StreamingUtf8ValidatorPerfTest,OneByteRange)179 TEST(StreamingUtf8ValidatorPerfTest, OneByteRange) {
180   RunSomeTests("%s: bytes=1 ranged length=%d repeat=%d",
181                base::BindRepeating(ConstructRangedTestString,
182                                    kOneByteSeqRangeStart, kOneByteSeqRangeEnd),
183                kTestFunctions, 3);
184 }
185 
TEST(StreamingUtf8ValidatorPerfTest,TwoByteRepeated)186 TEST(StreamingUtf8ValidatorPerfTest, TwoByteRepeated) {
187   RunSomeTests(
188       "%s: bytes=2 repeated length=%d repeat=%d",
189       base::BindRepeating(ConstructRepeatedTestString, kTwoByteSeqRangeStart),
190       kTestFunctions, 2);
191 }
192 
TEST(StreamingUtf8ValidatorPerfTest,TwoByteRange)193 TEST(StreamingUtf8ValidatorPerfTest, TwoByteRange) {
194   RunSomeTests("%s: bytes=2 ranged length=%d repeat=%d",
195                base::BindRepeating(ConstructRangedTestString,
196                                    kTwoByteSeqRangeStart, kTwoByteSeqRangeEnd),
197                kTestFunctions, 2);
198 }
199 
TEST(StreamingUtf8ValidatorPerfTest,ThreeByteRepeated)200 TEST(StreamingUtf8ValidatorPerfTest, ThreeByteRepeated) {
201   RunSomeTests(
202       "%s: bytes=3 repeated length=%d repeat=%d",
203       base::BindRepeating(ConstructRepeatedTestString, kThreeByteSeqRangeStart),
204       kTestFunctions, 2);
205 }
206 
TEST(StreamingUtf8ValidatorPerfTest,ThreeByteRange)207 TEST(StreamingUtf8ValidatorPerfTest, ThreeByteRange) {
208   RunSomeTests(
209       "%s: bytes=3 ranged length=%d repeat=%d",
210       base::BindRepeating(ConstructRangedTestString, kThreeByteSeqRangeStart,
211                           kThreeByteSeqRangeEnd),
212       kTestFunctions, 2);
213 }
214 
TEST(StreamingUtf8ValidatorPerfTest,FourByteRepeated)215 TEST(StreamingUtf8ValidatorPerfTest, FourByteRepeated) {
216   RunSomeTests(
217       "%s: bytes=4 repeated length=%d repeat=%d",
218       base::BindRepeating(ConstructRepeatedTestString, kFourByteSeqRangeStart),
219       kTestFunctions, 2);
220 }
221 
TEST(StreamingUtf8ValidatorPerfTest,FourByteRange)222 TEST(StreamingUtf8ValidatorPerfTest, FourByteRange) {
223   RunSomeTests(
224       "%s: bytes=4 ranged length=%d repeat=%d",
225       base::BindRepeating(ConstructRangedTestString, kFourByteSeqRangeStart,
226                           kFourByteSeqRangeEnd),
227       kTestFunctions, 2);
228 }
229 
230 }  // namespace
231 }  // namespace base
232