• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

polserver / polserver / 39373964661

20 Oct 2026 05:15AM UTC coverage: 81.995% (+0.001%) from 81.994%
39373964661

push

github

web-flow
Fix the UBSan and ASan reports and make sanitizer findings fail CI (#990)

* Read packet fields through memcpy instead of cast pointers
Packet buffers hold u16/u32 fields at odd offsets. Reading or writing
them through reinterpret_cast pointers is undefined
behaviour, which
UBSan reports as misaligned loads and stores. Add
Clib::load_unaligned/store_unaligned and use them in the
packet hooks,
the BPacket get/set methods, custom house packets, speech
tokens and
String::fromUTF16. Copy the proxy protocol header and
payload out of
the receive buffer instead of casting it.

fromUTF16 also read one code unit past `len` when the text
had no null terminator; the bound is now checked first.

* Fix the remaining UBSan reports from the shard tests

- Pool 0xF0 packets per subcommand. A single queue handed a recycled
  party packet (PktOut_F0_Sub01) to the guild query as PktOut_F0_Sub02.
- Compute the client encryption keys in unsigned arithmetic; the formulas overflow int by design.
- Mask the shift count in LoginCrypt::Decrypt_1_25_36 to 5 bits,
  as the client's x86 shift does. Counts of 32 and more are undefined.
  A new test pins the keystream, which is unchanged.
- Give ECryptType and USKILLID a fixed underlying type, so unknown crypt types,
  the -1 "no skill" sentinel and skill ids up to 499 are valid values.

* Turn on more sanitizer checks in CI

Make UBSan fail the job instead of only logging. Enable
libstdc++
assertions in all sanitizer builds and vector
container-overflow
annotations under ASan. Turn on ASan's
stack-use-after-return,
initialization-order and strict string checks.

* Read listing tokens through memcpy instead of cast pointers

StoredTokenDecoder read doubles, ints, uint16_t and
DebugToken from
the program data by casting a pointer at an arbitrary
offset. These
reads are misaligned, which UBSan reports. They now use
Clib::load_unaligned. debug_token_at returns a copy instead
of a
reference into the buffer.

StoredToken is packed. deco... (continued)

143 of 150 new or added lines in 16 files covered. (95.33%)

15 existing lines in 5 files now uncovered.

60301 of 73542 relevant lines covered (82.0%)

727669.95 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

92.61
/pol-core/bscript/bstring.cpp
1
/** @file
2
 *
3
 * @par History
4
 * - 2007/12/09 Shinigami: removed ( is.peek() != EOF ) check from String::unpackWithLen()
5
 *                         will not work with Strings in Arrays, Dicts, etc.
6
 * - 2008/02/08 Turley:    String::unpackWithLen() will accept zero length Strings
7
 * - 2009/09/12 MuadDib:   Disabled 4244 in this file due to it being on a string iter. Makes no
8
 * sense.
9
 */
10

11
#include "bscript/bstring.h"
12

13
#include <cstdlib>
14
#include <ctype.h>
15
#include <cwctype>
16
#include <fmt/compile.h>
17
#include <iterator>
18
#include <string>
19
#include <utf8cpp/utf8.h>
20

21
#include "bscript/barray.h"
22
#include "bscript/bboolean.h"
23
#include "bscript/bcontiter.h"
24
#include "bscript/bdouble.h"
25
#include "bscript/berror.h"
26
#include "bscript/bfuncref.h"
27
#include "bscript/blong.h"
28
#include "bscript/bobject.h"
29
#include "bscript/bregexp.h"
30
#include "bscript/buninit.h"
31
#include "bscript/executor.h"
32
#include "bscript/objmethods.h"
33
#include "bscript/str.h"
34
#include "clib/clib_endian.h"
35
#include "clib/stlutil.h"
36
#include "clib/strutil.h"
37

38
#ifdef __GNUG__
39
#include <streambuf>
40
#endif
41

42
#ifdef _MSC_VER
43
#include "clib/Header_Windows.h"
44
#include <codecvt>
45
#endif
46

47

48
namespace Pol::Bscript
49
{
50
using namespace fmt::literals;
51

52
String::String( BObjectImp& objimp ) : BObjectImp( OTString ), value_( objimp.getStringRep() ) {}
×
53

54
String::String( const char* s, size_t len, Tainted san ) : BObjectImp( OTString ), value_( s, len )
41,078 ✔
55
{
56
  if ( san == Tainted::YES )
20,539 ✔
57
    Clib::sanitizeUnicodeWithIso( &value_ );
33 ✔
58
}
20,539 ✔
59

60
String::String( const std::string& str, std::string::size_type pos, std::string::size_type n )
22,028 ✔
61
    : BObjectImp( OTString ), value_( str, pos, n )
22,028 ✔
62
{
63
}
22,028 ✔
64

65
String::String( const char* str, Tainted san ) : BObjectImp( OTString ), value_( str )
1,303,722 ✔
66
{
67
  if ( san == Tainted::YES )
651,861 ✔
68
    Clib::sanitizeUnicodeWithIso( &value_ );
185 ✔
69
}
651,861 ✔
70

71
String::String( const std::string& str, Tainted san ) : BObjectImp( OTString ), value_( str )
1,350,080 ✔
72
{
73
  if ( san == Tainted::YES )
1,350,080 ✔
74
    Clib::sanitizeUnicodeWithIso( &value_ );
1,201,076 ✔
75
}
1,350,080 ✔
76

77
String::String( const std::string_view& str, Tainted san ) : BObjectImp( OTString ), value_( str )
28 ✔
78
{
79
  if ( san == Tainted::YES )
14 ✔
80
    Clib::sanitizeUnicodeWithIso( &value_ );
14 ✔
81
}
14 ✔
82

83
String::String( const std::wstring& str, Tainted san ) : BObjectImp( OTString )
147 ✔
84
{
85
  for ( const auto& c : str )
348 ✔
86
  {
87
    utf8::unchecked::append( c, std::back_inserter( value_ ) );
201 ✔
88
  }
89

90
  if ( san == Tainted::YES )
147 ✔
91
    Clib::sanitizeUnicodeWithIso( &value_ );
×
92
}
147 ✔
93

94
String* String::StrStr( int begin, int len ) const
45 ✔
95
{
96
  auto itr = value_.cbegin();
45 ✔
97
  --begin;
45 ✔
98
  size_t startpos = getBytePosition( &itr, begin );
45 ✔
99
  size_t endpos = getBytePosition( &itr, len );
45 ✔
100
  if ( startpos != std::string::npos )
45 ✔
101
    return new String( value_.substr( startpos, endpos - startpos ) );
42 ✔
102
  return new String( value_ );
3 ✔
103
}
104

105
size_t String::length() const
53,217 ✔
106
{
107
  return utf8::unchecked::distance( value_.begin(), value_.end() );
53,217 ✔
108
}
109

110
String* String::ETrim( const char* CRSet, int type ) const
42 ✔
111
{
112
  std::string tmp = value_;
42 ✔
113

114
  if ( type == 1 )  // This is for Leading Only.
42 ✔
115
  {
116
    // Find the first character position after excluding leading blank spaces
117
    size_t startpos = tmp.find_first_not_of( CRSet );
12 ✔
118
    if ( std::string::npos != startpos )
12 ✔
119
      tmp = tmp.substr( startpos );
9 ✔
120
    else
121
      tmp = "";
3 ✔
122
    return new String( tmp );
12 ✔
123
  }
124
  if ( type == 2 )  // This is for Trailing Only.
30 ✔
125
  {
126
    // Find the first character position from reverse
127
    size_t endpos = tmp.find_last_not_of( CRSet );
9 ✔
128
    if ( std::string::npos != endpos )
9 ✔
129
      tmp = tmp.substr( 0, endpos + 1 );
6 ✔
130
    else
131
      tmp = "";
3 ✔
132
    return new String( tmp );
9 ✔
133
  }
134
  if ( type == 3 )
21 ✔
135
  {
136
    // Find the first character position after excluding leading blank spaces
137
    size_t startpos = tmp.find_first_not_of( CRSet );
21 ✔
138
    // Find the first character position from reverse af
139
    size_t endpos = tmp.find_last_not_of( CRSet );
21 ✔
140

141
    // if all spaces or empty return on empty string
142
    if ( ( std::string::npos == startpos ) || ( std::string::npos == endpos ) )
21 ✔
143
      tmp = "";
3 ✔
144
    else
145
      tmp = tmp.substr( startpos, endpos - startpos + 1 );
18 ✔
146
    return new String( tmp );
21 ✔
147
  }
148
  return new String( tmp );
×
149
}
42 ✔
150

151
void String::EStrReplace( String* str1, String* str2 )
12 ✔
152
{
153
  std::string::size_type valpos = 0;
12 ✔
154
  while ( std::string::npos != ( valpos = value_.find( str1->value_, valpos ) ) )
24 ✔
155
  {
156
    value_.replace( valpos, str1->value_.size(), str2->value_ );
12 ✔
157
    valpos += str2->value_.size();
12 ✔
158
  }
159
}
12 ✔
160

161
void String::ESubStrReplace( String* replace_with, unsigned int index, unsigned int len )
12 ✔
162
{
163
  auto itr = value_.cbegin();
12 ✔
164
  size_t begin = getBytePosition( &itr, index - 1 );
12 ✔
165
  size_t end = getBytePosition( &itr, len );
12 ✔
166
  if ( begin != std::string::npos )
12 ✔
167
    value_.replace( begin, end - begin, replace_with->value_ );
12 ✔
168
}
12 ✔
169

170
std::string String::pack() const
91 ✔
171
{
172
  return fmt::format( "s{}"_cf, value_ );
182 ✔
173
}
174
void String::packonto( std::string& str ) const
320 ✔
175
{
176
  fmt::format_to( std::back_inserter( str ), "S{}:{}"_cf, value_.size(), value_ );
320 ✔
177
}
320 ✔
178
void String::packonto( std::string& str, const std::string& value )
813 ✔
179
{
180
  fmt::format_to( std::back_inserter( str ), "S{}:{}"_cf, value.size(), value );
813 ✔
181
}
813 ✔
182

183
BObjectImp* String::unpack( std::istream& is )
75 ✔
184
{
185
  std::string tmp;
75 ✔
186
  getline( is, tmp );
75 ✔
187

188
  return new String( tmp );
150 ✔
189
}
75 ✔
190

191
BObjectImp* String::unpackWithLen( std::istream& is )
798 ✔
192
{
193
  unsigned len;
194
  char colon;
195
  if ( !( is >> len >> colon ) )
798 ✔
196
  {
197
    return new BError( "Unable to unpack string length." );
6 ✔
198
  }
199
  if ( (int)len < 0 )
792 ✔
200
  {
201
    return new BError( "Unable to unpack string length. Invalid length!" );
3 ✔
202
  }
203
  if ( colon != ':' )
789 ✔
204
  {
205
    return new BError( "Unable to unpack string length. Bad format. Colon not found!" );
3 ✔
206
  }
207

208
  is.unsetf( std::ios::skipws );
786 ✔
209
  std::string tmp;
786 ✔
210
  tmp.reserve( len );
786 ✔
211
  while ( len-- )
168,433 ✔
212
  {
213
    char ch = '\0';
167,650 ✔
214
    if ( !( is >> ch ) || ch == '\0' )
167,650 ✔
215
    {
216
      return new BError( "Unable to unpack string length. String length excessive." );
3 ✔
217
    }
218
    tmp += ch;
167,647 ✔
219
  }
220

221
  is.setf( std::ios::skipws );
783 ✔
222
  return new String( tmp );
783 ✔
223
}
786 ✔
224

225
size_t String::sizeEstimate() const
2,170 ✔
226
{
227
  return sizeof( String ) + value_.capacity();
2,170 ✔
228
}
229

230
/*
231
    0-based string find
232
    find( "str srch", 2, "srch"):
233
    01^-- start
234
    */
235
int String::find( int begin, const char* target ) const
26,237 ✔
236
{
237
  // returns -1 when begin is out of range for string
238
  auto itr = value_.cbegin();
26,237 ✔
239
  size_t pos = getBytePosition( &itr, begin );
26,237 ✔
240
  pos = value_.find( target, pos );
26,237 ✔
241
  if ( pos == std::string::npos )
26,237 ✔
242
    return -1;
18,542 ✔
243

244
  pos = utf8::unchecked::distance( value_.cbegin(), std::next( value_.cbegin(), pos ) );
15,390 ✔
245
  return static_cast<int>( pos );
7,695 ✔
246
}
247

248
unsigned int String::SafeCharAmt() const
72 ✔
249
{
250
  unsigned int strlen = static_cast<unsigned int>( length() );
72 ✔
251
  for ( unsigned int i = 0; i < strlen; ++i )
743 ✔
252
  {
253
    unsigned char tmp = value_[i];
671 ✔
254
    if ( tmp >= 0x80 )  // Ascii range
671 ✔
255
      return i;
×
256
    if ( isalnum( tmp ) )  // a-z A-Z 0-9
671 ✔
257
      continue;
643 ✔
258
    if ( ispunct( tmp ) )  // !"#$%&'()*+,-./:;<=>?@{|}~
28 ✔
259
    {
260
      if ( tmp == '{' || tmp == '}' )
28 ✔
261
        return i;
×
262
      continue;
28 ✔
263
    }
264

265
    return i;
×
266
  }
267
  return strlen;
72 ✔
268
}
269

270
BObjectImp* String::selfPlusObjImp( const BObjectImp& objimp ) const
53,269 ✔
271
{
272
  return objimp.selfPlusObj( *this );
53,269 ✔
273
}
274
BObjectImp* String::selfPlusObj( const BObjectImp& objimp ) const
1,269 ✔
275
{
276
  return new String( value_ + objimp.getStringRep() );
1,269 ✔
277
}
278
BObjectImp* String::selfPlusObj( const BLong& objimp ) const
2,795 ✔
279
{
280
  return new String( value_ + objimp.getStringRep() );
2,795 ✔
281
}
282
BObjectImp* String::selfPlusObj( const Double& objimp ) const
13,051 ✔
283
{
284
  return new String( value_ + objimp.getStringRep() );
13,051 ✔
285
}
286
BObjectImp* String::selfPlusObj( const String& objimp ) const
53,131 ✔
287
{
288
  return new String( value_ + objimp.getStringRep() );
53,131 ✔
289
}
290
BObjectImp* String::selfPlusObj( const ObjArray& objimp ) const
321 ✔
291
{
292
  return new String( value_ + objimp.getStringRep() );
321 ✔
293
}
294
void String::selfPlusObjImp( BObjectImp& objimp, BObject& obj )
12,956 ✔
295
{
296
  objimp.selfPlusObj( *this, obj );
12,956 ✔
297
}
12,956 ✔
298
void String::selfPlusObj( BObjectImp& objimp, BObject& /*obj*/ )
24 ✔
299
{
300
  value_ += objimp.getStringRep();
24 ✔
301
}
24 ✔
302
void String::selfPlusObj( BLong& objimp, BObject& /*obj*/ )
15 ✔
303
{
304
  value_ += objimp.getStringRep();
15 ✔
305
}
15 ✔
306
void String::selfPlusObj( Double& objimp, BObject& /*obj*/ )
12 ✔
307
{
308
  value_ += objimp.getStringRep();
12 ✔
309
}
12 ✔
310
void String::selfPlusObj( String& objimp, BObject& /*obj*/ )
12,938 ✔
311
{
312
  value_ += objimp.getStringRep();
12,938 ✔
313
}
12,938 ✔
314
void String::selfPlusObj( ObjArray& objimp, BObject& /*obj*/ )
9 ✔
315
{
316
  value_ += objimp.getStringRep();
9 ✔
317
}
9 ✔
318

319

320
void String::remove( const std::string& rm )
357 ✔
321
{
322
  auto pos = value_.find( rm );
357 ✔
323
  if ( pos != std::string::npos )
357 ✔
324
    value_.erase( pos, rm.size() );
75 ✔
325
}
357 ✔
326

327
BObjectImp* String::selfMinusObjImp( const BObjectImp& objimp ) const
207 ✔
328
{
329
  return objimp.selfMinusObj( *this );
207 ✔
330
}
331
BObjectImp* String::selfMinusObj( const BObjectImp& objimp ) const
42 ✔
332
{
333
  String* tmp = (String*)copy();
42 ✔
334
  tmp->remove( objimp.getStringRep() );
42 ✔
335
  return tmp;
42 ✔
336
}
337
BObjectImp* String::selfMinusObj( const BLong& objimp ) const
24 ✔
338
{
339
  String* tmp = (String*)copy();
24 ✔
340
  tmp->remove( objimp.getStringRep() );
24 ✔
341
  return tmp;
24 ✔
342
}
343
BObjectImp* String::selfMinusObj( const Double& objimp ) const
21 ✔
344
{
345
  String* tmp = (String*)copy();
21 ✔
346
  tmp->remove( objimp.getStringRep() );
21 ✔
347
  return tmp;
21 ✔
348
}
349
BObjectImp* String::selfMinusObj( const String& objimp ) const
174 ✔
350
{
351
  String* tmp = (String*)copy();
174 ✔
352
  tmp->remove( objimp.value_ );
174 ✔
353
  return tmp;
174 ✔
354
}
355
void String::selfMinusObjImp( BObjectImp& objimp, BObject& obj )
57 ✔
356
{
357
  objimp.selfMinusObj( *this, obj );
57 ✔
358
}
57 ✔
359
void String::selfMinusObj( BObjectImp& objimp, BObject& /*obj*/ )
33 ✔
360
{
361
  remove( objimp.getStringRep() );
33 ✔
362
}
33 ✔
363
void String::selfMinusObj( BLong& objimp, BObject& /*obj*/ )
12 ✔
364
{
365
  remove( objimp.getStringRep() );
12 ✔
366
}
12 ✔
367
void String::selfMinusObj( Double& objimp, BObject& /*obj*/ )
12 ✔
368
{
369
  remove( objimp.getStringRep() );
12 ✔
370
}
12 ✔
371
void String::selfMinusObj( String& objimp, BObject& /*obj*/ )
39 ✔
372
{
373
  remove( objimp.value_ );
39 ✔
374
}
39 ✔
375

376
bool String::operator==( const BObjectImp& objimp ) const
60,766 ✔
377
{
378
  if ( objimp.isa( OTString ) )
60,766 ✔
379
    return ( value_ == static_cast<const String&>( objimp ).value_ );
60,389 ✔
380

381
  if ( objimp.isa( OTBoolean ) )
377 ✔
382
    return isTrue() == static_cast<const BBoolean&>( objimp ).isTrue();
36 ✔
383
  return base::operator==( objimp );
341 ✔
384
}
385

386
bool String::operator<( const BObjectImp& objimp ) const
5,009 ✔
387
{
388
  if ( objimp.isa( OTString ) )
5,009 ✔
389
    return ( value_ < static_cast<const String&>( objimp ).value_ );
4,684 ✔
390

391
  // operator== compares a String to a Boolean by truthiness; order them the same way, or the
392
  // <=, >, >= that BObjectImp derives from the pair contradict it.
393
  if ( objimp.isa( OTBoolean ) )
325 ✔
394
    return !isTrue() && static_cast<const BBoolean&>( objimp ).isTrue();
12 ✔
395

396
  return base::operator<( objimp );
313 ✔
397
}
398

399
void String::toUpper()
42 ✔
400
{
401
  if ( !hasUTF8Characters() )
42 ✔
402
  {
403
    Clib::mkupperASCII( value_ );
33 ✔
404
    return;
33 ✔
405
  }
406
#ifndef WINDOWS
407
  auto wstr = Clib::to_wstring( value_ );
9 ✔
408
  value_.clear();
9 ✔
409
  for ( const auto& c : wstr )
237 ✔
410
  {
411
    utf8::unchecked::append( std::towupper( c ), std::back_inserter( value_ ) );
228 ✔
412
  }
413
#else
414
  std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>> converter;
415
  std::wstring str = converter.from_bytes( value_ );
416

417
  int len = LCMapStringW( LOCALE_USER_DEFAULT, LCMAP_UPPERCASE | LCMAP_LINGUISTIC_CASING, &str[0],
418
                          static_cast<int>( str.size() ), 0, 0 );
419
  if ( !len )
420
    return;
421
  else if ( len == static_cast<int>( str.size() ) )
422
  {
423
    LCMapStringW( LOCALE_USER_DEFAULT, LCMAP_UPPERCASE | LCMAP_LINGUISTIC_CASING, &str[0],
424
                  static_cast<int>( str.size() ), &str[0], static_cast<int>( str.size() ) );
425
    value_ = converter.to_bytes( str );
426
  }
427
  else
428
  {
429
    std::wstring buf;
430
    buf.reserve( len );
431
    LCMapStringW( LOCALE_USER_DEFAULT, LCMAP_UPPERCASE | LCMAP_LINGUISTIC_CASING, &str[0],
432
                  static_cast<int>( str.size() ), &buf[0], static_cast<int>( buf.size() ) );
433
    value_ = converter.to_bytes( buf );
434
  }
435
#endif
436
}
9 ✔
437

438
void String::toLower()
27 ✔
439
{
440
  if ( !hasUTF8Characters() )
27 ✔
441
  {
442
    Clib::mklowerASCII( value_ );
18 ✔
443
    return;
18 ✔
444
  }
445
#ifndef WINDOWS
446
  auto wstr = Clib::to_wstring( value_ );
9 ✔
447
  value_.clear();
9 ✔
448
  for ( const auto& c : wstr )
237 ✔
449
  {
450
    utf8::unchecked::append( std::towlower( c ), std::back_inserter( value_ ) );
228 ✔
451
  }
452
#else
453
  std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>> converter;
454
  std::wstring str = converter.from_bytes( value_ );
455

456
  int len = LCMapStringW( LOCALE_USER_DEFAULT, LCMAP_LOWERCASE | LCMAP_LINGUISTIC_CASING, &str[0],
457
                          static_cast<int>( str.size() ), 0, 0 );
458
  if ( !len )
459
    return;
460
  else if ( len == static_cast<int>( str.size() ) )
461
  {
462
    LCMapStringW( LOCALE_USER_DEFAULT, LCMAP_LOWERCASE | LCMAP_LINGUISTIC_CASING, &str[0],
463
                  static_cast<int>( str.size() ), &str[0], static_cast<int>( str.size() ) );
464
    value_ = converter.to_bytes( str );
465
  }
466
  else
467
  {
468
    std::wstring buf;
469
    buf.reserve( len );
470
    LCMapStringW( LOCALE_USER_DEFAULT, LCMAP_LOWERCASE | LCMAP_LINGUISTIC_CASING, &str[0],
471
                  static_cast<int>( str.size() ), &buf[0], static_cast<int>( buf.size() ) );
472
    value_ = converter.to_bytes( buf );
473
  }
474
#endif
475
}
9 ✔
476

477
size_t String::getBytePosition( std::string::const_iterator* itr, size_t codeindex ) const
90,513 ✔
478
{
479
  auto itr_end = value_.cend();
90,513 ✔
480
  for ( size_t i = 0; i < codeindex && *itr != itr_end; ++i )
197,656 ✔
481
    utf8::unchecked::next( *itr );
107,143 ✔
482

483
  if ( *itr != itr_end )
90,513 ✔
484
  {
485
    return std::distance( value_.cbegin(), *itr );
171,232 ✔
486
  }
487
  return std::string::npos;
4,897 ✔
488
}
489

490
BObjectImp* String::array_assign( BObjectImp* idx, BObjectImp* target, bool /*copy*/ )
18 ✔
491
{
492
  std::string::size_type pos, len;
493

494
  // first, determine position and length.
495
  if ( idx->isa( OTString ) )
18 ✔
496
  {
497
    String& rtstr = (String&)*idx;
15 ✔
498
    pos = value_.find( rtstr.value_ );
15 ✔
499
    len = rtstr.value_.size();
15 ✔
500
  }
501
  else if ( idx->isa( OTLong ) )
3 ✔
502
  {
503
    BLong& lng = (BLong&)*idx;
3 ✔
504
    len = 1;
3 ✔
505
    pos = lng.value() - 1;
3 ✔
506
    auto itr = value_.cbegin();
3 ✔
507
    pos = getBytePosition( &itr, pos );
3 ✔
508
    if ( pos != std::string::npos )
3 ✔
509
    {
510
      utf8::unchecked::next( itr );
3 ✔
511
      len = std::distance( value_.cbegin(), itr ) - pos;
6 ✔
512
    }
513
    else
514
      pos = std::string::npos;
×
515
  }
516
  else if ( idx->isa( OTDouble ) )
×
517
  {
518
    Double& dbl = (Double&)*idx;
×
519
    pos = static_cast<std::string::size_type>( dbl.value() ) - 1;
×
520
    len = 1;
×
521
    auto itr = value_.cbegin();
×
522
    pos = getBytePosition( &itr, pos );
×
523
    if ( pos != std::string::npos )
×
524
    {
525
      utf8::unchecked::next( itr );
×
526
      len = std::distance( value_.cbegin(), itr ) - pos;
×
527
    }
528
    else
529
      pos = std::string::npos;
×
530
  }
531
  else
532
  {
533
    return UninitObject::create();
×
534
  }
535

536
  if ( pos != std::string::npos )
18 ✔
537
  {
538
    if ( target->isa( OTString ) )
18 ✔
539
    {
540
      String* target_str = (String*)target;
18 ✔
541
      value_.replace( pos, len, target_str->value_ );
18 ✔
542
    }
543
    return this;
18 ✔
544
  }
545

546
  return UninitObject::create();
×
547
}
548

549
BObjectRef String::OperMultiSubscriptAssign( std::stack<BObjectRef>& indices, BObjectImp* target )
15 ✔
550
{
551
  BObjectRef start_ref = indices.top();
15 ✔
552
  indices.pop();
15 ✔
553
  BObjectRef length_ref = indices.top();
15 ✔
554
  indices.pop();
15 ✔
555

556
  BObject& length_obj = *length_ref;
15 ✔
557
  BObject& start_obj = *start_ref;
15 ✔
558

559
  BObjectImp& length = length_obj.impref();
15 ✔
560
  BObjectImp& start = start_obj.impref();
15 ✔
561

562
  // first deal with the start position.
563
  size_t index;
564
  if ( start.isa( OTLong ) )
15 ✔
565
  {
566
    BLong& lng = (BLong&)start;
15 ✔
567
    index = (size_t)lng.value();
15 ✔
568
    if ( index == 0 || index > value_.size() )
15 ✔
569
      return BObjectRef( new BError( "Subscript out of range" ) );
3 ✔
570
    --index;
12 ✔
571
    auto itr = value_.cbegin();
12 ✔
572
    index = getBytePosition( &itr, index );
12 ✔
573
    if ( index == std::string::npos )
12 ✔
574
      return BObjectRef( new BError( "Subscript out of range" ) );
×
575
  }
576
  else if ( start.isa( OTString ) )
×
577
  {
578
    String& rtstr = (String&)start;
×
579
    std::string::size_type pos = value_.find( rtstr.value_ );
×
580
    if ( pos != std::string::npos )
×
581
      index = static_cast<size_t>( pos );
×
582
    else
583
      return BObjectRef( new UninitObject );
×
584
  }
585
  else
586
  {
587
    return BObjectRef( copy() );
×
588
  }
589

590
  // now deal with the length.
591
  size_t len;
592
  if ( length.isa( OTLong ) )
12 ✔
593
  {
594
    BLong& lng = (BLong&)length;
12 ✔
595

596
    len = (size_t)lng.value();
12 ✔
597
  }
598
  else if ( length.isa( OTDouble ) )
×
599
  {
600
    Double& dbl = (Double&)length;
×
601

602
    len = (size_t)dbl.value();
×
603
  }
604
  else
605
  {
606
    return BObjectRef( copy() );
×
607
  }
608
  auto itr = value_.cbegin();
12 ✔
609
  std::advance( itr, index );
610
  size_t index_len = getBytePosition( &itr, len );
12 ✔
611

612
  if ( index_len != std::string::npos )
12 ✔
613
    len = index_len - index;
12 ✔
614
  else
615
    len = index_len;
×
616

617
  if ( target->isa( OTString ) )
12 ✔
618
  {
619
    String* target_str = (String*)target;
12 ✔
620
    value_.replace( index, len, target_str->value_ );
12 ✔
621
  }
622
  else
623
  {
624
    return BObjectRef( UninitObject::create() );
×
625
  }
626

627
  return BObjectRef( this );
12 ✔
628
}
15 ✔
629

630

631
BObjectRef String::OperMultiSubscript( std::stack<BObjectRef>& indices )
24,288 ✔
632
{
633
  BObjectRef start_ref = indices.top();
24,288 ✔
634
  indices.pop();
24,288 ✔
635

636
  // At least two indices always arrive: the compiler emits this instruction only for a
637
  // subscript with more than one index (InstructionGenerator::visit_element_access).
638
  BObjectRef length_ref = indices.top();
24,288 ✔
639
  indices.pop();
24,288 ✔
640

641
  BObject& length_obj = *length_ref;
24,288 ✔
642
  BObject& start_obj = *start_ref;
24,288 ✔
643

644
  BObjectImp& length = length_obj.impref();
24,288 ✔
645
  BObjectImp& start = start_obj.impref();
24,288 ✔
646

647
  // first deal with the start position.
648
  size_t index;
649
  if ( start.isa( OTLong ) )
24,288 ✔
650
  {
651
    BLong& lng = (BLong&)start;
24,279 ✔
652
    index = (size_t)lng.value();
24,279 ✔
653
    if ( index == 0 || index > value_.size() )
24,279 ✔
654
      return BObjectRef( new BError( "Subscript out of range" ) );
2,499 ✔
655
    --index;
21,780 ✔
656
    auto itr = value_.cbegin();
21,780 ✔
657
    index = getBytePosition( &itr, index );
21,780 ✔
658
    if ( index == std::string::npos )
21,780 ✔
659
      return BObjectRef( new BError( "Subscript out of range" ) );
×
660
  }
661
  else if ( start.isa( OTString ) )
9 ✔
662
  {
663
    String& rtstr = (String&)start;
9 ✔
664
    std::string::size_type pos = value_.find( rtstr.value_ );
9 ✔
665
    if ( pos != std::string::npos )
9 ✔
666
      index = static_cast<unsigned int>( pos );
9 ✔
667
    else
668
      return BObjectRef( new UninitObject );
×
669
  }
670
  else
671
  {
672
    return BObjectRef( copy() );
×
673
  }
674

675
  // now deal with the length.
676
  size_t len;
677
  if ( length.isa( OTLong ) )
21,789 ✔
678
  {
679
    BLong& lng = (BLong&)length;
21,786 ✔
680

681
    len = (size_t)lng.value();
21,786 ✔
682
  }
683
  else if ( length.isa( OTDouble ) )
3 ✔
684
  {
685
    Double& dbl = (Double&)length;
3 ✔
686

687
    len = (size_t)dbl.value();
3 ✔
688
  }
689
  else
690
  {
691
    return BObjectRef( copy() );
×
692
  }
693
  auto itr = value_.cbegin();
21,789 ✔
694
  std::advance( itr, index );
695
  size_t index_len = getBytePosition( &itr, len );
21,789 ✔
696

697
  if ( index_len != std::string::npos )
21,789 ✔
698
    len = index_len - index;
17,582 ✔
699
  else
700
    len = index_len;
4,207 ✔
701
  return BObjectRef( new String( value_, index, len ) );
21,789 ✔
702
}
24,288 ✔
703

704
BObjectRef String::OperSubscript( const BObject& rightobj )
23,100 ✔
705
{
706
  const BObjectImp& right = rightobj.impref();
23,100 ✔
707
  if ( right.isa( OTLong ) )
23,100 ✔
708
  {
709
    BLong& lng = (BLong&)right;
20,512 ✔
710

711
    if ( lng.value() < 0 )
20,512 ✔
712
      return BObjectRef( new BError( "Subscript out of range" ) );
3 ✔
713

714
    size_t index = (size_t)lng.value();
20,509 ✔
715

716
    if ( index == 0 || index > value_.size() )
20,509 ✔
717
      return BObjectRef( new BError( "Subscript out of range" ) );
12 ✔
718

719
    --index;
20,497 ✔
720
    auto itr = value_.cbegin();
20,497 ✔
721
    index = getBytePosition( &itr, index );
20,497 ✔
722
    if ( index != std::string::npos )
20,497 ✔
723
    {
724
      utf8::unchecked::next( itr );
20,497 ✔
725
      size_t len = std::distance( value_.cbegin(), itr ) - index;
20,497 ✔
726
      return BObjectRef( new BObject( new String( value_.c_str() + index, len ) ) );
20,497 ✔
727
    }
728
    return BObjectRef( new BError( "Subscript out of range" ) );
×
729
  }
730
  if ( right.isa( OTDouble ) )
2,588 ✔
731
  {
732
    Double& dbl = (Double&)right;
18 ✔
733

734
    if ( dbl.value() < 0 )
18 ✔
735
      return BObjectRef( new BError( "Subscript out of range" ) );
3 ✔
736
    size_t index = (size_t)dbl.value();
15 ✔
737

738
    if ( index == 0 || index > value_.size() )
15 ✔
739
      return BObjectRef( new BError( "Subscript out of range" ) );
6 ✔
740

741
    --index;
9 ✔
742
    auto itr = value_.cbegin();
9 ✔
743
    index = getBytePosition( &itr, index );
9 ✔
744
    if ( index != std::string::npos )
9 ✔
745
    {
746
      utf8::unchecked::next( itr );
9 ✔
747
      size_t len = std::distance( value_.cbegin(), itr ) - index;
9 ✔
748
      return BObjectRef( new BObject( new String( value_.c_str() + index, len ) ) );
9 ✔
749
    }
750
    return BObjectRef( new BError( "Subscript out of range" ) );
×
751
  }
752
  if ( right.isa( OTString ) )
2,570 ✔
753
  {
754
    String& rtstr = (String&)right;
2,549 ✔
755
    auto pos = value_.find( rtstr.value_ );
2,549 ✔
756
    if ( pos != std::string::npos )
2,549 ✔
757
    {
758
      auto itr = value_.cbegin();
239 ✔
759
      std::advance( itr, pos );
760
      utf8::unchecked::next( itr );
239 ✔
761
      size_t len = std::distance( value_.cbegin(), itr ) - pos;
239 ✔
762
      return BObjectRef( new BObject( new String( value_, pos, len ) ) );
239 ✔
763
    }
764
    return BObjectRef( new UninitObject );
2,310 ✔
765
  }
766

767
  return BObjectRef( new UninitObject );
21 ✔
768
}
769

770
// -- format related stuff --
771
bool s_parse_int( int& i, std::string const& s )
81 ✔
772
{
773
  if ( s.empty() )
81 ✔
774
    return false;
×
775

776
  char* end;
777
  i = strtol( s.c_str(), &end, 10 );
81 ✔
778

779
  if ( !*end )
81 ✔
780
  {
781
    return true;
66 ✔
782
  }
783

784
  return false;
15 ✔
785
}
786

787
BObjectImp* String::call_method( const char* methodname, Executor& ex )
6 ✔
788
{
789
  ObjMethod* objmethod = getKnownObjMethod( methodname );
6 ✔
790
  if ( objmethod != nullptr )
6 ✔
791
    return this->call_method_id( objmethod->id, ex );
×
792
  return nullptr;
6 ✔
793
}
794
BObjectImp* String::call_method_id( const int id, Executor& ex, bool /*forcebuiltin*/ )
47,219 ✔
795
{
796
  switch ( id )
47,219 ✔
797
  {
798
  case MTH_LENGTH:
22,174 ✔
799
    if ( ex.numParams() == 0 )
22,174 ✔
800
      return new BLong( static_cast<int>( length() ) );
22,171 ✔
801
    else
802
      return new BError( "string.length() doesn't take parameters." );
3 ✔
803
    break;
804
  case MTH_FIND:
21,131 ✔
805
  {
806
    if ( ex.numParams() > 2 )
21,131 ✔
807
      return new BError( "string.find(Search, [Start]) takes only two parameters" );
3 ✔
808
    if ( ex.numParams() < 1 )
21,128 ✔
809
      return new BError( "string.find(Search, [Start]) takes at least one parameter" );
3 ✔
810

811
    int d = 0;
21,125 ✔
812
    if ( ex.numParams() == 2 )
21,125 ✔
813
      d = ex.paramAsLong( 1 ) - 1;
113 ✔
814

815
    if ( auto s = impptrIf<String>( ex.getParamImp( 0 ) ) )
21,125 ✔
816
    {
817
      int posn = find( d, s->data() ) + 1;
21,038 ✔
818
      return new BLong( posn );
21,038 ✔
819
    }
820
    if ( auto regex = impptrIf<BRegExp>( ex.getParamImp( 0 ) ) )
87 ✔
821
    {
822
      return regex->find( this, d );
84 ✔
823
    }
824
    return new BError( "string.find(Search, [Start]): Search must be a string or regex" );
3 ✔
825
  }
826
  case MTH_MATCH:
63 ✔
827
  {
828
    if ( ex.numParams() != 1 )
63 ✔
829
      return new BError( "string.match(Pattern) takes exactly one parameter" );
6 ✔
830

831
    auto regex = impptrIf<BRegExp>( ex.getParamImp( 0 ) );
57 ✔
832

833
    if ( !regex )
57 ✔
834
      return new BError( "string.match(Pattern): Pattern must be a RegExp" );
6 ✔
835

836
    return regex->match( this );
51 ✔
837
  }
838
  case MTH_REPLACE:
75 ✔
839
  {
840
    if ( ex.numParams() != 2 )
75 ✔
841
      return new BError( "string.replace(Search, Replace) takes exactly two parameters" );
6 ✔
842

843
    auto regex = impptrIf<BRegExp>( ex.getParamImp( 0 ) );
69 ✔
844
    if ( !regex )
69 ✔
845
      return new BError( "string.replace(Search, Replace): Search must be a RegExp" );
6 ✔
846

847
    if ( auto s = impptrIf<String>( ex.getParamImp( 1 ) ) )
63 ✔
848
    {
849
      return regex->replace( this, s );
36 ✔
850
    }
851
    if ( auto funcref = impptrIf<BFunctionRef>( ex.getParamImp( 1 ) ) )
27 ✔
852
    {
853
      return regex->replace( ex, this, funcref );
21 ✔
854
    }
855

856
    return new BError( "string.replace(Search, Replace): Replace must be a string or function" );
6 ✔
857
  }
858
  case MTH_SPLIT:
66 ✔
859
  {
860
    size_t limit = std::numeric_limits<size_t>::max();
66 ✔
861

862
    if ( ex.numParams() == 0 )
66 ✔
863
      return new BError( "string.split(Separator[, Max_Split]) takes at least one parameter" );
3 ✔
864
    if ( ex.numParams() > 2 )
63 ✔
865
      return new BError( "string.split(Separator[, Max_Split]) takes at most two parameters" );
3 ✔
866

867
    if ( ex.numParams() == 2 )
60 ✔
868
    {
869
      int max_splits = ex.paramAsLong( 1 );
21 ✔
870
      if ( max_splits > -1 )
21 ✔
871
        limit = Clib::clamp_convert<size_t>( max_splits + 1 );
21 ✔
872
    }
873

874
    if ( auto regex = impptrIf<BRegExp>( ex.getParamImp( 0 ) ) )
60 ✔
875
    {
876
      return regex->split( this, limit );
30 ✔
877
    }
878
    if ( auto string_sep = impptrIf<String>( ex.getParamImp( 0 ) ) )
30 ✔
879
    {
880
      std::unique_ptr<ObjArray> result( new ObjArray );
27 ✔
881
      const auto& sep = string_sep->value_;
27 ✔
882

883
      // Empty separator splits into characters
884
      if ( sep.empty() )
27 ✔
885
      {
886
        return getCharacters( value_, limit );
12 ✔
887
      }
888

889
      // Has a non-empty separator, splits by `sep`
890
      std::size_t start = 0;
15 ✔
891
      while ( start < value_.size() )
60 ✔
892
      {
893
        // Limit reached, push rest and break
894
        if ( result->ref_arr.size() == limit - 1 )
57 ✔
895
        {
896
          result->addElement( new String( value_.substr( start ) ) );
9 ✔
897
          break;
9 ✔
898
        }
899

900
        std::size_t pos = value_.find( sep, start );
48 ✔
901
        if ( pos == std::string::npos )
48 ✔
902
        {
903
          // No more separators, push rest
904
          result->addElement( new String( value_.substr( start ) ) );
3 ✔
905
          break;
3 ✔
906
        }
907

908
        // Extract part
909
        result->addElement( new String( value_.substr( start, pos - start ) ) );
45 ✔
910
        start = pos + sep.size();
45 ✔
911
      }
912

913
      return result.release();
15 ✔
914
    }
27 ✔
915

916
    return new BError(
917
        "string.split(Separator[, Max_Split]): Separator must be a RegExp or string" );
3 ✔
918
  }
919

920
  case MTH_UPPER:
12 ✔
921
  {
922
    if ( ex.numParams() == 0 )
12 ✔
923
    {
924
      toUpper();
9 ✔
925
      return this;
9 ✔
926
    }
927
    return new BError( "string.upper() doesn't take parameters." );
3 ✔
928
  }
929

930
  case MTH_LOWER:
12 ✔
931
  {
932
    if ( ex.numParams() == 0 )
12 ✔
933
    {
934
      toLower();
9 ✔
935
      return this;
9 ✔
936
    }
937
    return new BError( "string.lower() doesn't take parameters." );
3 ✔
938
  }
939
  case MTH_FORMAT:
3,645 ✔
940
  {
941
    if ( ex.numParams() > 0 )
3,645 ✔
942
    {
943
      // string s = this->getStringRep(); // string itself
944
      std::stringstream result;
3,642 ✔
945

946
      size_t tag_start_pos;  // the position of tag's start "{"
947
      size_t tag_stop_pos;   // the position of tag's end "}"
948
      size_t tag_dot_pos;
949

950
      int tag_param_idx;
951

952
      size_t str_pos = 0;               // current string position
3,642 ✔
953
      unsigned int next_param_idx = 0;  // next index of .format() parameter
3,642 ✔
954

955
      char w_spaces[] = "\t ";
3,642 ✔
956

957
      // Tells whether last found tag was an integer
958
      bool last_tag_was_int = true;
3,642 ✔
959

960
      while ( ( tag_start_pos = value_.find( '{', str_pos ) ) != std::string::npos )
7,549 ✔
961
      {
962
        if ( ( tag_stop_pos = value_.find( '}', tag_start_pos ) ) != std::string::npos )
3,910 ✔
963
        {
964
          result << value_.substr( str_pos, tag_start_pos - str_pos );
3,907 ✔
965
          str_pos = tag_stop_pos + 1;
3,907 ✔
966

967
          std::string tag_body =
968
              value_.substr( tag_start_pos + 1, ( tag_stop_pos - tag_start_pos ) - 1 );
3,907 ✔
969

970
          tag_start_pos = tag_body.find_first_not_of( w_spaces );
3,907 ✔
971
          tag_stop_pos = tag_body.find_last_not_of( w_spaces );
3,907 ✔
972

973
          // cout << "' tag_body1: '" << tag_body << "'";
974

975
          // trim the tag of whitespaces (slightly faster code ~25%)
976
          if ( tag_start_pos != std::string::npos && tag_stop_pos != std::string::npos )
3,907 ✔
977
            tag_body = tag_body.substr( tag_start_pos, ( tag_stop_pos - tag_start_pos ) + 1 );
93 ✔
978
          else if ( tag_start_pos != std::string::npos )
3,814 ✔
979
            tag_body = tag_body.substr( tag_start_pos );
×
980
          else if ( tag_stop_pos != std::string::npos )
3,814 ✔
981
            tag_body = tag_body.substr( 0, tag_stop_pos + 1 );
×
982

983
          // cout << "' tag_body2: '" << tag_body << "'";
984

985
          std::string frmt;
3,907 ✔
986
          size_t formatter_pos = tag_body.find( ':' );
3,907 ✔
987

988
          if ( formatter_pos != std::string::npos )
3,907 ✔
989
          {
990
            frmt = tag_body.substr( formatter_pos + 1, std::string::npos );  //
39 ✔
991
            tag_body = tag_body.substr( 0, formatter_pos );  // remove property from the tag
39 ✔
992
          }
993

994
          std::string prop_name;
3,907 ✔
995
          // parsing {1.this_part}
996
          tag_dot_pos = tag_body.find( '.', 0 );
3,907 ✔
997

998
          // '.' is found within the tag, there is a property name
999
          if ( tag_dot_pos != std::string::npos )
3,907 ✔
1000
          {
1001
            last_tag_was_int = true;
21 ✔
1002
            prop_name = tag_body.substr( tag_dot_pos + 1, std::string::npos );  //
21 ✔
1003
            tag_body = tag_body.substr( 0, tag_dot_pos );  // remove property from the tag
21 ✔
1004

1005
            // if s_tag_body is numeric then use it as an index
1006
            if ( s_parse_int( tag_param_idx, tag_body ) )
21 ✔
1007
            {
1008
              tag_param_idx -= 1;  // sinse POL indexes are 1-based
18 ✔
1009
            }
1010
            else
1011
            {
1012
              result << "<idx required before: '" << prop_name << "'>";
3 ✔
1013
              continue;
3 ✔
1014
            }
1015
          }
1016
          else
1017
          {
1018
            if ( tag_body.empty() )
3,886 ✔
1019
            {
1020
              // empty body just takes next integer idx
1021
              last_tag_was_int = true;
3,826 ✔
1022
              tag_param_idx = next_param_idx++;
3,826 ✔
1023
            }
1024
            else if ( s_parse_int( tag_param_idx, tag_body ) )
60 ✔
1025
            {
1026
              last_tag_was_int = true;
48 ✔
1027
              tag_param_idx -= 1;  // sinse POL indexes are 1-based
48 ✔
1028
            }
1029
            else
1030
            {
1031
              // string body takes next idx in line if this is
1032
              // the first string body occurrence,
1033
              // will reuse last idx if this is 2nd or more in a row
1034
              last_tag_was_int = false;
12 ✔
1035
              prop_name = tag_body;
12 ✔
1036
              tag_param_idx = last_tag_was_int ? next_param_idx++ : next_param_idx;
12 ✔
1037
            }
1038
          }
1039

1040
          // -- end of property parsing
1041

1042
          // cout << "prop_name: '" << prop_name << "' tag_body: '" << tag_body << "'";
1043
          if ( tag_param_idx < 0 || (int)ex.numParams() <= tag_param_idx )
3,904 ✔
1044
          {
1045
            result << "<invalid index: #" << ( tag_param_idx + 1 ) << ">";
12 ✔
1046
            continue;
12 ✔
1047
          }
1048

1049
          BObjectImp* imp = ex.getParamImp( tag_param_idx );
3,892 ✔
1050

1051
          if ( !prop_name.empty() )
3,892 ✔
1052
          {  // accesing object
1053
            BObjectRef obj_member = imp->get_member( prop_name.c_str() );
30 ✔
1054
            BObjectImp* member_imp = obj_member->impptr();
30 ✔
1055
            try_to_format( result, member_imp, frmt );
30 ✔
1056
          }
30 ✔
1057
          else
1058
          {
1059
            try_to_format( result, imp, frmt );
3,862 ✔
1060
          }
1061
        }
3,937 ✔
1062
        else
1063
        {
1064
          break;
3 ✔
1065
        }
1066
      }
1067

1068
      if ( str_pos < value_.length() )
3,642 ✔
1069
      {
1070
        result << value_.substr( str_pos, std::string::npos );
3,315 ✔
1071
      }
1072

1073
      return new String( result.str() );
3,642 ✔
1074
    }
3,642 ✔
1075

1076
    return new BError( "string.format() requires a parameter." );
3 ✔
1077
  }
1078
  case MTH_JOIN:
35 ✔
1079
  {
1080
    BObject* cont;
1081
    if ( ex.numParams() == 1 && ( cont = ex.getParamObj( 0 ) ) != nullptr )
35 ✔
1082
    {
1083
      if ( !( cont->isa( OTArray ) ) )
32 ✔
1084
        return new BError( "string.join expects an array" );
3 ✔
1085
      ObjArray* container = cont->impptr<ObjArray>();
29 ✔
1086
      // no empty check here on purpose
1087
      std::string joined;
29 ✔
1088
      bool first = true;
29 ✔
1089
      for ( const BObjectRef& ref : container->ref_arr )
203 ✔
1090
      {
1091
        if ( ref.get() )
174 ✔
1092
        {
1093
          BObject* bo = ref.get();
156 ✔
1094

1095
          if ( bo == nullptr )
156 ✔
1096
            continue;
×
1097
          if ( !first )
156 ✔
1098
            joined += value_;
130 ✔
1099
          else
1100
            first = false;
26 ✔
1101
          joined += bo->impptr()->getStringRep();
156 ✔
1102
        }
1103
      }
1104
      return new String( joined );
29 ✔
1105
    }
29 ✔
1106
    return new BError( "string.join(array) requires a parameter." );
3 ✔
1107
  }
1108
  default:
6 ✔
1109
    return nullptr;
6 ✔
1110
  }
1111
}
1112

1113
ObjArray* String::getCharacters( const std::string& source, size_t limit )
27 ✔
1114
{
1115
  std::unique_ptr<ObjArray> result( new ObjArray );
27 ✔
1116

1117
  for ( auto rit = source.cbegin(); rit != source.cend(); )
150 ✔
1118
  {
1119
    // Limit reached, push rest and break
1120
    if ( result->ref_arr.size() == limit - 1 )
138 ✔
1121
    {
1122
      result->addElement( new String( std::string( rit, source.cend() ) ) );
30 ✔
1123
      break;
15 ✔
1124
    }
1125

1126
    auto previous = rit;
123 ✔
1127
    utf8::unchecked::next( rit );
123 ✔
1128

1129
    result->addElement( new String( std::string( previous, rit ) ) );
369 ✔
1130
  }
1131

1132
  return result.release();
54 ✔
1133
}
27 ✔
1134

1135
bool String::hasUTF8Characters() const
124 ✔
1136
{
1137
  return hasUTF8Characters( value_ );
124 ✔
1138
}
1139

1140
bool String::hasUTF8Characters( const std::string& str )
142 ✔
1141
{
1142
  for ( const auto& c : str )
1,753 ✔
1143
  {
1144
    if ( c & 0x80 )
1,635 ✔
1145
      return true;
24 ✔
1146
  }
1147
  return false;
118 ✔
1148
}
1149

1150
std::vector<unsigned short> String::toUTF16() const
73 ✔
1151
{
1152
  std::vector<unsigned short> u16;
73 ✔
1153
  utf8::unchecked::utf8to16( value_.begin(), value_.end(), std::back_inserter( u16 ) );
73 ✔
1154
  return u16;
73 ✔
1155
}
×
1156

1157
std::string String::fromUTF16( unsigned short code )
623 ✔
1158
{
1159
  std::string s;
623 ✔
1160
  std::vector<unsigned short> utf16( 1, code );
623 ✔
1161
  utf8::unchecked::utf16to8( utf16.begin(), utf16.end(), std::back_inserter( s ) );
623 ✔
1162
  Clib::sanitizeUnicode( &s );
623 ✔
1163
  return s;
623 ✔
1164
}
623 ✔
1165

1166
std::string String::fromUTF16( const unsigned short* code, size_t len, bool big_endian )
111 ✔
1167
{
1168
  std::string s;
111 ✔
1169
  // minimum incomplete iterator implementation, just for the internal usage with utf8lib.
1170
  // `code` usually points into a packet buffer and need not be 2-byte aligned.
1171
  struct UnitIterator
1172
  {
1173
    const u16* ptr;
1174
    bool big_endian;
1175
    UnitIterator& operator++()
1,879 ✔
1176
    {
1177
      ++ptr;
1,879 ✔
1178
      return *this;
1,879 ✔
1179
    };
1180
    UnitIterator operator++( int )
1,879 ✔
1181
    {
1182
      UnitIterator itr = *this;
1,879 ✔
1183
      ++ptr;
1,879 ✔
1184
      return itr;
1,879 ✔
1185
    };
1186
    u16 operator*() const
3,758 ✔
1187
    {
1188
      u16 c = Clib::load_unaligned<u16>( ptr );
3,758 ✔
1189
      return big_endian ? cfBEu16( c ) : c;
3,758 ✔
1190
    };
1191
    bool operator!=( const UnitIterator& o ) const { return ptr != o.ptr; };
1,990 ✔
NEW
1192
    bool operator==( const UnitIterator& o ) const { return ptr == o.ptr; };
×
1193
  };
1194
  UnitIterator begin{ code, big_endian };
111 ✔
1195
  UnitIterator end = begin;
111 ✔
1196
  // convert until the first null terminator
1197
  while ( end.ptr != code + len && *end != 0 )
1,990 ✔
1198
    ++end;
1,879 ✔
1199
  utf8::unchecked::utf16to8( begin, end, std::back_inserter( s ) );
111 ✔
1200
  Clib::sanitizeUnicode( &s );
111 ✔
1201
  return s;
111 ✔
1202
}
×
1203

1204
std::string String::fromUTF8( const char* code, size_t len )
3 ✔
1205
{
1206
  size_t short_len = 0;
3 ✔
1207
  // convert until the first null terminator
1208
  while ( code[short_len] != 0 && short_len < len )
23 ✔
1209
    ++short_len;
20 ✔
1210
  std::string s( code, short_len );
3 ✔
1211
  Clib::sanitizeUnicode( &s );
3 ✔
1212
  return s;
3 ✔
1213
}
×
1214

1215
std::vector<unsigned short> String::toUTF16( const std::string& text )
437 ✔
1216
{
1217
  std::vector<unsigned short> utf16;
437 ✔
1218
  if ( text.empty() )
437 ✔
1219
    return utf16;
197 ✔
1220
  utf8::unchecked::utf8to16( text.begin(), text.end(), std::back_inserter( utf16 ) );
240 ✔
1221
  return utf16;
240 ✔
1222
}
×
1223

1224
bool String::compare( const String& str ) const
9 ✔
1225
{
1226
  return value_.compare( str.value_ ) == 0;
9 ✔
1227
}
1228

1229
bool String::compare( size_t pos1, size_t len1, const String& str ) const
6 ✔
1230
{
1231
  auto itr1 = value_.cbegin();
6 ✔
1232
  pos1 = getBytePosition( &itr1, pos1 );
6 ✔
1233
  len1 = getBytePosition( &itr1, len1 ) - pos1;
6 ✔
1234
  return value_.compare( pos1, len1, str.value_ ) == 0;
12 ✔
1235
}
1236

1237
bool String::compare( size_t pos1, size_t len1, const String& str, size_t pos2, size_t len2 ) const
12 ✔
1238
{
1239
  auto itr1 = value_.cbegin();
12 ✔
1240
  pos1 = getBytePosition( &itr1, pos1 );
12 ✔
1241
  len1 = getBytePosition( &itr1, len1 ) - pos1;
12 ✔
1242
  auto itr2 = str.value_.cbegin();
12 ✔
1243
  pos2 = str.getBytePosition( &itr2, pos2 );
12 ✔
1244
  len2 = str.getBytePosition( &itr2, len2 ) - pos2;
12 ✔
1245
  return value_.compare( pos1, len1, str.value_, pos2, len2 ) == 0;
24 ✔
1246
}
1247

1248
String* String::fromUCArray( ObjArray* array, bool break_first_null )
10 ✔
1249
{
1250
  std::string res;
10 ✔
1251
  std::vector<u16> utf16;
10 ✔
1252
  for ( const auto& c : array->ref_arr )
42 ✔
1253
  {
1254
    if ( !c )
32 ✔
1255
      continue;
3 ✔
1256
    if ( auto* blong = c.get()->impptr_if<BLong>() )
29 ✔
1257
    {
1258
      if ( blong->value() == 0 && break_first_null )
29 ✔
1259
        break;
×
1260
      utf16.push_back( blong->value() & 0xFFFF );
29 ✔
1261
    }
1262
  }
1263
  if ( !utf16.empty() )
10 ✔
1264
    utf8::unchecked::utf16to8( utf16.begin(), utf16.end(), std::back_inserter( res ) );
10 ✔
1265

1266
  Clib::sanitizeUnicode( &res );
10 ✔
1267
  return new String( res );
20 ✔
1268
}
10 ✔
1269

1270
class StringIterator final : public ContIterator
1271
{
1272
public:
1273
  StringIterator( String* str, BObject* pIter );
1274
  BObject* step() override;
1275

1276
private:
1277
  // Keep String alive, to ensure iterators stay valid
1278
  BObject m_StringObj;
1279
  BObjectRef m_IterVal;
1280
  BLong* m_pIterVal;
1281
  std::string::const_iterator itr;
1282
  std::string::const_iterator end;
1283
};
1284

1285
StringIterator::StringIterator( String* pString, BObject* pIterVal )
54 ✔
1286
    : ContIterator(),
1287
      m_StringObj( pString ),
54 ✔
1288
      m_IterVal( pIterVal ),
54 ✔
1289
      m_pIterVal( new BLong( 0 ) ),
54 ✔
1290
      itr( pString->value().cbegin() ),
54 ✔
1291
      end( pString->value().cend() )
108 ✔
1292
{
1293
}
54 ✔
1294

1295
BObject* StringIterator::step()
324 ✔
1296
{
1297
  if ( itr == end )
324 ✔
1298
    return nullptr;
57 ✔
1299

1300
  // Iterate one utf8 character
1301
  auto previous = itr;
267 ✔
1302
  utf8::unchecked::next( itr );
267 ✔
1303
  m_pIterVal->increment();
267 ✔
1304
  m_IterVal->setimp( m_pIterVal );
267 ✔
1305

1306
  // Set current value to new substring
1307
  return new BObject( new String( std::string( previous, itr ) ) );
801 ✔
1308
}
1309

1310
ContIterator* String::createIterator( BObject* pIterVal )
54 ✔
1311
{
1312
  return new StringIterator( this, pIterVal );
54 ✔
1313
}
1314
}  // namespace Pol::Bscript
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc