diff --git a/unified/extractor/ast_types.yml b/unified/extractor/ast_types.yml index 1d8500447696..5c97b12b2ab0 100644 --- a/unified/extractor/ast_types.yml +++ b/unified/extractor/ast_types.yml @@ -34,6 +34,7 @@ supertypes: - switch_expr - unresolved_operator_sequence - unsupported_node + - unhandled_node - or_pattern - conditional_pattern - bulk_importing_pattern @@ -84,6 +85,7 @@ supertypes: - type_alias_declaration - associated_type_declaration - unsupported_node + - unhandled_node type_constraint: - equality_type_constraint - bound_type_constraint @@ -421,6 +423,9 @@ named: # A node that we don't yet translate unsupported_node: + # A node kind that has no explicit translation or unsupported classification + unhandled_node: + infix_operator: prefix_operator: diff --git a/unified/extractor/src/languages/swift/swift.rs b/unified/extractor/src/languages/swift/swift.rs index 62561c6a1620..d7026b19092d 100644 --- a/unified/extractor/src/languages/swift/swift.rs +++ b/unified/extractor/src/languages/swift/swift.rs @@ -129,9 +129,9 @@ fn translation_rules() -> Vec> { vec![ // ---- Top-level ---- // These rules translate the swift-syntax AST (camelCase kind names), - // produced by the sibling `adapter` module from the `swift-syntax-parse` - // binary's JSON. Anything unmatched falls through to the - // `unsupported_node` fallback at the end. + // produced by the sibling `adapter` module from swift-syntax JSON. + // Known kinds without dedicated rules become `unsupported_node`; + // genuinely unknown kinds become `unhandled_node`. // // `sourceFile` holds its top-level statements in an (elided) // `statements` collection; each element is a `codeBlockItem` wrapping @@ -198,13 +198,13 @@ fn translation_rules() -> Vec> { => (unsupported_node) ), - rule!((declReferenceExpr baseName: (identifier) @name) => expr { + rule!((declReferenceExpr baseName: (identifier) @@name) => expr { tree!((identifier #{name})) }), // A bare name reference (`x`), and an operator used as a value (`+` in // `reduce(0, +)`), are both `declReferenceExpr`; its `baseName` is the // referenced identifier / operator symbol. - rule!((declReferenceExpr baseName: @name) => (identifier #{name})), + rule!((declReferenceExpr baseName: @@name) => (identifier #{name})), // A discard `_` used as an expression — e.g. the target of a discarding // assignment `_ = x`. swift-syntax models it as a `discardAssignmentExpr`. rule!((discardAssignmentExpr wildcard: @@w) => (ignore_pattern #{w})), @@ -215,7 +215,7 @@ fn translation_rules() -> Vec> { // `generic_type_expr`, so we map it directly to that shape. rule!( (genericSpecializationExpr - expression: (declReferenceExpr baseName: @name) + expression: (declReferenceExpr baseName: @@name) genericArgumentClause: (genericArgumentClause arguments: (genericArgument argument: @args)*)) => (generic_type_expr @@ -230,7 +230,7 @@ fn translation_rules() -> Vec> { // A `binaryOperatorExpr` wraps the operator token; unwrap it to the // operator leaf. Used by `infixOperatorExpr` (folded) and `sequenceExpr` // (unresolved). - rule!((binaryOperatorExpr operator: @op) => (infix_operator #{op})), + rule!((binaryOperatorExpr operator: @@op) => (infix_operator #{op})), // A `binaryOperator`-based `infixOperatorExpr` represents both ordinary // binary applications (`a + b`) and compound assignments (`x += y`). // Both have the same target AST shape; the QL library distinguishes @@ -276,7 +276,7 @@ fn translation_rules() -> Vec> { // infix operators, rather than guessing a structure. rule!((sequenceExpr elements: _* @els) => (unresolved_operator_sequence element: {els})), // Prefix unary operators (`!a`, `-x`). - rule!((prefixOperatorExpr operator: @op expression: @operand) => (unary_expr operator: (prefix_operator #{op}) operand: {operand})), + rule!((prefixOperatorExpr operator: @@op expression: @operand) => (unary_expr operator: (prefix_operator #{op}) operand: {operand})), // A parenthesised expression has a single tuple element; elide the // grouping and preserve the expression itself. Actual tuple literals // retain their translated labeled elements as `argument` children. @@ -468,7 +468,7 @@ fn translation_rules() -> Vec> { // `chained_declaration` tag. rule!( (enumCaseElement - name: @name + name: @@name parameterClause: (enumCaseParameterClause parameters: _* @params) @@clause) => class_like_declaration { @@ -486,7 +486,7 @@ fn translation_rules() -> Vec> { } ), rule!( - (enumCaseElement name: @name rawValue: (initializerClause value: @val)) + (enumCaseElement name: @@name rawValue: (initializerClause value: @val)) => (variable_declaration modifier: {ctx.outer_modifiers.clone()} @@ -496,7 +496,7 @@ fn translation_rules() -> Vec> { value: {val}) ), rule!( - (enumCaseElement name: @name) + (enumCaseElement name: @@name) => (variable_declaration modifier: {ctx.outer_modifiers.clone()} @@ -526,7 +526,7 @@ fn translation_rules() -> Vec> { ), // `identifierPattern` wraps a single identifier token. rule!( - (identifierPattern identifier: @name) + (identifierPattern identifier: @@name) => (identifier #{name}) ), @@ -574,7 +574,7 @@ fn translation_rules() -> Vec> { rule!( (functionDecl modifiers: _* @mods - name: @name + name: @@name genericParameterClause: (genericParameterClause parameters: _* @type_params)? signature: (functionSignature parameterClause: (functionParameterClause parameters: _* @params) @@ -592,7 +592,7 @@ fn translation_rules() -> Vec> { rule!( (functionDecl modifiers: _* @mods - name: @name + name: @@name genericParameterClause: (genericParameterClause parameters: _* @type_params)? signature: (functionSignature parameterClause: (functionParameterClause parameters: _* @params) @@ -715,7 +715,7 @@ fn translation_rules() -> Vec> { (memberAccessExpr base: (arrayExpr elements: (arrayElement expression: (genericSpecializationExpr) @element)) @@array - declName: (declReferenceExpr baseName: @member)) + declName: (declReferenceExpr baseName: @@member)) => member_access_expr { let base = tree_at!( @@ -731,12 +731,12 @@ fn translation_rules() -> Vec> { } ), rule!( - (memberAccessExpr base: @base declName: (declReferenceExpr baseName: @member)) + (memberAccessExpr base: @base declName: (declReferenceExpr baseName: @@member)) => (member_access_expr base: {base} member_name_node: (identifier #{member})) ), rule!( - (memberAccessExpr period: @dot declName: (declReferenceExpr baseName: @member)) + (memberAccessExpr period: @@dot declName: (declReferenceExpr baseName: @@member)) => (member_access_expr base: (inferred_type_expr #{dot}) member_name_node: (identifier #{member})) ), @@ -790,14 +790,14 @@ fn translation_rules() -> Vec> { // A closure parameter (`x: Int`, or just `x`). Unlike a function // parameter it has no external label; the type is optional. rule!( - (closureParameter firstName: @name type: _? @ty) + (closureParameter firstName: @@name type: _? @ty) => (parameter pattern: (identifier #{name}) type: {ty}) ), // A shorthand closure parameter (`x` in `{ x, y in … }`): a bare name // with no parentheses and no type. rule!( - (closureShorthandParameter name: @name) + (closureShorthandParameter name: @@name) => (parameter pattern: (identifier #{name})) ), @@ -872,7 +872,7 @@ fn translation_rules() -> Vec> { rule!( (optionalBindingCondition bindingSpecifier: @@spec - pattern: (identifierPattern identifier: @name) + pattern: (identifierPattern identifier: @@name) initializer: (initializerClause value: @val)) => (pattern_guard_expr @@ -886,7 +886,7 @@ fn translation_rules() -> Vec> { rule!( (optionalBindingCondition bindingSpecifier: @@spec - pattern: (identifierPattern identifier: @name)) + pattern: (identifierPattern identifier: @@name)) => (pattern_guard_expr value: (identifier #{name}) @@ -1091,7 +1091,7 @@ fn translation_rules() -> Vec> { rule!((declModifier) @m => (modifier #{m})), // Preserve the `super` keyword as a dedicated expression, normally used // as the base of a member access (`super.foo`). - rule!((superExpr superKeyword: @keyword) => (super_expr #{keyword})), + rule!((superExpr superKeyword: @@keyword) => (super_expr #{keyword})), // Type expressions. A generic type applied with explicit arguments // (`Set`) becomes a `generic_type_expr` whose `base` is the type // name and whose `type_argument`s are the (structured) arguments — the @@ -1213,9 +1213,9 @@ fn translation_rules() -> Vec> { // Class declaration with body containing members rule!( (classDecl - classKeyword: @kind + classKeyword: @@kind modifiers: _* @mods - name: @name + name: @@name genericParameterClause: (genericParameterClause parameters: _* @params genericWhereClause: (genericWhereClause requirements: _* @parameter_constraints)?)? @@ -1236,9 +1236,9 @@ fn translation_rules() -> Vec> { // Enum class declaration: same as a regular class but with an enum body. rule!( (enumDecl - enumKeyword: @kind + enumKeyword: @@kind modifiers: _* @mods - name: @name + name: @@name genericParameterClause: (genericParameterClause parameters: _* @params genericWhereClause: (genericWhereClause requirements: _* @parameter_constraints)?)? @@ -1259,9 +1259,9 @@ fn translation_rules() -> Vec> { // A `struct` declaration. rule!( (structDecl - structKeyword: @kind + structKeyword: @@kind modifiers: _* @mods - name: @name + name: @@name genericParameterClause: (genericParameterClause parameters: _* @params genericWhereClause: (genericWhereClause requirements: _* @parameter_constraints)?)? @@ -1282,9 +1282,9 @@ fn translation_rules() -> Vec> { // Protocol declaration rule!( (protocolDecl - protocolKeyword: @kind + protocolKeyword: @@kind modifiers: _* @mods - name: @name + name: @@name genericParameterClause: (genericParameterClause parameters: _* @params)? inheritanceClause: (inheritanceClause inheritedTypes: (inheritedType type: @bases)*)? genericWhereClause: (genericWhereClause requirements: _* @declaration_constraints)? @@ -1302,7 +1302,7 @@ fn translation_rules() -> Vec> { // An `extension Foo.Bar { … }` is likewise a `class_like_declaration`. rule!( (extensionDecl - extensionKeyword: @kind + extensionKeyword: @@kind modifiers: _* @mods extendedType: @extendedType inheritanceClause: (inheritanceClause inheritedTypes: (inheritedType type: @bases)*)? @@ -1322,7 +1322,7 @@ fn translation_rules() -> Vec> { // nest under `signature` (as for `functionDecl`). rule!( (initializerDecl - initKeyword: @initK + initKeyword: @@initK modifiers: _* @mods signature: (functionSignature parameterClause: (functionParameterClause parameters: _* @params)) @@ -1336,7 +1336,7 @@ fn translation_rules() -> Vec> { ), rule!( (initializerDecl - initKeyword: @initK + initKeyword: @@initK modifiers: _* @mods signature: (functionSignature parameterClause: (functionParameterClause parameters: _* @params))) @@ -1383,18 +1383,58 @@ fn translation_rules() -> Vec> { name_node: (identifier #{name}) bound: {bound}) ), - // ---- Fallbacks ---- - // Bare `_` (rather than `(_)`) so this matches both named nodes - // and unnamed tokens. Any unnamed token that escapes the - // input-schema-specific rules (e.g. captured operators in - // `additive_expression op: @op`) has its auto-translated value - // replaced with an `unsupported_node` whose source range is - // inherited from the original token, so `#{op}` still reads the - // original text. - rule!( - _ - => - (unsupported_node) + // ---- Explicitly unsupported roots ---- + // These kinds can reach translation independently, but we do not + // currently map them to the unified AST. Syntax nested inside one of + // these roots is discarded with its parent and needs no separate rule. + rule!((actorDecl) => (unsupported_node)), + rule!((attributedType) => (unsupported_node)), + rule!((borrowExpr) => (unsupported_node)), + rule!((classRestrictionType) => (unsupported_node)), + rule!((compositionType) => (unsupported_node)), + rule!((consumeExpr) => (unsupported_node)), + rule!((copyExpr) => (unsupported_node)), + rule!((deferStmt) => (unsupported_node)), + rule!((discardStmt) => (unsupported_node)), + rule!((fallThroughStmt) => (unsupported_node)), + rule!((ifConfigDecl) => (unsupported_node)), + rule!((implicitlyUnwrappedOptionalType) => (unsupported_node)), + rule!((inOutExpr) => (unsupported_node)), + rule!((inlineArrayType) => (unsupported_node)), + rule!((keyPathExpr) => (unsupported_node)), + rule!((macroDecl) => (unsupported_node)), + rule!((metatypeType) => (unsupported_node)), + rule!((namedOpaqueReturnType) => (unsupported_node)), + rule!((operatorDecl) => (unsupported_node)), + rule!((packElementExpr) => (unsupported_node)), + rule!((packElementType) => (unsupported_node)), + rule!((packExpansionExpr) => (unsupported_node)), + rule!((packExpansionType) => (unsupported_node)), + rule!((postfixIfConfigExpr) => (unsupported_node)), + rule!((postfixOperatorExpr) => (unsupported_node)), + 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)), + rule!((yieldStmt) => (unsupported_node)), + // Anything reaching this final generic handler has neither a + // dedicated rule nor an explicit unsupported entry. + rule!( + _ @@node + => + unhandled_node { + let input = ctx.ast.get_node(node).expect("matched node must exist"); + tracing::error!( + target: "unified_extractor", + node_kind = input.kind_name(), + source_range = ?input.source_range(), + "Unhandled Swift syntax node reached translation" + ); + tree!((unhandled_node)) + } ), ] } diff --git a/unified/ql/lib/codeql/unified/internal/Ast.qll b/unified/ql/lib/codeql/unified/internal/Ast.qll index 0c4c9f65a3c6..cfc917b85580 100644 --- a/unified/ql/lib/codeql/unified/internal/Ast.qll +++ b/unified/ql/lib/codeql/unified/internal/Ast.qll @@ -1440,6 +1440,12 @@ module Unified { } } + /** A class representing `unhandled_node` tokens. */ + class UnhandledNode extends @unified_token_unhandled_node, F::Expr, F::Member, F::Token { + /** Gets the name of the primary QL class for this element. */ + final override string getAPrimaryQlClass() { result = "UnhandledNode" } + } + /** A class representing `unresolved_operator_sequence` nodes. */ class UnresolvedOperatorSequence extends @unified_unresolved_operator_sequence, F::Expr { /** Gets the name of the primary QL class for this element. */ @@ -1976,6 +1982,8 @@ module UnifiedFinal { final class UnaryExpr = F::UnaryExpr; + final class UnhandledNode = F::UnhandledNode; + final class UnresolvedOperatorSequence = F::UnresolvedOperatorSequence; final class UnsupportedNode = F::UnsupportedNode; diff --git a/unified/ql/lib/unified.dbscheme b/unified/ql/lib/unified.dbscheme index 8916955b5408..fda85ffc96e3 100644 --- a/unified/ql/lib/unified.dbscheme +++ b/unified/ql/lib/unified.dbscheme @@ -435,7 +435,7 @@ unified_equality_type_constraint_def( int right: @unified_expr ref ); -@unified_expr = @unified_array_literal | @unified_binary_expr | @unified_block | @unified_break_expr | @unified_bulk_importing_pattern | @unified_call_expr | @unified_conditional_pattern | @unified_continue_expr | @unified_expr_pattern | @unified_function_expr | @unified_generic_type_expr | @unified_if_expr | @unified_key_value_pair | @unified_map_literal | @unified_member_access_expr | @unified_named_pattern | @unified_or_pattern | @unified_pattern_guard_expr | @unified_return_expr | @unified_string_interpolation_expr | @unified_switch_expr | @unified_throw_expr | @unified_token_boolean_literal | @unified_token_builtin_expr | @unified_token_empty_expr | @unified_token_float_literal | @unified_token_identifier | @unified_token_ignore_pattern | @unified_token_inferred_type_expr | @unified_token_int_literal | @unified_token_regex_literal | @unified_token_string_literal | @unified_token_super_expr | @unified_token_unsupported_node | @unified_try_expr | @unified_tuple_expr | @unified_type_cast_expr | @unified_type_test_expr | @unified_unary_expr | @unified_unresolved_operator_sequence +@unified_expr = @unified_array_literal | @unified_binary_expr | @unified_block | @unified_break_expr | @unified_bulk_importing_pattern | @unified_call_expr | @unified_conditional_pattern | @unified_continue_expr | @unified_expr_pattern | @unified_function_expr | @unified_generic_type_expr | @unified_if_expr | @unified_key_value_pair | @unified_map_literal | @unified_member_access_expr | @unified_named_pattern | @unified_or_pattern | @unified_pattern_guard_expr | @unified_return_expr | @unified_string_interpolation_expr | @unified_switch_expr | @unified_throw_expr | @unified_token_boolean_literal | @unified_token_builtin_expr | @unified_token_empty_expr | @unified_token_float_literal | @unified_token_identifier | @unified_token_ignore_pattern | @unified_token_inferred_type_expr | @unified_token_int_literal | @unified_token_regex_literal | @unified_token_string_literal | @unified_token_super_expr | @unified_token_unhandled_node | @unified_token_unsupported_node | @unified_try_expr | @unified_tuple_expr | @unified_type_cast_expr | @unified_type_test_expr | @unified_unary_expr | @unified_unresolved_operator_sequence @unified_expr_or_operator = @unified_expr | @unified_token_infix_operator @@ -637,7 +637,7 @@ unified_map_literal_def( unique int id: @unified_map_literal ); -@unified_member = @unified_accessor_declaration | @unified_associated_type_declaration | @unified_class_like_declaration | @unified_constructor_declaration | @unified_destructor_declaration | @unified_function_declaration | @unified_initializer_declaration | @unified_token_unsupported_node | @unified_type_alias_declaration | @unified_variable_declaration +@unified_member = @unified_accessor_declaration | @unified_associated_type_declaration | @unified_class_like_declaration | @unified_constructor_declaration | @unified_destructor_declaration | @unified_function_declaration | @unified_initializer_declaration | @unified_token_unhandled_node | @unified_token_unsupported_node | @unified_type_alias_declaration | @unified_variable_declaration unified_member_access_expr_def( unique int id: @unified_member_access_expr, @@ -990,7 +990,8 @@ case @unified_token.kind of | 15 = @unified_token_regex_literal | 16 = @unified_token_string_literal | 17 = @unified_token_super_expr -| 18 = @unified_token_unsupported_node +| 18 = @unified_token_unhandled_node +| 19 = @unified_token_unsupported_node ;