From 0fbe6e5bdde754f0e88bd6dcc1f71347bf528fa5 Mon Sep 17 00:00:00 2001 From: Simon Friis Vindum Date: Wed, 11 Nov 2026 15:16:59 +0100 Subject: [PATCH 1/4] Unified: Add borrow expression corpus test Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 8dd55540-3d6b-43a8-873a-7d22380d548f --- .../expressions/borrow-expression.output | 81 +++++++++++++++++++ .../swift/expressions/borrow-expression.swift | 2 + 2 files changed, 83 insertions(+) create mode 100644 unified/extractor/tests/corpus/swift/expressions/borrow-expression.output create mode 100644 unified/extractor/tests/corpus/swift/expressions/borrow-expression.swift diff --git a/unified/extractor/tests/corpus/swift/expressions/borrow-expression.output b/unified/extractor/tests/corpus/swift/expressions/borrow-expression.output new file mode 100644 index 000000000000..23c02f292947 --- /dev/null +++ b/unified/extractor/tests/corpus/swift/expressions/borrow-expression.output @@ -0,0 +1,81 @@ +let original = [1, 2, 3] +let borrowed = borrow original + +--- + +sourceFile + endOfFileToken: endOfFile + statements: + codeBlockItem + item: + variableDecl + attributes: + modifiers: + bindingSpecifier: let + bindings: + patternBinding + initializer: + initializerClause + equal: = + value: + arrayExpr + elements: + arrayElement + expression: + integerLiteralExpr + literal: integerLiteral "1" + trailingComma: , + arrayElement + expression: + integerLiteralExpr + literal: integerLiteral "2" + trailingComma: , + arrayElement + expression: + integerLiteralExpr + literal: integerLiteral "3" + leftSquare: [ + rightSquare: ] + pattern: + identifierPattern + identifier: identifier "original" + codeBlockItem + item: + variableDecl + attributes: + modifiers: + bindingSpecifier: let + bindings: + patternBinding + initializer: + initializerClause + equal: = + value: + borrowExpr + expression: + declReferenceExpr + baseName: identifier "original" + borrowKeyword: borrow + pattern: + identifierPattern + identifier: identifier "borrowed" + +--- + +top_level source="⟨body⟩" + body: + block source="⟨stmt⟩\n⟨stmt⟩" + stmt: + variable_declaration source="⟨modifier⟩ ⟨pattern⟩ = ⟨value⟩" + modifier: modifier "let" source="let" + pattern: identifier "original" source="original" + value: + array_literal source="[⟨element⟩, ⟨element⟩, ⟨element⟩]" + element: + int_literal "1" source="1" + int_literal "2" source="2" + int_literal "3" source="3" + variable_declaration source="⟨modifier⟩ ⟨pattern⟩ = ⟨value⟩" + modifier: modifier "let" source="let" + pattern: identifier "borrowed" source="borrowed" + value: unsupported_node "borrow original" source="borrow original" diff --git a/unified/extractor/tests/corpus/swift/expressions/borrow-expression.swift b/unified/extractor/tests/corpus/swift/expressions/borrow-expression.swift new file mode 100644 index 000000000000..c754c46d9bf6 --- /dev/null +++ b/unified/extractor/tests/corpus/swift/expressions/borrow-expression.swift @@ -0,0 +1,2 @@ +let original = [1, 2, 3] +let borrowed = borrow original From 7862a67ea8f280335d716134c98b2c1e1c42d596 Mon Sep 17 00:00:00 2001 From: Simon Friis Vindum Date: Wed, 11 Nov 2026 15:28:31 +0100 Subject: [PATCH 2/4] Unified: Support Swift `borrow`, `consume`, `copy`, and `unsafe` expressions --- unified/extractor/src/languages/swift/swift.rs | 16 ++++++++++++---- .../swift/expressions/borrow-expression.output | 5 ++++- .../swift/expressions/consume-expression.output | 5 ++++- .../swift/expressions/copy-expression.output | 5 ++++- .../swift/expressions/unsafe-expression.output | 6 +++++- 5 files changed, 29 insertions(+), 8 deletions(-) diff --git a/unified/extractor/src/languages/swift/swift.rs b/unified/extractor/src/languages/swift/swift.rs index 9bfa36e3983c..481b2f55e9d4 100644 --- a/unified/extractor/src/languages/swift/swift.rs +++ b/unified/extractor/src/languages/swift/swift.rs @@ -1153,6 +1153,18 @@ fn translation_rules() -> Vec> { rule!((awaitExpr awaitKeyword: @@keyword expression: @val) => (unary_expr operator: (prefix_operator "await" location: {keyword}) operand: {val})), + rule!((borrowExpr borrowKeyword: @@keyword expression: @val) => (unary_expr + operator: (prefix_operator #{keyword}) + operand: {val})), + rule!((consumeExpr consumeKeyword: @@keyword expression: @val) => (unary_expr + operator: (prefix_operator #{keyword}) + operand: {val})), + rule!((copyExpr copyKeyword: @@keyword expression: @val) => (unary_expr + operator: (prefix_operator #{keyword}) + operand: {val})), + rule!((unsafeExpr unsafeKeyword: @@keyword expression: @val) => (unary_expr + operator: (prefix_operator #{keyword}) + operand: {val})), // Force-unwrap (`x!`) → postfix unary_expr, via swift-syntax's dedicated // `forceUnwrapExpr` node. rule!((forceUnwrapExpr expression: @e exclamationMark: @@mark) => (unary_expr @@ -1553,11 +1565,8 @@ fn translation_rules() -> Vec> { // these roots is discarded with its parent and needs no separate rule. // Explicit empty content preserves their existing representation // instead of inheriting the matched source text. - rule!((borrowExpr) => (unsupported_node "")), rule!((classRestrictionType) => (unsupported_node "")), rule!((compositionType) => (unsupported_node "")), - rule!((consumeExpr) => (unsupported_node "")), - rule!((copyExpr) => (unsupported_node "")), rule!((discardStmt) => (unsupported_node "")), rule!((fallThroughStmt) => (unsupported_node "")), rule!((ifConfigDecl) => (unsupported_node "")), @@ -1578,7 +1587,6 @@ fn translation_rules() -> Vec> { rule!((subscriptDecl) => (unsupported_node "")), rule!((suppressedType) => (unsupported_node "")), rule!((typeExpr) => (unsupported_node "")), - rule!((unsafeExpr) => (unsupported_node "")), rule!((yieldStmt) => (unsupported_node "")), // Anything reaching this final generic handler has neither a // dedicated rule nor an explicit unsupported entry. diff --git a/unified/extractor/tests/corpus/swift/expressions/borrow-expression.output b/unified/extractor/tests/corpus/swift/expressions/borrow-expression.output index 23c02f292947..22caedb6e03d 100644 --- a/unified/extractor/tests/corpus/swift/expressions/borrow-expression.output +++ b/unified/extractor/tests/corpus/swift/expressions/borrow-expression.output @@ -78,4 +78,7 @@ top_level source="⟨body⟩" variable_declaration source="⟨modifier⟩ ⟨pattern⟩ = ⟨value⟩" modifier: modifier "let" source="let" pattern: identifier "borrowed" source="borrowed" - value: unsupported_node "borrow original" source="borrow original" + value: + unary_expr source="⟨operator⟩ ⟨operand⟩" + operand: identifier "original" source="original" + operator: prefix_operator "borrow" source="borrow" diff --git a/unified/extractor/tests/corpus/swift/expressions/consume-expression.output b/unified/extractor/tests/corpus/swift/expressions/consume-expression.output index 3b9f369f8ad9..a7d7fb6ee00f 100644 --- a/unified/extractor/tests/corpus/swift/expressions/consume-expression.output +++ b/unified/extractor/tests/corpus/swift/expressions/consume-expression.output @@ -78,4 +78,7 @@ top_level source="⟨body⟩" variable_declaration source="⟨modifier⟩ ⟨pattern⟩ = ⟨value⟩" modifier: modifier "let" source="let" pattern: identifier "consumed" source="consumed" - value: unsupported_node "consume original" source="consume original" + value: + unary_expr source="⟨operator⟩ ⟨operand⟩" + operand: identifier "original" source="original" + operator: prefix_operator "consume" source="consume" diff --git a/unified/extractor/tests/corpus/swift/expressions/copy-expression.output b/unified/extractor/tests/corpus/swift/expressions/copy-expression.output index 523e8c9dddec..e1513161a2ae 100644 --- a/unified/extractor/tests/corpus/swift/expressions/copy-expression.output +++ b/unified/extractor/tests/corpus/swift/expressions/copy-expression.output @@ -78,4 +78,7 @@ top_level source="⟨body⟩" variable_declaration source="⟨modifier⟩ ⟨pattern⟩ = ⟨value⟩" modifier: modifier "let" source="let" pattern: identifier "copied" source="copied" - value: unsupported_node "copy original" source="copy original" + value: + unary_expr source="⟨operator⟩ ⟨operand⟩" + operand: identifier "original" source="original" + operator: prefix_operator "copy" source="copy" diff --git a/unified/extractor/tests/corpus/swift/expressions/unsafe-expression.output b/unified/extractor/tests/corpus/swift/expressions/unsafe-expression.output index 0acff823d2c8..91404ecf9f2b 100644 --- a/unified/extractor/tests/corpus/swift/expressions/unsafe-expression.output +++ b/unified/extractor/tests/corpus/swift/expressions/unsafe-expression.output @@ -48,4 +48,8 @@ top_level source="⟨body⟩" function_declaration source="func ⟨name_node⟩() ⟨body⟩" name_node: identifier "doWork" source="doWork" body: block "{}" source="{}" - unsupported_node "unsafe doWork()" source="unsafe doWork()" + unary_expr source="⟨operator⟩ ⟨operand⟩" + operand: + call_expr source="⟨callee⟩()" + callee: identifier "doWork" source="doWork" + operator: prefix_operator "unsafe" source="unsafe" From 4a8c7ccd2fd70d3c1ef08567e9b978fa30aba5dd Mon Sep 17 00:00:00 2001 From: Simon Friis Vindum Date: Fri, 13 Nov 2026 12:28:12 +0100 Subject: [PATCH 3/4] Unified: Add tests for `borrow`, `consume`, `copy`, and `unsafe` --- .../dataflow/modified-expressions.swift | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 unified/ql/test/library-tests/dataflow/modified-expressions.swift diff --git a/unified/ql/test/library-tests/dataflow/modified-expressions.swift b/unified/ql/test/library-tests/dataflow/modified-expressions.swift new file mode 100644 index 000000000000..7081117c0624 --- /dev/null +++ b/unified/ql/test/library-tests/dataflow/modified-expressions.swift @@ -0,0 +1,23 @@ +func testBorrow() { + let original = source("borrow") + let borrowed = borrow original + sink(borrowed) // $ MISSING: hasValueFlow=borrow +} + +func testConsume() { + let original = source("consume") + let consumed = consume original + sink(consumed) // $ MISSING: hasValueFlow=consume +} + +func testCopy() { + let original = source("copy") + let copied = copy original + sink(copied) // $ MISSING: hasValueFlow=copy +} + +func testUnsafe() { + let original = source("unsafe") + let unchecked = unsafe original + sink(unchecked) // $ MISSING: hasValueFlow=unsafe +} From 6f9cdeb83b10f432f5d6a1bfdf7020a4c87049f3 Mon Sep 17 00:00:00 2001 From: Simon Friis Vindum Date: Fri, 13 Nov 2026 14:38:06 +0100 Subject: [PATCH 4/4] Unified: Add data flow throug `borrow`, `consume`, `copy`, and `unsafe` --- .../internal/dataflow/DataFlowPluginSwift.qll | 3 +- .../dataflow/modified-expressions.swift | 8 ++-- .../test/library-tests/dataflow/test.expected | 40 +++++++++++++++++++ 3 files changed, 46 insertions(+), 5 deletions(-) diff --git a/unified/ql/lib/codeql/unified/internal/dataflow/DataFlowPluginSwift.qll b/unified/ql/lib/codeql/unified/internal/dataflow/DataFlowPluginSwift.qll index f845a5f20f3c..88ca1a400ae6 100644 --- a/unified/ql/lib/codeql/unified/internal/dataflow/DataFlowPluginSwift.qll +++ b/unified/ql/lib/codeql/unified/internal/dataflow/DataFlowPluginSwift.qll @@ -71,7 +71,8 @@ private class SwiftDataFlowPlugin extends DataFlowPlugin { ) or exists(UnaryExpr expr | - expr.getOperator().(PrefixOperator).getValue() = ["try", "try!", "try?", "await"] and + expr.getOperator().(PrefixOperator).getValue() = + ["try", "try!", "try?", "await", "borrow", "consume", "copy", "unsafe"] and node1.isResultValue(expr.getOperand()) and step.value() and node2.isResultValue(expr) diff --git a/unified/ql/test/library-tests/dataflow/modified-expressions.swift b/unified/ql/test/library-tests/dataflow/modified-expressions.swift index 7081117c0624..2543611692a1 100644 --- a/unified/ql/test/library-tests/dataflow/modified-expressions.swift +++ b/unified/ql/test/library-tests/dataflow/modified-expressions.swift @@ -1,23 +1,23 @@ func testBorrow() { let original = source("borrow") let borrowed = borrow original - sink(borrowed) // $ MISSING: hasValueFlow=borrow + sink(borrowed) // $ hasValueFlow=borrow } func testConsume() { let original = source("consume") let consumed = consume original - sink(consumed) // $ MISSING: hasValueFlow=consume + sink(consumed) // $ hasValueFlow=consume } func testCopy() { let original = source("copy") let copied = copy original - sink(copied) // $ MISSING: hasValueFlow=copy + sink(copied) // $ hasValueFlow=copy } func testUnsafe() { let original = source("unsafe") let unchecked = unsafe original - sink(unchecked) // $ MISSING: hasValueFlow=unsafe + sink(unchecked) // $ hasValueFlow=unsafe } diff --git a/unified/ql/test/library-tests/dataflow/test.expected b/unified/ql/test/library-tests/dataflow/test.expected index a479255f6d23..7438fb6e0078 100644 --- a/unified/ql/test/library-tests/dataflow/test.expected +++ b/unified/ql/test/library-tests/dataflow/test.expected @@ -364,6 +364,22 @@ edges | implicit-self.swift:85:9:85:14 | [incoming] ... .x | implicit-self.swift:85:9:85:12 | [post] self [x] | provenance | | | implicit-self.swift:85:29:85:43 | source(...) | implicit-self.swift:85:9:85:14 | [incoming] ... .x | provenance | | | implicit-self.swift:87:14:87:17 | self [x] | implicit-self.swift:87:14:87:19 | ... .x | provenance | | +| modified-expressions.swift:2:9:2:16 | original | modified-expressions.swift:3:27:3:34 | original | provenance | | +| modified-expressions.swift:2:20:2:35 | source(...) | modified-expressions.swift:2:9:2:16 | original | provenance | | +| modified-expressions.swift:3:9:3:16 | borrowed | modified-expressions.swift:4:10:4:17 | borrowed | provenance | | +| modified-expressions.swift:3:27:3:34 | original | modified-expressions.swift:3:9:3:16 | borrowed | provenance | | +| modified-expressions.swift:8:9:8:16 | original | modified-expressions.swift:9:28:9:35 | original | provenance | | +| modified-expressions.swift:8:20:8:36 | source(...) | modified-expressions.swift:8:9:8:16 | original | provenance | | +| modified-expressions.swift:9:9:9:16 | consumed | modified-expressions.swift:10:10:10:17 | consumed | provenance | | +| modified-expressions.swift:9:28:9:35 | original | modified-expressions.swift:9:9:9:16 | consumed | provenance | | +| modified-expressions.swift:14:9:14:16 | original | modified-expressions.swift:15:23:15:30 | original | provenance | | +| modified-expressions.swift:14:20:14:33 | source(...) | modified-expressions.swift:14:9:14:16 | original | provenance | | +| modified-expressions.swift:15:9:15:14 | copied | modified-expressions.swift:16:10:16:15 | copied | provenance | | +| modified-expressions.swift:15:23:15:30 | original | modified-expressions.swift:15:9:15:14 | copied | provenance | | +| modified-expressions.swift:20:9:20:16 | original | modified-expressions.swift:21:28:21:35 | original | provenance | | +| modified-expressions.swift:20:20:20:35 | source(...) | modified-expressions.swift:20:9:20:16 | original | provenance | | +| modified-expressions.swift:21:9:21:17 | unchecked | modified-expressions.swift:22:10:22:18 | unchecked | provenance | | +| modified-expressions.swift:21:28:21:35 | original | modified-expressions.swift:21:9:21:17 | unchecked | provenance | | | short-return.swift:7:33:7:46 | source(...) | short-return.swift:8:10:8:19 | getValue(...) | provenance | | | test.swift:17:10:17:23 | source(...) | test.swift:17:10:17:32 | ... + ... | provenance | | | test.swift:18:19:18:32 | source(...) | test.swift:18:10:18:32 | ... + ... | provenance | | @@ -985,6 +1001,26 @@ nodes | implicit-self.swift:86:14:86:14 | x | semmle.label | x | | implicit-self.swift:87:14:87:17 | self [x] | semmle.label | self [x] | | implicit-self.swift:87:14:87:19 | ... .x | semmle.label | ... .x | +| modified-expressions.swift:2:9:2:16 | original | semmle.label | original | +| modified-expressions.swift:2:20:2:35 | source(...) | semmle.label | source(...) | +| modified-expressions.swift:3:9:3:16 | borrowed | semmle.label | borrowed | +| modified-expressions.swift:3:27:3:34 | original | semmle.label | original | +| modified-expressions.swift:4:10:4:17 | borrowed | semmle.label | borrowed | +| modified-expressions.swift:8:9:8:16 | original | semmle.label | original | +| modified-expressions.swift:8:20:8:36 | source(...) | semmle.label | source(...) | +| modified-expressions.swift:9:9:9:16 | consumed | semmle.label | consumed | +| modified-expressions.swift:9:28:9:35 | original | semmle.label | original | +| modified-expressions.swift:10:10:10:17 | consumed | semmle.label | consumed | +| modified-expressions.swift:14:9:14:16 | original | semmle.label | original | +| modified-expressions.swift:14:20:14:33 | source(...) | semmle.label | source(...) | +| modified-expressions.swift:15:9:15:14 | copied | semmle.label | copied | +| modified-expressions.swift:15:23:15:30 | original | semmle.label | original | +| modified-expressions.swift:16:10:16:15 | copied | semmle.label | copied | +| modified-expressions.swift:20:9:20:16 | original | semmle.label | original | +| modified-expressions.swift:20:20:20:35 | source(...) | semmle.label | source(...) | +| modified-expressions.swift:21:9:21:17 | unchecked | semmle.label | unchecked | +| modified-expressions.swift:21:28:21:35 | original | semmle.label | original | +| modified-expressions.swift:22:10:22:18 | unchecked | semmle.label | unchecked | | short-return.swift:7:33:7:46 | source(...) | semmle.label | source(...) | | short-return.swift:8:10:8:19 | getValue(...) | semmle.label | getValue(...) | | test.swift:9:10:9:21 | source(...) | semmle.label | source(...) | @@ -1342,6 +1378,10 @@ testFailures | implicit-self.swift:80:14:80:19 | ... .x | implicit-self.swift:78:24:78:38 | source(...) | implicit-self.swift:80:14:80:19 | ... .x | $@ | implicit-self.swift:78:24:78:38 | source(...) | source(...) | | implicit-self.swift:86:14:86:14 | x | implicit-self.swift:85:29:85:43 | source(...) | implicit-self.swift:86:14:86:14 | x | $@ | implicit-self.swift:85:29:85:43 | source(...) | source(...) | | implicit-self.swift:87:14:87:19 | ... .x | implicit-self.swift:85:29:85:43 | source(...) | implicit-self.swift:87:14:87:19 | ... .x | $@ | implicit-self.swift:85:29:85:43 | source(...) | source(...) | +| modified-expressions.swift:4:10:4:17 | borrowed | modified-expressions.swift:2:20:2:35 | source(...) | modified-expressions.swift:4:10:4:17 | borrowed | $@ | modified-expressions.swift:2:20:2:35 | source(...) | source(...) | +| modified-expressions.swift:10:10:10:17 | consumed | modified-expressions.swift:8:20:8:36 | source(...) | modified-expressions.swift:10:10:10:17 | consumed | $@ | modified-expressions.swift:8:20:8:36 | source(...) | source(...) | +| modified-expressions.swift:16:10:16:15 | copied | modified-expressions.swift:14:20:14:33 | source(...) | modified-expressions.swift:16:10:16:15 | copied | $@ | modified-expressions.swift:14:20:14:33 | source(...) | source(...) | +| modified-expressions.swift:22:10:22:18 | unchecked | modified-expressions.swift:20:20:20:35 | source(...) | modified-expressions.swift:22:10:22:18 | unchecked | $@ | modified-expressions.swift:20:20:20:35 | source(...) | source(...) | | short-return.swift:8:10:8:19 | getValue(...) | short-return.swift:7:33:7:46 | source(...) | short-return.swift:8:10:8:19 | getValue(...) | $@ | short-return.swift:7:33:7:46 | source(...) | source(...) | | test.swift:9:10:9:21 | source(...) | test.swift:9:10:9:21 | source(...) | test.swift:9:10:9:21 | source(...) | $@ | test.swift:9:10:9:21 | source(...) | source(...) | | test.swift:17:10:17:32 | ... + ... | test.swift:17:10:17:23 | source(...) | test.swift:17:10:17:32 | ... + ... | $@ | test.swift:17:10:17:23 | source(...) | source(...) |