Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(476)

Side by Side Diff: base/json/json_reader.cc

Issue 9801007: Improve JSONReader performance by up to 55% by using std::string instead of wstring. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: Use HexStringToInt Created 8 years, 9 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
« no previous file with comments | « base/json/json_reader.h ('k') | base/json/json_reader_unittest.cc » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 // Copyright (c) 2011 The Chromium Authors. All rights reserved. 1 // Copyright (c) 2011 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be 2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file. 3 // found in the LICENSE file.
4 4
5 #include "base/json/json_reader.h" 5 #include "base/json/json_reader.h"
6 6
7 #include "base/float_util.h" 7 #include "base/float_util.h"
8 #include "base/logging.h" 8 #include "base/logging.h"
9 #include "base/memory/scoped_ptr.h" 9 #include "base/memory/scoped_ptr.h"
10 #include "base/stringprintf.h" 10 #include "base/stringprintf.h"
11 #include "base/string_number_conversions.h" 11 #include "base/string_number_conversions.h"
12 #include "base/string_piece.h"
12 #include "base/string_util.h" 13 #include "base/string_util.h"
14 #include "base/third_party/icu/icu_utf.h"
13 #include "base/utf_string_conversions.h" 15 #include "base/utf_string_conversions.h"
14 #include "base/values.h" 16 #include "base/values.h"
15 17
16 namespace { 18 namespace {
17 19
18 const wchar_t kNullString[] = L"null"; 20 const char kNullString[] = "null";
19 const wchar_t kTrueString[] = L"true"; 21 const char kTrueString[] = "true";
20 const wchar_t kFalseString[] = L"false"; 22 const char kFalseString[] = "false";
21 23
22 const int kStackLimit = 100; 24 const int kStackLimit = 100;
23 25
24 // A helper method for ParseNumberToken. It reads an int from the end of 26 // A helper method for ParseNumberToken. It reads an int from the end of
25 // token. The method returns false if there is no valid integer at the end of 27 // token. The method returns false if there is no valid integer at the end of
26 // the token. 28 // the token.
27 bool ReadInt(base::JSONReader::Token& token, bool can_have_leading_zeros) { 29 bool ReadInt(base::JSONReader::Token& token, bool can_have_leading_zeros) {
28 wchar_t first = token.NextChar(); 30 char first = token.NextChar();
29 int len = 0; 31 int len = 0;
30 32
31 // Read in more digits. 33 // Read in more digits.
32 wchar_t c = first; 34 char c = first;
33 while ('\0' != c && IsAsciiDigit(c)) { 35 while ('\0' != c && IsAsciiDigit(c)) {
34 ++token.length; 36 ++token.length;
35 ++len; 37 ++len;
36 c = token.NextChar(); 38 c = token.NextChar();
37 } 39 }
38 // We need at least 1 digit. 40 // We need at least 1 digit.
39 if (len == 0) 41 if (len == 0)
40 return false; 42 return false;
41 43
42 if (!can_have_leading_zeros && len > 1 && '0' == first) 44 if (!can_have_leading_zeros && len > 1 && '0' == first)
43 return false; 45 return false;
44 46
45 return true; 47 return true;
46 } 48 }
47 49
48 // A helper method for ParseStringToken. It reads |digits| hex digits from the 50 // A helper method for ParseStringToken. It reads |digits| hex digits from the
49 // token. If the sequence if digits is not valid (contains other characters), 51 // token. If the sequence if digits is not valid (contains other characters),
50 // the method returns false. 52 // the method returns false.
51 bool ReadHexDigits(base::JSONReader::Token& token, int digits) { 53 bool ReadHexDigits(base::JSONReader::Token& token, int digits) {
52 for (int i = 1; i <= digits; ++i) { 54 for (int i = 1; i <= digits; ++i) {
53 wchar_t c = *(token.begin + token.length + i); 55 char c = *(token.begin + token.length + i);
54 if (c == '\0' || !IsHexDigit(c)) 56 if (c == '\0' || !IsHexDigit(c))
55 return false; 57 return false;
56 } 58 }
57 59
58 token.length += digits; 60 token.length += digits;
59 return true; 61 return true;
60 } 62 }
61 63
62 } // namespace 64 } // namespace
63 65
(...skipping 12 matching lines...) Expand all
76 const char* JSONReader::kUnexpectedDataAfterRoot = 78 const char* JSONReader::kUnexpectedDataAfterRoot =
77 "Unexpected data after root element."; 79 "Unexpected data after root element.";
78 const char* JSONReader::kUnsupportedEncoding = 80 const char* JSONReader::kUnsupportedEncoding =
79 "Unsupported encoding. JSON must be UTF-8."; 81 "Unsupported encoding. JSON must be UTF-8.";
80 const char* JSONReader::kUnquotedDictionaryKey = 82 const char* JSONReader::kUnquotedDictionaryKey =
81 "Dictionary keys must be quoted."; 83 "Dictionary keys must be quoted.";
82 84
83 JSONReader::JSONReader() 85 JSONReader::JSONReader()
84 : start_pos_(NULL), 86 : start_pos_(NULL),
85 json_pos_(NULL), 87 json_pos_(NULL),
88 end_pos_(NULL),
86 stack_depth_(0), 89 stack_depth_(0),
87 allow_trailing_comma_(false), 90 allow_trailing_comma_(false),
88 error_code_(JSON_NO_ERROR), 91 error_code_(JSON_NO_ERROR),
89 error_line_(0), 92 error_line_(0),
90 error_col_(0) {} 93 error_col_(0) {}
91 94
92 // static 95 // static
93 Value* JSONReader::Read(const std::string& json, 96 Value* JSONReader::Read(const std::string& json,
94 bool allow_trailing_comma) { 97 bool allow_trailing_comma) {
95 return ReadAndReturnError(json, allow_trailing_comma, NULL, NULL); 98 return ReadAndReturnError(json, allow_trailing_comma, NULL, NULL);
(...skipping 45 matching lines...) Expand 10 before | Expand all | Expand 10 after
141 } 144 }
142 145
143 std::string JSONReader::GetErrorMessage() const { 146 std::string JSONReader::GetErrorMessage() const {
144 return FormatErrorMessage(error_line_, error_col_, 147 return FormatErrorMessage(error_line_, error_col_,
145 ErrorCodeToString(error_code_)); 148 ErrorCodeToString(error_code_));
146 } 149 }
147 150
148 Value* JSONReader::JsonToValue(const std::string& json, bool check_root, 151 Value* JSONReader::JsonToValue(const std::string& json, bool check_root,
149 bool allow_trailing_comma) { 152 bool allow_trailing_comma) {
150 // The input must be in UTF-8. 153 // The input must be in UTF-8.
151 if (!IsStringUTF8(json.c_str())) { 154 if (!IsStringUTF8(json.data())) {
152 error_code_ = JSON_UNSUPPORTED_ENCODING; 155 error_code_ = JSON_UNSUPPORTED_ENCODING;
153 return NULL; 156 return NULL;
154 } 157 }
155 158
156 // The conversion from UTF8 to wstring removes null bytes for us 159 start_pos_ = json.data();
157 // (a good thing). 160 end_pos_ = start_pos_ + json.size();
158 std::wstring json_wide(UTF8ToWide(json));
159 start_pos_ = json_wide.c_str();
160 161
161 // When the input JSON string starts with a UTF-8 Byte-Order-Mark 162 // When the input JSON string starts with a UTF-8 Byte-Order-Mark (U+FEFF)
162 // (0xEF, 0xBB, 0xBF), the UTF8ToWide() function converts it to a Unicode 163 // or <0xEF 0xBB 0xBF>, advance the start position to avoid the
163 // BOM (U+FEFF). To avoid the JSONReader::BuildValue() function from 164 // JSONReader::BuildValue() function from mis-treating a Unicode BOM as an
164 // mis-treating a Unicode BOM as an invalid character and returning NULL, 165 // invalid character and returning NULL.
165 // skip a converted Unicode BOM if it exists. 166 if (json.size() >= 3 && start_pos_[0] == 0xEF &&
166 if (!json_wide.empty() && start_pos_[0] == 0xFEFF) { 167 start_pos_[1] == 0xBB && start_pos_[2] == 0xBF) {
167 ++start_pos_; 168 start_pos_ += 3;
168 } 169 }
169 170
170 json_pos_ = start_pos_; 171 json_pos_ = start_pos_;
171 allow_trailing_comma_ = allow_trailing_comma; 172 allow_trailing_comma_ = allow_trailing_comma;
172 stack_depth_ = 0; 173 stack_depth_ = 0;
173 error_code_ = JSON_NO_ERROR; 174 error_code_ = JSON_NO_ERROR;
174 175
175 scoped_ptr<Value> root(BuildValue(check_root)); 176 scoped_ptr<Value> root(BuildValue(check_root));
176 if (root.get()) { 177 if (root.get()) {
177 if (ParseToken().type == Token::END_OF_INPUT) { 178 if (ParseToken().type == Token::END_OF_INPUT) {
(...skipping 171 matching lines...) Expand 10 before | Expand all | Expand 10 after
349 json_pos_ += token.length; 350 json_pos_ += token.length;
350 351
351 --stack_depth_; 352 --stack_depth_;
352 return node.release(); 353 return node.release();
353 } 354 }
354 355
355 JSONReader::Token JSONReader::ParseNumberToken() { 356 JSONReader::Token JSONReader::ParseNumberToken() {
356 // We just grab the number here. We validate the size in DecodeNumber. 357 // We just grab the number here. We validate the size in DecodeNumber.
357 // According to RFC4627, a valid number is: [minus] int [frac] [exp] 358 // According to RFC4627, a valid number is: [minus] int [frac] [exp]
358 Token token(Token::NUMBER, json_pos_, 0); 359 Token token(Token::NUMBER, json_pos_, 0);
359 wchar_t c = *json_pos_; 360 char c = *json_pos_;
360 if ('-' == c) { 361 if ('-' == c) {
361 ++token.length; 362 ++token.length;
362 c = token.NextChar(); 363 c = token.NextChar();
363 } 364 }
364 365
365 if (!ReadInt(token, false)) 366 if (!ReadInt(token, false))
366 return Token::CreateInvalidToken(); 367 return Token::CreateInvalidToken();
367 368
368 // Optional fraction part 369 // Optional fraction part
369 c = token.NextChar(); 370 c = token.NextChar();
(...skipping 13 matching lines...) Expand all
383 c = token.NextChar(); 384 c = token.NextChar();
384 } 385 }
385 if (!ReadInt(token, true)) 386 if (!ReadInt(token, true))
386 return Token::CreateInvalidToken(); 387 return Token::CreateInvalidToken();
387 } 388 }
388 389
389 return token; 390 return token;
390 } 391 }
391 392
392 Value* JSONReader::DecodeNumber(const Token& token) { 393 Value* JSONReader::DecodeNumber(const Token& token) {
393 const std::wstring num_string(token.begin, token.length); 394 const std::string num_string(token.begin, token.length);
394 395
395 int num_int; 396 int num_int;
396 if (StringToInt(WideToUTF8(num_string), &num_int)) 397 if (StringToInt(num_string, &num_int))
397 return Value::CreateIntegerValue(num_int); 398 return Value::CreateIntegerValue(num_int);
398 399
399 double num_double; 400 double num_double;
400 if (StringToDouble(WideToUTF8(num_string), &num_double) && 401 if (StringToDouble(num_string, &num_double) && base::IsFinite(num_double))
401 base::IsFinite(num_double))
402 return Value::CreateDoubleValue(num_double); 402 return Value::CreateDoubleValue(num_double);
403 403
404 return NULL; 404 return NULL;
405 } 405 }
406 406
407 JSONReader::Token JSONReader::ParseStringToken() { 407 JSONReader::Token JSONReader::ParseStringToken() {
408 Token token(Token::STRING, json_pos_, 1); 408 Token token(Token::STRING, json_pos_, 1);
409 wchar_t c = token.NextChar(); 409 char c = token.NextChar();
410 while ('\0' != c) { 410 while (json_pos_ + token.length < end_pos_) {
411 if ('\\' == c) { 411 if ('\\' == c) {
412 ++token.length; 412 ++token.length;
413 c = token.NextChar(); 413 c = token.NextChar();
414 // Make sure the escaped char is valid. 414 // Make sure the escaped char is valid.
415 switch (c) { 415 switch (c) {
416 case 'x': 416 case 'x':
417 if (!ReadHexDigits(token, 2)) { 417 if (!ReadHexDigits(token, 2)) {
418 SetErrorCode(JSON_INVALID_ESCAPE, json_pos_ + token.length); 418 SetErrorCode(JSON_INVALID_ESCAPE, json_pos_ + token.length);
419 return Token::CreateInvalidToken(); 419 return Token::CreateInvalidToken();
420 } 420 }
(...skipping 22 matching lines...) Expand all
443 ++token.length; 443 ++token.length;
444 return token; 444 return token;
445 } 445 }
446 ++token.length; 446 ++token.length;
447 c = token.NextChar(); 447 c = token.NextChar();
448 } 448 }
449 return Token::CreateInvalidToken(); 449 return Token::CreateInvalidToken();
450 } 450 }
451 451
452 Value* JSONReader::DecodeString(const Token& token) { 452 Value* JSONReader::DecodeString(const Token& token) {
453 std::wstring decoded_str; 453 std::string decoded_str;
454 decoded_str.reserve(token.length - 2); 454 decoded_str.reserve(token.length - 2);
455 455
456 for (int i = 1; i < token.length - 1; ++i) { 456 for (int i = 1; i < token.length - 1; ++i) {
457 wchar_t c = *(token.begin + i); 457 char c = *(token.begin + i);
458 if ('\\' == c) { 458 if ('\\' == c) {
459 ++i; 459 ++i;
460 c = *(token.begin + i); 460 c = *(token.begin + i);
461 switch (c) { 461 switch (c) {
462 case '"': 462 case '"':
463 case '/': 463 case '/':
464 case '\\': 464 case '\\':
465 decoded_str.push_back(c); 465 decoded_str.push_back(c);
466 break; 466 break;
467 case 'b': 467 case 'b':
468 decoded_str.push_back('\b'); 468 decoded_str.push_back('\b');
469 break; 469 break;
470 case 'f': 470 case 'f':
471 decoded_str.push_back('\f'); 471 decoded_str.push_back('\f');
472 break; 472 break;
473 case 'n': 473 case 'n':
474 decoded_str.push_back('\n'); 474 decoded_str.push_back('\n');
475 break; 475 break;
476 case 'r': 476 case 'r':
477 decoded_str.push_back('\r'); 477 decoded_str.push_back('\r');
478 break; 478 break;
479 case 't': 479 case 't':
480 decoded_str.push_back('\t'); 480 decoded_str.push_back('\t');
481 break; 481 break;
482 case 'v': 482 case 'v':
483 decoded_str.push_back('\v'); 483 decoded_str.push_back('\v');
484 break; 484 break;
485 485
486 case 'x': 486 case 'x':
487 decoded_str.push_back((HexDigitToInt(*(token.begin + i + 1)) << 4) + 487 decoded_str.push_back((HexDigitToInt(*(token.begin + i + 1)) << 4) +
Mark Mentovai 2012/03/22 21:06:20 HexStringToInt here too?
488 HexDigitToInt(*(token.begin + i + 2))); 488 HexDigitToInt(*(token.begin + i + 2)));
Mark Mentovai 2012/03/22 21:06:20 All of this +1, +2, +3 stuff seems kind of unsafe.
brettw 2012/03/23 16:29:24 Let's do Mark's suggestion. It's minor compared to
Robert Sesek 2012/03/23 18:40:27 Fine, done. If it was wrong for 18 releases, I do
489 i += 2; 489 i += 2;
490 break; 490 break;
491 case 'u': 491 case 'u':
492 decoded_str.push_back((HexDigitToInt(*(token.begin + i + 1)) << 12 ) + 492 if (!ConvertUTF16Units(token, &i, &decoded_str))
493 (HexDigitToInt(*(token.begin + i + 2)) << 8) + 493 return NULL;
494 (HexDigitToInt(*(token.begin + i + 3)) << 4) +
495 HexDigitToInt(*(token.begin + i + 4)));
496 i += 4;
497 break; 494 break;
498 495
499 default: 496 default:
500 // We should only have valid strings at this point. If not, 497 // We should only have valid strings at this point. If not,
501 // ParseStringToken didn't do it's job. 498 // ParseStringToken didn't do it's job.
502 NOTREACHED(); 499 NOTREACHED();
503 return NULL; 500 return NULL;
504 } 501 }
505 } else { 502 } else {
506 // Not escaped 503 // Not escaped
507 decoded_str.push_back(c); 504 decoded_str.push_back(c);
508 } 505 }
509 } 506 }
510 return Value::CreateStringValue(WideToUTF16Hack(decoded_str)); 507 return Value::CreateStringValue(decoded_str);
508 }
509
510 bool JSONReader::ConvertUTF16Units(const Token& token,
511 int* i,
512 std::string* dest_string) {
513 if (*i + 4 >= token.length)
514 return false;
515
516 // This is a 32-bit field because the shift operations in the
517 // conversion process below cause MSVC to error about "data loss."
518 // This only stores UTF-16 code units, though.
519 // Consume the UTF-16 code unit, which may be a high surrogate.
520 int code_unit16_high = 0;
521 if (!HexStringToInt(StringPiece(token.begin + *i + 1, 4), &code_unit16_high))
522 return false;
523 *i += 4;
524
525 // If this is a high surrogate, consume the next code unit to get the
526 // low surrogate.
527 int code_unit16_low = 0;
528 if (CBU16_IS_SURROGATE(code_unit16_high)) {
529 // Make sure this is the high surrogate. If not, it's an encoding
530 // error.
531 if (!CBU16_IS_SURROGATE_LEAD(code_unit16_high))
532 return false;
533
534 // Make sure that the token has more characters to consume the
535 // lower surrogate.
536 if (*i + 6 >= token.length)
537 return false;
538 if (*(++(*i) + token.begin) != '\\' || *(++(*i) + token.begin) != 'u')
539 return false;
540
541 if (!HexStringToInt(StringPiece(token.begin + *i + 1, 4), &code_unit16_low))
542 return false;
543 *i += 4;
544 if (!CBU16_IS_SURROGATE(code_unit16_low) ||
545 !CBU16_IS_TRAIL(code_unit16_low)) {
546 return false;
547 }
548 } else if (!CBU16_IS_SINGLE(code_unit16_high)) {
549 // If this is not a code point, it's an encoding error.
550 return false;
551 }
552
553 // Convert the UTF-16 code units to a code point and then to a UTF-8
554 // code unit sequence.
555 char code_point[8] = { 0 };
556 size_t offset = 0;
557 if (!code_unit16_low) {
558 CBU8_APPEND_UNSAFE(code_point, offset, code_unit16_high);
559 } else {
560 uint32 code_unit32 = CBU16_GET_SUPPLEMENTARY(code_unit16_high,
561 code_unit16_low);
562 offset = 0;
563 CBU8_APPEND_UNSAFE(code_point, offset, code_unit32);
564 }
565 dest_string->append(code_point);
566 return true;
511 } 567 }
512 568
513 JSONReader::Token JSONReader::ParseToken() { 569 JSONReader::Token JSONReader::ParseToken() {
514 EatWhitespaceAndComments(); 570 EatWhitespaceAndComments();
515 571
516 Token token(Token::INVALID_TOKEN, 0, 0); 572 Token token(Token::INVALID_TOKEN, 0, 0);
517 switch (*json_pos_) { 573 switch (*json_pos_) {
518 case '\0': 574 case '\0':
519 token.type = Token::END_OF_INPUT; 575 token.type = Token::END_OF_INPUT;
520 break; 576 break;
(...skipping 52 matching lines...) Expand 10 before | Expand all | Expand 10 after
573 break; 629 break;
574 630
575 case '"': 631 case '"':
576 token = ParseStringToken(); 632 token = ParseStringToken();
577 break; 633 break;
578 } 634 }
579 return token; 635 return token;
580 } 636 }
581 637
582 void JSONReader::EatWhitespaceAndComments() { 638 void JSONReader::EatWhitespaceAndComments() {
583 while ('\0' != *json_pos_) { 639 while (json_pos_ != end_pos_) {
584 switch (*json_pos_) { 640 switch (*json_pos_) {
585 case ' ': 641 case ' ':
586 case '\n': 642 case '\n':
587 case '\r': 643 case '\r':
588 case '\t': 644 case '\t':
589 ++json_pos_; 645 ++json_pos_;
590 break; 646 break;
591 case '/': 647 case '/':
592 // TODO(tc): This isn't in the RFC so it should be a parser flag. 648 // TODO(tc): This isn't in the RFC so it should be a parser flag.
593 if (!EatComment()) 649 if (!EatComment())
594 return; 650 return;
595 break; 651 break;
596 default: 652 default:
597 // Not a whitespace char, just exit. 653 // Not a whitespace char, just exit.
598 return; 654 return;
599 } 655 }
600 } 656 }
601 } 657 }
602 658
603 bool JSONReader::EatComment() { 659 bool JSONReader::EatComment() {
604 if ('/' != *json_pos_) 660 if ('/' != *json_pos_)
605 return false; 661 return false;
606 662
607 wchar_t next_char = *(json_pos_ + 1); 663 char next_char = *(json_pos_ + 1);
608 if ('/' == next_char) { 664 if ('/' == next_char) {
609 // Line comment, read until \n or \r 665 // Line comment, read until \n or \r
610 json_pos_ += 2; 666 json_pos_ += 2;
611 while ('\0' != *json_pos_) { 667 while (json_pos_ != end_pos_) {
612 switch (*json_pos_) { 668 switch (*json_pos_) {
613 case '\n': 669 case '\n':
614 case '\r': 670 case '\r':
615 ++json_pos_; 671 ++json_pos_;
616 return true; 672 return true;
617 default: 673 default:
618 ++json_pos_; 674 ++json_pos_;
619 } 675 }
620 } 676 }
621 } else if ('*' == next_char) { 677 } else if ('*' == next_char) {
622 // Block comment, read until */ 678 // Block comment, read until */
623 json_pos_ += 2; 679 json_pos_ += 2;
624 while ('\0' != *json_pos_) { 680 while (json_pos_ != end_pos_) {
625 if ('*' == *json_pos_ && '/' == *(json_pos_ + 1)) { 681 if ('*' == *json_pos_ && '/' == *(json_pos_ + 1)) {
626 json_pos_ += 2; 682 json_pos_ += 2;
627 return true; 683 return true;
628 } 684 }
629 ++json_pos_; 685 ++json_pos_;
630 } 686 }
631 } else { 687 } else {
632 return false; 688 return false;
633 } 689 }
634 return true; 690 return true;
635 } 691 }
636 692
637 bool JSONReader::NextStringMatch(const wchar_t* str, size_t length) { 693 bool JSONReader::NextStringMatch(const char* str, size_t length) {
638 return wcsncmp(json_pos_, str, length) == 0; 694 return strncmp(json_pos_, str, length) == 0;
639 } 695 }
640 696
641 void JSONReader::SetErrorCode(JsonParseError error, 697 void JSONReader::SetErrorCode(JsonParseError error,
642 const wchar_t* error_pos) { 698 const char* error_pos) {
643 int line_number = 1; 699 int line_number = 1;
644 int column_number = 1; 700 int column_number = 1;
645 701
646 // Figure out the line and column the error occured at. 702 // Figure out the line and column the error occured at.
647 for (const wchar_t* pos = start_pos_; pos != error_pos; ++pos) { 703 for (const char* pos = start_pos_; pos != error_pos; ++pos) {
648 if (*pos == '\0') { 704 if (pos > end_pos_) {
649 NOTREACHED(); 705 NOTREACHED();
650 return; 706 return;
651 } 707 }
652 708
653 if (*pos == '\n') { 709 if (*pos == '\n') {
654 ++line_number; 710 ++line_number;
655 column_number = 1; 711 column_number = 1;
656 } else { 712 } else {
657 ++column_number; 713 ++column_number;
658 } 714 }
659 } 715 }
660 716
661 error_line_ = line_number; 717 error_line_ = line_number;
662 error_col_ = column_number; 718 error_col_ = column_number;
663 error_code_ = error; 719 error_code_ = error;
664 } 720 }
665 721
666 } // namespace base 722 } // namespace base
OLDNEW
« no previous file with comments | « base/json/json_reader.h ('k') | base/json/json_reader_unittest.cc » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698