Coverage Report

Created: 2026-07-16 07:15

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/jsonnet/core/string_utils.cpp
Line
Count
Source
1
/*
2
Copyright 2015 Google Inc. All rights reserved.
3
4
Licensed under the Apache License, Version 2.0 (the "License");
5
you may not use this file except in compliance with the License.
6
You may obtain a copy of the License at
7
8
    http://www.apache.org/licenses/LICENSE-2.0
9
10
Unless required by applicable law or agreed to in writing, software
11
distributed under the License is distributed on an "AS IS" BASIS,
12
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
See the License for the specific language governing permissions and
14
limitations under the License.
15
*/
16
17
#include <iomanip>
18
19
#include "static_error.h"
20
#include "string_utils.h"
21
22
namespace jsonnet::internal {
23
24
UString jsonnet_string_unparse(const UString &str, bool single)
25
1.52M
{
26
1.52M
    UStringStream ss;
27
1.52M
    ss << (single ? U'\'' : U'\"');
28
1.52M
    ss << jsonnet_string_escape(str, single);
29
1.52M
    ss << (single ? U'\'' : U'\"');
30
1.52M
    return ss.str();
31
1.52M
}
32
33
UString jsonnet_string_escape(const UString &str, bool single)
34
1.52M
{
35
1.52M
    UStringStream ss;
36
2.26G
    for (std::size_t i = 0; i < str.length(); ++i) {
37
2.26G
        char32_t c = str[i];
38
2.26G
        switch (c) {
39
1.72M
            case U'\"': ss << (single ? U"\"" : U"\\\""); break;
40
989k
            case U'\'': ss << (single ? U"\\\'" : U"\'"); break;
41
2.07G
            case U'\\': ss << U"\\\\"; break;
42
1.47M
            case U'\b': ss << U"\\b"; break;
43
1.56M
            case U'\f': ss << U"\\f"; break;
44
18.7M
            case U'\n': ss << U"\\n"; break;
45
773k
            case U'\r': ss << U"\\r"; break;
46
2.59M
            case U'\t': ss << U"\\t"; break;
47
202k
            case U'\0': ss << U"\\u0000"; break;
48
159M
            default: {
49
159M
                if (c < 0x20 || (c >= 0x7f && c <= 0x9f)) {
50
                    // Unprintable, use \u
51
27.0M
                    std::stringstream ss8;
52
27.0M
                    ss8 << "\\u" << std::hex << std::setfill('0') << std::setw(4)
53
27.0M
                        << (unsigned long)(c);
54
27.0M
                    ss << decode_utf8(ss8.str());
55
132M
                } else {
56
                    // Printable, write verbatim
57
132M
                    ss << c;
58
132M
                }
59
159M
            }
60
2.26G
        }
61
2.26G
    }
62
1.52M
    return ss.str();
63
1.52M
}
64
65
unsigned long jsonnet_string_parse_unicode(const LocationRange &loc, const char32_t *c)
66
68.6k
{
67
68.6k
    unsigned long codepoint = 0;
68
    // Expect 4 hex digits.
69
342k
    for (unsigned i = 0; i < 4; ++i) {
70
274k
        auto x = (unsigned char)(c[i]);
71
274k
        unsigned digit;
72
274k
        if (x == '\0') {
73
60
            auto msg = "Truncated unicode escape sequence in string literal.";
74
60
            throw StaticError(loc, msg);
75
274k
        } else if (x >= '0' && x <= '9') {
76
214k
            digit = x - '0';
77
214k
        } else if (x >= 'a' && x <= 'f') {
78
25.4k
            digit = x - 'a' + 10;
79
34.3k
        } else if (x >= 'A' && x <= 'F') {
80
34.2k
            digit = x - 'A' + 10;
81
34.2k
        } else {
82
110
            std::stringstream ss;
83
110
            ss << "Malformed unicode escape character, "
84
110
               << "should be hex: '" << x << "'";
85
110
            throw StaticError(loc, ss.str());
86
110
        }
87
274k
        codepoint *= 16;
88
274k
        codepoint += digit;
89
274k
    }
90
68.4k
    return codepoint;
91
68.6k
}
92
93
bool is_bmp_codepoint(const unsigned long codepoint)
94
67.4k
{
95
67.4k
    return codepoint < 0xd800 || (codepoint >= 0xe000 && codepoint < 0x10000);
96
67.4k
}
97
98
char32_t decode_utf16_surrogates(const LocationRange &loc, const unsigned long high, const unsigned long low)
99
1.07k
{
100
1.07k
    if (high >= 0xd800 && high < 0xdc00 && low >= 0xdc00 && low < 0xe000) {
101
1.03k
        return 0x10000 + ((high & 0x03ff) << 10) + (low & 0x03ff);
102
1.03k
    } else {
103
42
        std::stringstream ss;
104
42
        ss << "Invalid UTF-16 bytes";
105
42
        throw StaticError(loc, ss.str());
106
42
    }
107
1.07k
}
108
109
UString jsonnet_string_unescape(const LocationRange &loc, const UString &s)
110
9.45M
{
111
9.45M
    UString r;
112
9.45M
    const char32_t *s_ptr = s.c_str();
113
177M
    for (const char32_t *c = s_ptr; *c != U'\0'; ++c) {
114
168M
        switch (*c) {
115
1.17M
            case '\\':
116
1.17M
                switch (*(++c)) {
117
42.4k
                    case '"':
118
51.8k
                    case '\'': r += *c; break;
119
120
240k
                    case '\\': r += *c; break;
121
122
7.29k
                    case '/': r += *c; break;
123
124
30.4k
                    case 'b': r += '\b'; break;
125
126
79.5k
                    case 'f': r += '\f'; break;
127
128
552k
                    case 'n': r += '\n'; break;
129
130
40.7k
                    case 'r': r += '\r'; break;
131
132
101k
                    case 't': r += '\t'; break;
133
134
67.5k
                    case 'u': {
135
67.5k
                        ++c;  // Consume the 'u'.
136
67.5k
                        unsigned long codepoint = jsonnet_string_parse_unicode(loc, c);
137
138
                        // Leave us on the last char, ready for the ++c at
139
                        // the outer for loop.
140
67.5k
                        c += 3;
141
67.5k
                        if (!is_bmp_codepoint(codepoint)) {
142
1.14k
                           if (*(++c) != '\\') {
143
41
                                std::stringstream ss;
144
41
                                ss << "Invalid non-BMP Unicode escape in string literal";
145
41
                                throw StaticError(loc, ss.str());
146
41
                           }
147
1.10k
                           if (*(++c) != 'u') {
148
18
                                std::stringstream ss;
149
18
                                ss << "Invalid non-BMP Unicode escape in string literal";
150
18
                                throw StaticError(loc, ss.str());
151
18
                           }
152
1.08k
                           ++c;
153
1.08k
                           unsigned long codepoint2 = jsonnet_string_parse_unicode(loc, c);
154
1.08k
                           c += 3;
155
1.08k
                           codepoint = decode_utf16_surrogates(loc, codepoint, codepoint2);
156
1.08k
                       }
157
67.5k
                       r += codepoint;
158
67.5k
                    } break;
159
160
10
                    case '\0': {
161
10
                        auto msg = "Truncated escape sequence in string literal.";
162
10
                        throw StaticError(loc, msg);
163
67.5k
                    }
164
165
366
                    default: {
166
366
                        std::stringstream ss;
167
366
                        std::string utf8;
168
366
                        encode_utf8(*c, utf8);
169
366
                        ss << "Unknown escape sequence in string literal: '" << utf8 << "'";
170
366
                        throw StaticError(loc, ss.str());
171
67.5k
                    }
172
1.17M
                }
173
1.17M
                break;
174
175
166M
            default:
176
                // Just a regular letter.
177
166M
                r += *c;
178
168M
        }
179
168M
    }
180
9.45M
    return r;
181
9.45M
}
182
183
}  // namespace jsonnet::internal