diff --git a/shared/namebinding/codeql/namebinding/LocalNameBinding.qll b/shared/namebinding/codeql/namebinding/LocalNameBinding.qll index 2dd12bbc8abc..4bfde31af811 100644 --- a/shared/namebinding/codeql/namebinding/LocalNameBinding.qll +++ b/shared/namebinding/codeql/namebinding/LocalNameBinding.qll @@ -34,8 +34,10 @@ signature module LocalNameBindingInputSig { AstNode getChild(AstNode n, int index); /** - * A conditional where any local declarations in the condition are in scope - * in the then-branch but not the else-branch. + * A conditional with a condition, a then-branch, and an optional else-branch. + * + * Unless `isUnscopedConditional` holds, local declarations in the condition + * are in scope in the then-branch but not the else-branch. * * Example: * @@ -62,6 +64,25 @@ signature module LocalNameBindingInputSig { AstNode getElse(); } + /** + * Holds if local declarations in a branch of `cond` are in scope in the + * remainder of the branch _and_ following the conditional itself. + * + * Example: + * + * ```swift + * #if DEBUG + * let x = def1 + * // x is in scope here and refers to `def1` + * #else + * let x = def2 + * // x is in scope here and refers to `def2` + * #endif + * // x is in scope here and refers to either `def1` or `def2` + * ``` + */ + default predicate isUnscopedConditional(Conditional cond) { none() } + /** * A declaration where all local declarations in the left-hand side are in * scope _after_ the declaration, and where any sibling declarations with @@ -195,17 +216,23 @@ module LocalNameBinding */ private AstNode getChildAdj(AstNode parent, int index) { result = getChild(parent, index) and - not exists(Conditional cond | result = [cond.getElse(), cond.getThen()]) + not exists(Conditional cond | + not isUnscopedConditional(cond) and + result = [cond.getElse(), cond.getThen()] + ) or exists(Conditional cond | - parent = cond and - result = cond.getElse() and - index = -1 - or - exists(int last | - result = cond.getThen() and - last = max(int i | conditionHasChildAt(cond, parent, i)) and - index = last + 1 + not isUnscopedConditional(cond) and + ( + parent = cond and + result = cond.getElse() and + index = -1 + or + exists(int last | + result = cond.getThen() and + last = max(int i | conditionHasChildAt(cond, parent, i)) and + index = last + 1 + ) ) ) } @@ -217,31 +244,137 @@ module LocalNameBinding int getRank(C parent, Ranked child) { child = getChildAdj(parent, result) and - getChildAdj(parent, _) instanceof SiblingShadowingDecl + // Only rank children when necessary, i.e., when there are sibling + // shadowing declarations or unscoped conditionals among the children. + ( + getChildAdj(parent, _) instanceof SiblingShadowingDecl + or + isUnscopedConditional(getChildAdj(parent, _)) + ) } } private predicate getRankedChild = DenseRank1::denseRank/2; + /** Holds if `decl` is a possible final declaration introduced by `node`. */ + private predicate lastDeclAfter(AstNode node, SiblingShadowingDecl decl) { + node = decl + or + node = any(Conditional cond | isUnscopedConditional(cond) and lastDeclInBranch(cond, decl)) + } + + private predicate lastDeclInBranch(Conditional cond, SiblingShadowingDecl decl) { + exists(AstNode branch | branch = [cond.getThen(), cond.getElse()] | + // An `#elseif` is itself a conditional rather than a block of statements. + isUnscopedConditional(branch) and lastDeclAfter(branch, decl) + or + not isUnscopedConditional(branch) and + lastDeclInPrefix(branch, max(int i | exists(getRankedChild(branch, i))), decl) + ) + } + /** - * Holds if `n` is the `i`th child of `parent`, but should instead be considered - * a child of a shadowing sibling declaration `decl` when resolving accesses. + * Holds if the unscoped conditional `cond` might not introduce any shadowing + * declarations. We need this property to determine whether declarations prior + * to the conditional are still in scope following the conditional. * - * This is the case when `decl` is the nearest shadowing sibling declaration - * preceding `n` amongst all the children of `parent`. + * For instance, in + * ```swift + * let x = 1 + * #if DEBUG + * let x = 2 + * #else + * let x = 3 + * #endif + * print(x) // prints 2 or 3 + * ``` + * the initial declaration of `x` is certain to be shadowed after the + * conditional. * - * Note that `decl` may itself also have to be nested under another shadowing - * sibling declaration. + * If the conditional instead had no else branch or had one of the branches + * not declared any shadowing declarations, then the initial declaration of + * `x` might not have been shadowed after the conditional. */ - private predicate shouldBeShadowingDeclChild( - AstNode parent, SiblingShadowingDecl decl, int i, AstNode n - ) { - n = getRankedChild(parent, i) and + private predicate unscopedConditionalPreservesIncoming(Conditional cond) { + isUnscopedConditional(cond) and + ( + not exists(cond.getElse()) + or + branchPreservesIncoming([cond.getThen(), cond.getElse()]) + ) + } + + pragma[inline] + private predicate branchPreservesIncoming(AstNode branch) { + unscopedConditionalPreservesIncoming(branch) + or + not isUnscopedConditional(branch) and ( - decl = getRankedChild(parent, i - 1) + // If the children are not ranked, then they do not contain any sibling + // shadowing declarations. + not exists(getRankedChild(branch, _)) or - shouldBeShadowingDeclChild(parent, decl, i - 1, - any(AstNode prev | not prev instanceof SiblingShadowingDecl)) + prefixPreservesIncoming(branch, max(int i | exists(getRankedChild(branch, i)))) + ) + } + + /** + * Holds if `child` is a ranked child and might not introduce any shadowing + * declaration. + * + * For example, the following satisfies the predicate since the declaration + * might be removed at compile time. + * ```swift + * #if DEBUG + * let value = 1 + * #endif + * ``` + */ + private predicate preservesIncoming(AstNode child) { + child = getRankedChild(_, _) and + ( + // A shadowing declaration always introduces a shadowing declaration. + not child instanceof SiblingShadowingDecl and + not isUnscopedConditional(child) + or + unscopedConditionalPreservesIncoming(child) + ) + } + + /** Summarizes the first `i` ranked children without entering ordinary nested scopes. */ + private predicate lastDeclInPrefix(AstNode parent, int i, SiblingShadowingDecl decl) { + exists(AstNode child | child = getRankedChild(parent, i) | + lastDeclAfter(child, decl) + or + preservesIncoming(child) and lastDeclInPrefix(parent, i - 1, decl) + ) + } + + /** + * Holds if the first `i` children might not introduce any shadowing + * declarations. + */ + private predicate prefixPreservesIncoming(AstNode parent, int i) { + i = 0 and exists(getRankedChild(parent, _)) + or + prefixPreservesIncoming(parent, i - 1) and + preservesIncoming(getRankedChild(parent, i)) + } + + /** + * Holds if, instead of its parent, `n` should be considered a child of a + * shadowing sibling declaration `decl` when resolving accesses. + * + * Each alternative through preceding unscoped conditionals contributes its own + * nearest shadowing declaration. + * + * Note that `decl` may itself also have to be nested under another shadowing + * sibling declaration. + */ + private predicate shouldBeShadowingDeclChild(SiblingShadowingDecl decl, AstNode n) { + exists(AstNode parent, int i | + n = getRankedChild(parent, i) and + lastDeclInPrefix(parent, i - 1, decl) ) } @@ -289,11 +422,18 @@ module LocalNameBinding * ``` */ private AstNode getParentForScoping(AstNode n) { - not shouldBeShadowingDeclChild(_, _, _, n) and not exists(SiblingShadowingDecl decl | n = [decl.getRhs(), decl.getElse()]) and - n = getChildAdj(result, _) + n = getChildAdj(result, _) and + ( + not n = getRankedChild(result, _) + or + exists(int i | + n = getRankedChild(result, i) and + prefixPreservesIncoming(result, i - 1) + ) + ) or - shouldBeShadowingDeclChild(_, result, _, n) + shouldBeShadowingDeclChild(result, n) or exists(SiblingShadowingDecl decl | result = getParentForScoping(decl) and diff --git a/unified/extractor/ast_types.yml b/unified/extractor/ast_types.yml index fb9d88333087..5cf5538e1a58 100644 --- a/unified/extractor/ast_types.yml +++ b/unified/extractor/ast_types.yml @@ -261,7 +261,10 @@ named: block: stmt*: stmt + # An if expression or a conditional compilation directive (which we view as a + # compile-time evaluated if expression). if_expr: + modifier*: modifier condition: expr then?: expr else?: expr diff --git a/unified/extractor/src/languages/swift/swift.rs b/unified/extractor/src/languages/swift/swift.rs index af05d93ad172..7d95c8e2b907 100644 --- a/unified/extractor/src/languages/swift/swift.rs +++ b/unified/extractor/src/languages/swift/swift.rs @@ -38,6 +38,12 @@ 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, + /// True when a conditional compilation block should be translated into an + /// if expression. + conditional_compilation_as_expr: bool, + /// When iterating over the clauses of a conditional compilation block, this + /// holds the tail of the clauses that have already been translated. + conditional_compilation_tail: Option, } impl SwiftContext { @@ -153,6 +159,14 @@ fn translation_rules() -> Vec> { // but a single wrapped `variableDecl` can translate to *several* // declarations (`let x = 1, y = 2`), so the wrapped node is captured with // `_*` and the result annotated `stmt*` to splice all of them. + rule!( + (codeBlockItem item: (ifConfigDecl) @@item) + => + stmt* { + ctx.conditional_compilation_as_expr = true; + ctx.translate(item)? + } + ), rule!((codeBlockItem item: _* @item) => stmt* { item }), // ---- Literals ---- // swift-syntax does not distinguish the lexical integer/string forms @@ -876,6 +890,61 @@ fn translation_rules() -> Vec> { => (parameter pattern: (identifier #{name})) ), + // ---- Conditional compilation blocks ---- + // Conditional compilation blocks (`#if … #elseif … #else … #endif`) are + // represented with a `ifConfigDecl` node for the entire block and an + // `ifConfigClause` for each branch. + + // When a conditional compilation block is not in a context where it + // should be translated into an if expression we simply extract the + // first branch (for now). + rule!( + (ifConfigDecl + clauses: (ifConfigClause elements: _* @items) + clauses: _* @@rest) + where !ctx.conditional_compilation_as_expr + => + _* { items } + ), + // A conditional compilation block is translated into `if_expr` nodes + // with branches occurring nested within the else branch. + rule!( + (ifConfigDecl clauses: _* @@clauses) @@declaration + => + expr { + // Revert `conditional_compilation_as_expr` before processing + // clauses. Note that if a clause contains a nested conditional + // compilation block, then that block will be wrapped in another + // `codeBlockItem` which will flip this back to `true`. + ctx.conditional_compilation_as_expr = false; + ctx.conditional_compilation_tail = None; + for clause in clauses.into_iter().rev() { + ctx.conditional_compilation_tail = ctx.translate(clause)?.into_iter().next(); + } + let result = ctx.conditional_compilation_tail.ok_or("conditional compilation has no clauses")?; + ctx.set_node_source_range(result, ctx.source_range(declaration)) + } + ), + // A single clause is mapped to an if expression with the tail of + // already processed clauses (stored in `conditional_compilation_tail`) + // inserted into the else branch. + rule!( + (ifConfigClause condition: @condition elements: _* @items) + => + (if_expr + // Mark this `if_expr` as coming from conditional compilation. + modifier: (modifier "conditional_compilation") + condition: {condition} + then: (block stmt: {items}) + else: {ctx.conditional_compilation_tail}) + ), + // A conditional compilation clause without a condition (a final `#else` + // block). + rule!( + (ifConfigClause elements: _* @items) + => + (block stmt: {items}) + ), // ---- Control flow ---- // An `if`/`else` expression. Conditions are joined via `and_chain`; the // then-body and optional else-body (another block, or an `ifExpr` for an @@ -1510,7 +1579,6 @@ fn translation_rules() -> Vec> { rule!((copyExpr) => (unsupported_node "")), rule!((discardStmt) => (unsupported_node "")), rule!((fallThroughStmt) => (unsupported_node "")), - rule!((ifConfigDecl) => (unsupported_node "")), rule!((implicitlyUnwrappedOptionalType) => (unsupported_node "")), rule!((inlineArrayType) => (unsupported_node "")), rule!((keyPathExpr) => (unsupported_node "")), diff --git a/unified/extractor/tests/corpus/swift/conditional-compilation/conditional-compilation-in-attributes.output b/unified/extractor/tests/corpus/swift/conditional-compilation/conditional-compilation-in-attributes.output new file mode 100644 index 000000000000..a0ce29bc4b59 --- /dev/null +++ b/unified/extractor/tests/corpus/swift/conditional-compilation/conditional-compilation-in-attributes.output @@ -0,0 +1,111 @@ +#if DEBUG + @available(*, deprecated) +#endif +func f() {} + +#if DEBUG + @available(*, deprecated) +#endif +let x = 1 + +--- + +sourceFile + endOfFileToken: endOfFile + statements: + codeBlockItem + item: + functionDecl + attributes: + ifConfigDecl + clauses: + ifConfigClause + elements: + attribute + leftParen: ( + rightParen: ) + arguments: + availabilityArgument + trailingComma: , + argument: binaryOperator "*" + availabilityArgument + argument: deprecated + atSign: @ + attributeName: + identifierType + name: identifier "available" + condition: + declReferenceExpr + baseName: identifier "DEBUG" + poundKeyword: #if + poundEndif: #endif + body: + codeBlock + leftBrace: { + rightBrace: } + statements: + modifiers: + name: identifier "f" + signature: + functionSignature + parameterClause: + functionParameterClause + leftParen: ( + rightParen: ) + parameters: + funcKeyword: func + codeBlockItem + item: + variableDecl + attributes: + ifConfigDecl + clauses: + ifConfigClause + elements: + attribute + leftParen: ( + rightParen: ) + arguments: + availabilityArgument + trailingComma: , + argument: binaryOperator "*" + availabilityArgument + argument: deprecated + atSign: @ + attributeName: + identifierType + name: identifier "available" + condition: + declReferenceExpr + baseName: identifier "DEBUG" + poundKeyword: #if + poundEndif: #endif + modifiers: + bindingSpecifier: let + bindings: + patternBinding + initializer: + initializerClause + equal: = + value: + integerLiteralExpr + literal: integerLiteral "1" + pattern: + identifierPattern + identifier: identifier "x" + +--- + +top_level source="⟨body⟩" + body: + block source="⟨stmt⟩\n\n#if DEBUG\n ⟨stmt⟩" + stmt: + function_declaration source="#if DEBUG\n @available(*, deprecated)\n#endif\nfunc ⟨name_node⟩() ⟨body⟩" + name_node: identifier "f" source="f" + body: block "{}" source="{}" + variable_declaration source="⟨modifier⟩\n#endif\n⟨modifier⟩ ⟨pattern⟩ = ⟨value⟩" + modifier: + modifier "let" source="let" + modifier "@available(*, deprecated)" source="@available(*, deprecated)" + pattern: identifier "x" source="x" + value: int_literal "1" source="1" diff --git a/unified/extractor/tests/corpus/swift/conditional-compilation/conditional-compilation-in-attributes.swift b/unified/extractor/tests/corpus/swift/conditional-compilation/conditional-compilation-in-attributes.swift new file mode 100644 index 000000000000..00310f5ab5c1 --- /dev/null +++ b/unified/extractor/tests/corpus/swift/conditional-compilation/conditional-compilation-in-attributes.swift @@ -0,0 +1,9 @@ +#if DEBUG + @available(*, deprecated) +#endif +func f() {} + +#if DEBUG + @available(*, deprecated) +#endif +let x = 1 diff --git a/unified/extractor/tests/corpus/swift/types/conditional-compilation-in-class-body.output b/unified/extractor/tests/corpus/swift/conditional-compilation/conditional-compilation-in-class-body.output similarity index 78% rename from unified/extractor/tests/corpus/swift/types/conditional-compilation-in-class-body.output rename to unified/extractor/tests/corpus/swift/conditional-compilation/conditional-compilation-in-class-body.output index 4d6fdc40564a..c844c1485e66 100644 --- a/unified/extractor/tests/corpus/swift/types/conditional-compilation-in-class-body.output +++ b/unified/extractor/tests/corpus/swift/conditional-compilation/conditional-compilation-in-class-body.output @@ -1,12 +1,9 @@ -// Conditional compilation is not yet supported: swift-syntax reports a -// structured `ifConfigDecl` (whose branches hold ordinary member items), but -// the mapping has no rule for it, so the whole block becomes one -// `unsupported_node` and its members are not extracted. +// Conditional compilation branches hold ordinary member declarations. class C { -#if DEBUG - init(x: Int) {} - deinit {} -#endif + #if DEBUG + init(x: Int) {} + deinit {} + #endif } --- @@ -81,12 +78,17 @@ top_level source="⟨body⟩" body: block source="⟨stmt⟩" stmt: - class_like_declaration source="⟨modifier⟩ ⟨name_node⟩ {\n⟨member⟩\n}" + class_like_declaration source="⟨modifier⟩ ⟨name_node⟩ {\n #if DEBUG\n ⟨member⟩\n ⟨member⟩\n #endif\n}" modifier: modifier "class" source="class" name_node: identifier "C" source="C" member: - unsupported_node "#if DEBUG\n init(x: Int) {}\n deinit {}\n#endif" source="#if DEBUG\n init(x: Int) {}\n deinit {}\n#endif" - constructor_declaration source="" - modifier: modifier "generated" source="" - name_node: identifier "init" source="" - body: block source="" + constructor_declaration source="⟨name_node⟩(⟨parameter⟩) ⟨body⟩" + name_node: identifier "init" source="init" + parameter: + parameter source="⟨external_name_node⟩⟨pattern⟩: ⟨type⟩" + external_name_node: identifier "x" source="x" + type: identifier "Int" source="Int" + pattern: identifier "x" source="x" + body: block "{}" source="{}" + destructor_declaration source="deinit ⟨body⟩" + body: block "{}" source="{}" diff --git a/unified/extractor/tests/corpus/swift/conditional-compilation/conditional-compilation-in-class-body.swift b/unified/extractor/tests/corpus/swift/conditional-compilation/conditional-compilation-in-class-body.swift new file mode 100644 index 000000000000..09e6f7852f4e --- /dev/null +++ b/unified/extractor/tests/corpus/swift/conditional-compilation/conditional-compilation-in-class-body.swift @@ -0,0 +1,7 @@ +// Conditional compilation branches hold ordinary member declarations. +class C { + #if DEBUG + init(x: Int) {} + deinit {} + #endif +} diff --git a/unified/extractor/tests/corpus/swift/conditional-compilation/conditional-compilation-in-switch-cases.output b/unified/extractor/tests/corpus/swift/conditional-compilation/conditional-compilation-in-switch-cases.output new file mode 100644 index 000000000000..e806a526fe97 --- /dev/null +++ b/unified/extractor/tests/corpus/swift/conditional-compilation/conditional-compilation-in-switch-cases.output @@ -0,0 +1,142 @@ +func f(_ x: Int) { + switch x { + #if DEBUG + case 0: break + case 1: break + #endif + default: break + } +} + +--- + +sourceFile + endOfFileToken: endOfFile + statements: + codeBlockItem + item: + functionDecl + attributes: + body: + codeBlock + leftBrace: { + rightBrace: } + statements: + codeBlockItem + item: + expressionStmt + expression: + switchExpr + leftBrace: { + rightBrace: } + cases: + ifConfigDecl + clauses: + ifConfigClause + elements: + switchCase + label: + switchCaseLabel + colon: : + caseKeyword: case + caseItems: + switchCaseItem + pattern: + expressionPattern + expression: + integerLiteralExpr + literal: integerLiteral "0" + statements: + codeBlockItem + item: + breakStmt + breakKeyword: break + switchCase + label: + switchCaseLabel + colon: : + caseKeyword: case + caseItems: + switchCaseItem + pattern: + expressionPattern + expression: + integerLiteralExpr + literal: integerLiteral "1" + statements: + codeBlockItem + item: + breakStmt + breakKeyword: break + condition: + declReferenceExpr + baseName: identifier "DEBUG" + poundKeyword: #if + poundEndif: #endif + switchCase + label: + switchDefaultLabel + colon: : + defaultKeyword: default + statements: + codeBlockItem + item: + breakStmt + breakKeyword: break + subject: + declReferenceExpr + baseName: identifier "x" + switchKeyword: switch + modifiers: + name: identifier "f" + signature: + functionSignature + parameterClause: + functionParameterClause + leftParen: ( + rightParen: ) + parameters: + functionParameter + colon: : + attributes: + modifiers: + type: + identifierType + name: identifier "Int" + firstName: _ + secondName: identifier "x" + funcKeyword: func + +--- + +top_level source="⟨body⟩" + body: + block source="⟨stmt⟩" + stmt: + function_declaration source="func ⟨name_node⟩(⟨parameter⟩) ⟨body⟩" + name_node: identifier "f" source="f" + parameter: + parameter source="⟨external_name_node⟩ ⟨pattern⟩: ⟨type⟩" + external_name_node: identifier "_" source="_" + type: identifier "Int" source="Int" + pattern: identifier "x" source="x" + body: + block source="{\n ⟨stmt⟩\n}" + stmt: + switch_expr source="switch ⟨value⟩ {\n #if DEBUG\n ⟨case⟩\n ⟨case⟩\n #endif\n ⟨case⟩\n }" + value: identifier "x" source="x" + case: + switch_case source="case ⟨pattern⟩: ⟨body⟩" + pattern: int_literal "0" source="0" + body: + block source="⟨stmt⟩" + stmt: break_expr "break" source="break" + switch_case source="case ⟨pattern⟩: ⟨body⟩" + pattern: int_literal "1" source="1" + body: + block source="⟨stmt⟩" + stmt: break_expr "break" source="break" + switch_case source="default: ⟨body⟩" + body: + block source="⟨stmt⟩" + stmt: break_expr "break" source="break" diff --git a/unified/extractor/tests/corpus/swift/conditional-compilation/conditional-compilation-in-switch-cases.swift b/unified/extractor/tests/corpus/swift/conditional-compilation/conditional-compilation-in-switch-cases.swift new file mode 100644 index 000000000000..ac09c5660fb9 --- /dev/null +++ b/unified/extractor/tests/corpus/swift/conditional-compilation/conditional-compilation-in-switch-cases.swift @@ -0,0 +1,9 @@ +func f(_ x: Int) { + switch x { + #if DEBUG + case 0: break + case 1: break + #endif + default: break + } +} diff --git a/unified/extractor/tests/corpus/swift/conditional-compilation/conditional-compilation-nested.output b/unified/extractor/tests/corpus/swift/conditional-compilation/conditional-compilation-nested.output new file mode 100644 index 000000000000..5db8f1cff0f5 --- /dev/null +++ b/unified/extractor/tests/corpus/swift/conditional-compilation/conditional-compilation-nested.output @@ -0,0 +1,351 @@ +#if DEBUG + let first = 1 + let second = 2 + #if EXTRA + func f() {} + #elseif OTHER + #else + let third = 3 + #endif +#else +#endif + +protocol P { + #if DEBUG + associatedtype T + #endif +} + +func nested_in_statement_context() { + #if DEBUG + #if EXTRA + let value = 0 + #else + let value = 1 + #endif + #else + let value = 2 + #endif +} + +--- + +sourceFile + endOfFileToken: endOfFile + statements: + codeBlockItem + item: + ifConfigDecl + clauses: + ifConfigClause + elements: + codeBlockItem + item: + variableDecl + attributes: + modifiers: + bindingSpecifier: let + bindings: + patternBinding + initializer: + initializerClause + equal: = + value: + integerLiteralExpr + literal: integerLiteral "1" + pattern: + identifierPattern + identifier: identifier "first" + codeBlockItem + item: + variableDecl + attributes: + modifiers: + bindingSpecifier: let + bindings: + patternBinding + initializer: + initializerClause + equal: = + value: + integerLiteralExpr + literal: integerLiteral "2" + pattern: + identifierPattern + identifier: identifier "second" + codeBlockItem + item: + ifConfigDecl + clauses: + ifConfigClause + elements: + codeBlockItem + item: + functionDecl + attributes: + body: + codeBlock + leftBrace: { + rightBrace: } + statements: + modifiers: + name: identifier "f" + signature: + functionSignature + parameterClause: + functionParameterClause + leftParen: ( + rightParen: ) + parameters: + funcKeyword: func + condition: + declReferenceExpr + baseName: identifier "EXTRA" + poundKeyword: #if + ifConfigClause + elements: + condition: + declReferenceExpr + baseName: identifier "OTHER" + poundKeyword: #elseif + ifConfigClause + elements: + codeBlockItem + item: + variableDecl + attributes: + modifiers: + bindingSpecifier: let + bindings: + patternBinding + initializer: + initializerClause + equal: = + value: + integerLiteralExpr + literal: integerLiteral "3" + pattern: + identifierPattern + identifier: identifier "third" + poundKeyword: #else + poundEndif: #endif + condition: + declReferenceExpr + baseName: identifier "DEBUG" + poundKeyword: #if + ifConfigClause + elements: + poundKeyword: #else + poundEndif: #endif + codeBlockItem + item: + protocolDecl + attributes: + modifiers: + name: identifier "P" + memberBlock: + memberBlock + leftBrace: { + rightBrace: } + members: + memberBlockItem + decl: + ifConfigDecl + clauses: + ifConfigClause + elements: + memberBlockItem + decl: + associatedTypeDecl + attributes: + modifiers: + name: identifier "T" + associatedtypeKeyword: associatedtype + condition: + declReferenceExpr + baseName: identifier "DEBUG" + poundKeyword: #if + poundEndif: #endif + protocolKeyword: protocol + codeBlockItem + item: + functionDecl + attributes: + body: + codeBlock + leftBrace: { + rightBrace: } + statements: + codeBlockItem + item: + ifConfigDecl + clauses: + ifConfigClause + elements: + codeBlockItem + item: + ifConfigDecl + clauses: + ifConfigClause + elements: + codeBlockItem + item: + variableDecl + attributes: + modifiers: + bindingSpecifier: let + bindings: + patternBinding + initializer: + initializerClause + equal: = + value: + integerLiteralExpr + literal: integerLiteral "0" + pattern: + identifierPattern + identifier: identifier "value" + condition: + declReferenceExpr + baseName: identifier "EXTRA" + poundKeyword: #if + ifConfigClause + elements: + codeBlockItem + item: + variableDecl + attributes: + modifiers: + bindingSpecifier: let + bindings: + patternBinding + initializer: + initializerClause + equal: = + value: + integerLiteralExpr + literal: integerLiteral "1" + pattern: + identifierPattern + identifier: identifier "value" + poundKeyword: #else + poundEndif: #endif + condition: + declReferenceExpr + baseName: identifier "DEBUG" + poundKeyword: #if + ifConfigClause + elements: + codeBlockItem + item: + variableDecl + attributes: + modifiers: + bindingSpecifier: let + bindings: + patternBinding + initializer: + initializerClause + equal: = + value: + integerLiteralExpr + literal: integerLiteral "2" + pattern: + identifierPattern + identifier: identifier "value" + poundKeyword: #else + poundEndif: #endif + modifiers: + name: identifier "nested_in_statement_context" + signature: + functionSignature + parameterClause: + functionParameterClause + leftParen: ( + rightParen: ) + parameters: + funcKeyword: func + +--- + +top_level source="⟨body⟩" + body: + block source="⟨stmt⟩\n\n⟨stmt⟩\n\n⟨stmt⟩" + stmt: + if_expr source="#if ⟨condition⟩\n ⟨then⟩\n⟨else⟩\n#endif" + modifier: modifier "conditional_compilation" source="" + condition: identifier "DEBUG" source="DEBUG" + then: + block source="⟨stmt⟩\n ⟨stmt⟩\n ⟨stmt⟩" + stmt: + variable_declaration source="⟨modifier⟩ ⟨pattern⟩ = ⟨value⟩" + modifier: modifier "let" source="let" + pattern: identifier "first" source="first" + value: int_literal "1" source="1" + variable_declaration source="⟨modifier⟩ ⟨pattern⟩ = ⟨value⟩" + modifier: modifier "let" source="let" + pattern: identifier "second" source="second" + value: int_literal "2" source="2" + if_expr source="#if ⟨condition⟩\n ⟨then⟩\n ⟨else⟩\n #endif" + modifier: modifier "conditional_compilation" source="" + condition: identifier "EXTRA" source="EXTRA" + then: + block source="⟨stmt⟩" + stmt: + function_declaration source="func ⟨name_node⟩() ⟨body⟩" + name_node: identifier "f" source="f" + body: block "{}" source="{}" + else: + if_expr source="#elseif ⟨condition⟩\n ⟨else⟩" + modifier: modifier "conditional_compilation" source="" + condition: identifier "OTHER" source="OTHER" + then: block source="" + else: + block source="#else\n ⟨stmt⟩" + stmt: + variable_declaration source="⟨modifier⟩ ⟨pattern⟩ = ⟨value⟩" + modifier: modifier "let" source="let" + pattern: identifier "third" source="third" + value: int_literal "3" source="3" + else: block "#else" source="#else" + class_like_declaration source="⟨modifier⟩ ⟨name_node⟩ {\n #if DEBUG\n ⟨member⟩\n #endif\n}" + modifier: modifier "protocol" source="protocol" + name_node: identifier "P" source="P" + member: + associated_type_declaration source="associatedtype ⟨name_node⟩" + name_node: identifier "T" source="T" + function_declaration source="func ⟨name_node⟩() ⟨body⟩" + name_node: identifier "nested_in_statement_context" source="nested_in_statement_context" + body: + block source="{\n ⟨stmt⟩\n}" + stmt: + if_expr source="#if ⟨condition⟩\n ⟨then⟩\n ⟨else⟩\n #endif" + modifier: modifier "conditional_compilation" source="" + condition: identifier "DEBUG" source="DEBUG" + then: + block source="⟨stmt⟩" + stmt: + if_expr source="#if ⟨condition⟩\n ⟨then⟩\n ⟨else⟩\n #endif" + modifier: modifier "conditional_compilation" source="" + condition: identifier "EXTRA" source="EXTRA" + then: + block source="⟨stmt⟩" + stmt: + variable_declaration source="⟨modifier⟩ ⟨pattern⟩ = ⟨value⟩" + modifier: modifier "let" source="let" + pattern: identifier "value" source="value" + value: int_literal "0" source="0" + else: + block source="#else\n ⟨stmt⟩" + stmt: + variable_declaration source="⟨modifier⟩ ⟨pattern⟩ = ⟨value⟩" + modifier: modifier "let" source="let" + pattern: identifier "value" source="value" + value: int_literal "1" source="1" + else: + block source="#else\n ⟨stmt⟩" + stmt: + variable_declaration source="⟨modifier⟩ ⟨pattern⟩ = ⟨value⟩" + modifier: modifier "let" source="let" + pattern: identifier "value" source="value" + value: int_literal "2" source="2" diff --git a/unified/extractor/tests/corpus/swift/conditional-compilation/conditional-compilation-nested.swift b/unified/extractor/tests/corpus/swift/conditional-compilation/conditional-compilation-nested.swift new file mode 100644 index 000000000000..00ea9f372cca --- /dev/null +++ b/unified/extractor/tests/corpus/swift/conditional-compilation/conditional-compilation-nested.swift @@ -0,0 +1,29 @@ +#if DEBUG + let first = 1 + let second = 2 + #if EXTRA + func f() {} + #elseif OTHER + #else + let third = 3 + #endif +#else +#endif + +protocol P { + #if DEBUG + associatedtype T + #endif +} + +func nested_in_statement_context() { + #if DEBUG + #if EXTRA + let value = 0 + #else + let value = 1 + #endif + #else + let value = 2 + #endif +} diff --git a/unified/extractor/tests/corpus/swift/conditional-compilation/conditional-compilation-postfix-expression.output b/unified/extractor/tests/corpus/swift/conditional-compilation/conditional-compilation-postfix-expression.output new file mode 100644 index 000000000000..ba310a0faffe --- /dev/null +++ b/unified/extractor/tests/corpus/swift/conditional-compilation/conditional-compilation-postfix-expression.output @@ -0,0 +1,89 @@ +let result = value + #if DEBUG + .foo(1) + #else + .bar("hi") + #endif + +--- + +sourceFile + endOfFileToken: endOfFile + statements: + codeBlockItem + item: + variableDecl + attributes: + modifiers: + bindingSpecifier: let + bindings: + patternBinding + initializer: + initializerClause + equal: = + value: + postfixIfConfigExpr + base: + declReferenceExpr + baseName: identifier "value" + config: + ifConfigDecl + clauses: + ifConfigClause + elements: + functionCallExpr + leftParen: ( + rightParen: ) + arguments: + labeledExpr + expression: + integerLiteralExpr + literal: integerLiteral "1" + additionalTrailingClosures: + calledExpression: + memberAccessExpr + period: . + declName: + declReferenceExpr + baseName: identifier "foo" + condition: + declReferenceExpr + baseName: identifier "DEBUG" + poundKeyword: #if + ifConfigClause + elements: + functionCallExpr + leftParen: ( + rightParen: ) + arguments: + labeledExpr + expression: + stringLiteralExpr + closingQuote: " + openingQuote: " + segments: + stringSegment + content: stringSegment "hi" + additionalTrailingClosures: + calledExpression: + memberAccessExpr + period: . + declName: + declReferenceExpr + baseName: identifier "bar" + poundKeyword: #else + poundEndif: #endif + pattern: + identifierPattern + identifier: identifier "result" + +--- + +top_level source="⟨body⟩" + body: + block source="⟨stmt⟩" + stmt: + variable_declaration source="⟨modifier⟩ ⟨pattern⟩ = ⟨value⟩" + modifier: modifier "let" source="let" + pattern: identifier "result" source="result" + value: unsupported_node "value\n #if DEBUG\n .foo(1)\n #else\n .bar(\"hi\")\n #endif" source="value\n #if DEBUG\n .foo(1)\n #else\n .bar(\"hi\")\n #endif" diff --git a/unified/extractor/tests/corpus/swift/conditional-compilation/conditional-compilation-postfix-expression.swift b/unified/extractor/tests/corpus/swift/conditional-compilation/conditional-compilation-postfix-expression.swift new file mode 100644 index 000000000000..a947fd60ded9 --- /dev/null +++ b/unified/extractor/tests/corpus/swift/conditional-compilation/conditional-compilation-postfix-expression.swift @@ -0,0 +1,6 @@ +let result = value + #if DEBUG + .foo(1) + #else + .bar("hi") + #endif diff --git a/unified/extractor/tests/corpus/swift/conditional-compilation/conditional-compilation-with-elseif-in-class-body.output b/unified/extractor/tests/corpus/swift/conditional-compilation/conditional-compilation-with-elseif-in-class-body.output new file mode 100644 index 000000000000..ec47d9901e62 --- /dev/null +++ b/unified/extractor/tests/corpus/swift/conditional-compilation/conditional-compilation-with-elseif-in-class-body.output @@ -0,0 +1,136 @@ +class C { + #if DEBUG + init(x: Int) {} + #elseif RELEASE + init(x: String) {} + #else + init() {} + #endif +} + +--- + +sourceFile + endOfFileToken: endOfFile + statements: + codeBlockItem + item: + classDecl + attributes: + modifiers: + name: identifier "C" + memberBlock: + memberBlock + leftBrace: { + rightBrace: } + members: + memberBlockItem + decl: + ifConfigDecl + clauses: + ifConfigClause + elements: + memberBlockItem + decl: + initializerDecl + attributes: + body: + codeBlock + leftBrace: { + rightBrace: } + statements: + modifiers: + signature: + functionSignature + parameterClause: + functionParameterClause + leftParen: ( + rightParen: ) + parameters: + functionParameter + colon: : + attributes: + modifiers: + type: + identifierType + name: identifier "Int" + firstName: identifier "x" + initKeyword: init + condition: + declReferenceExpr + baseName: identifier "DEBUG" + poundKeyword: #if + ifConfigClause + elements: + memberBlockItem + decl: + initializerDecl + attributes: + body: + codeBlock + leftBrace: { + rightBrace: } + statements: + modifiers: + signature: + functionSignature + parameterClause: + functionParameterClause + leftParen: ( + rightParen: ) + parameters: + functionParameter + colon: : + attributes: + modifiers: + type: + identifierType + name: identifier "String" + firstName: identifier "x" + initKeyword: init + condition: + declReferenceExpr + baseName: identifier "RELEASE" + poundKeyword: #elseif + ifConfigClause + elements: + memberBlockItem + decl: + initializerDecl + attributes: + body: + codeBlock + leftBrace: { + rightBrace: } + statements: + modifiers: + signature: + functionSignature + parameterClause: + functionParameterClause + leftParen: ( + rightParen: ) + parameters: + initKeyword: init + poundKeyword: #else + poundEndif: #endif + classKeyword: class + +--- + +top_level source="⟨body⟩" + body: + block source="⟨stmt⟩" + stmt: + class_like_declaration source="⟨modifier⟩ ⟨name_node⟩ {\n #if DEBUG\n ⟨member⟩\n #elseif RELEASE\n init(x: String) {}\n #else\n init() {}\n #endif\n}" + modifier: modifier "class" source="class" + name_node: identifier "C" source="C" + member: + constructor_declaration source="⟨name_node⟩(⟨parameter⟩) ⟨body⟩" + name_node: identifier "init" source="init" + parameter: + parameter source="⟨external_name_node⟩⟨pattern⟩: ⟨type⟩" + external_name_node: identifier "x" source="x" + type: identifier "Int" source="Int" + pattern: identifier "x" source="x" + body: block "{}" source="{}" diff --git a/unified/extractor/tests/corpus/swift/conditional-compilation/conditional-compilation-with-elseif-in-class-body.swift b/unified/extractor/tests/corpus/swift/conditional-compilation/conditional-compilation-with-elseif-in-class-body.swift new file mode 100644 index 000000000000..8422a35d69b6 --- /dev/null +++ b/unified/extractor/tests/corpus/swift/conditional-compilation/conditional-compilation-with-elseif-in-class-body.swift @@ -0,0 +1,9 @@ +class C { + #if DEBUG + init(x: Int) {} + #elseif RELEASE + init(x: String) {} + #else + init() {} + #endif +} diff --git a/unified/extractor/tests/corpus/swift/conditional-compilation/conditional-compilation-with-elseif.output b/unified/extractor/tests/corpus/swift/conditional-compilation/conditional-compilation-with-elseif.output new file mode 100644 index 000000000000..145be9553002 --- /dev/null +++ b/unified/extractor/tests/corpus/swift/conditional-compilation/conditional-compilation-with-elseif.output @@ -0,0 +1,119 @@ +#if DEBUG +let x = 1 +#elseif RELEASE +let x = 2 +#else +let x = 3 +#endif + +--- + +sourceFile + endOfFileToken: endOfFile + statements: + codeBlockItem + item: + ifConfigDecl + clauses: + ifConfigClause + elements: + codeBlockItem + item: + variableDecl + attributes: + modifiers: + bindingSpecifier: let + bindings: + patternBinding + initializer: + initializerClause + equal: = + value: + integerLiteralExpr + literal: integerLiteral "1" + pattern: + identifierPattern + identifier: identifier "x" + condition: + declReferenceExpr + baseName: identifier "DEBUG" + poundKeyword: #if + ifConfigClause + elements: + codeBlockItem + item: + variableDecl + attributes: + modifiers: + bindingSpecifier: let + bindings: + patternBinding + initializer: + initializerClause + equal: = + value: + integerLiteralExpr + literal: integerLiteral "2" + pattern: + identifierPattern + identifier: identifier "x" + condition: + declReferenceExpr + baseName: identifier "RELEASE" + poundKeyword: #elseif + ifConfigClause + elements: + codeBlockItem + item: + variableDecl + attributes: + modifiers: + bindingSpecifier: let + bindings: + patternBinding + initializer: + initializerClause + equal: = + value: + integerLiteralExpr + literal: integerLiteral "3" + pattern: + identifierPattern + identifier: identifier "x" + poundKeyword: #else + poundEndif: #endif + +--- + +top_level source="⟨body⟩" + body: + block source="⟨stmt⟩" + stmt: + if_expr source="#if ⟨condition⟩\n⟨then⟩\n⟨else⟩\n#endif" + modifier: modifier "conditional_compilation" source="" + condition: identifier "DEBUG" source="DEBUG" + then: + block source="⟨stmt⟩" + stmt: + variable_declaration source="⟨modifier⟩ ⟨pattern⟩ = ⟨value⟩" + modifier: modifier "let" source="let" + pattern: identifier "x" source="x" + value: int_literal "1" source="1" + else: + if_expr source="#elseif ⟨condition⟩\n⟨then⟩\n⟨else⟩" + modifier: modifier "conditional_compilation" source="" + condition: identifier "RELEASE" source="RELEASE" + then: + block source="⟨stmt⟩" + stmt: + variable_declaration source="⟨modifier⟩ ⟨pattern⟩ = ⟨value⟩" + modifier: modifier "let" source="let" + pattern: identifier "x" source="x" + value: int_literal "2" source="2" + else: + block source="#else\n⟨stmt⟩" + stmt: + variable_declaration source="⟨modifier⟩ ⟨pattern⟩ = ⟨value⟩" + modifier: modifier "let" source="let" + pattern: identifier "x" source="x" + value: int_literal "3" source="3" diff --git a/unified/extractor/tests/corpus/swift/conditional-compilation/conditional-compilation-with-elseif.swift b/unified/extractor/tests/corpus/swift/conditional-compilation/conditional-compilation-with-elseif.swift new file mode 100644 index 000000000000..f96cda7a9a6c --- /dev/null +++ b/unified/extractor/tests/corpus/swift/conditional-compilation/conditional-compilation-with-elseif.swift @@ -0,0 +1,7 @@ +#if DEBUG +let x = 1 +#elseif RELEASE +let x = 2 +#else +let x = 3 +#endif diff --git a/unified/extractor/tests/corpus/swift/conditional-compilation/conditional-compilation-with-operators.output b/unified/extractor/tests/corpus/swift/conditional-compilation/conditional-compilation-with-operators.output new file mode 100644 index 000000000000..d7268f066d72 --- /dev/null +++ b/unified/extractor/tests/corpus/swift/conditional-compilation/conditional-compilation-with-operators.output @@ -0,0 +1,111 @@ +#if (DEBUG && !RELEASE) || swift(<6.0) + let x = 1 +#endif + +--- + +sourceFile + endOfFileToken: endOfFile + statements: + codeBlockItem + item: + ifConfigDecl + clauses: + ifConfigClause + elements: + codeBlockItem + item: + variableDecl + attributes: + modifiers: + bindingSpecifier: let + bindings: + patternBinding + initializer: + initializerClause + equal: = + value: + integerLiteralExpr + literal: integerLiteral "1" + pattern: + identifierPattern + identifier: identifier "x" + condition: + infixOperatorExpr + operator: + binaryOperatorExpr + operator: binaryOperator "||" + leftOperand: + tupleExpr + leftParen: ( + rightParen: ) + elements: + labeledExpr + expression: + infixOperatorExpr + operator: + binaryOperatorExpr + operator: binaryOperator "&&" + leftOperand: + declReferenceExpr + baseName: identifier "DEBUG" + rightOperand: + prefixOperatorExpr + expression: + declReferenceExpr + baseName: identifier "RELEASE" + operator: prefixOperator "!" + rightOperand: + functionCallExpr + leftParen: ( + rightParen: ) + arguments: + labeledExpr + expression: + prefixOperatorExpr + expression: + floatLiteralExpr + literal: floatLiteral "6.0" + operator: prefixOperator "<" + additionalTrailingClosures: + calledExpression: + declReferenceExpr + baseName: identifier "swift" + poundKeyword: #if + poundEndif: #endif + +--- + +top_level source="⟨body⟩" + body: + block source="⟨stmt⟩" + stmt: + if_expr source="#if ⟨condition⟩\n ⟨then⟩\n#endif" + modifier: modifier "conditional_compilation" source="" + condition: + binary_expr source="(⟨left⟩) ⟨operator⟩ ⟨right⟩" + left: + binary_expr source="⟨left⟩ ⟨operator⟩ ⟨right⟩" + left: identifier "DEBUG" source="DEBUG" + operator: infix_operator "&&" source="&&" + right: + unary_expr source="⟨operator⟩⟨operand⟩" + operand: identifier "RELEASE" source="RELEASE" + operator: prefix_operator "!" source="!" + operator: infix_operator "||" source="||" + right: + call_expr source="⟨callee⟩(⟨argument⟩)" + callee: identifier "swift" source="swift" + argument: + argument source="⟨value⟩" + value: + unary_expr source="⟨operator⟩⟨operand⟩" + operand: float_literal "6.0" source="6.0" + operator: prefix_operator "<" source="<" + then: + block source="⟨stmt⟩" + stmt: + variable_declaration source="⟨modifier⟩ ⟨pattern⟩ = ⟨value⟩" + modifier: modifier "let" source="let" + pattern: identifier "x" source="x" + value: int_literal "1" source="1" diff --git a/unified/extractor/tests/corpus/swift/conditional-compilation/conditional-compilation-with-operators.swift b/unified/extractor/tests/corpus/swift/conditional-compilation/conditional-compilation-with-operators.swift new file mode 100644 index 000000000000..9a8eb0b5eeb6 --- /dev/null +++ b/unified/extractor/tests/corpus/swift/conditional-compilation/conditional-compilation-with-operators.swift @@ -0,0 +1,3 @@ +#if (DEBUG && !RELEASE) || swift(<6.0) + let x = 1 +#endif diff --git a/unified/extractor/tests/corpus/swift/types/conditional-compilation-in-class-body.swift b/unified/extractor/tests/corpus/swift/types/conditional-compilation-in-class-body.swift deleted file mode 100644 index fe50583118c9..000000000000 --- a/unified/extractor/tests/corpus/swift/types/conditional-compilation-in-class-body.swift +++ /dev/null @@ -1,10 +0,0 @@ -// Conditional compilation is not yet supported: swift-syntax reports a -// structured `ifConfigDecl` (whose branches hold ordinary member items), but -// the mapping has no rule for it, so the whole block becomes one -// `unsupported_node` and its members are not extracted. -class C { -#if DEBUG - init(x: Int) {} - deinit {} -#endif -} diff --git a/unified/ql/lib/codeql/unified/internal/Ast.qll b/unified/ql/lib/codeql/unified/internal/Ast.qll index 5f410460536e..dc484607609a 100644 --- a/unified/ql/lib/codeql/unified/internal/Ast.qll +++ b/unified/ql/lib/codeql/unified/internal/Ast.qll @@ -858,6 +858,12 @@ module Unified { /** Gets the node corresponding to the field `else`. */ final F::Expr getElse() { unified_if_expr_else(this, result) } + /** Gets the node corresponding to the field `modifier`. */ + final F::Modifier getModifier(int i) { unified_if_expr_modifier(this, i, result) } + + /** Gets the node corresponding to the field `modifier`. */ + final F::Modifier getAModifier() { result = this.getModifier(_) } + /** Gets the node corresponding to the field `then`. */ final F::Expr getThen() { unified_if_expr_then(this, result) } @@ -865,6 +871,7 @@ module Unified { final override F::AstNode getAFieldOrChild() { unified_if_expr_def(this, result) or unified_if_expr_else(this, result) or + unified_if_expr_modifier(this, _, result) or unified_if_expr_then(this, result) } } @@ -1772,6 +1779,8 @@ module Unified { or result = node.(IfExpr).getElse() and i = -1 and name = "getElse" or + result = node.(IfExpr).getModifier(i) and name = "getModifier" + or result = node.(IfExpr).getThen() and i = -1 and name = "getThen" or result = node.(ImportDeclaration).getImportedExpr() and i = -1 and name = "getImportedExpr" diff --git a/unified/ql/lib/codeql/unified/internal/AstPluginSwift.qll b/unified/ql/lib/codeql/unified/internal/AstPluginSwift.qll index ff2d03fb9b01..858cdefc42e6 100644 --- a/unified/ql/lib/codeql/unified/internal/AstPluginSwift.qll +++ b/unified/ql/lib/codeql/unified/internal/AstPluginSwift.qll @@ -4,6 +4,11 @@ module; private import unified private import AstPlugin +/** + * Holds if a constructor declaration `cd` of a struct has a parameter for the + * field `decl` at member index `i`, with name `name`. `hasDefault` indicates + * whether the parameter has a default value. + */ private predicate structHasParameterForField( ConstructorDeclaration cd, VariableDeclaration decl, int i, string name, boolean hasDefault ) { diff --git a/unified/ql/lib/codeql/unified/internal/FacadeAst.qll b/unified/ql/lib/codeql/unified/internal/FacadeAst.qll index 8d40d3f6b099..51a512a34d87 100644 --- a/unified/ql/lib/codeql/unified/internal/FacadeAst.qll +++ b/unified/ql/lib/codeql/unified/internal/FacadeAst.qll @@ -470,6 +470,9 @@ module Unified { /** An `if` statement or conditional expression. */ class IfExpr extends G::IfExpr { + /** Holds if this expression represents conditional compilation. */ + predicate isConditionalCompilation() { this.hasModifier("conditional_compilation") } + /** Gets then `then` or `else` branch. */ Expr getABranch() { result = [this.getThen(), this.getElse()] } } diff --git a/unified/ql/lib/codeql/unified/internal/LocalNameBinding.qll b/unified/ql/lib/codeql/unified/internal/LocalNameBinding.qll index 7f88d1c496bf..841c3facd0dc 100644 --- a/unified/ql/lib/codeql/unified/internal/LocalNameBinding.qll +++ b/unified/ql/lib/codeql/unified/internal/LocalNameBinding.qll @@ -129,6 +129,8 @@ private module LocalNameBindingInput implements LocalNameBindingInputSig a -> Argument -V kpGet_mayB_x -^ a(...)' | | 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 | | '' | +| 478 | cfg.swift:478:1:484:6 | IfExpr | 'IfExpr -V FOO' | +| 479 | cfg.swift:479:3:480:3 | Block | 'Block -V 1' | +| 480 | cfg.swift:480:3:480:3 | 2 | '2' | +| 481 | cfg.swift:481:1:483:3 | Block | 'Block' | +| 482 | cfg.swift:482:3:482:3 | 3 | '3' | +| 483 | cfg.swift:483:3:483:3 | 4 | '4' | | 486 | cfg.swift:486:3:486:3 | 5 | '5' | -| 488 | cfg.swift:488:1:491:6 | | '' | +| 488 | cfg.swift:488:1:491:6 | IfExpr | 'IfExpr -V BAR' | +| 489 | cfg.swift:489:3:490:3 | Block | 'Block -V 6' | +| 490 | cfg.swift:490:3:490:3 | 7 | '7' | | 493 | cfg.swift:493:3:493:3 | 8 | '8' | -| 495 | cfg.swift:495:1:501:6 | | '' | +| 495 | cfg.swift:495:1:501:6 | IfExpr | 'IfExpr -V FOO' | +| 496 | cfg.swift:496:3:497:4 | Block | 'Block -V 9' | +| 497 | cfg.swift:497:3:497:4 | 10 | '10' | +| 498 | cfg.swift:498:1:500:4 | IfExpr | 'IfExpr -V true' | +| 499 | cfg.swift:499:3:500:4 | Block | 'Block -V 11' | +| 500 | cfg.swift:500:3:500:4 | 12 | '12' | | 503 | cfg.swift:503:3:503:4 | 13 | '13' | | 506 | cfg.swift:506:1:531:1 | func testAvailable | 'func testAvailable -V testAvailable' | | 506 | cfg.swift:506:29:531:1 | Block | 'Block' | diff --git a/unified/ql/test/library-tests/controlflow/cfg.expected b/unified/ql/test/library-tests/controlflow/cfg.expected index 46994ef52c9b..79aeb2956ca0 100644 --- a/unified/ql/test/library-tests/controlflow/cfg.expected +++ b/unified/ql/test/library-tests/controlflow/cfg.expected @@ -114,6 +114,17 @@ bbStep | cfg.swift:369:9:369:14 | ... < ... | '... < ... : true -> Block(+0)' | | cfg.swift:370:5:370:10 | ... += ... | '... += ... : successor -> x(-1)' | | cfg.swift:407:9:407:24 | mightThrow(...) | 'mightThrow(...) : successor -> try ...(+0)' | +| cfg.swift:478:5:478:7 | FOO | 'FOO : false -> Block(+3)' | +| cfg.swift:478:5:478:7 | FOO | 'FOO : true -> Block(+1)' | +| cfg.swift:480:3:480:3 | 2 | '2 : successor -> 5(+6)' | +| cfg.swift:483:3:483:3 | 4 | '4 : successor -> 5(+3)' | +| cfg.swift:488:5:488:7 | BAR | 'BAR : false -> 8(+5)' | +| cfg.swift:488:5:488:7 | BAR | 'BAR : true -> Block(+1)' | +| cfg.swift:490:3:490:3 | 7 | '7 : successor -> 8(+3)' | +| cfg.swift:495:5:495:7 | FOO | 'FOO : false -> IfExpr(+3)' | +| cfg.swift:495:5:495:7 | FOO | 'FOO : true -> Block(+1)' | +| cfg.swift:497:3:497:4 | 10 | '10 : successor -> 13(+6)' | +| cfg.swift:500:3:500:4 | 12 | '12 : successor -> 13(+3)' | | cfg.swift:509:6:509:28 | | ' : false -> IfExpr(+4)' | | cfg.swift:509:6:509:28 | | ' : true -> Block(+0)' | | cfg.swift:510:5:510:10 | ... += ... | '... += ... : successor -> IfExpr(+3)' | diff --git a/unified/ql/test/library-tests/controlflow/cfg.swift b/unified/ql/test/library-tests/controlflow/cfg.swift index f00bea336a97..249832270920 100644 --- a/unified/ql/test/library-tests/controlflow/cfg.swift +++ b/unified/ql/test/library-tests/controlflow/cfg.swift @@ -475,29 +475,29 @@ func test(a : A) { } func testIfConfig() { -#if FOO +#if FOO // $ bbStep='FOO : false -> Block(+3)' bbStep='FOO : true -> Block(+1)' 1 - 2 + 2 // $ bbStep='2 : successor -> 5(+6)' #else 3 - 4 + 4 // $ bbStep='4 : successor -> 5(+3)' #endif 5 -#if BAR +#if BAR // $ bbStep='BAR : false -> 8(+5)' bbStep='BAR : true -> Block(+1)' 6 - 7 + 7 // $ bbStep='7 : successor -> 8(+3)' #endif 8 -#if FOO +#if FOO // $ bbStep='FOO : false -> IfExpr(+3)' bbStep='FOO : true -> Block(+1)' 9 - 10 + 10 // $ bbStep='10 : successor -> 13(+6)' #elseif true 11 - 12 + 12 // $ bbStep='12 : successor -> 13(+3)' #endif 13 diff --git a/unified/ql/test/library-tests/local-name-binding/conditional-compilation.swift b/unified/ql/test/library-tests/local-name-binding/conditional-compilation.swift new file mode 100644 index 000000000000..57ebf09d7aef --- /dev/null +++ b/unified/ql/test/library-tests/local-name-binding/conditional-compilation.swift @@ -0,0 +1,108 @@ +func conditionalIfElse() { + // The same variable is declared in _two_ branches. + #if DEBUG + let inCond = 1 // name=debugValue + #else + let inCond = 2 // name=releaseValue + print("release") + #endif + print(inCond) // $ access=debugValue access=releaseValue +} + +func conditionalIfElseifElse() { + // The same variable is declared in _three_ branches. + #if DEBUG + let alternative = 1 // name=debugValue + #elseif RELEASE + let alternative = 2 // name=releaseValue + print(alternative) // $ access=releaseValue + #else + let alternative = 3 // name=fallbackValue + print(alternative) // $ access=fallbackValue + #endif + print(alternative) // $ access=debugValue access=releaseValue access=fallbackValue +} + +func conditionalNoElse(value: Int) { // name=outerValue + // The variable is declared only in one skipable branch. + #if DEBUG + let value = value + 1 // $ access=outerValue // name=conditionalValue + print(value) // $ access=conditionalValue + #endif + print(value) // $ access=outerValue access=conditionalValue +} + +func conditionalIfNesting(nesting: Int) { // name=nestingFirst + // Nested conditional compilation. + #if DEBUG + #if EXTRA + print(nesting) // $ access=nestingFirst + let nesting = nesting + 1 // $ access=nestingFirst // name=nestingExtra + #else + let nesting = 2 // name=nestingNotExtra + #endif + print(nesting) // $ access=nestingExtra access=nestingNotExtra + #else + let nesting = 3 // name=outerNotDebug + #endif + print(nesting) // $ access=nestingExtra access=nestingNotExtra access=outerNotDebug +} + +func conditionalRealScopes() { + // Conditional compilation, but variables are declared in a real scope. + let value = 0 // name=realOuterValue + #if DEBUG + if true { + let value = 1 // name=runtimeValue + print(value) // $ access=runtimeValue + } + #endif + print(value) // $ access=realOuterValue +} + +func conditionalGuard(opt: Int?) { // name=guardOpt + #if DEBUG + guard let value = opt else { return } // $ access=guardOpt // name=guardValue + print(value) // $ access=guardValue + #else + guard let value = opt else { return } // $ access=guardOpt // name=fallbackGuardValue + #endif + print(value) // $ access=guardValue access=fallbackGuardValue +} + +func conditionalSequentialBindings(value: Int) { // name=sequentialOuter + #if DEBUG + print(value) // $ access=sequentialOuter + let first = 1 // name=sequentialFirst + print(first) // $ access=sequentialFirst + let value = first + 1 // $ access=sequentialFirst // name=sequentialSecond + print(value) // $ access=sequentialSecond + #else + print(value) // $ access=sequentialOuter + #endif + print(value) // $ access=sequentialSecond access=sequentialOuter +} + +func conditionalCapture() { + #if DEBUG + let value = 1 // $ captured=capturedDebug // name=capturedDebug + #else + let value = 2 // $ captured=capturedElse // name=capturedElse + #endif + let closure = { // name=conditionalClosure + print(value) // $ access=capturedDebug access=capturedElse + } + closure() // $ access=conditionalClosure +} + +func conditionalNestedFallback(value: Int) { // name=nestedFallbackOuter + #if DEBUG + #if EXTRA + let value = 1 // name=nestedFallbackExtra + #else + #endif + #else + print(value) // $ access=nestedFallbackOuter + #endif + print(value) // $ access=nestedFallbackOuter access=nestedFallbackExtra +}