$30 off During Our Annual Pro Sale. View Details »

Swiftで始める静的解析

matsuji
September 21, 2020

 Swiftで始める静的解析

matsuji

September 21, 2020
Tweet

More Decks by matsuji

Other Decks in Technology

Transcript

  1. 静的解析
    まつじ
    Swiftで始める

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  12. 1. 静的解析の基礎知識

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  23. ソースコードは木構造

    View Slide

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

    View Slide

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

    View Slide

  26. 構文木
    (Syntax Tree)

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  35. 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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  52. 2. 実装の例

    View Slide

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

    View Slide

  54. View Slide

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

    View Slide

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

    View Slide

  57. 同様のコード!

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  63. func hoge(_ str: String) {
    let count = str.count
    (0..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..let indent = String(repeating: " ", count: i)
    let index = text.index(text.startIndex, offsetBy: i)
    print(" ∖(indent) ∖(text[index])")
    }
    }
    }

    View Slide

  64. func hoge(_ str: String) {
    let count = str.count
    (0..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..let indent = String(repeating: " ", count: i)
    let index = text.index(text.startIndex, offsetBy: i)
    print(" ∖(indent) ∖(text[index])")
    }
    }
    }

    View Slide

  65. func hoge(_ str: String) {
    let count = str.count
    (0..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..let indent = String(repeating: " ", count: i)
    let index = text.index(text.startIndex, offsetBy: i)
    print(" ∖(indent) ∖(text[index])")
    }
    }
    }
    これらがどのような
    構文木になっているか調べる

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  69. func hoge(_ str: String) {
    let count = str.count
    (0..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..let indent = String(repeating: " ", count: i)
    let index = text.index(text.startIndex, offsetBy: i)
    print(" ∖(indent) ∖(text[index])")
    }
    }
    }
    CodeBlockSyntax

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  74. Visitor の実装

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  79. 呼び出し元の処理

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  87. 完成
    ではありません

    View Slide

  88. View Slide

  89. View Slide

  90. 変数名が違う

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  118. View Slide

  119. 3. まとめ

    View Slide

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

    View Slide