Skip to content

Commit 2d6b899

Browse files
authored
orm, cgen: escape ORM insert object variables named after reserved words (#28133)
1 parent d1ac745 commit 2d6b899

7 files changed

Lines changed: 270 additions & 28 deletions

vlib/v/gen/c/cgen.v

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,11 @@ import sync.pool
2020
// in C++, or have special meaning in V
2121
const c_reserved = ['asm', 'array', 'auto', 'bool', 'break', 'calloc', 'case', 'char', 'class',
2222
'complex', 'const', 'continue', 'default', 'delete', 'do', 'double', 'else', 'enum', 'error',
23-
'exit', 'export', 'extern', 'false', 'float', 'for', 'free', 'goto', 'if', 'inline', 'int',
24-
'long', 'malloc', 'namespace', 'new', 'nil', 'panic', 'register', 'restrict', 'return', 'short',
25-
'signed', 'sizeof', 'static', 'string', 'struct', 'switch', 'typedef', 'typename', 'typeof',
26-
'union', 'unix', 'unsigned', 'void', 'volatile', 'while', 'template', 'true', 'stdout', 'stdin',
27-
'stderr', 'errno', 'environ', 'requires']
23+
'exit', 'explicit', 'export', 'extern', 'false', 'float', 'for', 'free', 'goto', 'if', 'inline',
24+
'int', 'long', 'malloc', 'namespace', 'new', 'nil', 'operator', 'panic', 'register', 'restrict',
25+
'return', 'short', 'signed', 'sizeof', 'static', 'string', 'struct', 'switch', 'typedef',
26+
'typename', 'typeof', 'union', 'unix', 'unsigned', 'void', 'volatile', 'while', 'template',
27+
'true', 'stdout', 'stdin', 'stderr', 'errno', 'environ', 'requires']
2828
const c_reserved_chk = token.new_keywords_matcher_from_array_trie(c_reserved)
2929
// same order as in token.Kind
3030
const cmp_str = ['eq', 'ne', 'gt', 'lt', 'ge', 'le']
@@ -5169,7 +5169,7 @@ fn (mut g Gen) write_sumtype_casting_fn(fun SumtypeCastingFn) {
51695169
field_styp := g.styp(field.typ)
51705170
if got_sym.kind in [.sum_type, .interface] {
51715171
// the field is already a wrapped pointer; we shouldn't wrap it once again
5172-
sb.write_string(', .${c_name(field.name)} = ptr->${field.name}')
5172+
sb.write_string(', .${c_name(field.name)} = ptr->${c_name(field.name)}')
51735173
} else {
51745174
sb.write_string(', .${c_name(field.name)} = (${field_styp}*)((char*)${ptr} + __offsetof_ptr(${ptr}, ${type_cname}, ${c_name(field.name)}))')
51755175
}
@@ -8208,7 +8208,7 @@ fn (mut g Gen) selector_expr(node ast.SelectorExpr) {
82088208
g.write('I_${field_sym.cname}_as_I_${cast_sym.cname}(${ptr}')
82098209
g.expr(node.expr)
82108210
dot := if lhs_expr_type.is_ptr() { '->' } else { '.' }
8211-
g.write('${dot}${node.field_name}))')
8211+
g.write('${dot}${field_name}))')
82128212
return
82138213
} else if !is_option_unwrap {
82148214
if i != 0 {
@@ -10524,14 +10524,14 @@ fn (mut g Gen) ident(node ast.Ident) {
1052410524
if obj_sym.kind == .interface && cast_sym.kind == .interface {
1052510525
if cast_sym.cname != obj_sym.cname {
1052610526
ptr := '*'.repeat(resolved_var.typ.nr_muls())
10527-
g.write('I_${obj_sym.cname}_as_I_${cast_sym.cname}(${ptr}${node.name})')
10527+
g.write('I_${obj_sym.cname}_as_I_${cast_sym.cname}(${ptr}${name})')
1052810528
} else {
1052910529
ptr := if is_option {
1053010530
''
1053110531
} else {
1053210532
'*'.repeat(resolved_var.typ.nr_muls())
1053310533
}
10534-
g.write('${ptr}${node.name}')
10534+
g.write('${ptr}${name}')
1053510535
}
1053610536
} else {
1053710537
if sumtype_fn_value_smartcast {

vlib/v/gen/c/orm.v

Lines changed: 56 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -928,25 +928,46 @@ fn (mut g Gen) write_orm_insert(node &ast.SqlStmtLine, table_name string, connec
928928
result_var_name, '', '', or_expr)
929929
}
930930

931+
// orm_object_var_c_name escapes a top-level ORM object variable (the `x` in
932+
// `insert x into T` / `upsert x into T`, or the array in a bulk `insert xs into T`)
933+
// so a variable named after a C/C++ reserved word matches its escaped C
934+
// declaration. Sub-struct and array object vars are already-built C expressions
935+
// (they contain `.`, `*`, `(`, ...), i.e. not bare identifiers, so they are
936+
// returned unchanged.
937+
fn orm_object_var_c_name(object_var string) string {
938+
if object_var == '' {
939+
return object_var
940+
}
941+
for ch in object_var {
942+
if !util.is_name_char(ch) {
943+
return object_var
944+
}
945+
}
946+
return c_name(object_var)
947+
}
948+
931949
fn (mut g Gen) write_orm_bulk_insert(node &ast.SqlStmtLine, table_name string, connection_var_name string, result_var_name string, or_expr &ast.OrExpr) {
932950
fields := g.orm_non_array_fields(node.fields)
933951
auto_fields := get_auto_field_idxs(fields)
934952
row_type := g.styp(node.table_expr.typ)
935953
row_var := g.new_tmp_var()
936954
idx_var := g.new_tmp_var()
937955
data_var := g.new_tmp_var()
956+
// Escape the top-level array variable so a variable named after a C/C++ reserved
957+
// word matches its escaped C declaration (see write_orm_insert_with_last_ids).
958+
object_var := orm_object_var_c_name(node.object_var)
938959
g.writeln('${result_name}_void ${result_var_name};')
939-
g.writeln('if (${node.object_var}.len == 0) {')
960+
g.writeln('if (${object_var}.len == 0) {')
940961
g.indent++
941962
g.writeln('${result_var_name} = (${result_name}_void){0};')
942963
g.indent--
943964
g.writeln('} else {')
944965
g.indent++
945966
if auto_fields.len > 0 {
946967
g.writeln('${result_var_name} = (${result_name}_void){0};')
947-
g.writeln('for (${ast.int_type_name} ${idx_var} = 0; ${idx_var} < ${node.object_var}.len; ${idx_var}++) {')
968+
g.writeln('for (${ast.int_type_name} ${idx_var} = 0; ${idx_var} < ${object_var}.len; ${idx_var}++) {')
948969
g.indent++
949-
g.writeln('${row_type} ${row_var} = (*(${row_type}*)builtin__array_get(${node.object_var}, ${idx_var}));')
970+
g.writeln('${row_type} ${row_var} = (*(${row_type}*)builtin__array_get(${object_var}, ${idx_var}));')
950971
row_result_var := g.new_tmp_var()
951972
mut row_node := *node
952973
row_node.object_var = row_var
@@ -962,9 +983,9 @@ fn (mut g Gen) write_orm_bulk_insert(node &ast.SqlStmtLine, table_name string, c
962983
return
963984
}
964985
g.writeln('Array_orm__Primitive ${data_var} = builtin____new_array_with_default_noscan(0, 0, sizeof(orm__Primitive), 0);')
965-
g.writeln('for (${ast.int_type_name} ${idx_var} = 0; ${idx_var} < ${node.object_var}.len; ${idx_var}++) {')
986+
g.writeln('for (${ast.int_type_name} ${idx_var} = 0; ${idx_var} < ${object_var}.len; ${idx_var}++) {')
966987
g.indent++
967-
g.writeln('${row_type} ${row_var} = (*(${row_type}*)builtin__array_get(${node.object_var}, ${idx_var}));')
988+
g.writeln('${row_type} ${row_var} = (*(${row_type}*)builtin__array_get(${object_var}, ${idx_var}));')
968989
for field in fields {
969990
g.write('builtin__array_push(&${data_var}, _MOV((orm__Primitive[1]){')
970991
g.write_orm_field_access_to_primitive(field, row_var, node.table_expr.typ,
@@ -1013,7 +1034,7 @@ fn (mut g Gen) write_orm_bulk_insert(node &ast.SqlStmtLine, table_name string, c
10131034
g.writeln('.kinds = builtin____new_array_with_default_noscan(0, 0, sizeof(orm__OperationKind), 0),')
10141035
g.writeln('.is_and = builtin____new_array_with_default_noscan(0, 0, sizeof(bool), 0),')
10151036
g.writeln('.parentheses = builtin____new_array_with_default_noscan(0, 0, sizeof(Array_${ast.int_type_name}), 0),')
1016-
g.writeln('.batch_rows = ${node.object_var}.len,')
1037+
g.writeln('.batch_rows = ${object_var}.len,')
10171038
g.indent--
10181039
g.writeln('}')
10191040
g.indent--
@@ -1029,6 +1050,9 @@ fn (mut g Gen) write_orm_upsert(node &ast.SqlStmtLine, table_name string, connec
10291050
auto_fields := get_auto_field_idxs(fields)
10301051
mut inserting_object_type := ast.void_type
10311052
mut member_access_type := '.'
1053+
// See write_orm_insert_with_last_ids: escape the top-level object var so a
1054+
// variable named after a C/C++ reserved word matches its escaped C declaration.
1055+
object_var := orm_object_var_c_name(node.object_var)
10321056
if node.scope != unsafe { nil } {
10331057
if inserting_object := node.scope.find(node.object_var) {
10341058
if inserting_object.typ.is_ptr() {
@@ -1065,12 +1089,12 @@ fn (mut g Gen) write_orm_upsert(node &ast.SqlStmtLine, table_name string, connec
10651089
mut typ := g.orm_primitive_field_name(field.typ)
10661090
mut ctyp := sym.cname
10671091
typ = vint2int(typ)
1068-
var := '${node.object_var}${member_access_type}${orm_field_access_name(field.name)}'
1092+
var := '${object_var}${member_access_type}${orm_field_access_name(field.name)}'
10691093
if final_field_typ.has_flag(.option) {
10701094
g.writeln('${var}.state == 2 ? _const_orm__null_primitive : orm__${typ}_to_primitive(*(${ctyp}*)(${var}.data)),')
10711095
} else if inserting_object_sym.kind == .sum_type {
10721096
table_sym := g.table.sym(node.table_expr.typ)
1073-
sum_type_var := '(*${node.object_var}._${table_sym.cname})${member_access_type}${orm_field_access_name(field.name)}'
1097+
sum_type_var := '(*${object_var}._${table_sym.cname})${member_access_type}${orm_field_access_name(field.name)}'
10741098
g.writeln('orm__${typ}_to_primitive(${sum_type_var}),')
10751099
} else {
10761100
g.writeln('orm__${typ}_to_primitive(${var}),')
@@ -1501,6 +1525,12 @@ fn (mut g Gen) write_orm_insert_with_last_ids(node ast.SqlStmtLine, connection_v
15011525

15021526
mut inserting_object_type := ast.void_type
15031527
mut member_access_type := '.'
1528+
// The top-level insert variable (e.g. `insert explicit into T`) is a plain V
1529+
// identifier, so its C name must be escaped the same way its declaration is,
1530+
// otherwise a variable named after a C/C++ reserved word (`new`, `explicit`, ...)
1531+
// won't match. Sub-struct/array object vars are already-built C expressions, so
1532+
// orm_object_var_c_name leaves them untouched.
1533+
object_var := orm_object_var_c_name(node.object_var)
15041534
if node.scope != unsafe { nil } {
15051535
if inserting_object := node.scope.find(node.object_var) {
15061536
if inserting_object.typ.is_ptr() {
@@ -1514,13 +1544,16 @@ fn (mut g Gen) write_orm_insert_with_last_ids(node ast.SqlStmtLine, connection_v
15141544

15151545
inserting_object_sym := g.table.sym(inserting_object_type)
15161546
for i, mut sub in subs {
1547+
// The relation field name is stored verbatim, so escape it like any other
1548+
// struct member (a field named after a reserved word is declared __v_<name>).
1549+
sub_field := orm_field_access_name(sub.object_var)
15171550
if subs_unwrapped_c_typ[i].len > 0 {
1518-
var := '${node.object_var}${member_access_type}${sub.object_var}'
1551+
var := '${object_var}${member_access_type}${sub_field}'
15191552
g.writeln('if(${var}.state == 0) {')
15201553
g.indent++
1521-
sub.object_var = '(*(${subs_unwrapped_c_typ[i]}*)${node.object_var}${member_access_type}${sub.object_var}.data)'
1554+
sub.object_var = '(*(${subs_unwrapped_c_typ[i]}*)${object_var}${member_access_type}${sub_field}.data)'
15221555
} else {
1523-
sub.object_var = '${node.object_var}${member_access_type}${sub.object_var}'
1556+
sub.object_var = '${object_var}${member_access_type}${sub_field}'
15241557
}
15251558
g.sql_stmt_line(sub, connection_var_name, or_expr)
15261559
g.writeln('builtin__array_push(&${last_ids_arr}, _MOV((orm__Primitive[1]){')
@@ -1581,12 +1614,12 @@ fn (mut g Gen) write_orm_insert_with_last_ids(node ast.SqlStmtLine, connection_v
15811614
}
15821615
// fields processed hereafter can be NULL...
15831616
typ = vint2int(typ)
1584-
var := '${node.object_var}${member_access_type}${orm_field_access_name(field.name)}'
1617+
var := '${object_var}${member_access_type}${orm_field_access_name(field.name)}'
15851618
if final_field_typ.has_flag(.option) {
15861619
g.writeln('${var}.state == 2? _const_orm__null_primitive : orm__${typ}_to_primitive(*(${ctyp}*)(${var}.data)),')
15871620
} else if inserting_object_sym.kind == .sum_type {
15881621
table_sym := g.table.sym(node.table_expr.typ)
1589-
sum_type_var := '(*${node.object_var}._${table_sym.cname})${member_access_type}${orm_field_access_name(field.name)}'
1622+
sum_type_var := '(*${object_var}._${table_sym.cname})${member_access_type}${orm_field_access_name(field.name)}'
15901623
g.writeln('orm__${typ}_to_primitive(${sum_type_var}),')
15911624
} else {
15921625
g.writeln('orm__${typ}_to_primitive(${var}),')
@@ -1630,26 +1663,30 @@ fn (mut g Gen) write_orm_insert_with_last_ids(node ast.SqlStmtLine, connection_v
16301663
// else use the primary key value
16311664
mut typ := g.orm_primitive_field_name(primary_field.typ)
16321665
typ = vint2int(typ)
1633-
g.writeln('orm__Primitive ${id_name} = orm__${typ}_to_primitive(${node.object_var}${member_access_type}${orm_field_access_name(primary_field.name)});')
1666+
g.writeln('orm__Primitive ${id_name} = orm__${typ}_to_primitive(${object_var}${member_access_type}${orm_field_access_name(primary_field.name)});')
16341667
}
16351668
for i, mut arr in arrs {
16361669
idx := g.new_tmp_var()
16371670
ctyp := g.styp(arr.table_expr.typ)
16381671
is_option := opt_fields.contains(i)
1672+
// The array relation field name is stored verbatim, so escape it like any
1673+
// other struct member (a field named after a reserved word is declared
1674+
// __v_<name>).
1675+
arr_field := orm_field_access_name(arr.object_var)
16391676
if is_option {
1640-
g.writeln('for (${ast.int_type_name} ${idx} = 0; ${node.object_var}${member_access_type}${arr.object_var}.state != 2 && ${idx} < (*(Array_${ctyp}*)${node.object_var}${member_access_type}${arr.object_var}.data).len; ${idx}++) {')
1677+
g.writeln('for (${ast.int_type_name} ${idx} = 0; ${object_var}${member_access_type}${arr_field}.state != 2 && ${idx} < (*(Array_${ctyp}*)${object_var}${member_access_type}${arr_field}.data).len; ${idx}++) {')
16411678
} else {
1642-
g.writeln('for (${ast.int_type_name} ${idx} = 0; ${idx} < ${node.object_var}${member_access_type}${arr.object_var}.len; ${idx}++) {')
1679+
g.writeln('for (${ast.int_type_name} ${idx} = 0; ${idx} < ${object_var}${member_access_type}${arr_field}.len; ${idx}++) {')
16431680
}
16441681
g.indent++
16451682
last_ids := g.new_tmp_var()
16461683
res_ := g.new_tmp_var()
16471684
tmp_var := g.new_tmp_var()
16481685
g.writeln('Array_orm__Primitive ${last_ids} = builtin____new_array_with_default_noscan(0, 0, sizeof(orm__Primitive), 0);')
16491686
if is_option {
1650-
g.writeln('${ctyp} ${tmp_var} = (*(${ctyp}*)builtin__array_get(*(Array_${ctyp}*)${node.object_var}${member_access_type}${arr.object_var}.data, ${idx}));')
1687+
g.writeln('${ctyp} ${tmp_var} = (*(${ctyp}*)builtin__array_get(*(Array_${ctyp}*)${object_var}${member_access_type}${arr_field}.data, ${idx}));')
16511688
} else {
1652-
g.writeln('${ctyp} ${tmp_var} = (*(${ctyp}*)builtin__array_get(${node.object_var}${member_access_type}${arr.object_var}, ${idx}));')
1689+
g.writeln('${ctyp} ${tmp_var} = (*(${ctyp}*)builtin__array_get(${object_var}${member_access_type}${arr_field}, ${idx}));')
16531690
}
16541691
arr.object_var = tmp_var
16551692
mut fff := []ast.StructField{}
@@ -1820,7 +1857,7 @@ fn (mut g Gen) write_orm_primitive(t ast.Type, expr ast.Expr) {
18201857
}
18211858
}
18221859

1823-
g.writeln(' .operator = ${kind},')
1860+
g.writeln(' .${c_name('operator')} = ${kind},')
18241861
g.write(' .right = ')
18251862
g.write_orm_expr_to_primitive(expr.right)
18261863
g.indent--
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
I_main__Base_as_I_main__Extended(__v_operator)
2+
I_main__Base_as_I_main__Extended(h.__v_explicit)
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
// An interface-typed local or struct field named after a C/C++ reserved word is
2+
// declared as `__v_<name>`. The interface-to-interface smartcast paths (ident and
3+
// selector_expr) must reference that escaped name, not the raw identifier, or the
4+
// generated C references a nonexistent local/member.
5+
interface Base {
6+
name string
7+
}
8+
9+
interface Extended {
10+
Base
11+
extra() string
12+
}
13+
14+
struct Thing {
15+
name string
16+
}
17+
18+
fn (t Thing) extra() string {
19+
return 'e'
20+
}
21+
22+
fn use_ext(e Extended) string {
23+
return e.name + e.extra()
24+
}
25+
26+
struct Holder {
27+
explicit Base
28+
}
29+
30+
fn main() {
31+
// ident form: interface-typed local named after a reserved word
32+
operator := Base(Thing{
33+
name: 'op'
34+
})
35+
if operator is Extended {
36+
println(use_ext(operator))
37+
}
38+
// selector_expr form: interface-typed struct field named after a reserved word
39+
h := Holder{
40+
explicit: Base(Thing{
41+
name: 'ex'
42+
})
43+
}
44+
if h.explicit is Extended {
45+
println(use_ext(h.explicit))
46+
}
47+
}
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
// vtest build: present_sqlite3?
2+
// Regression test: the ORM object variable (`insert x into T`, `upsert x into T`,
3+
// bulk `insert xs into T`) must be escaped like any other C identifier. Otherwise a
4+
// variable named after a C/C++ reserved word (`new`, `delete`, `operator`, ...) is
5+
// declared as `__v_delete` but referenced by the ORM codegen as `delete`, producing
6+
// an "undeclared identifier" C error. See write_orm_insert_with_last_ids /
7+
// write_orm_upsert / write_orm_bulk_insert in vlib/v/gen/c/orm.v.
8+
import db.sqlite
9+
10+
struct User {
11+
id int @[primary; sql: serial]
12+
name string
13+
}
14+
15+
fn test_insert_with_reserved_keyword_object_var() {
16+
mut db := sqlite.connect(':memory:')!
17+
sql db {
18+
create table User
19+
}!
20+
// `delete` is a reserved word; used here as the inserted object variable.
21+
delete := User{
22+
name: 'single'
23+
}
24+
sql db {
25+
insert delete into User
26+
}!
27+
rows := sql db {
28+
select from User
29+
}!
30+
assert rows.len == 1
31+
assert rows[0].name == 'single'
32+
}
33+
34+
fn test_bulk_insert_with_reserved_keyword_object_var() {
35+
mut db := sqlite.connect(':memory:')!
36+
sql db {
37+
create table User
38+
}!
39+
// `new` is a reserved word; used here as the inserted array variable.
40+
new := [User{
41+
name: 'a'
42+
}, User{
43+
name: 'b'
44+
}]
45+
sql db {
46+
insert new into User
47+
}!
48+
rows := sql db {
49+
select from User
50+
}!
51+
assert rows.len == 2
52+
assert rows[0].name == 'a'
53+
assert rows[1].name == 'b'
54+
}

0 commit comments

Comments
 (0)