Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Sign up for free
Menu
Search
Features
All features
Private URLs
Password Protection
Custom URLS
Scheduled publishing
Remove Branding
Restrict embedding
Deck Collections
Notes
Features
All features
Private URLs
Password Protection
Custom URLS
Scheduled publishing
Remove Branding
Restrict embedding
Deck Collections
Notes
Explore
Featured decks
Featured speakers
Programming
Technology
Storyboards
Explore
Featured decks
Featured speakers
Programming
Technology
Storyboards
Pricing
Search
Sign in
Sign up for free
Swiftで始める静的解析
Search
matsuji
September 21, 2020
Technology
5.4k
10
Share
Embed
Copy iframe code
Copy JS code
Copy link
Start on current slide
Swiftで始める静的解析
matsuji
September 21, 2020
More Decks by matsuji
See All by matsuji
アプリをもっと"iOSアプリっぽく"する小さな工夫 / Small Touches That Make Your App Feel More Like an iOS App
matsuji
1
740
Polishing Liquid Glass: Practical Tips for iOS 26
matsuji
0
70
カスタムUIを作る覚悟 / The determination to create a custom UI
matsuji
3
6.6k
Xcode Previewを気軽に利用するためのDI戦略
matsuji
2
6.6k
iOS15からのCommunication NotificationとSiri
matsuji
3
3.3k
Other Decks in Technology
See All in Technology
10Xに技術的負債をもたらした「2つの境界の歪み」その構造と解消への営み
10xinc
0
640
アプリログインとWeb認証基盤をつなぐ ASWebAuthenticationSession 作法
shimastripe
1
260
コーディングエージェントでM5Stack系の開発を少し試した時の話 / M5 Japan Tour 2026 Autumn 東京
you
PRO
0
180
Where Is JetBrains AI Heading- — Central CLI, Air Alpha, and the Agentic Development Stack
x5gtrn
PRO
0
160
「図書館」という名前のままでいいのか -Code4Lib JAPANカンファレンス2026 アンカンファレンス報告- / Code4Lib JAPAN Conference 2026: Unconference Report
ykiyota
0
170
クロスボーダーM&AのValue Upを支えるプロダクト開発。日米チームのハブになったプロダクトエンジニアの実践 / Product Engineering Conference 2026
genda
0
120
HRC_Frontend_Conference_Fukuoka_2026.pdf
ts020
0
620
AIに丸投げしないトイル削減 / Eliminating Toil Without Leaving It All to AI
kohbis
4
1.1k
顧客に向き合う開発組織へ。リアーキテクチャとフィーチャーチーム化で挑む組織改革
safie
0
510
新機種発売前に見直そう!端末移行で再ログインが要るアプリ・要らないアプリは何が違うのか 〜シームレスに再開できる設計と実装〜
zozotech
PRO
0
130
range over func 2年間の軌跡 Issue #56413 はGoのエコシステムをどう変えたか
ryujicre8ive
0
120
AI時代の「技術的負債」の変質ー概念の終焉と再解釈、エージェントと共に向かう先
nwiizo
0
870
Featured
See All Featured
SEO in 2025: How to Prepare for the Future of Search
ipullrank
3
3.8k
End of SEO as We Know It (SMX Advanced Version)
ipullrank
3
4.4k
brightonSEO & MeasureFest 2025 - Christian Goodrich - Winning strategies for Black Friday CRO & PPC
cargoodrich
3
830
VelocityConf: Rendering Performance Case Studies
addyosmani
331
25k
Ten Tips & Tricks for a 🌱 transition
stuffmc
0
220
Performance Is Good for Brains [We Love Speed 2024]
tammyeverts
12
1.8k
Agile Actions for Facilitating Distributed Teams - ADO2019
mkilby
0
280
Ruling the World: When Life Gets Gamed
codingconduct
0
330
Git: the NoSQL Database
bkeepers
PRO
432
67k
The browser strikes back
jonoalderson
0
1.7k
Public Speaking Without Barfing On Your Shoes - THAT 2023
reverentgeek
1
560
Building a Scalable Design System with Sketch
lauravandoore
464
34k
Transcript
静的解析 まつじ Swiftで始める ! @mtj_j @mtj0928 https://github.com/mtj0928/iOSDC-2020 資料等はこちらから!
まつじ • 20 卒 の新卒アプリ開発者 • 大学院では ソフトウェア工学 を研究 •
個人開発しています! 奈良出身です https://github.com/mtj0928/iOSDC-2020 資料等はこちらから!
大学生のための時間割 140 万 DL https://github.com/mtj0928/iOSDC-2020 資料等はこちらから!
https://github.com/mtj0928/iOSDC-2020 資料等はこちらから!
まつじ • 20 卒 の新卒アプリ開発者 • 大学院では ソフトウェア工学 を研究 •
個人開発しています! 奈良出身です https://github.com/mtj0928/iOSDC-2020 資料等はこちらから! もうすぐこのリンク 非表示にします!
まつじ • 20 卒 の新卒アプリ開発者 • 大学院では ソフトウェア工学 を研究 •
個人開発しています! 奈良出身です https://github.com/mtj0928/iOSDC-2020 資料等はこちらから! もうすぐこのリンク 非表示にします!
ソフトウェア工学を研究する上で 静的解析の知見を得たので... https://github.com/mtj0928/iOSDC-2020 資料等はこちらから! もうすぐこのリンク 非表示にします!
Swift で書かれたソースコードを 静的解析する方法についてお話します! https://github.com/mtj0928/iOSDC-2020 資料等はこちらから! 非表示にします!
そもそも静的解析とは ソースコードを 実行することなく 解析する手法 実行して解析する手法は動的解析!
SwiftLint https://github.com/realm/SwiftLint
では静的解析はどのようにしていくのか 見ていきましょう
1. 静的解析の基礎知識
import UIKit class ViewController: UIViewController { var num: Int? override
func viewDidLoad() { super.viewDidLoad() if let num = num { /* 略 */ } else { /* 略 */ } } override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) } }
import UIKit class ViewController: UIViewController { var num: Int? override
func viewDidLoad() { super.viewDidLoad() if let num = num { /* 略 */ } else { /* 略 */ } } override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) } } ソースコード
import UIKit class ViewController: UIViewController { var num: Int? override
func viewDidLoad() { super.viewDidLoad() if let num = num { /* 略 */ } else { /* 略 */ } } override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) } } import 文 ソースコード
ソースコード import UIKit class ViewController: UIViewController { var num: Int?
override func viewDidLoad() { super.viewDidLoad() if let num = num { /* 略 */ } else { /* 略 */ } } override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) } } ViewController クラス import 文
ViewController クラス ソースコード import UIKit class ViewController: UIViewController { var
num: Int? override func viewDidLoad() { super.viewDidLoad() if let num = num { /* 略 */ } else { /* 略 */ } } override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) } } import 文 num プロパティ
num プロパティ ViewController クラス ソースコード import 文 viewDidLoad() import UIKit
class ViewController: UIViewController { var num: Int? override func viewDidLoad() { super.viewDidLoad() if let num = num { /* 略 */ } else { /* 略 */ } } override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) } }
num プロパティ ViewController クラス ソースコード import 文 import UIKit class
ViewController: UIViewController { var num: Int? override func viewDidLoad() { super.viewDidLoad() if let num = num { /* 略 */ } else { /* 略 */ } } override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) } } viewDidLoad() viewDidLoad()
num プロパティ ViewController クラス ソースコード import 文 import UIKit class
ViewController: UIViewController { var num: Int? override func viewDidLoad() { super.viewDidLoad() if let num = num { /* 略 */ } else { /* 略 */ } } override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) } } if 文 viewDidLoad() viewDidLoad()
num プロパティ ViewController クラス ソースコード import 文 if 文 viewDidLoad()
viewDidLoad() viewWillAppear(_:) import UIKit class ViewController: UIViewController { var num: Int? override func viewDidLoad() { super.viewDidLoad() if let num = num { /* 略 */ } else { /* 略 */ } } override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) } }
num プロパティ ViewController クラス ソースコード import 文 if 文 viewDidLoad()
viewDidLoad() viewWillAppear(_:) import UIKit class ViewController: UIViewController { var num: Int? override func viewDidLoad() { super.viewDidLoad() if let num = num { /* 略 */ } else { /* 略 */ } } override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) } } つまり
ソースコードは木構造
num プロパティ ViewController クラス ソースコード import 文 if 文 viewDidLoad()
viewDidLoad() viewWillAppear(_:) import UIKit class ViewController: UIViewController { var num: Int? override func viewDidLoad() { super.viewDidLoad() if let num = num { /* 略 */ } else { /* 略 */ } } override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) } }
ただし、静的解析をする時は もっと細かい粒度の木構造を対象にします
構文木 (Syntax Tree)
import UIKit class ViewController: UIViewController { var num: Int? override
func viewDidLoad() { super.viewDidLoad() if let num = num { /* 略 */ } else { /* 略 */ } } override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) } }
SourceFileSyntax CodeBlockItemListSyntax CodeBlockItemSyntax ImportDeclSyntax TokenSyntax("import") AccessPathSyntax AccessPathComponentSyntax TokenSyntax("UIKit") CodeBlockItemSyntax ClassDeclSyntax
TokenSyntax("class") TokenSyntax("ViewController") TypeInheritanceClauseSyntax TokenSyntax(":") InheritedTypeListSyntax InheritedTypeSyntax SimpleTypeIdentifierSyntax TokenSyntax("UIViewController") MemberDeclBlockSyntax TokenSyntax("{") MemberDeclListSyntax MemberDeclListItemSyntax VariableDeclSyntax TokenSyntax("var") PatternBindingListSyntax PatternBindingSyntax IdentifierPatternSyntax TokenSyntax("num") TypeAnnotationSyntax TokenSyntax(":") OptionalTypeSyntax SimpleTypeIdentifierSyntax TokenSyntax("Int") TokenSyntax("?") MemberDeclListItemSyntax FunctionDeclSyntax ModifierListSyntax DeclModifierSyntax TokenSyntax("override") TokenSyntax("func") import UIKit class ViewController: UIViewController { var num: Int? override func viewDidLoad() { super.viewDidLoad() if let num = num { /* 略 */ } else { /* 略 */ } } override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) } }
SourceFileSyntax CodeBlockItemListSyntax CodeBlockItemSyntax ImportDeclSyntax TokenSyntax("import") AccessPathSyntax AccessPathComponentSyntax TokenSyntax("UIKit") CodeBlockItemSyntax ClassDeclSyntax
TokenSyntax("class") TokenSyntax("ViewController") TypeInheritanceClauseSyntax TokenSyntax(":") InheritedTypeListSyntax InheritedTypeSyntax SimpleTypeIdentifierSyntax TokenSyntax("UIViewController") MemberDeclBlockSyntax TokenSyntax("{") MemberDeclListSyntax MemberDeclListItemSyntax VariableDeclSyntax TokenSyntax("var") PatternBindingListSyntax PatternBindingSyntax IdentifierPatternSyntax TokenSyntax("num") TypeAnnotationSyntax TokenSyntax(":") OptionalTypeSyntax SimpleTypeIdentifierSyntax TokenSyntax("Int") TokenSyntax("?") MemberDeclListItemSyntax FunctionDeclSyntax ModifierListSyntax DeclModifierSyntax TokenSyntax("override") TokenSyntax("func") import UIKit class ViewController: UIViewController { var num: Int? override func viewDidLoad() { super.viewDidLoad() if let num = num { /* 略 */ } else { /* 略 */ } } override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) } } このコードに対して
SourceFileSyntax CodeBlockItemListSyntax CodeBlockItemSyntax ImportDeclSyntax TokenSyntax("import") AccessPathSyntax AccessPathComponentSyntax TokenSyntax("UIKit") CodeBlockItemSyntax ClassDeclSyntax
TokenSyntax("class") TokenSyntax("ViewController") TypeInheritanceClauseSyntax TokenSyntax(":") InheritedTypeListSyntax InheritedTypeSyntax SimpleTypeIdentifierSyntax TokenSyntax("UIViewController") MemberDeclBlockSyntax TokenSyntax("{") MemberDeclListSyntax MemberDeclListItemSyntax VariableDeclSyntax TokenSyntax("var") PatternBindingListSyntax PatternBindingSyntax IdentifierPatternSyntax TokenSyntax("num") TypeAnnotationSyntax TokenSyntax(":") OptionalTypeSyntax SimpleTypeIdentifierSyntax TokenSyntax("Int") TokenSyntax("?") MemberDeclListItemSyntax FunctionDeclSyntax ModifierListSyntax DeclModifierSyntax TokenSyntax("override") TokenSyntax("func") import UIKit class ViewController: UIViewController { var num: Int? override func viewDidLoad() { super.viewDidLoad() if let num = num { /* 略 */ } else { /* 略 */ } } override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) } } このコードに対して これだけのノードが!
実際に構文木を解析していきましょう!
https://github.com/apple/swift-syntax SwiftSyntax Swift の構文木を生成 / 編集 するライブラリ SwiftPM で導入可能
import SwiftSyntax let source: String = "var num: Int?" //
ソースコード let rootNode = try! SyntaxParser.parse(source: source) 構文木を生成
import SwiftSyntax let source: String = "var num: Int?" //
ソースコード let rootNode = try! SyntaxParser.parse(source: source) 構文木のルートノード
import SwiftSyntax let source: String = "var num: Int?" //
ソースコード let rootNode = try! SyntaxParser.parse(source: source) let text = rootNode.statements .first! .item.as(VariableDeclSyntax.self)! .bindings .first! .pattern.as(IdentifierPatternSyntax.self)! .identifier.text print(text) // => num ルートノードから正しく辿れば 末端のノードを取得可能 現実的ではない SyntaxVisitor
SyntaxVisitor 構文木を 深さ優先探索 で走査し、 各ノードに対応する visit メソッドを呼び出す class SyntaxVisitor {
func visit(_ node: X) { } func visit(_ node: Y) { } func visit(_ node: Z) { } } X Y Y Z Z Z Z
SyntaxVisitor 構文木を 深さ優先探索 で走査し、 各ノードに対応する visit メソッドを呼び出す class SyntaxVisitor {
func visit(_ node: X) { } func visit(_ node: Y) { } func visit(_ node: Z) { } } X Y Y Z Z Z Z
SyntaxVisitor 構文木を 深さ優先探索 で走査し、 各ノードに対応する visit メソッドを呼び出す class SyntaxVisitor {
func visit(_ node: X) { } func visit(_ node: Y) { } func visit(_ node: Z) { } } X Y Y Z Z Z Z
SyntaxVisitor 構文木を 深さ優先探索 で走査し、 各ノードに対応する visit メソッドを呼び出す class SyntaxVisitor {
func visit(_ node: X) { } func visit(_ node: Y) { } func visit(_ node: Z) { } } X Y Y Z Z Z Z
SyntaxVisitor 構文木を 深さ優先探索 で走査し、 各ノードに対応する visit メソッドを呼び出す class SyntaxVisitor {
func visit(_ node: X) { } func visit(_ node: Y) { } func visit(_ node: Z) { } } X Y Y Z Z Z Z
SyntaxVisitor 構文木を 深さ優先探索 で走査し、 各ノードに対応する visit メソッドを呼び出す class SyntaxVisitor {
func visit(_ node: X) { } func visit(_ node: Y) { } func visit(_ node: Z) { } } X Y Y Z Z Z Z
SyntaxVisitor 構文木を 深さ優先探索 で走査し、 各ノードに対応する visit メソッドを呼び出す class SyntaxVisitor {
func visit(_ node: X) { } func visit(_ node: Y) { } func visit(_ node: Z) { } } X Y Y Z Z Z Z
var num: Int? VariableDeclSyntax TokenSyntax("var") PatternBindingListSyntax PatternBindingSyntax IdentifierPatternSyntax TokenSyntax("num") TypeAnnotationSyntax
TokenSyntax(":") OptionalTypeSyntax SimpleTypeIdentifierSyntax TokenSyntax("Int") TokenSyntax("?")
VariableDeclSyntax TokenSyntax("var") PatternBindingListSyntax PatternBindingSyntax IdentifierPatternSyntax TokenSyntax("num") TypeAnnotationSyntax TokenSyntax(":") OptionalTypeSyntax SimpleTypeIdentifierSyntax
TokenSyntax("Int") TokenSyntax("?") var num: Int?
VariableDeclSyntax TokenSyntax("var") PatternBindingListSyntax PatternBindingSyntax IdentifierPatternSyntax TokenSyntax("num") TypeAnnotationSyntax TokenSyntax(":") OptionalTypeSyntax SimpleTypeIdentifierSyntax
TokenSyntax("Int") TokenSyntax("?") var num: Int? 識別子を表すノード
class Visitor: SyntaxVisitor { override func visit(_ node: IdentifierPatternSyntax) ->
SyntaxVisitorContinueKind { let identifier: TokenSyntax = node.identifier print(identifier.text) return .visitChildren } }
class Visitor: SyntaxVisitor { override func visit(_ node: IdentifierPatternSyntax) ->
SyntaxVisitorContinueKind { let identifier: TokenSyntax = node.identifier print(identifier.text) return .visitChildren } } SyntaxVisitorを継承
class Visitor: SyntaxVisitor { override func visit(_ node: IdentifierPatternSyntax) ->
SyntaxVisitorContinueKind { let identifier: TokenSyntax = node.identifier print(identifier.text) return .visitChildren } } IdentifierPatternSyntax に 到達した時に呼ばれる
class Visitor: SyntaxVisitor { override func visit(_ node: IdentifierPatternSyntax) ->
SyntaxVisitorContinueKind { let identifier: TokenSyntax = node.identifier print(identifier.text) return .visitChildren } }
import SwiftSyntax let source: String = "var num: Int?" //
ソースコード let rootNode = try! SyntaxParser.parse(source: source)
import SwiftSyntax let source: String = "var num: Int?" //
ソースコード let rootNode = try! SyntaxParser.parse(source: source) let visitor = Visitor() visitor.walk(rootNode) 構文木を走査する
2. 実装の例
IntelliJ のこんな機能を知っていますか?
None
7行目とコードが重複している警告
7行目とコードが重複している警告
同様のコード!
ソフトウェア工学では 重複したコードを「コードクローン」と呼びます
コードクローンは同一のコードなので メソッドにしてまとめるチャンス
Swift でコードクローンを検出してみましょう!
実装の流れ 対象のノードを特定する 1 そのノードの中の トークン列に注目する 2
実装の流れ 対象のノードを特定する 1 そのノードの中の トークン列に注目する 2
func hoge(_ str: String) { let count = str.count (0..<count).forEach
{ index in let indent = String(repeating: " ", count: index) let stringIndex = str.index(str.startIndex, offsetBy: index) print("∖(indent) ∖(str[stringIndex])") } } func fuga(_ text: String) { if !text.isEmpty { let count = text.count (0..<count).forEach { i in let indent = String(repeating: " ", count: i) let index = text.index(text.startIndex, offsetBy: i) print(" ∖(indent) ∖(text[index])") } } }
func hoge(_ str: String) { let count = str.count (0..<count).forEach
{ index in let indent = String(repeating: " ", count: index) let stringIndex = str.index(str.startIndex, offsetBy: index) print("∖(indent) ∖(str[stringIndex])") } } func fuga(_ text: String) { if !text.isEmpty { let count = text.count (0..<count).forEach { i in let indent = String(repeating: " ", count: i) let index = text.index(text.startIndex, offsetBy: i) print(" ∖(indent) ∖(text[index])") } } }
func hoge(_ str: String) { let count = str.count (0..<count).forEach
{ index in let indent = String(repeating: " ", count: index) let stringIndex = str.index(str.startIndex, offsetBy: index) print("∖(indent) ∖(str[stringIndex])") } } func fuga(_ text: String) { if !text.isEmpty { let count = text.count (0..<count).forEach { i in let indent = String(repeating: " ", count: i) let index = text.index(text.startIndex, offsetBy: i) print(" ∖(indent) ∖(text[index])") } } } これらがどのような 構文木になっているか調べる
構文木の確認方法 1 swift-ast-explorer-playground https://swift-ast-explorer.kishikawakatsumi.com
構文木の確認方法 2 SwiftSyntax の dump メソッド let root = try!
SyntaxParser.parse(source: source) dump(root)
構文木の確認方法 2 SwiftSyntax の dump メソッド let root = try!
SyntaxParser.parse(source: source) dump(root) ▿ SwiftSyntax.SourceFileSyntax ▿ statements: SwiftSyntax.CodeBlockItemListSyntax ▿ SwiftSyntax.CodeBlockItemSyntax ▿ item: SwiftSyntax.ImportDeclSyntax - attributes: nil - modifiers: nil ▿ importTok: SwiftSyntax.TokenSyntax
func hoge(_ str: String) { let count = str.count (0..<count).forEach
{ index in let indent = String(repeating: " ", count: index) let stringIndex = str.index(str.startIndex, offsetBy: index) print("∖(indent) ∖(str[stringIndex])") } } func fuga(_ text: String) { if !text.isEmpty { let count = text.count (0..<count).forEach { i in let indent = String(repeating: " ", count: i) let index = text.index(text.startIndex, offsetBy: i) print(" ∖(indent) ∖(text[index])") } } } CodeBlockSyntax
class Visitor: SyntaxVisitor { override func visit(_ node: CodeBlockSyntax) ->
SyntaxVisitorContinueKind { return super.visit(node) } }
実装の流れ 対象のノードを特定する 1 そのノードの中の トークン列に注目する 2
{ let count = str.count } { let count =
str . count } トークン化 { print("iOSDC 2020") } トークン化 { print ( = "iOSDC 2020" } ) { let count = str.count } { let count = str . count } トークン化
{ let count = str.count } { let count =
str . count } トークン化 { print("iOSDC 2020") } トークン化 { print ( = "iOSDC 2020" } ) { let count = str.count } { let count = str . count } トークン化 1 トークンの並び を比較する
Visitor の実装
struct CodeChunk { let tokens: [String] let block: CodeBlockSyntax }
class Visitor: SyntaxVisitor { var chunks = [CodeChunk]() override func visit(_ node: CodeBlockSyntax) -> SyntaxVisitorContinueKind { let tokens: [String] = node.tokens.map { $0.text } let chunk = CodeChunk(tokens: tokens, block: node) chunks.append(chunk) return super.visit(node) } }
struct CodeChunk { let tokens: [String] let block: CodeBlockSyntax }
class Visitor: SyntaxVisitor { var chunks = [CodeChunk]() override func visit(_ node: CodeBlockSyntax) -> SyntaxVisitorContinueKind { let tokens: [String] = node.tokens.map { $0.text } let chunk = CodeChunk(tokens: tokens, block: node) chunks.append(chunk) return super.visit(node) } } トークンを保持する 構造体
struct CodeChunk { let tokens: [String] let block: CodeBlockSyntax }
class Visitor: SyntaxVisitor { var chunks = [CodeChunk]() override func visit(_ node: CodeBlockSyntax) -> SyntaxVisitorContinueKind { let tokens: [String] = node.tokens.map { $0.text } let chunk = CodeChunk(tokens: tokens, block: node) chunks.append(chunk) return super.visit(node) } }
struct CodeChunk { let tokens: [String] let block: CodeBlockSyntax }
class Visitor: SyntaxVisitor { var chunks = [CodeChunk]() override func visit(_ node: CodeBlockSyntax) -> SyntaxVisitorContinueKind { let tokens: [String] = node.tokens.map { $0.text } let chunk = CodeChunk(tokens: tokens, block: node) chunks.append(chunk) return super.visit(node) } } ブロックからコードを 取り出して保持
呼び出し元の処理
struct CodeClone { let chunks: [CodeChunk] } let chunks: [CodeChunk]
= visitor.chunks let clones = Dictionary(grouping: chunks, by: { $0.tokens.hashValue }) .filter { $0.value.count >= 2 } .map { CodeClone(chunks: $0.value) } clones.forEach { clone in // ここに Xcode で警告を出す処理を書く }
struct CodeClone { let chunks: [CodeChunk] } let chunks: [CodeChunk]
= visitor.chunks let clones = Dictionary(grouping: chunks, by: { $0.tokens.hashValue }) .filter { $0.value.count >= 2 } .map { CodeClone(chunks: $0.value) } clones.forEach { clone in // ここに Xcode で警告を出す処理を書く } クローンを表す構造体
struct CodeClone { let chunks: [CodeChunk] } let chunks: [CodeChunk]
= visitor.chunks let clones = Dictionary(grouping: chunks, by: { $0.tokens.hashValue }) .filter { $0.value.count >= 2 } .map { CodeClone(chunks: $0.value) } clones.forEach { clone in // ここに Xcode で警告を出す処理を書く }
struct CodeClone { let chunks: [CodeChunk] } let chunks: [CodeChunk]
= visitor.chunks let clones = Dictionary(grouping: chunks, by: { $0.tokens }) .filter { $0.value.count >= 2 } .map { CodeClone(chunks: $0.value) } clones.forEach { clone in // ここに Xcode で警告を出す処理を書く } トークン列で グループ分けして
struct CodeClone { let chunks: [CodeChunk] } let chunks: [CodeChunk]
= visitor.chunks let clones = Dictionary(grouping: chunks, by: { $0.tokens }) .filter { $0.value.count >= 2 } .map { CodeClone(chunks: $0.value) } clones.forEach { clone in // ここに Xcode で警告を出す処理を書く } 1 つのトークン列に対して 複数持つものだけ抽出 トークン列で グループ分けして
struct CodeClone { let chunks: [CodeChunk] } let chunks: [CodeChunk]
= visitor.chunks let clones = Dictionary(grouping: chunks, by: { $0.tokens }) .filter { $0.value.count >= 2 } .map { CodeClone(chunks: $0.value) } clones.forEach { clone in // ここに Xcode で警告を出す処理を書く } トークン列で グループ分けして 1 つのトークン列に対して 複数持つものだけ抽出
struct CodeClone { let chunks: [CodeChunk] } let chunks: [CodeChunk]
= visitor.chunks let clones = Dictionary(grouping: chunks, by: { $0.tokens.hashValue }) .filter { $0.value.count >= 2 } .map { CodeClone(chunks: $0.value) } clones.forEach { clone in // ここに Xcode で警告を出す処理を書く }
完成 ではありません
None
None
変数名が違う
このように変数名が異なると クローンの検出ができない
変数名が完全に一致するクローンを type-1 クローンと呼びます
変数名が異なるコードクローンを type-2 クローンと呼びます
変数名が異なる type-2 クローンをどう検出するか
変数の正規化 変数名を潰すこと
{ let count = str.count print(count) } 変数の正規化 { let
_$0 = _$1.count print(_$0) } { let length = text.count print(length) } 変数の正規化 { let _$0 = _$1.count print(_$0) } トークン列の比較で クローンとして検出できない... トークン列の比較で クローンとして検出できる!
変数名を表すトークンを 正規化すれば良さそう (手法によってはプロパティやメソッド名も正規化します)
{ let length = str.count print(length) } CodeBlockItemListSyntax CodeBlockItemSyntax VariableDeclSyntax
PatternBindingListSyntax PatternBindingSyntax IdentifierPatternSyntax TokenSyntax (length) InitializerClauseSyntax MemberAccessExprSyntax IdentifierExprSyntax TokenSyntax (str) TokenSyntax (count) CodeBlockItemSyntax FunctionCallExprSyntax IdentifierExprSyntax TokenSyntax (print) TupleExprElementListSyntax TupleExprElementSyntax IdentifierExprSyntax TokenSyntax (length)
{ let length = str.count print(length) } CodeBlockItemListSyntax CodeBlockItemSyntax VariableDeclSyntax
PatternBindingListSyntax PatternBindingSyntax IdentifierPatternSyntax TokenSyntax (length) InitializerClauseSyntax MemberAccessExprSyntax IdentifierExprSyntax TokenSyntax (str) TokenSyntax (count) CodeBlockItemSyntax FunctionCallExprSyntax IdentifierExprSyntax TokenSyntax (print) TupleExprElementListSyntax TupleExprElementSyntax IdentifierExprSyntax TokenSyntax (length) 正規化する
{ let length = str.count print(length) } CodeBlockItemListSyntax CodeBlockItemSyntax VariableDeclSyntax
PatternBindingListSyntax PatternBindingSyntax IdentifierPatternSyntax TokenSyntax (length) InitializerClauseSyntax MemberAccessExprSyntax IdentifierExprSyntax TokenSyntax (str) TokenSyntax (count) CodeBlockItemSyntax FunctionCallExprSyntax IdentifierExprSyntax TokenSyntax (print) TupleExprElementListSyntax TupleExprElementSyntax IdentifierExprSyntax TokenSyntax (length) 正規化する
{ let length = str.count print(length) } CodeBlockItemListSyntax CodeBlockItemSyntax VariableDeclSyntax
PatternBindingListSyntax PatternBindingSyntax IdentifierPatternSyntax TokenSyntax (length) InitializerClauseSyntax MemberAccessExprSyntax IdentifierExprSyntax TokenSyntax (str) TokenSyntax (count) CodeBlockItemSyntax FunctionCallExprSyntax IdentifierExprSyntax TokenSyntax (print) TupleExprElementListSyntax TupleExprElementSyntax IdentifierExprSyntax TokenSyntax (length) 正規化する
{ let length = str.count print(length) } CodeBlockItemListSyntax CodeBlockItemSyntax VariableDeclSyntax
PatternBindingListSyntax PatternBindingSyntax IdentifierPatternSyntax TokenSyntax (length) InitializerClauseSyntax MemberAccessExprSyntax IdentifierExprSyntax TokenSyntax (str) TokenSyntax (count) CodeBlockItemSyntax FunctionCallExprSyntax IdentifierExprSyntax TokenSyntax (print) TupleExprElementListSyntax TupleExprElementSyntax IdentifierExprSyntax TokenSyntax (length) 正規化する
{ let length = str.count print(length) } CodeBlockItemListSyntax CodeBlockItemSyntax VariableDeclSyntax
PatternBindingListSyntax PatternBindingSyntax IdentifierPatternSyntax TokenSyntax (length) InitializerClauseSyntax MemberAccessExprSyntax IdentifierExprSyntax TokenSyntax (str) TokenSyntax (count) CodeBlockItemSyntax FunctionCallExprSyntax IdentifierExprSyntax TokenSyntax (print) TupleExprElementListSyntax TupleExprElementSyntax IdentifierExprSyntax TokenSyntax (length) 正規化しない
{ let length = str.count print(length) } CodeBlockItemListSyntax CodeBlockItemSyntax VariableDeclSyntax
PatternBindingListSyntax PatternBindingSyntax IdentifierPatternSyntax TokenSyntax (length) InitializerClauseSyntax MemberAccessExprSyntax IdentifierExprSyntax TokenSyntax (str) TokenSyntax (count) CodeBlockItemSyntax FunctionCallExprSyntax IdentifierExprSyntax TokenSyntax (print) TupleExprElementListSyntax TupleExprElementSyntax IdentifierExprSyntax TokenSyntax (length) 正規化しない
{ let length = str.count print(length) } CodeBlockItemListSyntax CodeBlockItemSyntax VariableDeclSyntax
PatternBindingListSyntax PatternBindingSyntax IdentifierPatternSyntax TokenSyntax (length) InitializerClauseSyntax MemberAccessExprSyntax IdentifierExprSyntax TokenSyntax (str) TokenSyntax (count) CodeBlockItemSyntax FunctionCallExprSyntax IdentifierExprSyntax TokenSyntax (print) TupleExprElementListSyntax TupleExprElementSyntax IdentifierExprSyntax TokenSyntax (length)
{ let length = str.count print(length) } CodeBlockItemListSyntax CodeBlockItemSyntax VariableDeclSyntax
PatternBindingListSyntax PatternBindingSyntax IdentifierPatternSyntax TokenSyntax (length) InitializerClauseSyntax MemberAccessExprSyntax IdentifierExprSyntax TokenSyntax (str) TokenSyntax (count) CodeBlockItemSyntax FunctionCallExprSyntax IdentifierExprSyntax TokenSyntax (print) TupleExprElementListSyntax TupleExprElementSyntax IdentifierExprSyntax TokenSyntax (length) IdentifierPatternSyntax が 親なら正規化する
{ let length = str.count print(length) } CodeBlockItemListSyntax CodeBlockItemSyntax VariableDeclSyntax
PatternBindingListSyntax PatternBindingSyntax IdentifierPatternSyntax TokenSyntax (length) InitializerClauseSyntax MemberAccessExprSyntax IdentifierExprSyntax TokenSyntax (str) TokenSyntax (count) CodeBlockItemSyntax FunctionCallExprSyntax IdentifierExprSyntax TokenSyntax (print) TupleExprElementListSyntax TupleExprElementSyntax IdentifierExprSyntax TokenSyntax (length) IdentifierExprSyntax が 親なら正規化する
{ let length = str.count print(length) } CodeBlockItemListSyntax CodeBlockItemSyntax VariableDeclSyntax
PatternBindingListSyntax PatternBindingSyntax IdentifierPatternSyntax TokenSyntax (length) InitializerClauseSyntax MemberAccessExprSyntax IdentifierExprSyntax TokenSyntax (str) TokenSyntax (count) CodeBlockItemSyntax FunctionCallExprSyntax IdentifierExprSyntax TokenSyntax (print) TupleExprElementListSyntax TupleExprElementSyntax IdentifierExprSyntax TokenSyntax (length) IdentifierExprSyntax が親でも さらにその親が FunctionCallExprSyntax だと正規化しない
class Visitor: SyntaxVisitor { var chunks = [CodeChunk]() override func
visit(_ node: CodeBlockSyntax) -> SyntaxVisitorContinueKind { let tokens: [String] = node.tokens.map { $0.text } let chunk = CodeChunk(tokens: tokens, block: node) chunks.append(chunk) return super.visit(node) } }
class Visitor: SyntaxVisitor { var chunks = [CodeChunk]() override func
visit(_ node: CodeBlockSyntax) -> SyntaxVisitorContinueKind { let tokens: [String] = node.tokens.map { $0.text } let tokens: [String] = normalize(node) let chunk = CodeChunk(tokens: tokens, block: node) chunks.append(chunk) return super.visit(node) } }
func normalize(_ block: CodeBlockSyntax) -> [String] { return block.tokens.map {
token -> String in // 識別⼦を取り出す guard case .identifier(let identifier) = token.tokenKind else { return token.text // 予約語などの場合 } if token.parent!.is(IdentifierPatternSyntax.self) || (token.parent!.is(IdentifierExprSyntax.self) && !token.parent!.parent!.is(FunctionCallExprSyntax.self)) { let id: Int // 識別⼦に対応するIDを⽤意(今回は省略) return "_$¥(id)" } return identifier } }
func normalize(_ block: CodeBlockSyntax) -> [String] { return block.tokens.map {
token -> String in // 識別⼦を取り出す guard case .identifier(let identifier) = token.tokenKind else { return token.text // 予約語などの場合 } if token.parent!.is(IdentifierPatternSyntax.self) || (token.parent!.is(IdentifierExprSyntax.self) && !token.parent!.parent!.is(FunctionCallExprSyntax.self)) { let id: Int // 識別⼦に対応するIDを⽤意(今回は省略) return "_$¥(id)" } return identifier } }
func normalize(_ block: CodeBlockSyntax) -> [String] { return block.tokens.map {
token -> String in // 識別⼦を取り出す guard case .identifier(let identifier) = token.tokenKind else { return token.text // 予約語などの場合 } if token.parent!.is(IdentifierPatternSyntax.self) || (token.parent!.is(IdentifierExprSyntax.self) && !token.parent!.parent!.is(FunctionCallExprSyntax.self)) { let id: Int // 識別⼦に対応するIDを⽤意(今回は省略) return "_$¥(id)" } return identifier } }
func normalize(_ block: CodeBlockSyntax) -> [String] { return block.tokens.map {
token -> String in // 識別⼦を取り出す guard case .identifier(let identifier) = token.tokenKind else { return token.text // 予約語などの場合 } if token.parent!.is(IdentifierPatternSyntax.self) || (token.parent!.is(IdentifierExprSyntax.self) && !token.parent!.parent!.is(FunctionCallExprSyntax.self)) { let id: Int // 識別⼦に対応するIDを⽤意(今回は省略) return "_$¥(id)" } return identifier } }
これで変数名が異なっている type-2 クローンにも 警告を出すことが出来るようになりました
あとはこのプロジェクトをビルドし
クローンを探索したいプロジェクトの Build Phases のスクリプトに追加すれば
None
3. まとめ
まとめ 構文木の紹介 1 SwiftSyntax に関する基礎知識 2 SwiftSyntax を用いてコードクローンを検出 3 https://github.com/mtj0928/iOSDC-2020
資料等はこちらから!