vxx2 / vlib / v / checker / str.v
364 lines · 348 sloc · 11.72 KB · 5e934d589db08febfd9c80d6351e3b0f5cb626c2
Raw
1// Copyright (c) 2019-2024 Alexander Medvednikov. All rights reserved.
2// Use of this source code is governed by an MIT license
3// that can be found in the LICENSE file.
4module checker
5
6import v.ast
7import v.token
8import encoding.utf8.validate
9import v.util
10
11fn (mut c Checker) get_default_fmt(ftyp ast.Type, typ ast.Type) u8 {
12 if ftyp.has_option_or_result() {
13 return `s`
14 } else if typ.is_float() {
15 return `g`
16 } else if typ.is_signed() || typ.is_int_literal() {
17 return `d`
18 } else if typ.is_unsigned() {
19 return `u`
20 } else if typ.is_pointer() {
21 return `p`
22 } else {
23 mut sym := c.table.sym(c.unwrap_generic(ftyp))
24 if sym.kind == .alias {
25 // string aliases should be printable
26 info := sym.info as ast.Alias
27 sym = c.table.sym(info.parent_type)
28 if info.parent_type == ast.string_type {
29 return `s`
30 }
31 }
32 if sym.kind == .function {
33 return `s`
34 }
35 if ftyp in [ast.string_type, ast.bool_type]
36 || sym.kind in [.enum, .array, .array_fixed, .struct, .generic_inst, .map, .multi_return, .sum_type, .interface, .aggregate, .none]
37 || ftyp.has_option_or_result() || sym.has_method('str') {
38 return `s`
39 } else {
40 return `_`
41 }
42 }
43}
44
45fn (mut c Checker) get_string_inter_default_fmt(expr ast.Expr, ftyp ast.Type, typ ast.Type) u8 {
46 if expr is ast.Ident {
47 if expr.obj is ast.Var {
48 obj := expr.obj
49 if obj.typ.is_ptr() && !obj.is_arg {
50 pointee_typ := obj.typ.deref()
51 if c.table.final_sym(pointee_typ).kind != .enum {
52 final_pointee_typ := c.table.final_type(pointee_typ)
53 if final_pointee_typ in [ast.string_type, ast.bool_type] {
54 return `p`
55 }
56 }
57 }
58 }
59 }
60 return c.get_default_fmt(ftyp, typ)
61}
62
63fn (mut c Checker) check_string_inter_lit_format_expr(mut expr ast.Expr, what string) {
64 if expr is ast.EmptyExpr {
65 return
66 }
67 expected_type := c.expected_type
68 c.expected_type = ast.int_type
69 mut typ := c.expr(mut expr)
70 c.expected_type = expected_type
71 typ = c.type_resolver.get_type_or_default(expr, c.check_expr_option_or_result_call(expr, typ))
72 typ = c.table.unalias_num_type(typ)
73 if typ != ast.int_type && !typ.is_int_literal() {
74 c.error('${what} expression should return `int`', expr.pos())
75 }
76}
77
78fn (mut c Checker) string_inter_lit(mut node ast.StringInterLiteral) ast.Type {
79 inside_interface_deref_save := c.inside_interface_deref
80 c.inside_interface_deref = true
81 for i, mut expr in node.exprs {
82 expected_type := c.expected_type
83 c.expected_type = ast.string_type
84 mut ftyp := c.expr(mut expr)
85 c.expected_type = expected_type
86 ftyp = c.type_resolver.get_type_or_default(expr,
87 c.check_expr_option_or_result_call(expr, ftyp))
88 if ftyp == ast.void_type || ftyp == 0 {
89 c.error('expression does not return a value', expr.pos())
90 } else if ftyp == ast.char_type && ftyp.nr_muls() == 0 {
91 c.error('expression returning type `char` cannot be used in string interpolation directly, print its address or cast it to an integer instead',
92 expr.pos())
93 } else if c.fail_if_private_implicit_str(ftyp, expr.pos(), 'interpolate') {
94 return ast.string_type
95 }
96 if ftyp == 0 {
97 return ast.void_type
98 }
99 c.fail_if_unreadable(expr, ftyp, 'interpolation object')
100 node.expr_types << ftyp
101 if i < node.fwidth_exprs.len {
102 mut width_expr := node.fwidth_exprs[i]
103 c.check_string_inter_lit_format_expr(mut width_expr, 'width')
104 node.fwidth_exprs[i] = width_expr
105 }
106 if i < node.precision_exprs.len {
107 mut precision_expr := node.precision_exprs[i]
108 c.check_string_inter_lit_format_expr(mut precision_expr, 'precision')
109 node.precision_exprs[i] = precision_expr
110 }
111 ftyp_sym := c.table.sym(ftyp)
112 typ := if ftyp_sym.kind == .alias && !ftyp_sym.has_method('str') {
113 c.table.unalias_num_type(ftyp)
114 } else {
115 ftyp
116 }
117 mut fmt := node.fmts[i]
118 has_explicit_fmt := i < node.has_fmts.len && node.has_fmts[i]
119 // During generic recheck, reset auto-determined format specifiers
120 // since the type may have changed between instantiations
121 if c.table.cur_concrete_types.len > 0 && !has_explicit_fmt && !node.need_fmts[i]
122 && fmt != `_` {
123 fmt = `_`
124 }
125 // analyze and validate format specifier
126 if fmt !in [`E`, `F`, `G`, `e`, `f`, `g`, `d`, `u`, `x`, `X`, `o`, `c`, `s`, `S`, `p`,
127 `b`, `_`, `r`, `R`] {
128 c.error('unknown format specifier `${fmt:c}`', node.fmt_poss[i])
129 }
130 if fmt == `_` { // set default representation for type if none has been given
131 fmt = c.get_string_inter_default_fmt(expr, ftyp, typ)
132 if fmt == `_` {
133 if typ != ast.void_type && !(typ.has_flag(.generic) && (c.inside_lambda
134 || c.table.cur_concrete_types.len > 0
135 || (c.table.cur_fn != unsafe { nil } && c.table.cur_fn.generic_names.len > 0))) {
136 c.error('no known default format for type `${c.table.get_type_name(ftyp)}`',
137 node.fmt_poss[i])
138 }
139 } else if c.comptime.is_comptime(expr)
140 && c.type_resolver.get_type_or_default(expr, ast.void_type) != ast.void_type {
141 // still `_` placeholder for comptime variable without specifier
142 node.need_fmts[i] = false
143 } else {
144 node.fmts[i] = fmt
145 node.need_fmts[i] = false
146 }
147 } else { // check if given format specifier is valid for type
148 has_dynamic_precision := i < node.precision_exprs.len
149 && node.precision_exprs[i] !is ast.EmptyExpr
150 if (node.precisions[i] != 987698 || has_dynamic_precision) && !typ.is_float() {
151 c.error('precision specification only valid for float types', node.fmt_poss[i])
152 }
153 if node.pluss[i] && !typ.is_number() {
154 c.error('plus prefix only allowed for numbers', node.fmt_poss[i])
155 }
156 if ((typ.is_unsigned() && fmt !in [`u`, `x`, `X`, `o`, `c`, `b`])
157 || (typ.is_signed() && fmt !in [`d`, `x`, `X`, `o`, `c`, `b`])
158 || (typ.is_int_literal() && fmt !in [`d`, `c`, `x`, `X`, `o`, `u`, `b`])
159 || (typ.is_float() && fmt !in [`E`, `F`, `G`, `e`, `f`, `g`])
160 || (typ.is_pointer() && fmt !in [`p`, `x`, `X`])
161 || (typ.is_string() && fmt !in [`s`, `S`, `r`, `R`])
162 || (typ.idx() in [ast.i64_type_idx, ast.f64_type_idx] && fmt == `c`))
163 && !(typ.is_ptr() && fmt in [`p`, `x`, `X`]) {
164 c.error('illegal format specifier `${fmt:c}` for type `${c.table.get_type_name(ftyp)}`',
165 node.fmt_poss[i])
166 }
167 if c.table.final_sym(typ).kind in [.array, .array_fixed, .struct, .interface, .none, .map, .sum_type]
168 && fmt in [`E`, `F`, `G`, `e`, `f`, `g`, `d`, `u`, `x`, `X`, `o`, `c`, `p`, `b`, `r`, `R`]
169 && !(typ.is_ptr() && fmt in [`p`, `x`, `X`]) {
170 c.error('illegal format specifier `${fmt:c}` for type `${c.table.get_type_name(ftyp)}`',
171 node.fmt_poss[i])
172 }
173 node.need_fmts[i] = fmt != c.get_default_fmt(ftyp, typ)
174 || (typ.is_float() && fmt in [`g`, `G`])
175 }
176 // check recursive str
177 if c.table.cur_fn != unsafe { nil } && c.table.cur_fn.is_method
178 && c.table.cur_fn.name == 'str' && c.table.cur_fn.receiver.name == '${expr}' {
179 c.error('cannot call `str()` method recursively', expr.pos())
180 }
181 c.markused_string_inter_lit(mut node, ftyp, fmt)
182 }
183 c.inside_interface_deref = inside_interface_deref_save
184 if c.pref.warn_about_allocs {
185 c.warn_alloc('string interpolation', node.pos)
186 }
187 return ast.string_type
188}
189
190const unicode_lit_overflow_message = 'unicode character exceeds max allowed value of 0x10ffff, consider using a unicode literal (\\u####)'
191
192fn is_source_char_escaped(source string, idx int) bool {
193 mut backslashes := 0
194 mut i := idx - 1
195 for i >= 0 && source[i] == `\\` {
196 backslashes++
197 i--
198 }
199 return (backslashes & 1) == 1
200}
201
202fn raw_string_literal_source(source string, approx_pos int) ?string {
203 if source.len == 0 {
204 return none
205 }
206 mut hint := approx_pos
207 if hint < 0 {
208 hint = 0
209 } else if hint >= source.len {
210 hint = source.len - 1
211 }
212 mut start := hint
213 for start >= 0 {
214 if source[start] in [`'`, `"`] && !is_source_char_escaped(source, start) {
215 quote := source[start]
216 is_raw := start > 0 && source[start - 1] == `r`
217 mut end := start + 1
218 for end < source.len {
219 if source[end] == quote && (is_raw || !is_source_char_escaped(source, end)) {
220 if end >= hint {
221 return source[start..end + 1]
222 }
223 break
224 }
225 end++
226 }
227 }
228 start--
229 }
230 return none
231}
232
233fn (c &Checker) source_string_literal_is_valid_utf8(node ast.StringLiteral) bool {
234 if node.pos.file_idx < 0 || node.pos.file_idx >= c.table.filelist.len {
235 return validate.utf8_string(node.val)
236 }
237 source := util.read_file(c.table.filelist[node.pos.file_idx]) or {
238 return validate.utf8_string(node.val)
239 }
240 raw_source := raw_string_literal_source(source, node.pos.pos) or {
241 return validate.utf8_string(node.val)
242 }
243 return validate.utf8_string(raw_source)
244}
245
246// unicode character literals are limited to a maximum value of 0x10ffff
247// https://stackoverflow.com/questions/52203351/why-unicode-is-restricted-to-0x10ffff
248@[direct_array_access]
249fn (mut c Checker) string_lit(mut node ast.StringLiteral) ast.Type {
250 // Validate the bytes that came from the source file, not the decoded string value.
251 // `\x..` escapes are allowed to produce arbitrary bytes intentionally.
252 valid_utf8 := c.source_string_literal_is_valid_utf8(node)
253 if !valid_utf8 {
254 c.note('invalid utf8 byte sequence in string literal', node.pos)
255 }
256 mut idx := 0
257 for idx < node.val.len {
258 match node.val[idx] {
259 `\\` {
260 mut start_pos := token.Pos{
261 ...node.pos
262 col: u16(node.pos.col + 1 + idx)
263 }
264 start_idx := idx
265 idx++
266 next_ch := node.val[idx] or { return ast.string_type }
267 if next_ch == `\\` {
268 // ignore escaping char
269 idx++
270 } else if next_ch == `u` {
271 idx++
272 mut ch := node.val[idx] or { return ast.string_type }
273 mut hex_char_count := 0
274 for ch.is_hex_digit() {
275 hex_char_count++
276 end_pos := token.Pos{
277 ...start_pos
278 len: idx + 1 - start_idx
279 }
280 match hex_char_count {
281 1...5 {}
282 6 {
283 first_digit := node.val[idx - 5] - 48
284 second_digit := node.val[idx - 4] - 48
285 if first_digit > 1 {
286 c.error(unicode_lit_overflow_message, end_pos)
287 } else if first_digit == 1 && second_digit > 0 {
288 c.error(unicode_lit_overflow_message, end_pos)
289 }
290 }
291 else {
292 c.error(unicode_lit_overflow_message, end_pos)
293 }
294 }
295
296 idx++
297 ch = node.val[idx] or { return ast.string_type }
298 }
299 }
300 }
301 else {
302 idx++
303 }
304 }
305 }
306 return ast.string_type
307}
308
309struct LoHiLimit {
310 lower string
311 higher string
312}
313
314const iencoding_map = {
315 `B`: LoHiLimit{'1000000000000000000000000000000000000000000000000000000000000000', '1111111111111111111111111111111111111111111111111111111111111111'}
316 `O`: LoHiLimit{'1000000000000000000000', '1777777777777777777777'}
317 `_`: LoHiLimit{'9223372036854775808', '18446744073709551615'}
318 `X`: LoHiLimit{'8000000000000000', 'FFFFFFFFFFFFFFFF'}
319}
320
321fn (mut c Checker) int_lit(mut node ast.IntegerLiteral) ast.Type {
322 if node.val.len < 17 {
323 // can not be a too large number, no need for more expensive checks
324 return ast.int_literal_type
325 }
326 lit := node.val.replace('_', '').all_after('-').to_upper_ascii()
327 is_neg := node.val.starts_with('-')
328 if lit.len > 2 && lit[0] == `0` && lit[1] in [`B`, `X`, `O`] {
329 if lohi := iencoding_map[lit[1]] {
330 c.check_num_literal(lohi, is_neg, lit[2..]) or { c.num_lit_overflow_error(node) }
331 }
332 } else {
333 lohi := iencoding_map[`_`]
334 c.check_num_literal(lohi, is_neg, lit) or { c.num_lit_overflow_error(node) }
335 }
336 return ast.int_literal_type
337}
338
339@[direct_array_access]
340fn (mut c Checker) check_num_literal(lohi LoHiLimit, is_neg bool, lit string) ! {
341 limit := if is_neg { lohi.lower } else { lohi.higher }
342 if lit.len < limit.len {
343 return
344 }
345 if lit.len > limit.len {
346 return error('length overflow')
347 }
348 if lit.len == limit.len {
349 for i, digit in lit {
350 if digit > limit[i] {
351 return error('value overflow at i: ${i}')
352 } else if digit < limit[i] {
353 break
354 }
355 }
356 }
357}
358
359fn (mut c Checker) num_lit_overflow_error(node &ast.IntegerLiteral) {
360 if c.inside_integer_literal_cast {
361 return
362 }
363 c.error('integer literal ${node.val} overflows int', node.pos)
364}
365