Slide 1

Slide 1 text

静的解析 まつじ Swiftで始める ! @mtj_j @mtj0928 https://github.com/mtj0928/iOSDC-2020 資料等はこちらから!

Slide 2

Slide 2 text

まつじ • 20 卒 の新卒アプリ開発者 • 大学院では ソフトウェア工学 を研究 • 個人開発しています! 奈良出身です https://github.com/mtj0928/iOSDC-2020 資料等はこちらから!

Slide 3

Slide 3 text

大学生のための時間割 140 万 DL https://github.com/mtj0928/iOSDC-2020 資料等はこちらから!

Slide 4

Slide 4 text

https://github.com/mtj0928/iOSDC-2020 資料等はこちらから!

Slide 5

Slide 5 text

まつじ • 20 卒 の新卒アプリ開発者 • 大学院では ソフトウェア工学 を研究 • 個人開発しています! 奈良出身です https://github.com/mtj0928/iOSDC-2020 資料等はこちらから! もうすぐこのリンク 非表示にします!

Slide 6

Slide 6 text

まつじ • 20 卒 の新卒アプリ開発者 • 大学院では ソフトウェア工学 を研究 • 個人開発しています! 奈良出身です https://github.com/mtj0928/iOSDC-2020 資料等はこちらから! もうすぐこのリンク 非表示にします!

Slide 7

Slide 7 text

ソフトウェア工学を研究する上で 静的解析の知見を得たので... https://github.com/mtj0928/iOSDC-2020 資料等はこちらから! もうすぐこのリンク 非表示にします!

Slide 8

Slide 8 text

Swift で書かれたソースコードを 静的解析する方法についてお話します! https://github.com/mtj0928/iOSDC-2020 資料等はこちらから! 非表示にします!

Slide 9

Slide 9 text

そもそも静的解析とは ソースコードを 実行することなく 解析する手法 実行して解析する手法は動的解析!

Slide 10

Slide 10 text

SwiftLint https://github.com/realm/SwiftLint

Slide 11

Slide 11 text

では静的解析はどのようにしていくのか 見ていきましょう

Slide 12

Slide 12 text

1. 静的解析の基礎知識

Slide 13

Slide 13 text

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) } }

Slide 14

Slide 14 text

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) } } ソースコード

Slide 15

Slide 15 text

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 文 ソースコード

Slide 16

Slide 16 text

ソースコード 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 文

Slide 17

Slide 17 text

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 プロパティ

Slide 18

Slide 18 text

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) } }

Slide 19

Slide 19 text

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()

Slide 20

Slide 20 text

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()

Slide 21

Slide 21 text

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) } }

Slide 22

Slide 22 text

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) } } つまり

Slide 23

Slide 23 text

ソースコードは木構造

Slide 24

Slide 24 text

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) } }

Slide 25

Slide 25 text

ただし、静的解析をする時は もっと細かい粒度の木構造を対象にします

Slide 26

Slide 26 text

構文木 (Syntax Tree)

Slide 27

Slide 27 text

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) } }

Slide 28

Slide 28 text

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) } }

Slide 29

Slide 29 text

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) } } このコードに対して

Slide 30

Slide 30 text

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) } } このコードに対して これだけのノードが!

Slide 31

Slide 31 text

実際に構文木を解析していきましょう!

Slide 32

Slide 32 text

https://github.com/apple/swift-syntax SwiftSyntax Swift の構文木を生成 / 編集 するライブラリ SwiftPM で導入可能

Slide 33

Slide 33 text

import SwiftSyntax let source: String = "var num: Int?" // ソースコード let rootNode = try! SyntaxParser.parse(source: source) 構文木を生成

Slide 34

Slide 34 text

import SwiftSyntax let source: String = "var num: Int?" // ソースコード let rootNode = try! SyntaxParser.parse(source: source) 構文木のルートノード

Slide 35

Slide 35 text

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

Slide 36

Slide 36 text

SyntaxVisitor 構文木を 深さ優先探索 で走査し、 各ノードに対応する visit メソッドを呼び出す class SyntaxVisitor { func visit(_ node: X) { } func visit(_ node: Y) { } func visit(_ node: Z) { } } X Y Y Z Z Z Z

Slide 37

Slide 37 text

SyntaxVisitor 構文木を 深さ優先探索 で走査し、 各ノードに対応する visit メソッドを呼び出す class SyntaxVisitor { func visit(_ node: X) { } func visit(_ node: Y) { } func visit(_ node: Z) { } } X Y Y Z Z Z Z

Slide 38

Slide 38 text

SyntaxVisitor 構文木を 深さ優先探索 で走査し、 各ノードに対応する visit メソッドを呼び出す class SyntaxVisitor { func visit(_ node: X) { } func visit(_ node: Y) { } func visit(_ node: Z) { } } X Y Y Z Z Z Z

Slide 39

Slide 39 text

SyntaxVisitor 構文木を 深さ優先探索 で走査し、 各ノードに対応する visit メソッドを呼び出す class SyntaxVisitor { func visit(_ node: X) { } func visit(_ node: Y) { } func visit(_ node: Z) { } } X Y Y Z Z Z Z

Slide 40

Slide 40 text

SyntaxVisitor 構文木を 深さ優先探索 で走査し、 各ノードに対応する visit メソッドを呼び出す class SyntaxVisitor { func visit(_ node: X) { } func visit(_ node: Y) { } func visit(_ node: Z) { } } X Y Y Z Z Z Z

Slide 41

Slide 41 text

SyntaxVisitor 構文木を 深さ優先探索 で走査し、 各ノードに対応する visit メソッドを呼び出す class SyntaxVisitor { func visit(_ node: X) { } func visit(_ node: Y) { } func visit(_ node: Z) { } } X Y Y Z Z Z Z

Slide 42

Slide 42 text

SyntaxVisitor 構文木を 深さ優先探索 で走査し、 各ノードに対応する visit メソッドを呼び出す class SyntaxVisitor { func visit(_ node: X) { } func visit(_ node: Y) { } func visit(_ node: Z) { } } X Y Y Z Z Z Z

Slide 43

Slide 43 text

var num: Int? VariableDeclSyntax TokenSyntax("var") PatternBindingListSyntax PatternBindingSyntax IdentifierPatternSyntax TokenSyntax("num") TypeAnnotationSyntax TokenSyntax(":") OptionalTypeSyntax SimpleTypeIdentifierSyntax TokenSyntax("Int") TokenSyntax("?")

Slide 44

Slide 44 text

VariableDeclSyntax TokenSyntax("var") PatternBindingListSyntax PatternBindingSyntax IdentifierPatternSyntax TokenSyntax("num") TypeAnnotationSyntax TokenSyntax(":") OptionalTypeSyntax SimpleTypeIdentifierSyntax TokenSyntax("Int") TokenSyntax("?") var num: Int?

Slide 45

Slide 45 text

VariableDeclSyntax TokenSyntax("var") PatternBindingListSyntax PatternBindingSyntax IdentifierPatternSyntax TokenSyntax("num") TypeAnnotationSyntax TokenSyntax(":") OptionalTypeSyntax SimpleTypeIdentifierSyntax TokenSyntax("Int") TokenSyntax("?") var num: Int? 識別子を表すノード

Slide 46

Slide 46 text

class Visitor: SyntaxVisitor { override func visit(_ node: IdentifierPatternSyntax) -> SyntaxVisitorContinueKind { let identifier: TokenSyntax = node.identifier print(identifier.text) return .visitChildren } }

Slide 47

Slide 47 text

class Visitor: SyntaxVisitor { override func visit(_ node: IdentifierPatternSyntax) -> SyntaxVisitorContinueKind { let identifier: TokenSyntax = node.identifier print(identifier.text) return .visitChildren } } SyntaxVisitorを継承

Slide 48

Slide 48 text

class Visitor: SyntaxVisitor { override func visit(_ node: IdentifierPatternSyntax) -> SyntaxVisitorContinueKind { let identifier: TokenSyntax = node.identifier print(identifier.text) return .visitChildren } } IdentifierPatternSyntax に 到達した時に呼ばれる

Slide 49

Slide 49 text

class Visitor: SyntaxVisitor { override func visit(_ node: IdentifierPatternSyntax) -> SyntaxVisitorContinueKind { let identifier: TokenSyntax = node.identifier print(identifier.text) return .visitChildren } }

Slide 50

Slide 50 text

import SwiftSyntax let source: String = "var num: Int?" // ソースコード let rootNode = try! SyntaxParser.parse(source: source)

Slide 51

Slide 51 text

import SwiftSyntax let source: String = "var num: Int?" // ソースコード let rootNode = try! SyntaxParser.parse(source: source) let visitor = Visitor() visitor.walk(rootNode) 構文木を走査する

Slide 52

Slide 52 text

2. 実装の例

Slide 53

Slide 53 text

IntelliJ のこんな機能を知っていますか?

Slide 54

Slide 54 text

No content

Slide 55

Slide 55 text

7行目とコードが重複している警告

Slide 56

Slide 56 text

7行目とコードが重複している警告

Slide 57

Slide 57 text

同様のコード!

Slide 58

Slide 58 text

ソフトウェア工学では 重複したコードを「コードクローン」と呼びます

Slide 59

Slide 59 text

コードクローンは同一のコードなので メソッドにしてまとめるチャンス

Slide 60

Slide 60 text

Swift でコードクローンを検出してみましょう!

Slide 61

Slide 61 text

実装の流れ 対象のノードを特定する 1 そのノードの中の トークン列に注目する 2

Slide 62

Slide 62 text

実装の流れ 対象のノードを特定する 1 そのノードの中の トークン列に注目する 2

Slide 63

Slide 63 text

func hoge(_ str: String) { let count = str.count (0..

Slide 64

Slide 64 text

func hoge(_ str: String) { let count = str.count (0..

Slide 65

Slide 65 text

func hoge(_ str: String) { let count = str.count (0..

Slide 66

Slide 66 text

構文木の確認方法 1 swift-ast-explorer-playground https://swift-ast-explorer.kishikawakatsumi.com

Slide 67

Slide 67 text

構文木の確認方法 2 SwiftSyntax の dump メソッド let root = try! SyntaxParser.parse(source: source) dump(root)

Slide 68

Slide 68 text

構文木の確認方法 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

Slide 69

Slide 69 text

func hoge(_ str: String) { let count = str.count (0..

Slide 70

Slide 70 text

class Visitor: SyntaxVisitor { override func visit(_ node: CodeBlockSyntax) -> SyntaxVisitorContinueKind { return super.visit(node) } }

Slide 71

Slide 71 text

実装の流れ 対象のノードを特定する 1 そのノードの中の トークン列に注目する 2

Slide 72

Slide 72 text

{ let count = str.count } { let count = str . count } トークン化 { print("iOSDC 2020") } トークン化 { print ( = "iOSDC 2020" } ) { let count = str.count } { let count = str . count } トークン化

Slide 73

Slide 73 text

{ let count = str.count } { let count = str . count } トークン化 { print("iOSDC 2020") } トークン化 { print ( = "iOSDC 2020" } ) { let count = str.count } { let count = str . count } トークン化 1 トークンの並び を比較する

Slide 74

Slide 74 text

Visitor の実装

Slide 75

Slide 75 text

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) } }

Slide 76

Slide 76 text

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) } } トークンを保持する 構造体

Slide 77

Slide 77 text

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) } }

Slide 78

Slide 78 text

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) } } ブロックからコードを 取り出して保持

Slide 79

Slide 79 text

呼び出し元の処理

Slide 80

Slide 80 text

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 で警告を出す処理を書く }

Slide 81

Slide 81 text

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 で警告を出す処理を書く } クローンを表す構造体

Slide 82

Slide 82 text

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 で警告を出す処理を書く }

Slide 83

Slide 83 text

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 で警告を出す処理を書く } トークン列で グループ分けして

Slide 84

Slide 84 text

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 つのトークン列に対して 複数持つものだけ抽出 トークン列で グループ分けして

Slide 85

Slide 85 text

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 つのトークン列に対して 複数持つものだけ抽出

Slide 86

Slide 86 text

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 で警告を出す処理を書く }

Slide 87

Slide 87 text

完成 ではありません

Slide 88

Slide 88 text

No content

Slide 89

Slide 89 text

No content

Slide 90

Slide 90 text

変数名が違う

Slide 91

Slide 91 text

このように変数名が異なると クローンの検出ができない

Slide 92

Slide 92 text

変数名が完全に一致するクローンを type-1 クローンと呼びます

Slide 93

Slide 93 text

変数名が異なるコードクローンを type-2 クローンと呼びます

Slide 94

Slide 94 text

変数名が異なる type-2 クローンをどう検出するか

Slide 95

Slide 95 text

変数の正規化 変数名を潰すこと

Slide 96

Slide 96 text

{ let count = str.count print(count) } 変数の正規化 { let _$0 = _$1.count print(_$0) } { let length = text.count print(length) } 変数の正規化 { let _$0 = _$1.count print(_$0) } トークン列の比較で クローンとして検出できない... トークン列の比較で クローンとして検出できる!

Slide 97

Slide 97 text

変数名を表すトークンを 正規化すれば良さそう (手法によってはプロパティやメソッド名も正規化します)

Slide 98

Slide 98 text

{ 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)

Slide 99

Slide 99 text

{ 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) 正規化する

Slide 100

Slide 100 text

{ 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) 正規化する

Slide 101

Slide 101 text

{ 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) 正規化する

Slide 102

Slide 102 text

{ 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) 正規化する

Slide 103

Slide 103 text

{ 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) 正規化しない

Slide 104

Slide 104 text

{ 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) 正規化しない

Slide 105

Slide 105 text

{ 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)

Slide 106

Slide 106 text

{ 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 が 親なら正規化する

Slide 107

Slide 107 text

{ 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 が 親なら正規化する

Slide 108

Slide 108 text

{ 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 だと正規化しない

Slide 109

Slide 109 text

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) } }

Slide 110

Slide 110 text

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) } }

Slide 111

Slide 111 text

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 } }

Slide 112

Slide 112 text

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 } }

Slide 113

Slide 113 text

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 } }

Slide 114

Slide 114 text

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 } }

Slide 115

Slide 115 text

これで変数名が異なっている type-2 クローンにも 警告を出すことが出来るようになりました

Slide 116

Slide 116 text

あとはこのプロジェクトをビルドし

Slide 117

Slide 117 text

クローンを探索したいプロジェクトの Build Phases のスクリプトに追加すれば

Slide 118

Slide 118 text

No content

Slide 119

Slide 119 text

3. まとめ

Slide 120

Slide 120 text

まとめ 構文木の紹介 1 SwiftSyntax に関する基礎知識 2 SwiftSyntax を用いてコードクローンを検出 3 https://github.com/mtj0928/iOSDC-2020 資料等はこちらから!