From 2647ab699d5f097b44f54ff14ca68028118238ca Mon Sep 17 00:00:00 2001 From: Asger F Date: Fri, 14 Aug 2026 11:46:22 +0200 Subject: [PATCH 1/6] unified: Add string interpolation expression --- unified/extractor/ast_types.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/unified/extractor/ast_types.yml b/unified/extractor/ast_types.yml index 82b49f981180..6f929f115b0e 100644 --- a/unified/extractor/ast_types.yml +++ b/unified/extractor/ast_types.yml @@ -8,6 +8,7 @@ supertypes: - boolean_literal - string_literal - regex_literal + - string_interpolation_expr - builtin_expr - binary_expr - unary_expr @@ -129,6 +130,11 @@ named: # A regex literal regex_literal: + # A string interpolation expression. Constant parts are stored as string literals. + string_interpolation_expr: + modifier*: modifier + element*: expr + # Application of a binary operator, such as `a + b` binary_expr: left: expr From 4897af6a972e207c4878e3a0774f2b3d0bbaa83c Mon Sep 17 00:00:00 2001 From: Asger F Date: Fri, 14 Aug 2026 11:46:33 +0200 Subject: [PATCH 2/6] unified: Regenerate QL --- .../ql/lib/codeql/unified/internal/Ast.qll | 32 +++++++++++++++++++ unified/ql/lib/unified.dbscheme | 22 +++++++++++-- 2 files changed, 52 insertions(+), 2 deletions(-) diff --git a/unified/ql/lib/codeql/unified/internal/Ast.qll b/unified/ql/lib/codeql/unified/internal/Ast.qll index 85139e77ffb5..3de5e2ff3aad 100644 --- a/unified/ql/lib/codeql/unified/internal/Ast.qll +++ b/unified/ql/lib/codeql/unified/internal/Ast.qll @@ -1176,6 +1176,32 @@ module Unified { class Stmt extends @unified_stmt, F::AstNode { } + /** A class representing `string_interpolation_expr` nodes. */ + class StringInterpolationExpr extends @unified_string_interpolation_expr, F::Expr { + /** Gets the name of the primary QL class for this element. */ + final override string getAPrimaryQlClass() { result = "StringInterpolationExpr" } + + /** Gets the node corresponding to the field `element`. */ + final F::Expr getElement(int i) { unified_string_interpolation_expr_element(this, i, result) } + + /** Gets the node corresponding to the field `element`. */ + final F::Expr getAnElement() { result = this.getElement(_) } + + /** Gets the node corresponding to the field `modifier`. */ + final F::Modifier getModifier(int i) { + unified_string_interpolation_expr_modifier(this, i, result) + } + + /** Gets the node corresponding to the field `modifier`. */ + final F::Modifier getAModifier() { result = this.getModifier(_) } + + /** Gets a field or child node of this node. */ + final override F::AstNode getAFieldOrChild() { + unified_string_interpolation_expr_element(this, _, result) or + unified_string_interpolation_expr_modifier(this, _, result) + } + } + /** A class representing `string_literal` tokens. */ class StringLiteral extends @unified_token_string_literal, F::Expr, F::Token { /** Gets the name of the primary QL class for this element. */ @@ -1746,6 +1772,10 @@ module Unified { or result = node.(ReturnExpr).getValue() and i = -1 and name = "getValue" or + result = node.(StringInterpolationExpr).getElement(i) and name = "getElement" + or + result = node.(StringInterpolationExpr).getModifier(i) and name = "getModifier" + or result = node.(SwitchCase).getBody() and i = -1 and name = "getBody" or result = node.(SwitchCase).getModifier(i) and name = "getModifier" @@ -1951,6 +1981,8 @@ module UnifiedFinal { final class Stmt = F::Stmt; + final class StringInterpolationExpr = F::StringInterpolationExpr; + final class StringLiteral = F::StringLiteral; final class SuperExpr = F::SuperExpr; diff --git a/unified/ql/lib/unified.dbscheme b/unified/ql/lib/unified.dbscheme index 867740617449..b32deb29d367 100644 --- a/unified/ql/lib/unified.dbscheme +++ b/unified/ql/lib/unified.dbscheme @@ -443,7 +443,7 @@ unified_equality_type_constraint_def( int right: @unified_expr ref ); -@unified_expr = @unified_array_literal | @unified_assign_expr | @unified_binary_expr | @unified_block | @unified_break_expr | @unified_bulk_importing_pattern | @unified_call_expr | @unified_compound_assign_expr | @unified_conditional_pattern | @unified_continue_expr | @unified_expr_pattern | @unified_function_expr | @unified_generic_type_expr | @unified_if_expr | @unified_key_value_pair | @unified_map_literal | @unified_member_access_expr | @unified_named_pattern | @unified_or_pattern | @unified_pattern_guard_expr | @unified_return_expr | @unified_switch_expr | @unified_throw_expr | @unified_token_boolean_literal | @unified_token_builtin_expr | @unified_token_empty_expr | @unified_token_float_literal | @unified_token_identifier | @unified_token_inferred_type_expr | @unified_token_int_literal | @unified_token_regex_literal | @unified_token_string_literal | @unified_token_super_expr | @unified_token_unsupported_node | @unified_try_expr | @unified_tuple_expr | @unified_type_cast_expr | @unified_type_test_expr | @unified_unary_expr | @unified_unresolved_operator_sequence +@unified_expr = @unified_array_literal | @unified_assign_expr | @unified_binary_expr | @unified_block | @unified_break_expr | @unified_bulk_importing_pattern | @unified_call_expr | @unified_compound_assign_expr | @unified_conditional_pattern | @unified_continue_expr | @unified_expr_pattern | @unified_function_expr | @unified_generic_type_expr | @unified_if_expr | @unified_key_value_pair | @unified_map_literal | @unified_member_access_expr | @unified_named_pattern | @unified_or_pattern | @unified_pattern_guard_expr | @unified_return_expr | @unified_string_interpolation_expr | @unified_switch_expr | @unified_throw_expr | @unified_token_boolean_literal | @unified_token_builtin_expr | @unified_token_empty_expr | @unified_token_float_literal | @unified_token_identifier | @unified_token_inferred_type_expr | @unified_token_int_literal | @unified_token_regex_literal | @unified_token_string_literal | @unified_token_super_expr | @unified_token_unsupported_node | @unified_try_expr | @unified_tuple_expr | @unified_type_cast_expr | @unified_type_test_expr | @unified_unary_expr | @unified_unresolved_operator_sequence @unified_expr_or_operator = @unified_expr | @unified_token_infix_operator @@ -756,6 +756,24 @@ unified_return_expr_def( @unified_stmt = @unified_accessor_declaration | @unified_class_like_declaration | @unified_constructor_declaration | @unified_destructor_declaration | @unified_do_while_stmt | @unified_expr | @unified_for_each_stmt | @unified_function_declaration | @unified_guard_if_stmt | @unified_import_declaration | @unified_labeled_stmt | @unified_operator_syntax_declaration | @unified_type_alias_declaration | @unified_variable_declaration | @unified_while_stmt +#keyset[unified_string_interpolation_expr, index] +unified_string_interpolation_expr_element( + int unified_string_interpolation_expr: @unified_string_interpolation_expr ref, + int index: int ref, + unique int element: @unified_expr ref +); + +#keyset[unified_string_interpolation_expr, index] +unified_string_interpolation_expr_modifier( + int unified_string_interpolation_expr: @unified_string_interpolation_expr ref, + int index: int ref, + unique int modifier: @unified_token_modifier ref +); + +unified_string_interpolation_expr_def( + unique int id: @unified_string_interpolation_expr +); + #keyset[unified_switch_case, index] unified_switch_case_modifier( int unified_switch_case: @unified_switch_case ref, @@ -989,7 +1007,7 @@ unified_trivia_tokeninfo( string value: string ref ); -@unified_ast_node = @unified_accessor_declaration | @unified_argument | @unified_array_literal | @unified_assign_expr | @unified_associated_type_declaration | @unified_base_type | @unified_binary_expr | @unified_block | @unified_bound_type_constraint | @unified_break_expr | @unified_bulk_importing_pattern | @unified_call_expr | @unified_catch_clause | @unified_class_like_declaration | @unified_compound_assign_expr | @unified_conditional_pattern | @unified_constructor_declaration | @unified_continue_expr | @unified_destructor_declaration | @unified_do_while_stmt | @unified_equality_type_constraint | @unified_expr_pattern | @unified_for_each_stmt | @unified_function_declaration | @unified_function_expr | @unified_generic_type_expr | @unified_guard_if_stmt | @unified_if_expr | @unified_import_declaration | @unified_initializer_declaration | @unified_key_value_pair | @unified_labeled_stmt | @unified_map_literal | @unified_member_access_expr | @unified_named_pattern | @unified_operator_syntax_declaration | @unified_or_pattern | @unified_parameter | @unified_pattern_guard_expr | @unified_return_expr | @unified_switch_case | @unified_switch_expr | @unified_throw_expr | @unified_token | @unified_top_level | @unified_trivia_token | @unified_try_expr | @unified_tuple_expr | @unified_type_alias_declaration | @unified_type_cast_expr | @unified_type_parameter | @unified_type_test_expr | @unified_unary_expr | @unified_unresolved_operator_sequence | @unified_variable_declaration | @unified_while_stmt +@unified_ast_node = @unified_accessor_declaration | @unified_argument | @unified_array_literal | @unified_assign_expr | @unified_associated_type_declaration | @unified_base_type | @unified_binary_expr | @unified_block | @unified_bound_type_constraint | @unified_break_expr | @unified_bulk_importing_pattern | @unified_call_expr | @unified_catch_clause | @unified_class_like_declaration | @unified_compound_assign_expr | @unified_conditional_pattern | @unified_constructor_declaration | @unified_continue_expr | @unified_destructor_declaration | @unified_do_while_stmt | @unified_equality_type_constraint | @unified_expr_pattern | @unified_for_each_stmt | @unified_function_declaration | @unified_function_expr | @unified_generic_type_expr | @unified_guard_if_stmt | @unified_if_expr | @unified_import_declaration | @unified_initializer_declaration | @unified_key_value_pair | @unified_labeled_stmt | @unified_map_literal | @unified_member_access_expr | @unified_named_pattern | @unified_operator_syntax_declaration | @unified_or_pattern | @unified_parameter | @unified_pattern_guard_expr | @unified_return_expr | @unified_string_interpolation_expr | @unified_switch_case | @unified_switch_expr | @unified_throw_expr | @unified_token | @unified_top_level | @unified_trivia_token | @unified_try_expr | @unified_tuple_expr | @unified_type_alias_declaration | @unified_type_cast_expr | @unified_type_parameter | @unified_type_test_expr | @unified_unary_expr | @unified_unresolved_operator_sequence | @unified_variable_declaration | @unified_while_stmt unified_ast_node_location( unique int node: @unified_ast_node ref, From 4228e6c621ae66f82c487a7bbad82039debcef59 Mon Sep 17 00:00:00 2001 From: Asger F Date: Fri, 14 Aug 2026 12:54:51 +0200 Subject: [PATCH 3/6] unified: Add string interpolation corpus test --- .../literals/string-with-interpolation.output | 121 +++++++++++++++++- .../literals/string-with-interpolation.swift | 10 ++ 2 files changed, 130 insertions(+), 1 deletion(-) diff --git a/unified/extractor/tests/corpus/swift/literals/string-with-interpolation.output b/unified/extractor/tests/corpus/swift/literals/string-with-interpolation.output index 5207085d174c..273991dc3db6 100644 --- a/unified/extractor/tests/corpus/swift/literals/string-with-interpolation.output +++ b/unified/extractor/tests/corpus/swift/literals/string-with-interpolation.output @@ -1,5 +1,15 @@ +// Simple interpolation "hello \(name)" +// Multiple interpolations +"hello \(first) \(last)" + +// Interpolation with expression +"result: \(x + y)" + +// Plain string before and after interpolation +"prefix \(value) suffix" + --- sourceFile @@ -24,10 +34,119 @@ sourceFile baseName: identifier "name" stringSegment content: stringSegment + codeBlockItem + item: + stringLiteralExpr + closingQuote: " + openingQuote: " + segments: + stringSegment + content: stringSegment "hello " + expressionSegment + leftParen: ( + rightParen: ) + backslash: \ + expressions: + labeledExpr + expression: + declReferenceExpr + baseName: identifier "first" + stringSegment + content: stringSegment " " + expressionSegment + leftParen: ( + rightParen: ) + backslash: \ + expressions: + labeledExpr + expression: + declReferenceExpr + baseName: identifier "last" + stringSegment + content: stringSegment + codeBlockItem + item: + stringLiteralExpr + closingQuote: " + openingQuote: " + segments: + stringSegment + content: stringSegment "result: " + expressionSegment + leftParen: ( + rightParen: ) + backslash: \ + expressions: + labeledExpr + expression: + infixOperatorExpr + operator: + binaryOperatorExpr + operator: binaryOperator "+" + leftOperand: + declReferenceExpr + baseName: identifier "x" + rightOperand: + declReferenceExpr + baseName: identifier "y" + stringSegment + content: stringSegment + codeBlockItem + item: + stringLiteralExpr + closingQuote: " + openingQuote: " + segments: + stringSegment + content: stringSegment "prefix " + expressionSegment + leftParen: ( + rightParen: ) + backslash: \ + expressions: + labeledExpr + expression: + declReferenceExpr + baseName: identifier "value" + stringSegment + content: stringSegment " suffix" --- top_level body: block - stmt: string_literal "\"hello \\(name)\"" + stmt: + string_interpolation_expr + element: + string_literal "hello " + name_expr + identifier: identifier "name" + string_literal + string_interpolation_expr + element: + string_literal "hello " + name_expr + identifier: identifier "first" + string_literal " " + name_expr + identifier: identifier "last" + string_literal + string_interpolation_expr + element: + string_literal "result: " + binary_expr + left: + name_expr + identifier: identifier "x" + operator: infix_operator "+" + right: + name_expr + identifier: identifier "y" + string_literal + string_interpolation_expr + element: + string_literal "prefix " + name_expr + identifier: identifier "value" + string_literal " suffix" diff --git a/unified/extractor/tests/corpus/swift/literals/string-with-interpolation.swift b/unified/extractor/tests/corpus/swift/literals/string-with-interpolation.swift index 4c58b37b89e7..a4cbd681a367 100644 --- a/unified/extractor/tests/corpus/swift/literals/string-with-interpolation.swift +++ b/unified/extractor/tests/corpus/swift/literals/string-with-interpolation.swift @@ -1 +1,11 @@ +// Simple interpolation "hello \(name)" + +// Multiple interpolations +"hello \(first) \(last)" + +// Interpolation with expression +"result: \(x + y)" + +// Plain string before and after interpolation +"prefix \(value) suffix" From 03eb9969780b3de7fdc26e5ad7f46e8eb937bfc4 Mon Sep 17 00:00:00 2001 From: Asger F Date: Fri, 14 Aug 2026 13:30:45 +0200 Subject: [PATCH 4/6] unified: Add AST mappings for string interpolation --- .../extractor/src/languages/swift/swift.rs | 24 ++- .../literals/string-with-interpolation.output | 200 ++++++++++++++++-- .../literals/string-with-interpolation.swift | 6 + 3 files changed, 211 insertions(+), 19 deletions(-) diff --git a/unified/extractor/src/languages/swift/swift.rs b/unified/extractor/src/languages/swift/swift.rs index 960900070ba0..fdaa41ff0ba1 100644 --- a/unified/extractor/src/languages/swift/swift.rs +++ b/unified/extractor/src/languages/swift/swift.rs @@ -173,9 +173,27 @@ fn translation_rules() -> Vec> { rule!((nilLiteralExpr) @@node => expr { tree!((builtin_expr #{node})) }), - rule!((stringLiteralExpr) @@node => expr { - tree!((string_literal #{node})) - }), + rule!((simpleStringLiteralExpr) @@node => (string_literal #{node})), + // String literal with a single constant segment (for some reason not typed as simpleStringLiteralExpr) + rule!( + (stringLiteralExpr segments: (stringSegment) segments: _* @@rest) @@node + where rest.is_empty() + => + (string_literal #{node}) + ), + rule!( + (stringLiteralExpr segments: _* @segments) + => + (string_interpolation_expr element: {segments}) + ), + rule!((stringSegment content: @@content) => (string_literal #{content})), + rule!( + (expressionSegment expressions: _* @expressions) + => + (call_expr + callee: (builtin_expr "interpolation") + argument: {expressions}) + ), rule!((regexLiteralExpr) @@node => expr { tree!((regex_literal #{node})) }), diff --git a/unified/extractor/tests/corpus/swift/literals/string-with-interpolation.output b/unified/extractor/tests/corpus/swift/literals/string-with-interpolation.output index 273991dc3db6..4d8a2706c66e 100644 --- a/unified/extractor/tests/corpus/swift/literals/string-with-interpolation.output +++ b/unified/extractor/tests/corpus/swift/literals/string-with-interpolation.output @@ -10,6 +10,12 @@ // Plain string before and after interpolation "prefix \(value) suffix" +// Calls to custom DefaultStringInterpolation.appendInterpolation impls +"foo \(x, y)" +"foo \(x, y, z)" +"foo \(arg: x)" +"foo \(arg: x, arg2: y)" + --- sourceFile @@ -110,6 +116,108 @@ sourceFile baseName: identifier "value" stringSegment content: stringSegment " suffix" + codeBlockItem + item: + stringLiteralExpr + closingQuote: " + openingQuote: " + segments: + stringSegment + content: stringSegment "foo " + expressionSegment + leftParen: ( + rightParen: ) + backslash: \ + expressions: + labeledExpr + expression: + declReferenceExpr + baseName: identifier "x" + trailingComma: , + labeledExpr + expression: + declReferenceExpr + baseName: identifier "y" + stringSegment + content: stringSegment + codeBlockItem + item: + stringLiteralExpr + closingQuote: " + openingQuote: " + segments: + stringSegment + content: stringSegment "foo " + expressionSegment + leftParen: ( + rightParen: ) + backslash: \ + expressions: + labeledExpr + expression: + declReferenceExpr + baseName: identifier "x" + trailingComma: , + labeledExpr + expression: + declReferenceExpr + baseName: identifier "y" + trailingComma: , + labeledExpr + expression: + declReferenceExpr + baseName: identifier "z" + stringSegment + content: stringSegment + codeBlockItem + item: + stringLiteralExpr + closingQuote: " + openingQuote: " + segments: + stringSegment + content: stringSegment "foo " + expressionSegment + leftParen: ( + rightParen: ) + backslash: \ + expressions: + labeledExpr + colon: : + label: identifier "arg" + expression: + declReferenceExpr + baseName: identifier "x" + stringSegment + content: stringSegment + codeBlockItem + item: + stringLiteralExpr + closingQuote: " + openingQuote: " + segments: + stringSegment + content: stringSegment "foo " + expressionSegment + leftParen: ( + rightParen: ) + backslash: \ + expressions: + labeledExpr + colon: : + label: identifier "arg" + expression: + declReferenceExpr + baseName: identifier "x" + trailingComma: , + labeledExpr + colon: : + label: identifier "arg2" + expression: + declReferenceExpr + baseName: identifier "y" + stringSegment + content: stringSegment --- @@ -120,33 +228,93 @@ top_level string_interpolation_expr element: string_literal "hello " - name_expr - identifier: identifier "name" + call_expr + callee: builtin_expr "interpolation" + argument: + argument + value: identifier "name" string_literal string_interpolation_expr element: string_literal "hello " - name_expr - identifier: identifier "first" + call_expr + callee: builtin_expr "interpolation" + argument: + argument + value: identifier "first" string_literal " " - name_expr - identifier: identifier "last" + call_expr + callee: builtin_expr "interpolation" + argument: + argument + value: identifier "last" string_literal string_interpolation_expr element: string_literal "result: " - binary_expr - left: - name_expr - identifier: identifier "x" - operator: infix_operator "+" - right: - name_expr - identifier: identifier "y" + call_expr + callee: builtin_expr "interpolation" + argument: + argument + value: + binary_expr + left: identifier "x" + operator: infix_operator "+" + right: identifier "y" string_literal string_interpolation_expr element: string_literal "prefix " - name_expr - identifier: identifier "value" + call_expr + callee: builtin_expr "interpolation" + argument: + argument + value: identifier "value" string_literal " suffix" + string_interpolation_expr + element: + string_literal "foo " + call_expr + callee: builtin_expr "interpolation" + argument: + argument + value: identifier "x" + argument + value: identifier "y" + string_literal + string_interpolation_expr + element: + string_literal "foo " + call_expr + callee: builtin_expr "interpolation" + argument: + argument + value: identifier "x" + argument + value: identifier "y" + argument + value: identifier "z" + string_literal + string_interpolation_expr + element: + string_literal "foo " + call_expr + callee: builtin_expr "interpolation" + argument: + argument + name_node: identifier "arg" + value: identifier "x" + string_literal + string_interpolation_expr + element: + string_literal "foo " + call_expr + callee: builtin_expr "interpolation" + argument: + argument + name_node: identifier "arg" + value: identifier "x" + argument + name_node: identifier "arg2" + value: identifier "y" + string_literal diff --git a/unified/extractor/tests/corpus/swift/literals/string-with-interpolation.swift b/unified/extractor/tests/corpus/swift/literals/string-with-interpolation.swift index a4cbd681a367..b72a94faa00c 100644 --- a/unified/extractor/tests/corpus/swift/literals/string-with-interpolation.swift +++ b/unified/extractor/tests/corpus/swift/literals/string-with-interpolation.swift @@ -9,3 +9,9 @@ // Plain string before and after interpolation "prefix \(value) suffix" + +// Calls to custom DefaultStringInterpolation.appendInterpolation impls +"foo \(x, y)" +"foo \(x, y, z)" +"foo \(arg: x)" +"foo \(arg: x, arg2: y)" From 277a2822f98c38161978e22c17ba2550baaa37d4 Mon Sep 17 00:00:00 2001 From: Asger F Date: Fri, 11 Sep 2026 11:26:23 +0200 Subject: [PATCH 5/6] unified: Add qltest with string interpolation --- .../test/library-tests/BasicTest/strings.swift | 5 ++++- .../test/library-tests/BasicTest/test.expected | 18 +++++++++++++++--- .../ql/test/library-tests/BasicTest/test.ql | 4 +++- 3 files changed, 22 insertions(+), 5 deletions(-) diff --git a/unified/ql/test/library-tests/BasicTest/strings.swift b/unified/ql/test/library-tests/BasicTest/strings.swift index db9ae84e980b..0015521eae05 100644 --- a/unified/ql/test/library-tests/BasicTest/strings.swift +++ b/unified/ql/test/library-tests/BasicTest/strings.swift @@ -1 +1,4 @@ -let x = "hello" +let x1 = "hello" +let x2 = "hello \(123) world" +let x3 = "hello \(arg: 123) world" +let x4 = "hello \(1, 2, 3) world" diff --git a/unified/ql/test/library-tests/BasicTest/test.expected b/unified/ql/test/library-tests/BasicTest/test.expected index 0194a63380d7..e38b3e982635 100644 --- a/unified/ql/test/library-tests/BasicTest/test.expected +++ b/unified/ql/test/library-tests/BasicTest/test.expected @@ -1,7 +1,11 @@ identifier | name_expr.swift:1:5:1:5 | x | x | | name_expr.swift:1:9:1:9 | y | y | -| strings.swift:1:5:1:5 | x | x | +| strings.swift:1:5:1:6 | x1 | x1 | +| strings.swift:2:5:2:6 | x2 | x2 | +| strings.swift:3:5:3:6 | x3 | x3 | +| strings.swift:3:19:3:21 | arg | arg | +| strings.swift:4:5:4:6 | x4 | x4 | | test.swift:1:8:1:17 | Foundation | Foundation | | test.swift:1:8:1:17 | Foundation | Foundation | | test.swift:4:8:4:16 | Container | Container | @@ -151,5 +155,13 @@ identifier namedPattern | test.swift:1:1:1:17 | NamedPattern | Foundation | unsupported -stringValue -| strings.swift:1:9:1:15 | "hello" | "hello" | +rawStringValue +| strings.swift:1:10:1:16 | "hello" | "hello" | +| strings.swift:2:11:2:16 | hello | hello | +| strings.swift:2:23:2:28 | world | world | +| strings.swift:3:11:3:16 | hello | hello | +| strings.swift:3:28:3:33 | world | world | +| strings.swift:4:11:4:16 | hello | hello | +| strings.swift:4:27:4:32 | world | world | +exprStringValue +| strings.swift:1:10:1:16 | "hello" | hello | diff --git a/unified/ql/test/library-tests/BasicTest/test.ql b/unified/ql/test/library-tests/BasicTest/test.ql index 75fea1330e4a..62f5ac850e6e 100644 --- a/unified/ql/test/library-tests/BasicTest/test.ql +++ b/unified/ql/test/library-tests/BasicTest/test.ql @@ -6,4 +6,6 @@ query predicate namedPattern(NamedPattern node, string value) { value = node.get query predicate unsupported(UnsupportedNode node, string value) { value = node.getValue() } -query predicate stringValue(StringLiteral e, string value) { value = e.getValue() } +query predicate rawStringValue(StringLiteral e, string value) { value = e.getValue() } + +query predicate exprStringValue(Expr e, string value) { value = e.getStringValue() } From d9af62cf4965f81c88bf1c9963fd27ae5fbe1136 Mon Sep 17 00:00:00 2001 From: Asger F Date: Fri, 11 Sep 2026 11:29:41 +0200 Subject: [PATCH 6/6] unified: Handle string segments in Expr.getStringValue() --- unified/ql/lib/codeql/unified/internal/FacadeAst.qll | 8 +++++++- unified/ql/test/library-tests/BasicTest/test.expected | 6 ++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/unified/ql/lib/codeql/unified/internal/FacadeAst.qll b/unified/ql/lib/codeql/unified/internal/FacadeAst.qll index dcec57f0a4ae..77885e7c2d25 100644 --- a/unified/ql/lib/codeql/unified/internal/FacadeAst.qll +++ b/unified/ql/lib/codeql/unified/internal/FacadeAst.qll @@ -53,7 +53,13 @@ module Unified { string getStringValue() { // TODO: we'll want to cook the string literals extractor-side, but for now // just strip the quotes here and ignore escape sequences. - result = this.(StringLiteral).getValue().regexpCapture("\"(.*)\"", 1) + exists(string text | text = this.(StringLiteral).getValue() | + result = text.regexpCapture("\"(.*)\"", 1) + or + // Constant-segments of string interpolations are represented as string literals, but their raw text does not have quotes + not exists(text.regexpCapture("\"(.*)\"", 1)) and + result = text + ) } /** Gets the immediately-enclosing expression, skipping over intermediate sub-nodes like `Argument`, and without crossing a function boundary. */ diff --git a/unified/ql/test/library-tests/BasicTest/test.expected b/unified/ql/test/library-tests/BasicTest/test.expected index e38b3e982635..711b05c12d4c 100644 --- a/unified/ql/test/library-tests/BasicTest/test.expected +++ b/unified/ql/test/library-tests/BasicTest/test.expected @@ -165,3 +165,9 @@ rawStringValue | strings.swift:4:27:4:32 | world | world | exprStringValue | strings.swift:1:10:1:16 | "hello" | hello | +| strings.swift:2:11:2:16 | hello | hello | +| strings.swift:2:23:2:28 | world | world | +| strings.swift:3:11:3:16 | hello | hello | +| strings.swift:3:28:3:33 | world | world | +| strings.swift:4:11:4:16 | hello | hello | +| strings.swift:4:27:4:32 | world | world |