Skip to content

Commit 8d347ae

Browse files
committed
Validate a node's layout after it has been rewritten using the syntax rewriter
Otherwise it is possible to generate invalid syntax trees using the syntax rewriter.
1 parent b15030f commit 8d347ae

16 files changed

+6485
-3
lines changed

Sources/SwiftSyntax/Syntax.swift

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,11 @@ public struct Syntax: SyntaxProtocol, SyntaxHashable {
2424
self.data = data
2525
}
2626

27+
public func _validateLayout() {
28+
// Check the layout of the concrete type
29+
return self.as(SyntaxProtocol.self)._validateLayout()
30+
}
31+
2732
/// Create a `Syntax` node from a specialized syntax node.
2833
public init<S: SyntaxProtocol>(_ syntax: S) {
2934
self = syntax._syntaxNode
@@ -85,6 +90,11 @@ public protocol SyntaxProtocol: CustomStringConvertible,
8590
/// Converts the given `Syntax` node to this type. Returns `nil` if the
8691
/// conversion is not possible.
8792
init?(_ syntaxNode: Syntax)
93+
94+
/// Check that the raw layout of this node is valid. Used to verify a node's
95+
/// integrity after it has been rewritten by the syntax rewriter.
96+
/// Results in an assertion failure if the layout is invalid.
97+
func _validateLayout()
8898
}
8999

90100
internal extension SyntaxProtocol {

Sources/SwiftSyntax/SyntaxBaseNodes.swift.gyb

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,11 @@ public struct ${node.name}: ${node.name}Protocol, SyntaxHashable {
9090
self._syntaxNode = Syntax(data)
9191
}
9292

93+
public func _validateLayout() {
94+
// Check the layout of the concrete type
95+
return Syntax(self)._validateLayout()
96+
}
97+
9398
public func `is`<S: ${node.name}Protocol>(_ syntaxType: S.Type) -> Bool {
9499
return self.as(syntaxType) != nil
95100
}

Sources/SwiftSyntax/SyntaxChildren.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ struct RawSyntaxChildren: Sequence {
2727
self.nextChildInfo = startFrom
2828
}
2929

30-
mutating func next() -> (RawSyntax?, AbsoluteSyntaxInfo)? {
30+
mutating func next() -> (raw: RawSyntax?, syntaxInfo: AbsoluteSyntaxInfo)? {
3131
let idx = Int(nextChildInfo.indexInParent)
3232
guard idx < parent.numberOfChildren else {
3333
return nil

Sources/SwiftSyntax/SyntaxCollections.swift.gyb

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -200,6 +200,13 @@ public struct ${node.name}: SyntaxCollection, SyntaxHashable {
200200
self = withTrailingTrivia(newValue ?? [])
201201
}
202202
}
203+
204+
public func _validateLayout() {
205+
// Check that all children match the expected element type
206+
assert(self.allSatisfy { node in
207+
return Syntax(node).is(${node.collection_element_type}.self)
208+
})
209+
}
203210
}
204211

205212
/// Conformance for `${node.name}`` to the Sequence protocol.

Sources/SwiftSyntax/SyntaxNodes.swift.gyb.template

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,33 @@ public struct ${node.name}: ${base_type}Protocol, SyntaxHashable {
155155
return ${node.name}(newData)
156156
}
157157
% end
158+
159+
% # ===================
160+
% # Validate layout
161+
% # ===================
162+
163+
public func _validateLayout() {
164+
% if node.is_unknown():
165+
// We are verifying an unknown node. Since we don’t know anything about it
166+
// we need to assume it’s valid.
167+
% else:
168+
let rawChildren = Array(RawSyntaxChildren(Syntax(self)))
169+
assert(rawChildren.count == ${len(node.children)})
170+
% for i, child in enumerate(node.children):
171+
// Check ${i}-th child is ${child.type_name} ${"or missing" if child.is_optional else ""}
172+
% if not child.is_optional:
173+
assert(rawChildren[${i}].raw != nil)
174+
% end
175+
if let raw = rawChildren[${i}].raw {
176+
let info = rawChildren[${i}].syntaxInfo
177+
let absoluteRaw = AbsoluteRawSyntax(raw: raw, info: info)
178+
let syntaxData = SyntaxData(absoluteRaw, parent: Syntax(self))
179+
let syntaxChild = Syntax(syntaxData)
180+
assert(syntaxChild.is(${child.type_name}.self))
181+
}
182+
% end
183+
% end
184+
}
158185
}
159186

160187
extension ${node.name}: CustomReflectable {

Sources/SwiftSyntax/SyntaxOtherNodes.swift

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,11 @@ public struct UnknownSyntax: SyntaxProtocol, SyntaxHashable {
2525
self._syntaxNode = syntax
2626
}
2727

28+
public func _validateLayout() {
29+
// We are verifying an unknown node. Since we don’t know anything about it
30+
// we need to assume it’s valid.
31+
}
32+
2833
/// Creates an `UnknownSyntax` node from the given `SyntaxData`. This assumes
2934
/// that the `SyntaxData` is of the correct kind. If it is not, the behaviour
3035
/// is undefined.
@@ -61,6 +66,10 @@ public struct TokenSyntax: SyntaxProtocol, SyntaxHashable {
6166
self._syntaxNode = Syntax(data)
6267
}
6368

69+
public func _validateLayout() {
70+
/// A token is always valid as it has no children. Nothing to do here.
71+
}
72+
6473
public var presence: SourcePresence {
6574
return raw.presence
6675
}

Sources/SwiftSyntax/SyntaxRewriter.swift.gyb

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -203,7 +203,13 @@ open class SyntaxRewriter {
203203
assert(newLayout.count == node.raw.numberOfChildren)
204204

205205
let newRaw = node.raw.replacingLayout(Array(newLayout))
206-
return SyntaxType(Syntax(SyntaxData.forRoot(newRaw)))!
206+
let newNode = SyntaxType(Syntax(SyntaxData.forRoot(newRaw)))!
207+
assert({
208+
// In assertion builds inovoke the _validateLayout method
209+
newNode._validateLayout()
210+
return true
211+
}())
212+
return newNode
207213
} else {
208214
// No child node was rewritten. So no need to change this node as well.
209215
return node

Sources/SwiftSyntax/gyb_generated/SyntaxBaseNodes.swift

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,11 @@ public struct DeclSyntax: DeclSyntaxProtocol, SyntaxHashable {
7171
self._syntaxNode = Syntax(data)
7272
}
7373

74+
public func _validateLayout() {
75+
// Check the layout of the concrete type
76+
return Syntax(self)._validateLayout()
77+
}
78+
7479
public func `is`<S: DeclSyntaxProtocol>(_ syntaxType: S.Type) -> Bool {
7580
return self.as(syntaxType) != nil
7681
}
@@ -159,6 +164,11 @@ public struct ExprSyntax: ExprSyntaxProtocol, SyntaxHashable {
159164
self._syntaxNode = Syntax(data)
160165
}
161166

167+
public func _validateLayout() {
168+
// Check the layout of the concrete type
169+
return Syntax(self)._validateLayout()
170+
}
171+
162172
public func `is`<S: ExprSyntaxProtocol>(_ syntaxType: S.Type) -> Bool {
163173
return self.as(syntaxType) != nil
164174
}
@@ -247,6 +257,11 @@ public struct StmtSyntax: StmtSyntaxProtocol, SyntaxHashable {
247257
self._syntaxNode = Syntax(data)
248258
}
249259

260+
public func _validateLayout() {
261+
// Check the layout of the concrete type
262+
return Syntax(self)._validateLayout()
263+
}
264+
250265
public func `is`<S: StmtSyntaxProtocol>(_ syntaxType: S.Type) -> Bool {
251266
return self.as(syntaxType) != nil
252267
}
@@ -335,6 +350,11 @@ public struct TypeSyntax: TypeSyntaxProtocol, SyntaxHashable {
335350
self._syntaxNode = Syntax(data)
336351
}
337352

353+
public func _validateLayout() {
354+
// Check the layout of the concrete type
355+
return Syntax(self)._validateLayout()
356+
}
357+
338358
public func `is`<S: TypeSyntaxProtocol>(_ syntaxType: S.Type) -> Bool {
339359
return self.as(syntaxType) != nil
340360
}
@@ -423,6 +443,11 @@ public struct PatternSyntax: PatternSyntaxProtocol, SyntaxHashable {
423443
self._syntaxNode = Syntax(data)
424444
}
425445

446+
public func _validateLayout() {
447+
// Check the layout of the concrete type
448+
return Syntax(self)._validateLayout()
449+
}
450+
426451
public func `is`<S: PatternSyntaxProtocol>(_ syntaxType: S.Type) -> Bool {
427452
return self.as(syntaxType) != nil
428453
}

0 commit comments

Comments
 (0)