diff --git a/unified/extractor/ast_types.yml b/unified/extractor/ast_types.yml index fb9d88333087..28bab7c72ffd 100644 --- a/unified/extractor/ast_types.yml +++ b/unified/extractor/ast_types.yml @@ -529,15 +529,23 @@ named: # downstream. Stored properties with observers are emitted as a # variable_declaration followed by one accessor_declaration per observer # (each observer also tagged with `chained_declaration`). + # + # An accessor of kind `setter_method` is a setter invoked via call syntax, + # such as the setter of a Swift subscript. A call `x.foo(...)` that is the + # target of an assignment, or is otherwise updated in place, invokes the + # `setter_method` accessor named `foo` (whereas a call in a read position + # invokes the ordinary method `foo`). The setter takes the new value as an + # extra trailing positional parameter. accessor_declaration: modifier*: modifier name_node: identifier accessor_kind: accessor_kind + type_parameter*: type_parameter parameter*: parameter return_type?: expr body?: block - # "get", "set", or a language-specific kind like "didSet" + # "get", "set", "setter_method", or a language-specific kind like "didSet" accessor_kind: # Static or instance initializer block. That is, code that runs at initialization time of either the class or an instance. diff --git a/unified/extractor/src/languages/swift/swift.rs b/unified/extractor/src/languages/swift/swift.rs index 4d467539337e..dd65bf8d2bc5 100644 --- a/unified/extractor/src/languages/swift/swift.rs +++ b/unified/extractor/src/languages/swift/swift.rs @@ -38,6 +38,25 @@ struct SwiftContext { /// True while translating a pattern. Optional chaining in this context is /// represented as an `Optional.some` call rather than being unwrapped. in_pattern: bool, + /// The enclosing `subscriptDecl`, set while translating its accessor + /// block so the inner `accessorDecl` rule emits `subscript` methods + /// rather than property accessors. + subscript: Option, + /// True while translating the parameters of a subscript. Unlike function + /// parameters, a subscript parameter with a single name has no argument + /// label. + in_subscript_parameters: bool, +} + +/// The parts of a `subscriptDecl` shared by its getter and setter. These are +/// kept untranslated so that each emitted method gets its own copy. +#[derive(Clone)] +struct SubscriptContext { + keyword: yeast::Id, + modifiers: Vec, + type_params: Vec, + params: Vec, + return_type: yeast::Id, } impl SwiftContext { @@ -141,6 +160,77 @@ fn member_chain( ) } +/// The accessor-specific parts of a subscript getter or setter. +struct SubscriptAccessor { + /// Modifiers written on the accessor itself, such as `nonmutating`. + modifiers: Vec, + /// For a setter, the explicit name of its new-value parameter (`set(v)`), + /// if any, and the token at which to locate an implicit `newValue`. + setter: Option<(Option, yeast::Id)>, + /// The raw accessor body, if any. + body: Option, +} + +/// Emit the declaration for one accessor of a subscript: the getter is a +/// `function_declaration` named `subscript`, and the setter an +/// `accessor_declaration` of kind `setter_method`. The shared parts of the subscript (modifiers, generic and value +/// parameters and the element type) are translated afresh for each accessor, +/// so a getter/setter pair each get their own copy. A setter takes the new +/// value as an extra trailing positional parameter and has no return type. +fn subscript_method( + ctx: &mut yeast::build::BuildCtx<'_, SwiftContext>, + sub: SubscriptContext, + accessor: SubscriptAccessor, +) -> yeast::OutputId { + let is_chained = ctx.is_chained; + let (mods, type_params, params, element_type, body) = ctx.scoped(|ctx| { + ctx.reset(); + let mods = ctx.translate(sub.modifiers); + let type_params = ctx.translate(sub.type_params); + let element_type = ctx.translate(sub.return_type).into_iter().next(); + let body = ctx.translate(accessor.body).into_iter().next(); + ctx.in_subscript_parameters = true; + let params = ctx.translate(sub.params); + (mods, type_params, params, element_type, body) + }); + let body = body.unwrap_or_else(|| tree!((block))); + let kw = sub.keyword; + match accessor.setter { + Some((param_name, spec)) => { + let implicit_param_name = param_name.is_none().then_some("newValue"); + tree!((accessor_declaration + modifier: {mods} + modifier: {accessor.modifiers} + modifier: [(modifier "chained_declaration") if { is_chained }] + name_node: (identifier #{kw}) + accessor_kind: (accessor_kind "setter_method" location: {spec}) + type_parameter: {type_params} + parameter: {params} + parameter: [ + (parameter + pattern: (identifier #{param_name}) + type: {element_type} + location: {param_name}) + (parameter + modifier: (modifier "generated") + pattern: (identifier #{implicit_param_name} location: {spec}) + type: {element_type} + location: {spec}) + ] + body: {body})) + } + None => tree!((function_declaration + modifier: {mods} + modifier: {accessor.modifiers} + modifier: [(modifier "chained_declaration") if { is_chained }] + name_node: (identifier #{kw}) + type_parameter: {type_params} + parameter: {params} + return_type: {element_type} + body: {body})), + } +} + fn translation_rules() -> Vec> { yeast::rules! { input: "swift_node_types.yml", @@ -402,6 +492,89 @@ fn translation_rules() -> Vec> { result } ), + // ---- Subscripts ---- + // A subscript declaration becomes a `function_declaration` named + // `subscript` for its getter and, if it has a setter, an + // `accessor_declaration` of kind `setter_method` named `subscript`. + // A subscript with an explicit accessor block publishes its shared parts + // in `ctx` and lets the `accessorDecl` rule below emit one method per + // accessor; each one after the first is tagged `chained_declaration`. + rule!( + (subscriptDecl + modifiers: _* @@mods + subscriptKeyword: @@kw + genericParameterClause: (genericParameterClause parameters: _* @@type_params)? + parameterClause: (functionParameterClause parameters: _* @@params) + returnClause: (returnClause type: @@ret) + accessorBlock: (accessorBlock accessors: (accessorDecl)+ @@accessors)) + => + member* { + ctx.subscript = Some(SubscriptContext { + keyword: kw, + modifiers: mods, + type_params, + params, + return_type: ret, + }); + let mut result = Vec::new(); + for acc in accessors.into_iter() { + ctx.is_chained = !result.is_empty(); + result.extend(ctx.translate(acc)); + } + result + } + ), + // A subscript with a getter-only body (`subscript(i: Int) -> T { … }`). + rule!( + (subscriptDecl + modifiers: _* @@mods + subscriptKeyword: @@kw + genericParameterClause: (genericParameterClause parameters: _* @@type_params)? + parameterClause: (functionParameterClause parameters: _* @@params) + returnClause: (returnClause type: @@ret) + accessorBlock: _? @@body) + => + function_declaration { + let sub = SubscriptContext { + keyword: kw, + modifiers: mods, + type_params, + params, + return_type: ret, + }; + ctx.is_chained = false; + subscript_method(&mut ctx, sub, SubscriptAccessor { + modifiers: Vec::new(), + setter: None, + body, + }) + } + ), + // An accessor of a subscript (see above). Accessor kinds other than + // `get` and `set` (such as `_modify`) are not supported. + rule!( + (accessorDecl + modifiers: _* @acc_mods + accessorSpecifier: @@spec + parameters: (accessorParameters name: @@param_name)? + body: _? @@body) + where ctx.subscript.is_some() + => + member { + let sub = ctx.subscript.clone().expect("checked by guard"); + let spec_text = ctx.source_text(spec); + if spec_text == "get" || spec_text == "set" { + let setter = (spec_text == "set").then_some((param_name, spec)); + subscript_method(&mut ctx, sub, SubscriptAccessor { + modifiers: acc_mods, + setter, + body, + }) + } else { + tree!((unsupported_node "")) + } + } + ), // Each `accessorDecl` becomes an `accessor_declaration`, reading the // property name/type and the modifiers from `ctx`. The // accessor kind comes straight from the specifier keyword @@ -668,6 +841,27 @@ fn translation_rules() -> Vec> { return_type: {ret} body: (block)) ), + // A subscript parameter with a single name has no argument label + // (`subscript(i: Int)` is called as `x[0]`). It is translated in a reset + // context so its type and default value are not affected. + rule!( + (functionParameter + firstName: @@first + secondName: _? @@second + type: @@ty + defaultValue: (initializerClause value: @@val)?) + where ctx.in_subscript_parameters && second.is_none() + => + parameter { + ctx.reset(); + let ty = ctx.translate(ty).into_iter().next(); + let val = ctx.translate(val).into_iter().next(); + tree!((parameter + pattern: {parameter_pattern(&mut ctx, first)} + type: {ty} + default: {val})) + } + ), // A function parameter. With two names (`firstName`+`secondName`) the // first is the external argument label and the second the internal name; // with one name it is both the external and internal name. @@ -1054,22 +1248,29 @@ fn translation_rules() -> Vec> { => (key_value_pair key: {key} value: {value}) ), - // A subscript access (`xs[0]`) is modelled as a call. swift-syntax does - // report a distinct `subscriptCallExpr`, so giving - // subscripts their own shape needs only a `subscript_expr` node in - // ast_types.yml and a remap here. + // A subscript access (`xs[0]`) is modelled as a call to a member named + // `subscript` on the subscripted value, i.e. `xs.subscript(0)`. This + // matches the `subscript` methods emitted for `subscriptDecl`s. rule!( (subscriptCallExpr calledExpression: @callee + leftSquare: @@left_square arguments: _* @args trailingClosure: _? @trailing additionalTrailingClosures: _* @additional) => - (call_expr - callee: {callee} - argument: {args} - argument: {trailing.map(|trailing| tree!((argument value: {trailing})))} - argument: {additional}) + call_expr { + let member = tree!((member_access_expr + base: {callee} + member_name_node: (identifier "subscript" location: {left_square}) + location: {callee} + location: {left_square})); + tree!((call_expr + callee: {member} + argument: {args} + argument: {trailing.map(|trailing| tree!((argument value: {trailing})))} + argument: {additional})) + } ), // ---- Optionals and errors ---- // Optional chaining — unwrap the marker @@ -1569,7 +1770,6 @@ fn translation_rules() -> Vec> { rule!((poundSourceLocation) => (unsupported_node "")), rule!((precedenceGroupDecl) => (unsupported_node "")), rule!((someOrAnyType) => (unsupported_node "")), - rule!((subscriptDecl) => (unsupported_node "")), rule!((suppressedType) => (unsupported_node "")), rule!((typeExpr) => (unsupported_node "")), rule!((unsafeExpr) => (unsupported_node "")), diff --git a/unified/extractor/tests/corpus/swift/collections/dictionary-subscript.output b/unified/extractor/tests/corpus/swift/collections/dictionary-subscript.output index b7719365bbc6..08711dcbfc8d 100644 --- a/unified/extractor/tests/corpus/swift/collections/dictionary-subscript.output +++ b/unified/extractor/tests/corpus/swift/collections/dictionary-subscript.output @@ -49,8 +49,11 @@ top_level source="⟨body⟩" modifier: modifier "let" source="let" pattern: identifier "v" source="v" value: - call_expr source="⟨callee⟩[⟨argument⟩]" - callee: identifier "d" source="d" + call_expr source="⟨callee⟩⟨argument⟩]" + callee: + member_access_expr source="⟨base⟩⟨member_name_node⟩" + base: identifier "d" source="d" + member_name_node: identifier "subscript" source="[" argument: argument source="⟨value⟩" value: string_literal "\"key\"" source="\"key\"" diff --git a/unified/extractor/tests/corpus/swift/collections/subscript-access.output b/unified/extractor/tests/corpus/swift/collections/subscript-access.output index 771004ff717f..2681054a0972 100644 --- a/unified/extractor/tests/corpus/swift/collections/subscript-access.output +++ b/unified/extractor/tests/corpus/swift/collections/subscript-access.output @@ -46,8 +46,11 @@ top_level source="⟨body⟩" modifier: modifier "let" source="let" pattern: identifier "first" source="first" value: - call_expr source="⟨callee⟩[⟨argument⟩]" - callee: identifier "xs" source="xs" + call_expr source="⟨callee⟩⟨argument⟩]" + callee: + member_access_expr source="⟨base⟩⟨member_name_node⟩" + base: identifier "xs" source="xs" + member_name_node: identifier "subscript" source="[" argument: argument source="⟨value⟩" value: int_literal "0" source="0" diff --git a/unified/extractor/tests/corpus/swift/expressions/subscript-call.output b/unified/extractor/tests/corpus/swift/expressions/subscript-call.output index 138caa29eb62..351cc6441714 100644 --- a/unified/extractor/tests/corpus/swift/expressions/subscript-call.output +++ b/unified/extractor/tests/corpus/swift/expressions/subscript-call.output @@ -99,13 +99,19 @@ top_level source="⟨body⟩" body: block source="⟨stmt⟩\n\n⟨stmt⟩" stmt: - call_expr source="⟨callee⟩[⟨argument⟩]" - callee: identifier "handlers" source="handlers" + call_expr source="⟨callee⟩⟨argument⟩]" + callee: + member_access_expr source="⟨base⟩⟨member_name_node⟩" + base: identifier "handlers" source="handlers" + member_name_node: identifier "subscript" source="[" argument: argument source="⟨value⟩" value: int_literal "0" source="0" - call_expr source="⟨callee⟩[⟨argument⟩] ⟨argument⟩ ⟨argument⟩ ⟨argument⟩" - callee: identifier "handlers" source="handlers" + call_expr source="⟨callee⟩⟨argument⟩] ⟨argument⟩ ⟨argument⟩ ⟨argument⟩" + callee: + member_access_expr source="⟨base⟩⟨member_name_node⟩" + base: identifier "handlers" source="handlers" + member_name_node: identifier "subscript" source="[" argument: argument source="⟨value⟩" value: int_literal "0" source="0" diff --git a/unified/extractor/tests/corpus/swift/subscripts/subscript-assignment.output b/unified/extractor/tests/corpus/swift/subscripts/subscript-assignment.output new file mode 100644 index 000000000000..d84fd8cebd41 --- /dev/null +++ b/unified/extractor/tests/corpus/swift/subscripts/subscript-assignment.output @@ -0,0 +1,154 @@ +xs[0] = 1 +xs[at: 1, 2] += 3 +m["a"]["b"] = c + +--- + +sourceFile + endOfFileToken: endOfFile + statements: + codeBlockItem + item: + infixOperatorExpr + operator: + assignmentExpr + equal: = + leftOperand: + subscriptCallExpr + leftSquare: [ + rightSquare: ] + arguments: + labeledExpr + expression: + integerLiteralExpr + literal: integerLiteral "0" + additionalTrailingClosures: + calledExpression: + declReferenceExpr + baseName: identifier "xs" + rightOperand: + integerLiteralExpr + literal: integerLiteral "1" + codeBlockItem + item: + infixOperatorExpr + operator: + binaryOperatorExpr + operator: binaryOperator "+=" + leftOperand: + subscriptCallExpr + leftSquare: [ + rightSquare: ] + arguments: + labeledExpr + colon: : + label: identifier "at" + expression: + integerLiteralExpr + literal: integerLiteral "1" + trailingComma: , + labeledExpr + expression: + integerLiteralExpr + literal: integerLiteral "2" + additionalTrailingClosures: + calledExpression: + declReferenceExpr + baseName: identifier "xs" + rightOperand: + integerLiteralExpr + literal: integerLiteral "3" + codeBlockItem + item: + infixOperatorExpr + operator: + assignmentExpr + equal: = + leftOperand: + subscriptCallExpr + leftSquare: [ + rightSquare: ] + arguments: + labeledExpr + expression: + stringLiteralExpr + closingQuote: " + openingQuote: " + segments: + stringSegment + content: stringSegment "b" + additionalTrailingClosures: + calledExpression: + subscriptCallExpr + leftSquare: [ + rightSquare: ] + arguments: + labeledExpr + expression: + stringLiteralExpr + closingQuote: " + openingQuote: " + segments: + stringSegment + content: stringSegment "a" + additionalTrailingClosures: + calledExpression: + declReferenceExpr + baseName: identifier "m" + rightOperand: + declReferenceExpr + baseName: identifier "c" + +--- + +top_level source="⟨body⟩" + body: + block source="⟨stmt⟩\n⟨stmt⟩\n⟨stmt⟩" + stmt: + binary_expr source="⟨left⟩ ⟨operator⟩ ⟨right⟩" + left: + call_expr source="⟨callee⟩⟨argument⟩]" + callee: + member_access_expr source="⟨base⟩⟨member_name_node⟩" + base: identifier "xs" source="xs" + member_name_node: identifier "subscript" source="[" + argument: + argument source="⟨value⟩" + value: int_literal "0" source="0" + operator: infix_operator "=" source="=" + right: int_literal "1" source="1" + binary_expr source="⟨left⟩ ⟨operator⟩ ⟨right⟩" + left: + call_expr source="⟨callee⟩⟨argument⟩, ⟨argument⟩]" + callee: + member_access_expr source="⟨base⟩⟨member_name_node⟩" + base: identifier "xs" source="xs" + member_name_node: identifier "subscript" source="[" + argument: + argument source="⟨name_node⟩: ⟨value⟩" + name_node: identifier "at" source="at" + value: int_literal "1" source="1" + argument source="⟨value⟩" + value: int_literal "2" source="2" + operator: infix_operator "+=" source="+=" + right: int_literal "3" source="3" + binary_expr source="⟨left⟩ ⟨operator⟩ ⟨right⟩" + left: + call_expr source="⟨callee⟩⟨argument⟩]" + callee: + member_access_expr source="⟨base⟩⟨member_name_node⟩" + base: + call_expr source="⟨callee⟩⟨argument⟩]" + callee: + member_access_expr source="⟨base⟩⟨member_name_node⟩" + base: identifier "m" source="m" + member_name_node: identifier "subscript" source="[" + argument: + argument source="⟨value⟩" + value: string_literal "\"a\"" source="\"a\"" + member_name_node: identifier "subscript" source="[" + argument: + argument source="⟨value⟩" + value: string_literal "\"b\"" source="\"b\"" + operator: infix_operator "=" source="=" + right: identifier "c" source="c" diff --git a/unified/extractor/tests/corpus/swift/subscripts/subscript-assignment.swift b/unified/extractor/tests/corpus/swift/subscripts/subscript-assignment.swift new file mode 100644 index 000000000000..a34d687f4565 --- /dev/null +++ b/unified/extractor/tests/corpus/swift/subscripts/subscript-assignment.swift @@ -0,0 +1,3 @@ +xs[0] = 1 +xs[at: 1, 2] += 3 +m["a"]["b"] = c diff --git a/unified/extractor/tests/corpus/swift/subscripts/subscript-get-set.output b/unified/extractor/tests/corpus/swift/subscripts/subscript-get-set.output new file mode 100644 index 000000000000..95adcbba760f --- /dev/null +++ b/unified/extractor/tests/corpus/swift/subscripts/subscript-get-set.output @@ -0,0 +1,330 @@ +struct S { + var data: [Int] + subscript(at index: Int, _ other: Int) -> Int { + get { data[index] } + nonmutating set(v) { print(v) } + } + static subscript(key: T) -> T { + set { print(newValue) } + get { key } + } +} + +--- + +sourceFile + endOfFileToken: endOfFile + statements: + codeBlockItem + item: + structDecl + attributes: + modifiers: + name: identifier "S" + memberBlock: + memberBlock + leftBrace: { + rightBrace: } + members: + memberBlockItem + decl: + variableDecl + attributes: + modifiers: + bindingSpecifier: var + bindings: + patternBinding + pattern: + identifierPattern + identifier: identifier "data" + typeAnnotation: + typeAnnotation + colon: : + type: + arrayType + leftSquare: [ + rightSquare: ] + element: + identifierType + name: identifier "Int" + memberBlockItem + decl: + subscriptDecl + attributes: + modifiers: + parameterClause: + functionParameterClause + leftParen: ( + rightParen: ) + parameters: + functionParameter + colon: : + attributes: + modifiers: + trailingComma: , + type: + identifierType + name: identifier "Int" + firstName: identifier "at" + secondName: identifier "index" + functionParameter + colon: : + attributes: + modifiers: + type: + identifierType + name: identifier "Int" + firstName: _ + secondName: identifier "other" + returnClause: + returnClause + arrow: -> + type: + identifierType + name: identifier "Int" + accessorBlock: + accessorBlock + accessors: + accessorDecl + accessorSpecifier: get + attributes: + body: + codeBlock + leftBrace: { + rightBrace: } + statements: + codeBlockItem + item: + subscriptCallExpr + leftSquare: [ + rightSquare: ] + arguments: + labeledExpr + expression: + declReferenceExpr + baseName: identifier "index" + additionalTrailingClosures: + calledExpression: + declReferenceExpr + baseName: identifier "data" + modifiers: + accessorDecl + accessorSpecifier: set + attributes: + body: + codeBlock + leftBrace: { + rightBrace: } + statements: + codeBlockItem + item: + functionCallExpr + leftParen: ( + rightParen: ) + arguments: + labeledExpr + expression: + declReferenceExpr + baseName: identifier "v" + additionalTrailingClosures: + calledExpression: + declReferenceExpr + baseName: identifier "print" + modifiers: + declModifier + name: nonmutating + parameters: + accessorParameters + leftParen: ( + rightParen: ) + name: identifier "v" + leftBrace: { + rightBrace: } + subscriptKeyword: subscript + memberBlockItem + decl: + subscriptDecl + attributes: + modifiers: + declModifier + name: static + genericParameterClause: + genericParameterClause + parameters: + genericParameter + attributes: + name: identifier "T" + leftAngle: < + rightAngle: > + parameterClause: + functionParameterClause + leftParen: ( + rightParen: ) + parameters: + functionParameter + colon: : + attributes: + modifiers: + type: + identifierType + name: identifier "T" + firstName: identifier "key" + returnClause: + returnClause + arrow: -> + type: + identifierType + name: identifier "T" + accessorBlock: + accessorBlock + accessors: + accessorDecl + accessorSpecifier: set + attributes: + body: + codeBlock + leftBrace: { + rightBrace: } + statements: + codeBlockItem + item: + functionCallExpr + leftParen: ( + rightParen: ) + arguments: + labeledExpr + expression: + declReferenceExpr + baseName: identifier "newValue" + additionalTrailingClosures: + calledExpression: + declReferenceExpr + baseName: identifier "print" + modifiers: + accessorDecl + accessorSpecifier: get + attributes: + body: + codeBlock + leftBrace: { + rightBrace: } + statements: + codeBlockItem + item: + declReferenceExpr + baseName: identifier "key" + modifiers: + leftBrace: { + rightBrace: } + subscriptKeyword: subscript + structKeyword: struct + +--- + +top_level source="⟨body⟩" + body: + block source="⟨stmt⟩" + stmt: + class_like_declaration source="⟨modifier⟩ ⟨name_node⟩ {\n ⟨member⟩\n ⟨member⟩⟨member⟩\n }\n ⟨member⟩⟨member⟩\n }\n}" + modifier: modifier "struct" source="struct" + name_node: identifier "S" source="S" + member: + variable_declaration source="⟨modifier⟩ ⟨pattern⟩: ⟨type⟩" + modifier: modifier "var" source="var" + pattern: identifier "data" source="data" + type: + generic_type_expr source="[⟨type_argument⟩]" + base: identifier "Array" source="" + type_argument: identifier "Int" source="Int" + function_declaration source="⟨name_node⟩(⟨parameter⟩, ⟨parameter⟩) -> ⟨return_type⟩ {\n get ⟨body⟩" + name_node: identifier "subscript" source="subscript" + parameter: + parameter source="⟨external_name_node⟩ ⟨pattern⟩: ⟨type⟩" + external_name_node: identifier "at" source="at" + type: identifier "Int" source="Int" + pattern: identifier "index" source="index" + parameter source="⟨external_name_node⟩ ⟨pattern⟩: ⟨type⟩" + external_name_node: identifier "_" source="_" + type: identifier "Int" source="Int" + pattern: identifier "other" source="other" + return_type: identifier "Int" source="Int" + body: + block source="{ ⟨stmt⟩ }" + stmt: + call_expr source="⟨callee⟩⟨argument⟩]" + callee: + member_access_expr source="⟨base⟩⟨member_name_node⟩" + base: identifier "data" source="data" + member_name_node: identifier "subscript" source="[" + argument: + argument source="⟨value⟩" + value: identifier "index" source="index" + accessor_declaration source="⟨name_node⟩(⟨parameter⟩, ⟨parameter⟩) -> Int {\n get { data[index] }\n ⟨modifier⟩ ⟨accessor_kind⟩(⟨parameter⟩) ⟨body⟩" + modifier: + modifier "nonmutating" source="nonmutating" + modifier "chained_declaration" source="" + name_node: identifier "subscript" source="subscript" + accessor_kind: accessor_kind "setter_method" source="set" + parameter: + parameter source="⟨external_name_node⟩ ⟨pattern⟩: ⟨type⟩" + external_name_node: identifier "at" source="at" + type: identifier "Int" source="Int" + pattern: identifier "index" source="index" + parameter source="⟨external_name_node⟩ ⟨pattern⟩: ⟨type⟩" + external_name_node: identifier "_" source="_" + type: identifier "Int" source="Int" + pattern: identifier "other" source="other" + parameter source="⟨pattern⟩" + type: identifier "Int" source="Int" (external) + pattern: identifier "v" source="v" + body: + block source="{ ⟨stmt⟩ }" + stmt: + call_expr source="⟨callee⟩(⟨argument⟩)" + callee: identifier "print" source="print" + argument: + argument source="⟨value⟩" + value: identifier "v" source="v" + accessor_declaration source="⟨modifier⟩ ⟨name_node⟩<⟨type_parameter⟩>(⟨parameter⟩) -> T {\n ⟨accessor_kind⟩⟨parameter⟩ ⟨body⟩" + modifier: modifier "static" source="static" + name_node: identifier "subscript" source="subscript" + accessor_kind: accessor_kind "setter_method" source="set" + type_parameter: + type_parameter source="⟨name_node⟩" + name_node: identifier "T" source="T" + parameter: + parameter source="⟨pattern⟩: ⟨type⟩" + type: identifier "T" source="T" + pattern: identifier "key" source="key" + parameter source="⟨pattern⟩" + modifier: modifier "generated" source="" + type: identifier "T" source="T" (external) + pattern: identifier "newValue" source="set" + body: + block source="{ ⟨stmt⟩ }" + stmt: + call_expr source="⟨callee⟩(⟨argument⟩)" + callee: identifier "print" source="print" + argument: + argument source="⟨value⟩" + value: identifier "newValue" source="newValue" + function_declaration source="⟨modifier⟩ ⟨name_node⟩<⟨type_parameter⟩>(⟨parameter⟩) -> ⟨return_type⟩ {\n set { print(newValue) }\n get ⟨body⟩" + modifier: + modifier "static" source="static" + modifier "chained_declaration" source="" + name_node: identifier "subscript" source="subscript" + type_parameter: + type_parameter source="⟨name_node⟩" + name_node: identifier "T" source="T" + parameter: + parameter source="⟨pattern⟩: ⟨type⟩" + type: identifier "T" source="T" + pattern: identifier "key" source="key" + return_type: identifier "T" source="T" + body: + block source="{ ⟨stmt⟩ }" + stmt: identifier "key" source="key" + constructor_declaration source="" + modifier: modifier "generated" source="" + name_node: identifier "init" source="" + body: block source="" diff --git a/unified/extractor/tests/corpus/swift/subscripts/subscript-get-set.swift b/unified/extractor/tests/corpus/swift/subscripts/subscript-get-set.swift new file mode 100644 index 000000000000..f97ac3907068 --- /dev/null +++ b/unified/extractor/tests/corpus/swift/subscripts/subscript-get-set.swift @@ -0,0 +1,11 @@ +struct S { + var data: [Int] + subscript(at index: Int, _ other: Int) -> Int { + get { data[index] } + nonmutating set(v) { print(v) } + } + static subscript(key: T) -> T { + set { print(newValue) } + get { key } + } +} diff --git a/unified/extractor/tests/corpus/swift/subscripts/subscript-getter-only.output b/unified/extractor/tests/corpus/swift/subscripts/subscript-getter-only.output new file mode 100644 index 000000000000..2c25b9fed9b9 --- /dev/null +++ b/unified/extractor/tests/corpus/swift/subscripts/subscript-getter-only.output @@ -0,0 +1,94 @@ +struct S { + subscript(i: Int) -> Int { + i * 2 + } +} + +--- + +sourceFile + endOfFileToken: endOfFile + statements: + codeBlockItem + item: + structDecl + attributes: + modifiers: + name: identifier "S" + memberBlock: + memberBlock + leftBrace: { + rightBrace: } + members: + memberBlockItem + decl: + subscriptDecl + attributes: + modifiers: + parameterClause: + functionParameterClause + leftParen: ( + rightParen: ) + parameters: + functionParameter + colon: : + attributes: + modifiers: + type: + identifierType + name: identifier "Int" + firstName: identifier "i" + returnClause: + returnClause + arrow: -> + type: + identifierType + name: identifier "Int" + accessorBlock: + accessorBlock + accessors: + codeBlockItem + item: + infixOperatorExpr + operator: + binaryOperatorExpr + operator: binaryOperator "*" + leftOperand: + declReferenceExpr + baseName: identifier "i" + rightOperand: + integerLiteralExpr + literal: integerLiteral "2" + leftBrace: { + rightBrace: } + subscriptKeyword: subscript + structKeyword: struct + +--- + +top_level source="⟨body⟩" + body: + block source="⟨stmt⟩" + stmt: + class_like_declaration source="⟨modifier⟩ ⟨name_node⟩ {\n ⟨member⟩\n}" + modifier: modifier "struct" source="struct" + name_node: identifier "S" source="S" + member: + function_declaration source="⟨name_node⟩(⟨parameter⟩) -> ⟨return_type⟩ ⟨body⟩" + name_node: identifier "subscript" source="subscript" + parameter: + parameter source="⟨pattern⟩: ⟨type⟩" + type: identifier "Int" source="Int" + pattern: identifier "i" source="i" + return_type: identifier "Int" source="Int" + body: + block source="{\n ⟨stmt⟩\n }" + stmt: + binary_expr source="⟨left⟩ ⟨operator⟩ ⟨right⟩" + left: identifier "i" source="i" + operator: infix_operator "*" source="*" + right: int_literal "2" source="2" + constructor_declaration source="" + modifier: modifier "generated" source="" + name_node: identifier "init" source="" + body: block source="" diff --git a/unified/extractor/tests/corpus/swift/subscripts/subscript-getter-only.swift b/unified/extractor/tests/corpus/swift/subscripts/subscript-getter-only.swift new file mode 100644 index 000000000000..8d0e072f85df --- /dev/null +++ b/unified/extractor/tests/corpus/swift/subscripts/subscript-getter-only.swift @@ -0,0 +1,5 @@ +struct S { + subscript(i: Int) -> Int { + i * 2 + } +} diff --git a/unified/extractor/tests/corpus/swift/subscripts/subscript-protocol-requirement.output b/unified/extractor/tests/corpus/swift/subscripts/subscript-protocol-requirement.output new file mode 100644 index 000000000000..4a5833d5c58a --- /dev/null +++ b/unified/extractor/tests/corpus/swift/subscripts/subscript-protocol-requirement.output @@ -0,0 +1,91 @@ +protocol P { + subscript(i: Int) -> String { get set } +} + +--- + +sourceFile + endOfFileToken: endOfFile + statements: + codeBlockItem + item: + protocolDecl + attributes: + modifiers: + name: identifier "P" + memberBlock: + memberBlock + leftBrace: { + rightBrace: } + members: + memberBlockItem + decl: + subscriptDecl + attributes: + modifiers: + parameterClause: + functionParameterClause + leftParen: ( + rightParen: ) + parameters: + functionParameter + colon: : + attributes: + modifiers: + type: + identifierType + name: identifier "Int" + firstName: identifier "i" + returnClause: + returnClause + arrow: -> + type: + identifierType + name: identifier "String" + accessorBlock: + accessorBlock + accessors: + accessorDecl + accessorSpecifier: get + attributes: + modifiers: + accessorDecl + accessorSpecifier: set + attributes: + modifiers: + leftBrace: { + rightBrace: } + subscriptKeyword: subscript + protocolKeyword: protocol + +--- + +top_level source="⟨body⟩" + body: + block source="⟨stmt⟩" + stmt: + class_like_declaration source="⟨modifier⟩ ⟨name_node⟩ {\n ⟨member⟩⟨member⟩ }\n}" + modifier: modifier "protocol" source="protocol" + name_node: identifier "P" source="P" + member: + function_declaration source="⟨name_node⟩(⟨parameter⟩) -> ⟨return_type⟩ { get" + name_node: identifier "subscript" source="subscript" + parameter: + parameter source="⟨pattern⟩: ⟨type⟩" + type: identifier "Int" source="Int" + pattern: identifier "i" source="i" + return_type: identifier "String" source="String" + body: block source="" + accessor_declaration source="⟨name_node⟩(⟨parameter⟩) -> String { get ⟨accessor_kind⟩⟨parameter⟩" + modifier: modifier "chained_declaration" source="" + name_node: identifier "subscript" source="subscript" + accessor_kind: accessor_kind "setter_method" source="set" + parameter: + parameter source="⟨pattern⟩: ⟨type⟩" + type: identifier "Int" source="Int" + pattern: identifier "i" source="i" + parameter source="⟨pattern⟩" + modifier: modifier "generated" source="" + type: identifier "String" source="String" (external) + pattern: identifier "newValue" source="set" + body: block source="" diff --git a/unified/extractor/tests/corpus/swift/subscripts/subscript-protocol-requirement.swift b/unified/extractor/tests/corpus/swift/subscripts/subscript-protocol-requirement.swift new file mode 100644 index 000000000000..a11dd62de0de --- /dev/null +++ b/unified/extractor/tests/corpus/swift/subscripts/subscript-protocol-requirement.swift @@ -0,0 +1,3 @@ +protocol P { + subscript(i: Int) -> String { get set } +} diff --git a/unified/ql/consistency-queries/DataFlowConsistency.ql b/unified/ql/consistency-queries/DataFlowConsistency.ql index 71cd02531030..a57ab33e5743 100644 --- a/unified/ql/consistency-queries/DataFlowConsistency.ql +++ b/unified/ql/consistency-queries/DataFlowConsistency.ql @@ -21,6 +21,12 @@ module ConsistencyInput implements InputSig { // In summaries, 'inout' parameters result in flow directly into the post-update node for an argument n instanceof TSummaryNode } + + predicate multipleArgumentCallExclude(DataFlowInput::ArgumentNode arg, DataFlowCall call) { + // When a call expression is updated in place, such as `x[i]` in `x[i] += y`, it invokes both + // a getter and a setter, which share the explicit arguments. + exists(CallExpr e | e = call.asSetterCall() | arg.asExpr() = e.getAnArgument().getValue()) + } } module ConsistencyOutput = diff --git a/unified/ql/lib/codeql/unified/internal/Ast.qll b/unified/ql/lib/codeql/unified/internal/Ast.qll index 5f410460536e..91aba1b60327 100644 --- a/unified/ql/lib/codeql/unified/internal/Ast.qll +++ b/unified/ql/lib/codeql/unified/internal/Ast.qll @@ -128,6 +128,14 @@ module Unified { unified_accessor_declaration_return_type(this, result) } + /** Gets the node corresponding to the field `type_parameter`. */ + final F::TypeParameter getTypeParameter(int i) { + unified_accessor_declaration_type_parameter(this, i, result) + } + + /** Gets the node corresponding to the field `type_parameter`. */ + final F::TypeParameter getATypeParameter() { result = this.getTypeParameter(_) } + /** Gets a field or child node of this node. */ final override F::AstNode getAFieldOrChild() { unified_accessor_declaration_def(this, result, _) or @@ -135,7 +143,8 @@ module Unified { unified_accessor_declaration_modifier(this, _, result) or unified_accessor_declaration_def(this, _, result) or unified_accessor_declaration_parameter(this, _, result) or - unified_accessor_declaration_return_type(this, result) + unified_accessor_declaration_return_type(this, result) or + unified_accessor_declaration_type_parameter(this, _, result) } } @@ -1628,6 +1637,8 @@ module Unified { or result = node.(AccessorDeclaration).getReturnType() and i = -1 and name = "getReturnType" or + result = node.(AccessorDeclaration).getTypeParameter(i) and name = "getTypeParameter" + or result = node.(Argument).getModifier(i) and name = "getModifier" or result = node.(Argument).getNameNode() and i = -1 and name = "getNameNode" diff --git a/unified/ql/lib/codeql/unified/internal/FacadeAst.qll b/unified/ql/lib/codeql/unified/internal/FacadeAst.qll index d17e832d4202..d602ae9e71cb 100644 --- a/unified/ql/lib/codeql/unified/internal/FacadeAst.qll +++ b/unified/ql/lib/codeql/unified/internal/FacadeAst.qll @@ -475,6 +475,24 @@ module Unified { SetterDeclaration() { this.getAccessorKind().getValue() = "set" } } + /** + * An accessor declaration of kind `setter_method`, representing a setter that is invoked via call + * syntax when a call appears as an assignment target or is otherwise updated in place. Such a call + * to `foo` invokes the `setter_method` accessor named `foo`, rather than the ordinary method `foo`. + * + * The new value is passed as an additional trailing positional parameter. For example, the + * setter of a Swift subscript declaration: + * ```swift + * subscript(i: Int) -> String { + * get { "42" } + * set { print(newValue) } // setter method declaration, with parameters `i` and `newValue` + * } + * ``` + */ + final class SetterMethodDeclaration extends AccessorDeclaration { + SetterMethodDeclaration() { this.getAccessorKind().getValue() = "setter_method" } + } + /** An `if` statement or conditional expression. */ class IfExpr extends G::IfExpr { /** Gets then `then` or `else` branch. */ diff --git a/unified/ql/lib/codeql/unified/internal/dataflow/CallGraph.qll b/unified/ql/lib/codeql/unified/internal/dataflow/CallGraph.qll index b035a03c817a..64ed6699f1e4 100644 --- a/unified/ql/lib/codeql/unified/internal/dataflow/CallGraph.qll +++ b/unified/ql/lib/codeql/unified/internal/dataflow/CallGraph.qll @@ -11,5 +11,7 @@ DataFlowCallable viableCallable(DataFlowCall c) { or result.asSourceCallable() = T::resolveAccessorAccess(c.asSetterCall()).(SetterDeclaration) or - result.asSummarizedCallable().matchesCall(c.asExplicitCall()) + result.asSourceCallable() = T::resolveSetterCallTarget(c.asSetterCall()) + or + result.asSummarizedCallable().matchesCall(c) } diff --git a/unified/ql/lib/codeql/unified/internal/dataflow/DataFlowCall.qll b/unified/ql/lib/codeql/unified/internal/dataflow/DataFlowCall.qll index 3d4b32e191fc..c0dea00e66ac 100644 --- a/unified/ql/lib/codeql/unified/internal/dataflow/DataFlowCall.qll +++ b/unified/ql/lib/codeql/unified/internal/dataflow/DataFlowCall.qll @@ -6,7 +6,8 @@ private import codeql.unified.internal.typeinference.TypeInference as T private newtype TDataFlowCall = TExplicitCall(CallExpr call) { - not isInBindingContext(call, _) // ignore constructor patterns + not isInBindingContext(call, _) and // ignore constructor patterns + not isInAssignmentContext(call, any(AssignExpr a)) // only invokes a setter } or TGetterCall(Expr expr) { hasResultValue(expr) and @@ -20,7 +21,16 @@ private newtype TDataFlowCall = hasPostUpdateAfterCallOrAssign(expr, _) and mayWriteBackViaSetterCall(expr) ) and - T::resolveAccessorAccess(expr) instanceof SetterDeclaration + ( + T::resolveAccessorAccess(expr) instanceof SetterDeclaration + or + exists(T::resolveSetterCallTarget(expr)) + ) + or + // Assigning to a method call can only invoke a setter. Include such calls even if the setter + // can't be resolved, so library models can target them. + isInAssignmentContext(expr, _) and + expr.(CallExpr).getCallee() instanceof MemberAccessExpr } or TSummaryCall(SummarizedCallable callable, SummaryNode callee) { FlowSummaryOutput::summaryCallbackRange(callable, callee) @@ -39,9 +49,25 @@ class DataFlowCall extends TDataFlowCall { /** Gets the member/variable access for which this represents the call to a getter. */ Expr asGetterCall() { this = TGetterCall(result) } - /** Gets the member/variable access for which this represents the call to a setter. */ + /** + * Gets the member/variable access or call expression for which this represents the call to a setter. + * + * A call expression invokes a setter when it is the target of an assignment or is otherwise updated in place, + * such as `x[i]` in `x[i] = y` or `x[i].f = y` in Swift. + */ Expr asSetterCall() { this = TSetterCall(result) } + /** + * Gets the call expression that syntactically represents this call, if any. + * + * This is either an explicit call or a call expression that invokes a setter. + */ + CallExpr getCallExpr() { + result = this.asExplicitCall() + or + result = this.asSetterCall() + } + /** Holds if this is a synthesized call to `callee` in `callable`. */ predicate isSummaryCall(SummarizedCallable callable, SummaryNode callee) { this = TSummaryCall(callable, callee) diff --git a/unified/ql/lib/codeql/unified/internal/dataflow/DataFlowGraph.qll b/unified/ql/lib/codeql/unified/internal/dataflow/DataFlowGraph.qll index d804e790b7a7..6c8f577f5d7d 100644 --- a/unified/ql/lib/codeql/unified/internal/dataflow/DataFlowGraph.qll +++ b/unified/ql/lib/codeql/unified/internal/dataflow/DataFlowGraph.qll @@ -24,14 +24,14 @@ predicate step(BuilderNode node1, Step step, BuilderNode node2) { node2.isLocalVariableWrite(callable, getImplicitReceiverVariable(callable)) ) or - exists(CallExpr call, Expr receiverExpr | - receiverExpr = call.getCallee().(MemberAccessExpr).getBase() + exists(DataFlowCall call, Expr receiverExpr | + receiverExpr = call.getCallExpr().getCallee().(MemberAccessExpr).getBase() | node1.isResultValue(receiverExpr) and step.value() and - node2.isReceiverArgument(call) + node2.isReceiverArgumentEx(call) or - node1.isReceiverPostUpdate(call) and + node1.isReceiverPostUpdateEx(call) and step.value() and node2.isPostUpdate(receiverExpr) ) @@ -46,12 +46,14 @@ predicate step(BuilderNode node1, Step step, BuilderNode node2) { node2.isPostUpdate(call.getCallee()) ) or - exists(CallExpr call, UnqualifiedMemberAccess callee | callee = call.getCallee() | + exists(DataFlowCall call, UnqualifiedMemberAccess callee | + callee = call.getCallExpr().getCallee() + | node1.isLocalVariableRead(callee, callee.getImplicitQualifierVariable()) and step.value() and - node2.isReceiverArgument(call) + node2.isReceiverArgumentEx(call) or - node1.isReceiverPostUpdate(call) and + node1.isReceiverPostUpdateEx(call) and step.value() and node2.isLocalVariablePostUpdate(callee, callee.getImplicitQualifierVariable()) ) diff --git a/unified/ql/lib/codeql/unified/internal/dataflow/DataFlowInstantiation.qll b/unified/ql/lib/codeql/unified/internal/dataflow/DataFlowInstantiation.qll index 1c18b907c57c..c5785a2b0b16 100644 --- a/unified/ql/lib/codeql/unified/internal/dataflow/DataFlowInstantiation.qll +++ b/unified/ql/lib/codeql/unified/internal/dataflow/DataFlowInstantiation.qll @@ -98,7 +98,7 @@ module DataFlowInput implements InputSig { private predicate isArgumentNodeImpl(Node n, DataFlowCall call, ArgumentPosition pos) { exists(Argument arg | n.asExpr() = arg.getValue() and - call.asExplicitCall().getAnArgument() = arg + call.getCallExpr().getAnArgument() = arg | pos.asPositional() = arg.getPositionalIndex() or @@ -108,7 +108,11 @@ module DataFlowInput implements InputSig { n.(BuilderNode).isImplicitArgument(call, pos, false) or n.(BuilderNode).isSetterValueArgument(call) and - pos.asPositional() = 0 + // The new value is passed after the positional arguments, if any + pos.asPositional() = + count(Argument arg | + arg = call.asSetterCall().(CallExpr).getAnArgument() and arg.isPositional() + ) or exists(FlowSummaryOutput::SummaryNode callee | FlowSummaryOutput::summaryArgumentNode(callee, getSummaryNode(n), pos) and diff --git a/unified/ql/lib/codeql/unified/internal/dataflow/DataFlowPluginSwift.qll b/unified/ql/lib/codeql/unified/internal/dataflow/DataFlowPluginSwift.qll index f845a5f20f3c..91f0ea9b656b 100644 --- a/unified/ql/lib/codeql/unified/internal/dataflow/DataFlowPluginSwift.qll +++ b/unified/ql/lib/codeql/unified/internal/dataflow/DataFlowPluginSwift.qll @@ -68,6 +68,21 @@ private class SwiftDataFlowPlugin extends DataFlowPlugin { node1.isResultValue(call.getArgument(0).getValue()) and step.value() and node2.isResultValue(call) + or + // Simple flow in and out of subscript operators + call.getCallee().(MemberAccess).getMemberName() = "subscript" and + ( + node1.isReceiverArgument(call) and + step.readArrayElement() and + node2.isResultValue(call) + or + exists(DataFlowCall setter | + setter.asSetterCall() = call and + node1.isSetterValueArgument(setter) and + step.storeArrayElement() and + node2.isReceiverPostUpdateEx(setter) + ) + ) ) or exists(UnaryExpr expr | @@ -131,4 +146,12 @@ private predicate isInWriteBackPosition(Expr expr, boolean direct) { expr = member.getBase() and direct = false ) + or + // A call in write-back position, such as `x.p[i]` in `x.p[i] = y`, is a call to a setter, + // which mutates its receiver. Propagate to the callee so the receiver `x.p` is also written back. + exists(CallExpr call | + isInWriteBackPosition(call, _) and + expr = call.getCallee() and + direct = false + ) } diff --git a/unified/ql/lib/codeql/unified/internal/dataflow/FlowSummary.qll b/unified/ql/lib/codeql/unified/internal/dataflow/FlowSummary.qll index c4676aa46ea2..5e4bdde541e4 100644 --- a/unified/ql/lib/codeql/unified/internal/dataflow/FlowSummary.qll +++ b/unified/ql/lib/codeql/unified/internal/dataflow/FlowSummary.qll @@ -111,7 +111,7 @@ private predicate getSummaryNodeAlias = getSummaryNode/1; private module StepsInput implements StepsInputSig { predicate getSummaryNode = getSummaryNodeAlias/1; - DataFlowCall getACall(SummarizedCallable sc) { sc.matchesCall(result.asExplicitCall()) } + DataFlowCall getACall(SummarizedCallable sc) { sc.matchesCall(result) } } private import StepsInput diff --git a/unified/ql/lib/codeql/unified/internal/mad/LegacyMaD.qll b/unified/ql/lib/codeql/unified/internal/mad/LegacyMaD.qll index 09da505b7d0b..74ce318868a6 100644 --- a/unified/ql/lib/codeql/unified/internal/mad/LegacyMaD.qll +++ b/unified/ql/lib/codeql/unified/internal/mad/LegacyMaD.qll @@ -4,6 +4,8 @@ private import unified private import codeql.dataflow.internal.AccessPathSyntax +private import codeql.unified.internal.dataflow.DataFlowCall +private import codeql.unified.internal.dataflow.DataFlowNode extensible predicate legacySourceModel( string namespace, string type, boolean subtypes, string name, string signature, string ext, @@ -75,6 +77,18 @@ private predicate methodCallSelector(CallExpr call, string name, string argLabel argLabels = getArgLabelsFromCall(call) } +/** + * Holds if `call` invokes a setter that models refer to as `name`. By convention, the setter + * invoked by a call to `foo` is named `foo=` in models, such as `subscript=` for `x[i] = y`. + * + * The new value is passed as an extra positional argument after the explicit ones. + */ +pragma[nomagic] +private predicate setterCallSelector(CallExpr call, string name, string argLabels) { + name = call.getCallee().(MemberAccessExpr).getMemberName() + "=" and + argLabels = "_:" + getArgLabelsFromCall(call) +} + pragma[nomagic] private predicate constructorCallSelector(CallExpr call, string name, string argLabels) { ( @@ -173,13 +187,16 @@ class Selector extends TSelector { this = MkSelector(name, _, "init", argLabels, _) } - predicate matchesCall(CallExpr call) { + predicate matchesCall(DataFlowCall call) { exists(string name, string argLabels | this.matchesMethodCallSelector(name, argLabels) and - methodCallSelector(call, name, argLabels) + methodCallSelector(call.asExplicitCall(), name, argLabels) + or + this.matchesMethodCallSelector(name, argLabels) and + setterCallSelector(call.asSetterCall(), name, argLabels) or this.matchesConstructorCallSelector(name, argLabels) and - constructorCallSelector(call, name, argLabels) + constructorCallSelector(call.asExplicitCall(), name, argLabels) ) } @@ -213,10 +230,10 @@ private import AccessPathOutput * Gets the input to `call` specified by `token`. Only singleton access paths are supported. */ bindingset[call, token] -private DataFlow::Node getSinkFromCall(CallExpr call, AccessPathToken token) { +private DataFlow::Node getSinkFromCall(DataFlowCall call, AccessPathToken token) { token.getName() = "Argument" and exists(Argument arg | - arg = call.getAnArgument() and + arg = call.getCallExpr().getAnArgument() and result.asExpr() = arg.getValue() | arg.getPositionalIndex() = parseIntUnbounded(token.getAnArgument()) @@ -224,18 +241,24 @@ private DataFlow::Node getSinkFromCall(CallExpr call, AccessPathToken token) { arg.getName() + ":" = token.getAnArgument() ) or + // The new value passed to a setter comes after the positional arguments + token.getName() = "Argument" and + result.(BuilderNode).isSetterValueArgument(call) and + parseIntUnbounded(token.getAnArgument()) = + count(Argument arg | arg = call.getCallExpr().getAnArgument() and arg.isPositional()) + or token.getName() = "Argument" and token.getAnArgument() = "self" and - result.isReceiverArgument(call) + result.(BuilderNode).isReceiverArgumentEx(call) } /** * Gets the output from `call` specified by `token`. Only singleton access paths are supported. */ bindingset[call, token] -private DataFlow::Node getSourceFromCall(CallExpr call, AccessPathToken token) { +private DataFlow::Node getSourceFromCall(DataFlowCall call, AccessPathToken token) { token = "ReturnValue" and - result.asExpr() = call + result.asExpr() = call.asExplicitCall() or result = getSinkFromCall(call, token).getPostUpdateNode() } @@ -312,7 +335,7 @@ DataFlow::Node getASource(string kind, string provenance) { exists(Selector selector, AccessPath output | normalizedSourceModel(selector, output, kind, provenance) | - exists(CallExpr call | + exists(DataFlowCall call | selector.matchesCall(call) and result = getSourceFromCall(call, output) ) @@ -331,7 +354,7 @@ DataFlow::Node getASink(string kind, string provenance) { exists(Selector selector, AccessPath input | normalizedSinkModel(selector, input, kind, provenance) | - exists(CallExpr call | + exists(DataFlowCall call | selector.matchesCall(call) and result = getSinkFromCall(call, input) ) diff --git a/unified/ql/lib/codeql/unified/internal/typeinference/Type.qll b/unified/ql/lib/codeql/unified/internal/typeinference/Type.qll index 0ffe9fccdd7a..73aff31e9d9c 100644 --- a/unified/ql/lib/codeql/unified/internal/typeinference/Type.qll +++ b/unified/ql/lib/codeql/unified/internal/typeinference/Type.qll @@ -239,6 +239,8 @@ class TypeParameterType extends TypeParameter, TTypeParameterType { typeParam = result.(Unified::TypeAliasDeclaration).getATypeParameter() or typeParam = result.(Unified::FunctionDeclaration).getATypeParameter() + or + typeParam = result.(Unified::AccessorDeclaration).getATypeParameter() } override Identifier getNameNode() { result = typeParam.getNameNode() } diff --git a/unified/ql/lib/codeql/unified/internal/typeinference/TypeInference.qll b/unified/ql/lib/codeql/unified/internal/typeinference/TypeInference.qll index f7a773c1dc6d..742cce0f6124 100644 --- a/unified/ql/lib/codeql/unified/internal/typeinference/TypeInference.qll +++ b/unified/ql/lib/codeql/unified/internal/typeinference/TypeInference.qll @@ -131,6 +131,8 @@ module Input3 implements InputSig3 { (exists(resolveAccessorAccess(_)) implies any()) or (exists(resolveCallTarget(_)) implies any()) + or + (exists(resolveSetterCallTarget(_)) implies any()) } predicate inferTypeForDefaults = M3::inferType/2; @@ -282,7 +284,10 @@ module Input3 implements InputSig3 { private class AccessorField extends FieldImpl, Callable { private AccessorDeclaration accessor; - AccessorField() { accessor = this.getCallable() } + AccessorField() { + accessor = this.getCallable() and + not accessor instanceof SetterMethodDeclaration // invoked via call syntax, not a field + } override TypeMention getType() { result = accessor.getReturnType() } @@ -455,6 +460,8 @@ module Input3 implements InputSig3 { TypeParameter getTypeParameter(int i) { result = TTypeParameterType(c.(FunctionDeclaration).getTypeParameter(i)) + or + result = TTypeParameterType(c.(AccessorDeclaration).getTypeParameter(i)) } TypeMention getAdditionalTypeParameterConstraint(TypeParameter tp) { @@ -929,6 +936,28 @@ Callable resolveCallTarget(CallExpr ce) { result = ce.(Input3::Invocation).getTargetImpl(_).asCallable() } +/** + * Gets the setter invoked when the given call expression appears as an assignment target, + * or is otherwise updated in place. + * + * For a call `x.foo(...)` this is the `setter_method` accessor named `foo` of the receiver type, + * such as the setter of a subscript for a Swift subscript call `x[i]`. + */ +cached +SetterMethodDeclaration resolveSetterCallTarget(CallExpr ce) { + CachedStage::ref() and + exists(MemberAccessExpr callee, ClassLikeDeclaration cls, NamespaceNode ns | + callee = ce.getCallee() and + ns.getMember(callee.getMemberName()).isIdentifier(result.getNameNode()) + | + cls = inferType(callee.getBase()).asClass() and + ns.isInstanceMemberNamespace(cls) + or + cls = callee.getBase().(TypeMention).getType().asClass() and + ns.isStaticMemberNamespace(cls) + ) +} + private predicate typeTestAstNodeRepr(AstNode n, string repr) { repr = [n.toString(), n.(Identifier).getValue(), "." + n.(MemberAccessExpr).getMemberName()] or diff --git a/unified/ql/lib/unified.dbscheme b/unified/ql/lib/unified.dbscheme index 54ede4d9d45c..870c546c85f8 100644 --- a/unified/ql/lib/unified.dbscheme +++ b/unified/ql/lib/unified.dbscheme @@ -160,6 +160,13 @@ unified_accessor_declaration_return_type( unique int return_type: @unified_expr ref ); +#keyset[unified_accessor_declaration, index] +unified_accessor_declaration_type_parameter( + int unified_accessor_declaration: @unified_accessor_declaration ref, + int index: int ref, + unique int type_parameter: @unified_type_parameter ref +); + unified_accessor_declaration_def( unique int id: @unified_accessor_declaration, int accessor_kind: @unified_token_accessor_kind ref, diff --git a/unified/ql/test/library-tests/BasicTest/test.expected b/unified/ql/test/library-tests/BasicTest/test.expected index 95d54935f8ee..a512eea0164b 100644 --- a/unified/ql/test/library-tests/BasicTest/test.expected +++ b/unified/ql/test/library-tests/BasicTest/test.expected @@ -90,6 +90,7 @@ identifier | test.swift:48:37:48:40 | data | data | | test.swift:48:42:48:46 | count | count | | test.swift:49:16:49:19 | data | data | +| test.swift:49:20:49:20 | subscript | subscript | | test.swift:49:21:49:25 | index | index | | test.swift:52:10:52:12 | add | add | | test.swift:52:14:52:14 | _ | _ | @@ -131,8 +132,10 @@ identifier | test.swift:75:13:75:13 | i | i | | test.swift:75:23:75:27 | count | count | | test.swift:76:16:76:19 | self | self | +| test.swift:76:20:76:20 | subscript | subscript | | test.swift:76:21:76:21 | i | i | | test.swift:76:26:76:29 | self | self | +| test.swift:76:30:76:30 | subscript | subscript | | test.swift:76:31:76:31 | i | i | | test.swift:85:6:85:12 | combine | combine | | test.swift:85:14:85:14 | T | T | @@ -153,6 +156,7 @@ identifier | test.swift:87:19:87:27 | dropFirst | dropFirst | | test.swift:87:31:87:36 | reduce | reduce | | test.swift:87:38:87:43 | values | values | +| test.swift:87:44:87:44 | subscript | subscript | | test.swift:87:49:87:57 | transform | transform | | test.swift:90:5:90:9 | tuple | tuple | | test.swift:91:5:91:10 | unary1 | unary1 | diff --git a/unified/ql/test/library-tests/controlflow/basicblock-slices.expected b/unified/ql/test/library-tests/controlflow/basicblock-slices.expected index de0738748fb9..c5cdbc7a71e6 100644 --- a/unified/ql/test/library-tests/controlflow/basicblock-slices.expected +++ b/unified/ql/test/library-tests/controlflow/basicblock-slices.expected @@ -245,32 +245,32 @@ | 288 | cfg.swift:288:1:319:1 | func testSubscriptExpr | 'func testSubscriptExpr -V testSubscriptExpr' | | 288 | cfg.swift:288:55:319:1 | Block | 'Block' | | 289 | cfg.swift:289:3:289:44 | var a | 'var a -V a -> 0 -> 1 -> 2 -> 3 -> 4 -> 5 -> 6 -> 7 -> 8 -> 9 -> 10 -^ ArrayLiteral' | -| 290 | cfg.swift:290:3:290:3 | a | 'a -> Argument -V 0 -^ a(...) -> 0 -^ ... = ...' | -| 291 | cfg.swift:291:3:291:3 | a | 'a -> Argument -V 1 -^ a(...) -> 1 -^ ... += ...' | -| 292 | cfg.swift:292:3:292:3 | a | 'a -> Argument -V 2 -^ a(...) -> 1 -^ ... -= ...' | -| 293 | cfg.swift:293:3:293:3 | a | 'a -> Argument -V 3 -^ a(...) -> 1 -^ ... *= ...' | -| 294 | cfg.swift:294:3:294:3 | a | 'a -> Argument -V 4 -^ a(...) -> 1 -^ ... /= ...' | -| 295 | cfg.swift:295:3:295:3 | a | 'a -> Argument -V 5 -^ a(...) -> 1 -^ ... %= ...' | -| 296 | cfg.swift:296:3:296:3 | a | 'a -> Argument -V 6 -^ a(...) -> 1 -^ ... &= ...' | -| 297 | cfg.swift:297:3:297:3 | a | 'a -> Argument -V 7 -^ a(...) -> 1 -^ ... \|= ...' | -| 298 | cfg.swift:298:3:298:3 | a | 'a -> Argument -V 8 -^ a(...) -> 1 -^ ... ^= ...' | -| 299 | cfg.swift:299:3:299:3 | a | 'a -> Argument -V 9 -^ a(...) -> 1 -^ ... <<= ...' | -| 300 | cfg.swift:300:3:300:3 | a | 'a -> Argument -V 10 -^ a(...) -> 1 -^ ... >>= ...' | -| 302 | cfg.swift:302:3:302:49 | var tupleWithA | 'var tupleWithA -V tupleWithA -> Argument -V a -> Argument -V 0 -^ a(...) -> Argument -V a -> Argument -V 1 -^ a(...) -> Argument -V a -> Argument -V 2 -^ a(...) -> Argument -V a -> Argument -V 3 -^ a(...) -> Argument -V a -> Argument -V 4 -^ a(...) -^ TupleExpr' | +| 290 | cfg.swift:290:3:290:3 | a | 'a -^ ... .subscript -> Argument -V 0 -^ ... .subscript(...) -> 0 -^ ... = ...' | +| 291 | cfg.swift:291:3:291:3 | a | 'a -^ ... .subscript -> Argument -V 1 -^ ... .subscript(...) -> 1 -^ ... += ...' | +| 292 | cfg.swift:292:3:292:3 | a | 'a -^ ... .subscript -> Argument -V 2 -^ ... .subscript(...) -> 1 -^ ... -= ...' | +| 293 | cfg.swift:293:3:293:3 | a | 'a -^ ... .subscript -> Argument -V 3 -^ ... .subscript(...) -> 1 -^ ... *= ...' | +| 294 | cfg.swift:294:3:294:3 | a | 'a -^ ... .subscript -> Argument -V 4 -^ ... .subscript(...) -> 1 -^ ... /= ...' | +| 295 | cfg.swift:295:3:295:3 | a | 'a -^ ... .subscript -> Argument -V 5 -^ ... .subscript(...) -> 1 -^ ... %= ...' | +| 296 | cfg.swift:296:3:296:3 | a | 'a -^ ... .subscript -> Argument -V 6 -^ ... .subscript(...) -> 1 -^ ... &= ...' | +| 297 | cfg.swift:297:3:297:3 | a | 'a -^ ... .subscript -> Argument -V 7 -^ ... .subscript(...) -> 1 -^ ... \|= ...' | +| 298 | cfg.swift:298:3:298:3 | a | 'a -^ ... .subscript -> Argument -V 8 -^ ... .subscript(...) -> 1 -^ ... ^= ...' | +| 299 | cfg.swift:299:3:299:3 | a | 'a -^ ... .subscript -> Argument -V 9 -^ ... .subscript(...) -> 1 -^ ... <<= ...' | +| 300 | cfg.swift:300:3:300:3 | a | 'a -^ ... .subscript -> Argument -V 10 -^ ... .subscript(...) -> 1 -^ ... >>= ...' | +| 302 | cfg.swift:302:3:302:49 | var tupleWithA | 'var tupleWithA -V tupleWithA -> Argument -V a -^ ... .subscript -> Argument -V 0 -^ ... .subscript(...) -> Argument -V a -^ ... .subscript -> Argument -V 1 -^ ... .subscript(...) -> Argument -V a -^ ... .subscript -> Argument -V 2 -^ ... .subscript(...) -> Argument -V a -^ ... .subscript -> Argument -V 3 -^ ... .subscript(...) -> Argument -V a -^ ... .subscript -> Argument -V 4 -^ ... .subscript(...) -^ TupleExpr' | | 304 | cfg.swift:304:3:304:48 | var b | 'var b -V b -> 0 -> 1 -> 2 -> 3 -> 4 -> 5 -> 6 -> 7 -> 8 -> 9 -> 10 -> 11 -^ ArrayLiteral' | -| 305 | cfg.swift:305:3:305:3 | b | 'b -> Argument -V 0 -^ b(...) -> a -> Argument -V 10 -^ a(...) -^ ... = ...' | -| 306 | cfg.swift:306:3:306:3 | b | 'b -> Argument -V 1 -^ b(...) -> b -> Argument -V 0 -^ b(...) -> 1 -^ ... + ... -^ ... = ...' | -| 307 | cfg.swift:307:3:307:3 | b | 'b -> Argument -V 2 -^ b(...) -> b -> Argument -V 1 -^ b(...) -> 1 -^ ... - ... -^ ... = ...' | -| 308 | cfg.swift:308:3:308:3 | b | 'b -> Argument -V 3 -^ b(...) -> b -> Argument -V 2 -^ b(...) -> 1 -^ ... * ... -^ ... = ...' | -| 309 | cfg.swift:309:3:309:3 | b | 'b -> Argument -V 4 -^ b(...) -> b -> Argument -V 3 -^ b(...) -> 1 -^ ... / ... -^ ... = ...' | -| 310 | cfg.swift:310:3:310:3 | b | 'b -> Argument -V 5 -^ b(...) -> b -> Argument -V 4 -^ b(...) -> 1 -^ ... % ... -^ ... = ...' | -| 311 | cfg.swift:311:3:311:3 | b | 'b -> Argument -V 6 -^ b(...) -> b -> Argument -V 5 -^ b(...) -> 1 -^ ... & ... -^ ... = ...' | -| 312 | cfg.swift:312:3:312:3 | b | 'b -> Argument -V 7 -^ b(...) -> b -> Argument -V 6 -^ b(...) -> 1 -^ ... \| ... -^ ... = ...' | -| 313 | cfg.swift:313:3:313:3 | b | 'b -> Argument -V 8 -^ b(...) -> b -> Argument -V 7 -^ b(...) -> 1 -^ ... ^ ... -^ ... = ...' | -| 314 | cfg.swift:314:3:314:3 | b | 'b -> Argument -V 9 -^ b(...) -> b -> Argument -V 8 -^ b(...) -> 1 -^ ... << ... -^ ... = ...' | -| 315 | cfg.swift:315:3:315:3 | b | 'b -> Argument -V 10 -^ b(...) -> b -> Argument -V 9 -^ b(...) -> 1 -^ ... >> ... -^ ... = ...' | +| 305 | cfg.swift:305:3:305:3 | b | 'b -^ ... .subscript -> Argument -V 0 -^ ... .subscript(...) -> a -^ ... .subscript -> Argument -V 10 -^ ... .subscript(...) -^ ... = ...' | +| 306 | cfg.swift:306:3:306:3 | b | 'b -^ ... .subscript -> Argument -V 1 -^ ... .subscript(...) -> b -^ ... .subscript -> Argument -V 0 -^ ... .subscript(...) -> 1 -^ ... + ... -^ ... = ...' | +| 307 | cfg.swift:307:3:307:3 | b | 'b -^ ... .subscript -> Argument -V 2 -^ ... .subscript(...) -> b -^ ... .subscript -> Argument -V 1 -^ ... .subscript(...) -> 1 -^ ... - ... -^ ... = ...' | +| 308 | cfg.swift:308:3:308:3 | b | 'b -^ ... .subscript -> Argument -V 3 -^ ... .subscript(...) -> b -^ ... .subscript -> Argument -V 2 -^ ... .subscript(...) -> 1 -^ ... * ... -^ ... = ...' | +| 309 | cfg.swift:309:3:309:3 | b | 'b -^ ... .subscript -> Argument -V 4 -^ ... .subscript(...) -> b -^ ... .subscript -> Argument -V 3 -^ ... .subscript(...) -> 1 -^ ... / ... -^ ... = ...' | +| 310 | cfg.swift:310:3:310:3 | b | 'b -^ ... .subscript -> Argument -V 5 -^ ... .subscript(...) -> b -^ ... .subscript -> Argument -V 4 -^ ... .subscript(...) -> 1 -^ ... % ... -^ ... = ...' | +| 311 | cfg.swift:311:3:311:3 | b | 'b -^ ... .subscript -> Argument -V 6 -^ ... .subscript(...) -> b -^ ... .subscript -> Argument -V 5 -^ ... .subscript(...) -> 1 -^ ... & ... -^ ... = ...' | +| 312 | cfg.swift:312:3:312:3 | b | 'b -^ ... .subscript -> Argument -V 7 -^ ... .subscript(...) -> b -^ ... .subscript -> Argument -V 6 -^ ... .subscript(...) -> 1 -^ ... \| ... -^ ... = ...' | +| 313 | cfg.swift:313:3:313:3 | b | 'b -^ ... .subscript -> Argument -V 8 -^ ... .subscript(...) -> b -^ ... .subscript -> Argument -V 7 -^ ... .subscript(...) -> 1 -^ ... ^ ... -^ ... = ...' | +| 314 | cfg.swift:314:3:314:3 | b | 'b -^ ... .subscript -> Argument -V 9 -^ ... .subscript(...) -> b -^ ... .subscript -> Argument -V 8 -^ ... .subscript(...) -> 1 -^ ... << ... -^ ... = ...' | +| 315 | cfg.swift:315:3:315:3 | b | 'b -^ ... .subscript -> Argument -V 10 -^ ... .subscript(...) -> b -^ ... .subscript -> Argument -V 9 -^ ... .subscript(...) -> 1 -^ ... >> ... -^ ... = ...' | | 317 | cfg.swift:317:3:317:39 | let (pattern assignment) | 'let (pattern assignment) -V Argument -V a1 -> Argument -V a2 -> Argument -V a3 -> Argument -V a4 -> Argument -V a5 -^ TupleExpr -> tupleWithA' | -| 318 | cfg.swift:318:11:318:19 | Argument | 'Argument -V a1 -> b -> Argument -V 0 -^ b(...) -^ ... + ... -> Argument -V a2 -> b -> Argument -V 1 -^ b(...) -^ ... + ... -> Argument -V a3 -> b -> Argument -V 2 -^ b(...) -^ ... + ... -> Argument -V a4 -> b -> Argument -V 3 -^ b(...) -^ ... + ... -> Argument -V a5 -> b -> Argument -V 4 -^ b(...) -^ ... + ... -^ TupleExpr -^ ReturnExpr' | +| 318 | cfg.swift:318:11:318:19 | Argument | 'Argument -V a1 -> b -^ ... .subscript -> Argument -V 0 -^ ... .subscript(...) -^ ... + ... -> Argument -V a2 -> b -^ ... .subscript -> Argument -V 1 -^ ... .subscript(...) -^ ... + ... -> Argument -V a3 -> b -^ ... .subscript -> Argument -V 2 -^ ... .subscript(...) -^ ... + ... -> Argument -V a4 -> b -^ ... .subscript -> Argument -V 3 -^ ... .subscript(...) -^ ... + ... -> Argument -V a5 -> b -^ ... .subscript -> Argument -V 4 -^ ... .subscript(...) -^ ... + ... -^ TupleExpr -^ ReturnExpr' | | 321 | cfg.swift:321:1:326:1 | func loop1 | 'func loop1 -V loop1' | | 321 | cfg.swift:321:12:321:12 | x | 'x -> Block' | | 322 | cfg.swift:322:3:325:3 | WhileStmt | 'WhileStmt' | @@ -396,10 +396,10 @@ | 467 | cfg.swift:467:3:467:31 | var kpGet_bs_0_x | 'var kpGet_bs_0_x -V kpGet_bs_0_x -> ' | | 468 | cfg.swift:468:3:468:37 | var kpGet_mayB_force_x | 'var kpGet_mayB_force_x -V kpGet_mayB_force_x -> ' | | 469 | cfg.swift:469:3:469:31 | var kpGet_mayB_x | 'var kpGet_mayB_x -V kpGet_mayB_x -> ' | -| 471 | cfg.swift:471:3:471:45 | var apply_kpGet_b_x | 'var apply_kpGet_b_x -V apply_kpGet_b_x -> a -> Argument -V kpGet_b_x -^ a(...)' | -| 472 | cfg.swift:472:3:472:51 | var apply_kpGet_bs_0_x | 'var apply_kpGet_bs_0_x -V apply_kpGet_bs_0_x -> a -> Argument -V kpGet_bs_0_x -^ a(...)' | -| 473 | cfg.swift:473:3:473:63 | var apply_kpGet_mayB_force_x | 'var apply_kpGet_mayB_force_x -V apply_kpGet_mayB_force_x -> a -> Argument -V kpGet_mayB_force_x -^ a(...)' | -| 474 | cfg.swift:474:3:474:51 | var apply_kpGet_mayB_x | 'var apply_kpGet_mayB_x -V apply_kpGet_mayB_x -> a -> Argument -V kpGet_mayB_x -^ a(...)' | +| 471 | cfg.swift:471:3:471:45 | var apply_kpGet_b_x | 'var apply_kpGet_b_x -V apply_kpGet_b_x -> a -^ ... .subscript -> Argument -V kpGet_b_x -^ ... .subscript(...)' | +| 472 | cfg.swift:472:3:472:51 | var apply_kpGet_bs_0_x | 'var apply_kpGet_bs_0_x -V apply_kpGet_bs_0_x -> a -^ ... .subscript -> Argument -V kpGet_bs_0_x -^ ... .subscript(...)' | +| 473 | cfg.swift:473:3:473:63 | var apply_kpGet_mayB_force_x | 'var apply_kpGet_mayB_force_x -V apply_kpGet_mayB_force_x -> a -^ ... .subscript -> Argument -V kpGet_mayB_force_x -^ ... .subscript(...)' | +| 474 | cfg.swift:474:3:474:51 | var apply_kpGet_mayB_x | 'var apply_kpGet_mayB_x -V apply_kpGet_mayB_x -> a -^ ... .subscript -> Argument -V kpGet_mayB_x -^ ... .subscript(...)' | | 477 | cfg.swift:477:1:504:1 | func testIfConfig | 'func testIfConfig -V testIfConfig' | | 477 | cfg.swift:477:21:504:1 | Block | 'Block' | | 478 | cfg.swift:478:1:484:6 | | '' | diff --git a/unified/ql/test/library-tests/dataflow/subscripts.swift b/unified/ql/test/library-tests/dataflow/subscripts.swift new file mode 100644 index 000000000000..e656026ab1d8 --- /dev/null +++ b/unified/ql/test/library-tests/dataflow/subscripts.swift @@ -0,0 +1,154 @@ +func source(_ s: String) -> String { return s } + +@discardableResult +func sink(_ s: String) -> String { return "" } + +func s1() { + class C { + subscript(i: Int) -> String { source("s1.1") } + } + sink(C()[0]) // $ hasValueFlow=s1.1 +} + +func s2() { + class C { + subscript(key: String) -> String { + get { key } + set { sink(newValue) } // $ hasValueFlow=s2.2 + } + } + let c = C() + sink(c[source("s2.1")]) // $ hasValueFlow=s2.1 + c["safe"] = source("s2.2") +} + +func s3() { + class C { + subscript(key: String) -> String { + get { "safe" } + set(v) { sink(key); sink(v) } // $ hasValueFlow=s3.1 hasValueFlow=s3.2 + } + } + let c = C() + c[source("s3.1")] = source("s3.2") +} + +func s4() { + class C { + var field: String = "safe" + subscript(at i: Int, _ j: Int) -> String { + get { field } + set { field = newValue } + } + } + let c = C() + c[at: 0, 1] = source("s4.1") + sink(c[at: 1, 2]) // $ hasValueFlow=s4.1 + sink(c.field) // $ hasValueFlow=s4.1 +} + +func s5() { + class C { + subscript(i: Int) -> String { + get { source("s5.1") } + set { sink(newValue) } // $ hasTaintFlow=s5.1 + } + } + let c = C() + c[0] += "x" +} + +func s6() { + struct S { + var field: String = "safe" + } + class C { + subscript(i: Int) -> S { + get { S() } + set { sink(newValue.field) } // $ hasValueFlow=s6.1 + } + } + let c = C() + // `S` has value type, so this calls the subscript getter followed by the setter + c[0].field = source("s6.1") +} + +func s7() { + class C { + var field: String = "safe" + subscript(i: Int) -> String { + get { field } + set { field = newValue } + } + + func method() { + self[0] = source("s7.1") + sink(self[1]) // $ hasValueFlow=s7.1 + } + } +} + +func s8() { + struct S { + var field: String = "safe" + subscript(i: Int) -> String { + get { field } + set { field = newValue } + } + } + class C { + var s: S { + get { S() } + set { sink(newValue.field) } // $ hasValueFlow=s8.1 + } + } + let c = C() + // `S` has value type, so this writes back to `s` via its setter + c.s[0] = source("s8.1") +} + +func s9() { + struct S { + var field: String = "safe" + subscript(i: Int) -> String { + get { field } + set { field = newValue } + } + } + var s = S() + s[0] = source("s9.1") + sink(s[0]) // $ hasValueFlow=s9.1 +} + +func s10() { + class C { + static subscript(i: Int) -> String { + get { source("s10.1") } + set { sink(newValue) } // $ hasValueFlow=s10.2 + } + } + C[0] = source("s10.2") + sink(C[0]) // $ hasValueFlow=s10.1 +} + +func s11(t: TestType) { + sink(t[testKey: source("s11.1")]) // $ hasValueFlow=s11.1 + t[testKey: source("s11.2")] = "safe" + t[testKey: "safe"] = source("s11.3") + sink(t.getField()) // $ hasValueFlow=s11.3 +} + +func s12() { + struct S { + var field: String = "safe" + } + class C { + subscript(i: Int) -> S { + get { S() } + set { sink(newValue.field) } // $ hasValueFlow=s12.1 + } + } + func update(_ s: inout S) { s.field = source("s12.1") } + let c = C() + update(&c[0]) +} diff --git a/unified/ql/test/library-tests/dataflow/test.expected b/unified/ql/test/library-tests/dataflow/test.expected index a479255f6d23..119bfa22a5a6 100644 --- a/unified/ql/test/library-tests/dataflow/test.expected +++ b/unified/ql/test/library-tests/dataflow/test.expected @@ -365,6 +365,64 @@ edges | 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 | | | short-return.swift:7:33:7:46 | source(...) | short-return.swift:8:10:8:19 | getValue(...) | provenance | | +| subscripts.swift:8:39:8:52 | source(...) | subscripts.swift:10:10:10:15 | ... .subscript(...) | provenance | | +| subscripts.swift:15:19:15:21 | key | subscripts.swift:16:19:16:21 | key | provenance | | +| subscripts.swift:21:12:21:25 | source(...) | subscripts.swift:15:19:15:21 | key | provenance | | +| subscripts.swift:21:12:21:25 | source(...) | subscripts.swift:21:10:21:26 | ... .subscript(...) | provenance | | +| subscripts.swift:22:5:22:13 | ... .subscript(...) | subscripts.swift:17:24:17:31 | newValue | provenance | | +| subscripts.swift:22:17:22:30 | source(...) | subscripts.swift:22:5:22:13 | ... .subscript(...) | provenance | | +| subscripts.swift:27:19:27:21 | key | subscripts.swift:29:27:29:29 | key | provenance | | +| subscripts.swift:29:17:29:17 | v | subscripts.swift:29:38:29:38 | v | provenance | | +| subscripts.swift:33:5:33:21 | ... .subscript(...) | subscripts.swift:29:17:29:17 | v | provenance | | +| subscripts.swift:33:7:33:20 | source(...) | subscripts.swift:27:19:27:21 | key | provenance | | +| subscripts.swift:33:25:33:38 | source(...) | subscripts.swift:33:5:33:21 | ... .subscript(...) | provenance | | +| subscripts.swift:41:27:41:34 | newValue | subscripts.swift:41:19:41:23 | field | provenance | | +| subscripts.swift:45:5:45:15 | ... .subscript(...) | subscripts.swift:41:27:41:34 | newValue | provenance | | +| subscripts.swift:45:5:45:15 | ... .subscript(...) | subscripts.swift:46:10:46:10 | c [field] | provenance | | +| subscripts.swift:45:5:45:15 | ... .subscript(...) | subscripts.swift:47:10:47:10 | c [field] | provenance | | +| subscripts.swift:45:19:45:32 | source(...) | subscripts.swift:45:5:45:15 | ... .subscript(...) | provenance | | +| subscripts.swift:46:10:46:10 | c [field] | subscripts.swift:40:19:40:23 | field | provenance | | +| subscripts.swift:46:10:46:10 | c [field] | subscripts.swift:46:10:46:20 | ... .subscript(...) | provenance | | +| subscripts.swift:47:10:47:10 | c [field] | subscripts.swift:47:10:47:16 | ... .field | provenance | | +| subscripts.swift:53:19:53:32 | source(...) | subscripts.swift:58:5:58:8 | ... .subscript(...) | provenance | | +| subscripts.swift:58:5:58:8 | ... .subscript(...) | subscripts.swift:58:5:58:8 | [incoming] ... .subscript(...) | provenance | | +| subscripts.swift:58:5:58:8 | [incoming] ... .subscript(...) | subscripts.swift:54:24:54:31 | newValue | provenance | | +| subscripts.swift:68:24:68:31 | newValue [field] | subscripts.swift:68:24:68:37 | ... .field | provenance | | +| subscripts.swift:73:5:73:8 | [post] ... .subscript(...) [field] | subscripts.swift:68:24:68:31 | newValue [field] | provenance | | +| subscripts.swift:73:5:73:14 | ... .field | subscripts.swift:73:5:73:8 | [post] ... .subscript(...) [field] | provenance | | +| subscripts.swift:73:18:73:31 | source(...) | subscripts.swift:73:5:73:14 | ... .field | provenance | | +| subscripts.swift:81:27:81:34 | newValue | subscripts.swift:81:19:81:23 | field | provenance | | +| subscripts.swift:85:13:85:19 | ... .subscript(...) | subscripts.swift:81:27:81:34 | newValue | provenance | | +| subscripts.swift:85:13:85:19 | ... .subscript(...) | subscripts.swift:86:18:86:21 | self [field] | provenance | | +| subscripts.swift:85:23:85:36 | source(...) | subscripts.swift:85:13:85:19 | ... .subscript(...) | provenance | | +| subscripts.swift:86:18:86:21 | self [field] | subscripts.swift:80:19:80:23 | field | provenance | | +| subscripts.swift:86:18:86:21 | self [field] | subscripts.swift:86:18:86:24 | ... .subscript(...) | provenance | | +| subscripts.swift:96:27:96:34 | newValue | subscripts.swift:96:19:96:23 | field | provenance | | +| subscripts.swift:102:24:102:31 | newValue [field] | subscripts.swift:102:24:102:37 | ... .field | provenance | | +| subscripts.swift:107:5:107:7 | [post] ... .s [field] | subscripts.swift:102:24:102:31 | newValue [field] | provenance | | +| subscripts.swift:107:5:107:10 | ... .subscript(...) | subscripts.swift:96:27:96:34 | newValue | provenance | | +| subscripts.swift:107:5:107:10 | ... .subscript(...) | subscripts.swift:107:5:107:7 | [post] ... .s [field] | provenance | | +| subscripts.swift:107:14:107:27 | source(...) | subscripts.swift:107:5:107:10 | ... .subscript(...) | provenance | | +| subscripts.swift:115:27:115:34 | newValue | subscripts.swift:115:19:115:23 | field | provenance | | +| subscripts.swift:119:5:119:8 | ... .subscript(...) | subscripts.swift:115:27:115:34 | newValue | provenance | | +| subscripts.swift:119:5:119:8 | ... .subscript(...) | subscripts.swift:120:10:120:10 | s [field] | provenance | | +| subscripts.swift:119:12:119:25 | source(...) | subscripts.swift:119:5:119:8 | ... .subscript(...) | provenance | | +| subscripts.swift:120:10:120:10 | s [field] | subscripts.swift:114:19:114:23 | field | provenance | | +| subscripts.swift:120:10:120:10 | s [field] | subscripts.swift:120:10:120:13 | ... .subscript(...) | provenance | | +| subscripts.swift:126:19:126:33 | source(...) | subscripts.swift:131:10:131:13 | ... .subscript(...) | provenance | | +| subscripts.swift:130:5:130:8 | ... .subscript(...) | subscripts.swift:127:24:127:31 | newValue | provenance | | +| subscripts.swift:130:12:130:26 | source(...) | subscripts.swift:130:5:130:8 | ... .subscript(...) | provenance | | +| subscripts.swift:135:21:135:35 | source(...) | subscripts.swift:135:10:135:36 | ... .subscript(...) | provenance | | +| subscripts.swift:137:5:137:22 | ... .subscript(...) | subscripts.swift:138:10:138:10 | t [field] | provenance | | +| subscripts.swift:137:26:137:40 | source(...) | subscripts.swift:137:5:137:22 | ... .subscript(...) | provenance | | +| subscripts.swift:138:10:138:10 | t [field] | subscripts.swift:138:10:138:21 | ... .getField(...) | provenance | | +| subscripts.swift:148:24:148:31 | newValue [field] | subscripts.swift:148:24:148:37 | ... .field | provenance | | +| subscripts.swift:151:19:151:19 | s [Return] [field] | subscripts.swift:153:12:153:16 | [post] & ... [field] | provenance | | +| subscripts.swift:151:33:151:33 | [post] s [field] | subscripts.swift:151:19:151:19 | s [Return] [field] | provenance | | +| subscripts.swift:151:33:151:39 | ... .field | subscripts.swift:151:33:151:33 | [post] s [field] | provenance | | +| subscripts.swift:151:43:151:57 | source(...) | subscripts.swift:151:33:151:39 | ... .field | provenance | | +| subscripts.swift:153:12:153:16 | [post] & ... [field] | subscripts.swift:153:13:153:16 | [post] ... .subscript(...) [field] | provenance | | +| subscripts.swift:153:13:153:16 | [post] ... .subscript(...) [field] | subscripts.swift:148:24:148:31 | newValue [field] | 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 | | | test.swift:20:13:20:26 | source(...) | test.swift:20:10:20:33 | StringInterpolationExpr | provenance | | @@ -987,6 +1045,80 @@ nodes | implicit-self.swift:87:14:87:19 | ... .x | semmle.label | ... .x | | short-return.swift:7:33:7:46 | source(...) | semmle.label | source(...) | | short-return.swift:8:10:8:19 | getValue(...) | semmle.label | getValue(...) | +| subscripts.swift:8:39:8:52 | source(...) | semmle.label | source(...) | +| subscripts.swift:10:10:10:15 | ... .subscript(...) | semmle.label | ... .subscript(...) | +| subscripts.swift:15:19:15:21 | key | semmle.label | key | +| subscripts.swift:16:19:16:21 | key | semmle.label | key | +| subscripts.swift:17:24:17:31 | newValue | semmle.label | newValue | +| subscripts.swift:21:10:21:26 | ... .subscript(...) | semmle.label | ... .subscript(...) | +| subscripts.swift:21:12:21:25 | source(...) | semmle.label | source(...) | +| subscripts.swift:22:5:22:13 | ... .subscript(...) | semmle.label | ... .subscript(...) | +| subscripts.swift:22:17:22:30 | source(...) | semmle.label | source(...) | +| subscripts.swift:27:19:27:21 | key | semmle.label | key | +| subscripts.swift:29:17:29:17 | v | semmle.label | v | +| subscripts.swift:29:27:29:29 | key | semmle.label | key | +| subscripts.swift:29:38:29:38 | v | semmle.label | v | +| subscripts.swift:33:5:33:21 | ... .subscript(...) | semmle.label | ... .subscript(...) | +| subscripts.swift:33:7:33:20 | source(...) | semmle.label | source(...) | +| subscripts.swift:33:25:33:38 | source(...) | semmle.label | source(...) | +| subscripts.swift:40:19:40:23 | field | semmle.label | field | +| subscripts.swift:41:19:41:23 | field | semmle.label | field | +| subscripts.swift:41:27:41:34 | newValue | semmle.label | newValue | +| subscripts.swift:45:5:45:15 | ... .subscript(...) | semmle.label | ... .subscript(...) | +| subscripts.swift:45:19:45:32 | source(...) | semmle.label | source(...) | +| subscripts.swift:46:10:46:10 | c [field] | semmle.label | c [field] | +| subscripts.swift:46:10:46:20 | ... .subscript(...) | semmle.label | ... .subscript(...) | +| subscripts.swift:47:10:47:10 | c [field] | semmle.label | c [field] | +| subscripts.swift:47:10:47:16 | ... .field | semmle.label | ... .field | +| subscripts.swift:53:19:53:32 | source(...) | semmle.label | source(...) | +| subscripts.swift:54:24:54:31 | newValue | semmle.label | newValue | +| subscripts.swift:58:5:58:8 | ... .subscript(...) | semmle.label | ... .subscript(...) | +| subscripts.swift:58:5:58:8 | [incoming] ... .subscript(...) | semmle.label | [incoming] ... .subscript(...) | +| subscripts.swift:68:24:68:31 | newValue [field] | semmle.label | newValue [field] | +| subscripts.swift:68:24:68:37 | ... .field | semmle.label | ... .field | +| subscripts.swift:73:5:73:8 | [post] ... .subscript(...) [field] | semmle.label | [post] ... .subscript(...) [field] | +| subscripts.swift:73:5:73:14 | ... .field | semmle.label | ... .field | +| subscripts.swift:73:18:73:31 | source(...) | semmle.label | source(...) | +| subscripts.swift:80:19:80:23 | field | semmle.label | field | +| subscripts.swift:81:19:81:23 | field | semmle.label | field | +| subscripts.swift:81:27:81:34 | newValue | semmle.label | newValue | +| subscripts.swift:85:13:85:19 | ... .subscript(...) | semmle.label | ... .subscript(...) | +| subscripts.swift:85:23:85:36 | source(...) | semmle.label | source(...) | +| subscripts.swift:86:18:86:21 | self [field] | semmle.label | self [field] | +| subscripts.swift:86:18:86:24 | ... .subscript(...) | semmle.label | ... .subscript(...) | +| subscripts.swift:96:19:96:23 | field | semmle.label | field | +| subscripts.swift:96:27:96:34 | newValue | semmle.label | newValue | +| subscripts.swift:102:24:102:31 | newValue [field] | semmle.label | newValue [field] | +| subscripts.swift:102:24:102:37 | ... .field | semmle.label | ... .field | +| subscripts.swift:107:5:107:7 | [post] ... .s [field] | semmle.label | [post] ... .s [field] | +| subscripts.swift:107:5:107:10 | ... .subscript(...) | semmle.label | ... .subscript(...) | +| subscripts.swift:107:14:107:27 | source(...) | semmle.label | source(...) | +| subscripts.swift:114:19:114:23 | field | semmle.label | field | +| subscripts.swift:115:19:115:23 | field | semmle.label | field | +| subscripts.swift:115:27:115:34 | newValue | semmle.label | newValue | +| subscripts.swift:119:5:119:8 | ... .subscript(...) | semmle.label | ... .subscript(...) | +| subscripts.swift:119:12:119:25 | source(...) | semmle.label | source(...) | +| subscripts.swift:120:10:120:10 | s [field] | semmle.label | s [field] | +| subscripts.swift:120:10:120:13 | ... .subscript(...) | semmle.label | ... .subscript(...) | +| subscripts.swift:126:19:126:33 | source(...) | semmle.label | source(...) | +| subscripts.swift:127:24:127:31 | newValue | semmle.label | newValue | +| subscripts.swift:130:5:130:8 | ... .subscript(...) | semmle.label | ... .subscript(...) | +| subscripts.swift:130:12:130:26 | source(...) | semmle.label | source(...) | +| subscripts.swift:131:10:131:13 | ... .subscript(...) | semmle.label | ... .subscript(...) | +| subscripts.swift:135:10:135:36 | ... .subscript(...) | semmle.label | ... .subscript(...) | +| subscripts.swift:135:21:135:35 | source(...) | semmle.label | source(...) | +| subscripts.swift:137:5:137:22 | ... .subscript(...) | semmle.label | ... .subscript(...) | +| subscripts.swift:137:26:137:40 | source(...) | semmle.label | source(...) | +| subscripts.swift:138:10:138:10 | t [field] | semmle.label | t [field] | +| subscripts.swift:138:10:138:21 | ... .getField(...) | semmle.label | ... .getField(...) | +| subscripts.swift:148:24:148:31 | newValue [field] | semmle.label | newValue [field] | +| subscripts.swift:148:24:148:37 | ... .field | semmle.label | ... .field | +| subscripts.swift:151:19:151:19 | s [Return] [field] | semmle.label | s [Return] [field] | +| subscripts.swift:151:33:151:33 | [post] s [field] | semmle.label | [post] s [field] | +| subscripts.swift:151:33:151:39 | ... .field | semmle.label | ... .field | +| subscripts.swift:151:43:151:57 | source(...) | semmle.label | source(...) | +| subscripts.swift:153:12:153:16 | [post] & ... [field] | semmle.label | [post] & ... [field] | +| subscripts.swift:153:13:153:16 | [post] ... .subscript(...) [field] | semmle.label | [post] ... .subscript(...) [field] | | test.swift:9:10:9:21 | source(...) | semmle.label | source(...) | | test.swift:17:10:17:23 | source(...) | semmle.label | source(...) | | test.swift:17:10:17:32 | ... + ... | semmle.label | ... + ... | @@ -1213,6 +1345,15 @@ subpaths | capture.swift:93:9:93:9 | x | capture.swift:99:16:99:16 | x | capture.swift:99:16:99:16 | x | capture.swift:94:13:94:19 | local(...) | | flow-summaries.swift:54:35:54:48 | source(...) | flow-summaries.swift:54:53:54:53 | y | flow-summaries.swift:54:65:54:65 | y | flow-summaries.swift:54:10:54:67 | ... .throughCallback(...) | | flow-summaries.swift:55:35:55:48 | source(...) | flow-summaries.swift:55:53:55:53 | y | flow-summaries.swift:55:65:55:71 | ... + ... | flow-summaries.swift:55:10:55:73 | ... .throughCallback(...) | +| subscripts.swift:21:12:21:25 | source(...) | subscripts.swift:15:19:15:21 | key | subscripts.swift:16:19:16:21 | key | subscripts.swift:21:10:21:26 | ... .subscript(...) | +| subscripts.swift:45:5:45:15 | ... .subscript(...) | subscripts.swift:41:27:41:34 | newValue | subscripts.swift:41:19:41:23 | field | subscripts.swift:46:10:46:10 | c [field] | +| subscripts.swift:45:5:45:15 | ... .subscript(...) | subscripts.swift:41:27:41:34 | newValue | subscripts.swift:41:19:41:23 | field | subscripts.swift:47:10:47:10 | c [field] | +| subscripts.swift:46:10:46:10 | c [field] | subscripts.swift:40:19:40:23 | field | subscripts.swift:40:19:40:23 | field | subscripts.swift:46:10:46:20 | ... .subscript(...) | +| subscripts.swift:85:13:85:19 | ... .subscript(...) | subscripts.swift:81:27:81:34 | newValue | subscripts.swift:81:19:81:23 | field | subscripts.swift:86:18:86:21 | self [field] | +| subscripts.swift:86:18:86:21 | self [field] | subscripts.swift:80:19:80:23 | field | subscripts.swift:80:19:80:23 | field | subscripts.swift:86:18:86:24 | ... .subscript(...) | +| subscripts.swift:107:5:107:10 | ... .subscript(...) | subscripts.swift:96:27:96:34 | newValue | subscripts.swift:96:19:96:23 | field | subscripts.swift:107:5:107:7 | [post] ... .s [field] | +| subscripts.swift:119:5:119:8 | ... .subscript(...) | subscripts.swift:115:27:115:34 | newValue | subscripts.swift:115:19:115:23 | field | subscripts.swift:120:10:120:10 | s [field] | +| subscripts.swift:120:10:120:10 | s [field] | subscripts.swift:114:19:114:23 | field | subscripts.swift:114:19:114:23 | field | subscripts.swift:120:10:120:13 | ... .subscript(...) | | test.swift:196:30:196:44 | source(...) | test.swift:189:22:189:22 | s | test.swift:189:58:189:58 | s | test.swift:196:16:196:45 | asyncIdentity(...) | | test.swift:200:31:200:45 | source(...) | test.swift:191:25:191:25 | s | test.swift:191:62:191:62 | s | test.swift:200:14:200:46 | throwingIdentity(...) | | test.swift:201:37:201:51 | source(...) | test.swift:191:25:191:25 | s | test.swift:191:62:191:62 | s | test.swift:201:20:201:52 | throwingIdentity(...) | @@ -1343,6 +1484,23 @@ testFailures | 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(...) | | 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(...) | +| subscripts.swift:10:10:10:15 | ... .subscript(...) | subscripts.swift:8:39:8:52 | source(...) | subscripts.swift:10:10:10:15 | ... .subscript(...) | $@ | subscripts.swift:8:39:8:52 | source(...) | source(...) | +| subscripts.swift:17:24:17:31 | newValue | subscripts.swift:22:17:22:30 | source(...) | subscripts.swift:17:24:17:31 | newValue | $@ | subscripts.swift:22:17:22:30 | source(...) | source(...) | +| subscripts.swift:21:10:21:26 | ... .subscript(...) | subscripts.swift:21:12:21:25 | source(...) | subscripts.swift:21:10:21:26 | ... .subscript(...) | $@ | subscripts.swift:21:12:21:25 | source(...) | source(...) | +| subscripts.swift:29:27:29:29 | key | subscripts.swift:33:7:33:20 | source(...) | subscripts.swift:29:27:29:29 | key | $@ | subscripts.swift:33:7:33:20 | source(...) | source(...) | +| subscripts.swift:29:38:29:38 | v | subscripts.swift:33:25:33:38 | source(...) | subscripts.swift:29:38:29:38 | v | $@ | subscripts.swift:33:25:33:38 | source(...) | source(...) | +| subscripts.swift:46:10:46:20 | ... .subscript(...) | subscripts.swift:45:19:45:32 | source(...) | subscripts.swift:46:10:46:20 | ... .subscript(...) | $@ | subscripts.swift:45:19:45:32 | source(...) | source(...) | +| subscripts.swift:47:10:47:16 | ... .field | subscripts.swift:45:19:45:32 | source(...) | subscripts.swift:47:10:47:16 | ... .field | $@ | subscripts.swift:45:19:45:32 | source(...) | source(...) | +| subscripts.swift:54:24:54:31 | newValue | subscripts.swift:53:19:53:32 | source(...) | subscripts.swift:54:24:54:31 | newValue | $@ | subscripts.swift:53:19:53:32 | source(...) | source(...) | +| subscripts.swift:68:24:68:37 | ... .field | subscripts.swift:73:18:73:31 | source(...) | subscripts.swift:68:24:68:37 | ... .field | $@ | subscripts.swift:73:18:73:31 | source(...) | source(...) | +| subscripts.swift:86:18:86:24 | ... .subscript(...) | subscripts.swift:85:23:85:36 | source(...) | subscripts.swift:86:18:86:24 | ... .subscript(...) | $@ | subscripts.swift:85:23:85:36 | source(...) | source(...) | +| subscripts.swift:102:24:102:37 | ... .field | subscripts.swift:107:14:107:27 | source(...) | subscripts.swift:102:24:102:37 | ... .field | $@ | subscripts.swift:107:14:107:27 | source(...) | source(...) | +| subscripts.swift:120:10:120:13 | ... .subscript(...) | subscripts.swift:119:12:119:25 | source(...) | subscripts.swift:120:10:120:13 | ... .subscript(...) | $@ | subscripts.swift:119:12:119:25 | source(...) | source(...) | +| subscripts.swift:127:24:127:31 | newValue | subscripts.swift:130:12:130:26 | source(...) | subscripts.swift:127:24:127:31 | newValue | $@ | subscripts.swift:130:12:130:26 | source(...) | source(...) | +| subscripts.swift:131:10:131:13 | ... .subscript(...) | subscripts.swift:126:19:126:33 | source(...) | subscripts.swift:131:10:131:13 | ... .subscript(...) | $@ | subscripts.swift:126:19:126:33 | source(...) | source(...) | +| subscripts.swift:135:10:135:36 | ... .subscript(...) | subscripts.swift:135:21:135:35 | source(...) | subscripts.swift:135:10:135:36 | ... .subscript(...) | $@ | subscripts.swift:135:21:135:35 | source(...) | source(...) | +| subscripts.swift:138:10:138:21 | ... .getField(...) | subscripts.swift:137:26:137:40 | source(...) | subscripts.swift:138:10:138:21 | ... .getField(...) | $@ | subscripts.swift:137:26:137:40 | source(...) | source(...) | +| subscripts.swift:148:24:148:37 | ... .field | subscripts.swift:151:43:151:57 | source(...) | subscripts.swift:148:24:148:37 | ... .field | $@ | subscripts.swift:151:43:151:57 | 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(...) | | test.swift:18:10:18:32 | ... + ... | test.swift:18:19:18:32 | source(...) | test.swift:18:10:18:32 | ... + ... | $@ | test.swift:18:19:18:32 | source(...) | source(...) | diff --git a/unified/ql/test/library-tests/dataflow/test.ext.yml b/unified/ql/test/library-tests/dataflow/test.ext.yml index 352ac28dad95..a751df21c849 100644 --- a/unified/ql/test/library-tests/dataflow/test.ext.yml +++ b/unified/ql/test/library-tests/dataflow/test.ext.yml @@ -18,3 +18,5 @@ extensions: - ["", "TestType", true, "swap(_:_:)", "", "", "Argument[1]", "Argument[0]", "value", "manual"] - ["", "TestType", true, "getField()", "", "", "Argument[self].Member[field]", "ReturnValue", "value", "manual"] - ["", "TestType", true, "setField(_:)", "", "", "Argument[0]", "Argument[self].Member[field]", "value", "manual"] + - ["", "TestType", true, "subscript(testKey:)", "", "", "Argument[testKey:]", "ReturnValue", "value", "manual"] + - ["", "TestType", true, "subscript=(_:testKey:)", "", "", "Argument[0]", "Argument[self].Member[field]", "value", "manual"] diff --git a/unified/ql/test/library-tests/type-inference/type-inference.expected b/unified/ql/test/library-tests/type-inference/type-inference.expected index e978a676689b..177dcc387147 100644 --- a/unified/ql/test/library-tests/type-inference/type-inference.expected +++ b/unified/ql/test/library-tests/type-inference/type-inference.expected @@ -2728,7 +2728,7 @@ inferType | key_paths.swift:131:20:131:22 | 1.0 | | {EXTERNAL LOCATION} | Double | | key_paths.swift:131:28:131:30 | 2.0 | | {EXTERNAL LOCATION} | Double | | key_paths.swift:133:3:133:3 | p | | key_paths.swift:3:1:15:1 | Point | -| key_paths.swift:133:3:133:17 | p(...) | | {EXTERNAL LOCATION} | Double | +| key_paths.swift:133:3:133:17 | ... .subscript(...) | | {EXTERNAL LOCATION} | Double | | key_paths.swift:133:3:133:24 | ... = ... | | {EXTERNAL LOCATION} | Double | | key_paths.swift:133:21:133:24 | 10.0 | | {EXTERNAL LOCATION} | Double | | key_paths.swift:134:14:134:14 | p | | key_paths.swift:3:1:15:1 | Point |